Merge branch 'master' into feature/sql_2
Original commit: elastic/x-pack-elasticsearch@6ba2651f46
This commit is contained in:
commit
f66e01369a
|
@ -1,27 +1,17 @@
|
||||||
[[elasticsearch-reference]]
|
|
||||||
= Elasticsearch Reference
|
|
||||||
|
|
||||||
:include-xpack: true
|
include::{es-repo-dir}/index-shared1.asciidoc[]
|
||||||
:xes-repo-dir: {docdir}
|
|
||||||
:es-repo-dir: {docdir}/../../../../elasticsearch/docs
|
|
||||||
:es-test-dir: {docdir}/../../../../elasticsearch/docs/src/test
|
|
||||||
:plugins-examples-dir: {docdir}/../../../../elasticsearch/plugins/examples
|
|
||||||
|
|
||||||
include::{es-repo-dir}/Versions.asciidoc[]
|
|
||||||
|
|
||||||
include::{es-repo-dir}/reference/index-shared1.asciidoc[]
|
|
||||||
|
|
||||||
:edit_url!:
|
:edit_url!:
|
||||||
include::setup/setup-xes.asciidoc[]
|
include::setup/setup-xes.asciidoc[]
|
||||||
|
|
||||||
:edit_url:
|
:edit_url:
|
||||||
include::{es-repo-dir}/reference/index-shared2.asciidoc[]
|
include::{es-repo-dir}/index-shared2.asciidoc[]
|
||||||
|
|
||||||
:edit_url!:
|
:edit_url!:
|
||||||
include::release-notes/xpack-breaking.asciidoc[]
|
include::release-notes/xpack-breaking.asciidoc[]
|
||||||
|
|
||||||
:edit_url:
|
:edit_url:
|
||||||
include::{es-repo-dir}/reference/index-shared3.asciidoc[]
|
include::{es-repo-dir}/index-shared3.asciidoc[]
|
||||||
|
|
||||||
:edit_url!:
|
:edit_url!:
|
||||||
include::sql/index.asciidoc[]
|
include::sql/index.asciidoc[]
|
||||||
|
@ -36,10 +26,10 @@ include::rest-api/index.asciidoc[]
|
||||||
include::commands/index.asciidoc[]
|
include::commands/index.asciidoc[]
|
||||||
|
|
||||||
:edit_url:
|
:edit_url:
|
||||||
include::{es-repo-dir}/reference/index-shared4.asciidoc[]
|
include::{es-repo-dir}/index-shared4.asciidoc[]
|
||||||
|
|
||||||
:edit_url!:
|
:edit_url!:
|
||||||
include::release-notes/xpack-xes.asciidoc[]
|
include::release-notes/xpack-xes.asciidoc[]
|
||||||
|
|
||||||
:edit_url:
|
:edit_url:
|
||||||
include::{es-repo-dir}/reference/index-shared5.asciidoc[]
|
include::{es-repo-dir}/index-shared5.asciidoc[]
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-4.5-all.zip
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.3-all.zip
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
distributionSha256Sum=b3afcc2d5aaf4d23eeab2409d64c54046147322d05acc7fb5a63f84d8a2b8bd7
|
distributionSha256Sum=6ac2f8f9302f50241bf14cc5f4a3d88504ad20e61bb98c5fd048f7723b61397e
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
import com.carrotsearch.gradle.junit4.RandomizedTestingTask
|
|
||||||
import org.elasticsearch.gradle.BuildPlugin
|
|
||||||
import org.elasticsearch.gradle.MavenFilteringHack
|
import org.elasticsearch.gradle.MavenFilteringHack
|
||||||
|
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
|
@ -154,25 +152,5 @@ thirdPartyAudit.excludes = [
|
||||||
|
|
||||||
// xpack modules are installed in real clusters as the meta plugin, so
|
// xpack modules are installed in real clusters as the meta plugin, so
|
||||||
// installing them as individual plugins for integ tests doesn't make sense,
|
// installing them as individual plugins for integ tests doesn't make sense,
|
||||||
// so we disable integ tests
|
// so we disable integ tests and there are no integ tests in xpack core module
|
||||||
integTest.enabled = false
|
integTest.enabled = false
|
||||||
|
|
||||||
// Instead we create a separate task to run the
|
|
||||||
// tests based on ESIntegTestCase
|
|
||||||
task internalClusterTest(type: RandomizedTestingTask,
|
|
||||||
group: JavaBasePlugin.VERIFICATION_GROUP,
|
|
||||||
description: 'Multi-node tests',
|
|
||||||
dependsOn: test.dependsOn) {
|
|
||||||
configure(BuildPlugin.commonTestConfig(project))
|
|
||||||
classpath = project.test.classpath
|
|
||||||
testClassesDir = project.test.testClassesDir
|
|
||||||
include '**/*IT.class'
|
|
||||||
systemProperty 'es.set.netty.runtime.available.processors', 'false'
|
|
||||||
}
|
|
||||||
check.dependsOn internalClusterTest
|
|
||||||
internalClusterTest.mustRunAfter test
|
|
||||||
|
|
||||||
// also add an "alias" task to make typing on the command line easier
|
|
||||||
task icTest {
|
|
||||||
dependsOn internalClusterTest
|
|
||||||
}
|
|
||||||
|
|
|
@ -85,13 +85,13 @@ import org.elasticsearch.xpack.core.ml.action.ValidateJobConfigAction;
|
||||||
import org.elasticsearch.xpack.core.ml.datafeed.DatafeedState;
|
import org.elasticsearch.xpack.core.ml.datafeed.DatafeedState;
|
||||||
import org.elasticsearch.xpack.core.ml.job.config.JobTaskStatus;
|
import org.elasticsearch.xpack.core.ml.job.config.JobTaskStatus;
|
||||||
import org.elasticsearch.xpack.core.monitoring.MonitoringFeatureSetUsage;
|
import org.elasticsearch.xpack.core.monitoring.MonitoringFeatureSetUsage;
|
||||||
import org.elasticsearch.xpack.core.persistent.CompletionPersistentTaskAction;
|
import org.elasticsearch.persistent.CompletionPersistentTaskAction;
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTaskParams;
|
import org.elasticsearch.persistent.PersistentTaskParams;
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksCustomMetaData;
|
import org.elasticsearch.persistent.PersistentTasksCustomMetaData;
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksNodeService;
|
import org.elasticsearch.persistent.PersistentTasksNodeService;
|
||||||
import org.elasticsearch.xpack.core.persistent.RemovePersistentTaskAction;
|
import org.elasticsearch.persistent.RemovePersistentTaskAction;
|
||||||
import org.elasticsearch.xpack.core.persistent.StartPersistentTaskAction;
|
import org.elasticsearch.persistent.StartPersistentTaskAction;
|
||||||
import org.elasticsearch.xpack.core.persistent.UpdatePersistentTaskStatusAction;
|
import org.elasticsearch.persistent.UpdatePersistentTaskStatusAction;
|
||||||
import org.elasticsearch.xpack.core.security.SecurityFeatureSetUsage;
|
import org.elasticsearch.xpack.core.security.SecurityFeatureSetUsage;
|
||||||
import org.elasticsearch.xpack.core.security.SecurityField;
|
import org.elasticsearch.xpack.core.security.SecurityField;
|
||||||
import org.elasticsearch.xpack.core.security.SecuritySettings;
|
import org.elasticsearch.xpack.core.security.SecuritySettings;
|
||||||
|
|
|
@ -35,8 +35,8 @@ import org.elasticsearch.xpack.core.ml.job.messages.Messages;
|
||||||
import org.elasticsearch.xpack.core.ml.utils.ExceptionsHelper;
|
import org.elasticsearch.xpack.core.ml.utils.ExceptionsHelper;
|
||||||
import org.elasticsearch.xpack.core.ml.utils.NameResolver;
|
import org.elasticsearch.xpack.core.ml.utils.NameResolver;
|
||||||
import org.elasticsearch.xpack.core.ml.utils.ToXContentParams;
|
import org.elasticsearch.xpack.core.ml.utils.ToXContentParams;
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksCustomMetaData;
|
import org.elasticsearch.persistent.PersistentTasksCustomMetaData;
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksCustomMetaData.PersistentTask;
|
import org.elasticsearch.persistent.PersistentTasksCustomMetaData.PersistentTask;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
|
|
@ -26,7 +26,7 @@ import org.elasticsearch.tasks.Task;
|
||||||
import org.elasticsearch.xpack.core.ml.MachineLearningField;
|
import org.elasticsearch.xpack.core.ml.MachineLearningField;
|
||||||
import org.elasticsearch.xpack.core.ml.job.config.Job;
|
import org.elasticsearch.xpack.core.ml.job.config.Job;
|
||||||
import org.elasticsearch.xpack.core.ml.utils.ExceptionsHelper;
|
import org.elasticsearch.xpack.core.ml.utils.ExceptionsHelper;
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTaskParams;
|
import org.elasticsearch.persistent.PersistentTaskParams;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
|
@ -27,7 +27,7 @@ import org.elasticsearch.index.mapper.DateFieldMapper;
|
||||||
import org.elasticsearch.xpack.core.ml.datafeed.DatafeedConfig;
|
import org.elasticsearch.xpack.core.ml.datafeed.DatafeedConfig;
|
||||||
import org.elasticsearch.xpack.core.ml.job.messages.Messages;
|
import org.elasticsearch.xpack.core.ml.job.messages.Messages;
|
||||||
import org.elasticsearch.xpack.core.ml.utils.ExceptionsHelper;
|
import org.elasticsearch.xpack.core.ml.utils.ExceptionsHelper;
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTaskParams;
|
import org.elasticsearch.persistent.PersistentTaskParams;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
|
@ -14,7 +14,7 @@ import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||||
import org.elasticsearch.common.xcontent.XContentParser;
|
import org.elasticsearch.common.xcontent.XContentParser;
|
||||||
import org.elasticsearch.tasks.Task;
|
import org.elasticsearch.tasks.Task;
|
||||||
import org.elasticsearch.xpack.core.ml.action.OpenJobAction;
|
import org.elasticsearch.xpack.core.ml.action.OpenJobAction;
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksCustomMetaData.PersistentTask;
|
import org.elasticsearch.persistent.PersistentTasksCustomMetaData.PersistentTask;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
|
@ -5,6 +5,8 @@
|
||||||
*/
|
*/
|
||||||
package org.elasticsearch.xpack.core.ml.utils.time;
|
package org.elasticsearch.xpack.core.ml.utils.time;
|
||||||
|
|
||||||
|
import org.elasticsearch.cli.SuppressForbidden;
|
||||||
|
|
||||||
import java.time.DateTimeException;
|
import java.time.DateTimeException;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
|
@ -84,6 +86,11 @@ public class DateTimeFormatterTimestampConverter implements TimestampConverter {
|
||||||
if (hasTimeZone) {
|
if (hasTimeZone) {
|
||||||
return Instant.from(parsed);
|
return Instant.from(parsed);
|
||||||
}
|
}
|
||||||
|
return toInstantUnsafelyIgnoringAmbiguity(parsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressForbidden(reason = "TODO https://github.com/elastic/x-pack-elasticsearch/issues/3810")
|
||||||
|
private Instant toInstantUnsafelyIgnoringAmbiguity(TemporalAccessor parsed) {
|
||||||
return LocalDateTime.from(parsed).atZone(defaultZoneId).toInstant();
|
return LocalDateTime.from(parsed).atZone(defaultZoneId).toInstant();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,165 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.xpack.core.persistent;
|
|
||||||
|
|
||||||
import org.apache.logging.log4j.Logger;
|
|
||||||
import org.apache.logging.log4j.message.ParameterizedMessage;
|
|
||||||
import org.apache.logging.log4j.util.Supplier;
|
|
||||||
import org.elasticsearch.action.ActionListener;
|
|
||||||
import org.elasticsearch.action.admin.cluster.node.tasks.cancel.CancelTasksRequest;
|
|
||||||
import org.elasticsearch.common.Nullable;
|
|
||||||
import org.elasticsearch.common.unit.TimeValue;
|
|
||||||
import org.elasticsearch.tasks.CancellableTask;
|
|
||||||
import org.elasticsearch.tasks.Task;
|
|
||||||
import org.elasticsearch.tasks.TaskId;
|
|
||||||
import org.elasticsearch.tasks.TaskManager;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
|
||||||
import java.util.function.Predicate;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents a executor node operation that corresponds to a persistent task
|
|
||||||
*/
|
|
||||||
public class AllocatedPersistentTask extends CancellableTask {
|
|
||||||
private volatile String persistentTaskId;
|
|
||||||
private volatile long allocationId;
|
|
||||||
|
|
||||||
private final AtomicReference<State> state;
|
|
||||||
@Nullable
|
|
||||||
private volatile Exception failure;
|
|
||||||
|
|
||||||
private volatile PersistentTasksService persistentTasksService;
|
|
||||||
private volatile Logger logger;
|
|
||||||
private volatile TaskManager taskManager;
|
|
||||||
|
|
||||||
|
|
||||||
public AllocatedPersistentTask(long id, String type, String action, String description, TaskId parentTask,
|
|
||||||
Map<String, String> headers) {
|
|
||||||
super(id, type, action, description, parentTask, headers);
|
|
||||||
this.state = new AtomicReference<>(State.STARTED);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean shouldCancelChildrenOnCancellation() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// In case of persistent tasks we always need to return: `false`
|
|
||||||
// because in case of persistent task the parent task isn't a task in the task manager, but in cluster state.
|
|
||||||
// This instructs the task manager not to try to kill this persistent task when the task manager cannot find
|
|
||||||
// a fake parent node id "cluster" in the cluster state
|
|
||||||
@Override
|
|
||||||
public final boolean cancelOnParentLeaving() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Status getStatus() {
|
|
||||||
return new PersistentTasksNodeService.Status(state.get());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the persistent state for the corresponding persistent task.
|
|
||||||
* <p>
|
|
||||||
* This doesn't affect the status of this allocated task.
|
|
||||||
*/
|
|
||||||
public void updatePersistentStatus(Task.Status status, ActionListener<PersistentTasksCustomMetaData.PersistentTask<?>> listener) {
|
|
||||||
persistentTasksService.updateStatus(persistentTaskId, allocationId, status, listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getPersistentTaskId() {
|
|
||||||
return persistentTaskId;
|
|
||||||
}
|
|
||||||
|
|
||||||
void init(PersistentTasksService persistentTasksService, TaskManager taskManager, Logger logger, String persistentTaskId, long
|
|
||||||
allocationId) {
|
|
||||||
this.persistentTasksService = persistentTasksService;
|
|
||||||
this.logger = logger;
|
|
||||||
this.taskManager = taskManager;
|
|
||||||
this.persistentTaskId = persistentTaskId;
|
|
||||||
this.allocationId = allocationId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Exception getFailure() {
|
|
||||||
return failure;
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean markAsCancelled() {
|
|
||||||
return state.compareAndSet(AllocatedPersistentTask.State.STARTED, AllocatedPersistentTask.State.PENDING_CANCEL);
|
|
||||||
}
|
|
||||||
|
|
||||||
public State getState() {
|
|
||||||
return state.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getAllocationId() {
|
|
||||||
return allocationId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum State {
|
|
||||||
STARTED, // the task is currently running
|
|
||||||
PENDING_CANCEL, // the task is cancelled on master, cancelling it locally
|
|
||||||
COMPLETED // the task is done running and trying to notify caller
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Waits for this persistent task to have the desired state.
|
|
||||||
*/
|
|
||||||
public void waitForPersistentTaskStatus(Predicate<PersistentTasksCustomMetaData.PersistentTask<?>> predicate,
|
|
||||||
@Nullable TimeValue timeout,
|
|
||||||
PersistentTasksService.WaitForPersistentTaskStatusListener<?> listener) {
|
|
||||||
persistentTasksService.waitForPersistentTaskStatus(persistentTaskId, predicate, timeout, listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void markAsCompleted() {
|
|
||||||
completeAndNotifyIfNeeded(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void markAsFailed(Exception e) {
|
|
||||||
if (CancelTasksRequest.DEFAULT_REASON.equals(getReasonCancelled())) {
|
|
||||||
completeAndNotifyIfNeeded(null);
|
|
||||||
} else {
|
|
||||||
completeAndNotifyIfNeeded(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private void completeAndNotifyIfNeeded(@Nullable Exception failure) {
|
|
||||||
State prevState = state.getAndSet(AllocatedPersistentTask.State.COMPLETED);
|
|
||||||
if (prevState == State.COMPLETED) {
|
|
||||||
logger.warn("attempt to complete task [{}] with id [{}] in the [{}] state", getAction(), getPersistentTaskId(), prevState);
|
|
||||||
} else {
|
|
||||||
if (failure != null) {
|
|
||||||
logger.warn((Supplier<?>) () -> new ParameterizedMessage(
|
|
||||||
"task {} failed with an exception", getPersistentTaskId()), failure);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
this.failure = failure;
|
|
||||||
if (prevState == State.STARTED) {
|
|
||||||
logger.trace("sending notification for completed task [{}] with id [{}]", getAction(), getPersistentTaskId());
|
|
||||||
persistentTasksService.sendCompletionNotification(getPersistentTaskId(), getAllocationId(), failure, new
|
|
||||||
ActionListener<PersistentTasksCustomMetaData.PersistentTask<?>>() {
|
|
||||||
@Override
|
|
||||||
public void onResponse(PersistentTasksCustomMetaData.PersistentTask<?> persistentTask) {
|
|
||||||
logger.trace("notification for task [{}] with id [{}] was successful", getAction(),
|
|
||||||
getPersistentTaskId());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(Exception e) {
|
|
||||||
logger.warn((Supplier<?>) () ->
|
|
||||||
new ParameterizedMessage("notification for task [{}] with id [{}] failed",
|
|
||||||
getAction(), getPersistentTaskId()), e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
taskManager.unregister(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,178 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.xpack.core.persistent;
|
|
||||||
|
|
||||||
import org.elasticsearch.action.Action;
|
|
||||||
import org.elasticsearch.action.ActionListener;
|
|
||||||
import org.elasticsearch.action.ActionRequestValidationException;
|
|
||||||
import org.elasticsearch.action.support.ActionFilters;
|
|
||||||
import org.elasticsearch.action.support.master.MasterNodeOperationRequestBuilder;
|
|
||||||
import org.elasticsearch.action.support.master.MasterNodeRequest;
|
|
||||||
import org.elasticsearch.action.support.master.TransportMasterNodeAction;
|
|
||||||
import org.elasticsearch.client.ElasticsearchClient;
|
|
||||||
import org.elasticsearch.cluster.ClusterState;
|
|
||||||
import org.elasticsearch.cluster.block.ClusterBlockException;
|
|
||||||
import org.elasticsearch.cluster.block.ClusterBlockLevel;
|
|
||||||
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
|
|
||||||
import org.elasticsearch.cluster.service.ClusterService;
|
|
||||||
import org.elasticsearch.common.inject.Inject;
|
|
||||||
import org.elasticsearch.common.io.stream.StreamInput;
|
|
||||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
|
||||||
import org.elasticsearch.common.settings.Settings;
|
|
||||||
import org.elasticsearch.threadpool.ThreadPool;
|
|
||||||
import org.elasticsearch.transport.TransportService;
|
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksCustomMetaData.PersistentTask;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
import static org.elasticsearch.action.ValidateActions.addValidationError;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Action that is used by executor node to indicate that the persistent action finished or failed on the node and needs to be
|
|
||||||
* removed from the cluster state in case of successful completion or restarted on some other node in case of failure.
|
|
||||||
*/
|
|
||||||
public class CompletionPersistentTaskAction extends Action<CompletionPersistentTaskAction.Request,
|
|
||||||
PersistentTaskResponse,
|
|
||||||
CompletionPersistentTaskAction.RequestBuilder> {
|
|
||||||
|
|
||||||
public static final CompletionPersistentTaskAction INSTANCE = new CompletionPersistentTaskAction();
|
|
||||||
public static final String NAME = "cluster:admin/persistent/completion";
|
|
||||||
|
|
||||||
private CompletionPersistentTaskAction() {
|
|
||||||
super(NAME);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public RequestBuilder newRequestBuilder(ElasticsearchClient client) {
|
|
||||||
return new RequestBuilder(client, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PersistentTaskResponse newResponse() {
|
|
||||||
return new PersistentTaskResponse();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Request extends MasterNodeRequest<Request> {
|
|
||||||
|
|
||||||
private String taskId;
|
|
||||||
|
|
||||||
private Exception exception;
|
|
||||||
|
|
||||||
private long allocationId = -1;
|
|
||||||
|
|
||||||
public Request() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public Request(String taskId, long allocationId, Exception exception) {
|
|
||||||
this.taskId = taskId;
|
|
||||||
this.exception = exception;
|
|
||||||
this.allocationId = allocationId;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void readFrom(StreamInput in) throws IOException {
|
|
||||||
super.readFrom(in);
|
|
||||||
taskId = in.readString();
|
|
||||||
allocationId = in.readLong();
|
|
||||||
exception = in.readException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeTo(StreamOutput out) throws IOException {
|
|
||||||
super.writeTo(out);
|
|
||||||
out.writeString(taskId);
|
|
||||||
out.writeLong(allocationId);
|
|
||||||
out.writeException(exception);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ActionRequestValidationException validate() {
|
|
||||||
ActionRequestValidationException validationException = null;
|
|
||||||
if (taskId == null) {
|
|
||||||
validationException = addValidationError("task id is missing", validationException);
|
|
||||||
}
|
|
||||||
if (allocationId < 0) {
|
|
||||||
validationException = addValidationError("allocation id is negative or missing", validationException);
|
|
||||||
}
|
|
||||||
return validationException;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) return true;
|
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
|
||||||
Request request = (Request) o;
|
|
||||||
return Objects.equals(taskId, request.taskId) &&
|
|
||||||
allocationId == request.allocationId &&
|
|
||||||
Objects.equals(exception, request.exception);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return Objects.hash(taskId, allocationId, exception);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class RequestBuilder extends MasterNodeOperationRequestBuilder<CompletionPersistentTaskAction.Request,
|
|
||||||
PersistentTaskResponse, CompletionPersistentTaskAction.RequestBuilder> {
|
|
||||||
|
|
||||||
protected RequestBuilder(ElasticsearchClient client, CompletionPersistentTaskAction action) {
|
|
||||||
super(client, action, new Request());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class TransportAction extends TransportMasterNodeAction<Request, PersistentTaskResponse> {
|
|
||||||
|
|
||||||
private final PersistentTasksClusterService persistentTasksClusterService;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public TransportAction(Settings settings, TransportService transportService, ClusterService clusterService,
|
|
||||||
ThreadPool threadPool, ActionFilters actionFilters,
|
|
||||||
PersistentTasksClusterService persistentTasksClusterService,
|
|
||||||
IndexNameExpressionResolver indexNameExpressionResolver) {
|
|
||||||
super(settings, CompletionPersistentTaskAction.NAME, transportService, clusterService, threadPool, actionFilters,
|
|
||||||
indexNameExpressionResolver, Request::new);
|
|
||||||
this.persistentTasksClusterService = persistentTasksClusterService;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String executor() {
|
|
||||||
return ThreadPool.Names.GENERIC;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected PersistentTaskResponse newResponse() {
|
|
||||||
return new PersistentTaskResponse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected ClusterBlockException checkBlock(Request request, ClusterState state) {
|
|
||||||
// Cluster is not affected but we look up repositories in metadata
|
|
||||||
return state.blocks().globalBlockedException(ClusterBlockLevel.METADATA_WRITE);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected final void masterOperation(final Request request, ClusterState state,
|
|
||||||
final ActionListener<PersistentTaskResponse> listener) {
|
|
||||||
persistentTasksClusterService.completePersistentTask(request.taskId, request.allocationId, request.exception,
|
|
||||||
new ActionListener<PersistentTask<?>>() {
|
|
||||||
@Override
|
|
||||||
public void onResponse(PersistentTask<?> task) {
|
|
||||||
listener.onResponse(new PersistentTaskResponse(task));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(Exception e) {
|
|
||||||
listener.onFailure(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
@ -1,49 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.xpack.core.persistent;
|
|
||||||
|
|
||||||
import org.elasticsearch.common.Nullable;
|
|
||||||
import org.elasticsearch.common.util.concurrent.AbstractRunnable;
|
|
||||||
import org.elasticsearch.tasks.Task;
|
|
||||||
import org.elasticsearch.threadpool.ThreadPool;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This component is responsible for execution of persistent tasks.
|
|
||||||
*
|
|
||||||
* It abstracts away the execution of tasks and greatly simplifies testing of PersistentTasksNodeService
|
|
||||||
*/
|
|
||||||
public class NodePersistentTasksExecutor {
|
|
||||||
private final ThreadPool threadPool;
|
|
||||||
|
|
||||||
public NodePersistentTasksExecutor(ThreadPool threadPool) {
|
|
||||||
this.threadPool = threadPool;
|
|
||||||
}
|
|
||||||
|
|
||||||
public <Params extends PersistentTaskParams> void executeTask(@Nullable Params params,
|
|
||||||
@Nullable Task.Status status,
|
|
||||||
AllocatedPersistentTask task,
|
|
||||||
PersistentTasksExecutor<Params> executor) {
|
|
||||||
threadPool.executor(executor.getExecutor()).execute(new AbstractRunnable() {
|
|
||||||
@Override
|
|
||||||
public void onFailure(Exception e) {
|
|
||||||
task.markAsFailed(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
@Override
|
|
||||||
protected void doRun() throws Exception {
|
|
||||||
try {
|
|
||||||
executor.nodeOperation(task, params, status);
|
|
||||||
} catch (Exception ex) {
|
|
||||||
task.markAsFailed(ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.xpack.core.persistent;
|
|
||||||
|
|
||||||
import org.elasticsearch.common.io.stream.NamedWriteable;
|
|
||||||
import org.elasticsearch.common.xcontent.ToXContentObject;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parameters used to start persistent task
|
|
||||||
*/
|
|
||||||
public interface PersistentTaskParams extends NamedWriteable, ToXContentObject {
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,58 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.xpack.core.persistent;
|
|
||||||
|
|
||||||
import org.elasticsearch.action.ActionResponse;
|
|
||||||
import org.elasticsearch.common.io.stream.StreamInput;
|
|
||||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksCustomMetaData.PersistentTask;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Response upon a successful start or an persistent task
|
|
||||||
*/
|
|
||||||
public class PersistentTaskResponse extends ActionResponse {
|
|
||||||
private PersistentTask<?> task;
|
|
||||||
|
|
||||||
public PersistentTaskResponse() {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
public PersistentTaskResponse(PersistentTask<?> task) {
|
|
||||||
this.task = task;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void readFrom(StreamInput in) throws IOException {
|
|
||||||
super.readFrom(in);
|
|
||||||
task = in.readOptionalWriteable(PersistentTask::new);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeTo(StreamOutput out) throws IOException {
|
|
||||||
super.writeTo(out);
|
|
||||||
out.writeOptionalWriteable(task);
|
|
||||||
}
|
|
||||||
|
|
||||||
public PersistentTask<?> getTask() {
|
|
||||||
return task;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) return true;
|
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
|
||||||
PersistentTaskResponse that = (PersistentTaskResponse) o;
|
|
||||||
return Objects.equals(task, that.task);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return Objects.hash(task);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,315 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.xpack.core.persistent;
|
|
||||||
|
|
||||||
import org.apache.logging.log4j.Logger;
|
|
||||||
import org.elasticsearch.ResourceAlreadyExistsException;
|
|
||||||
import org.elasticsearch.ResourceNotFoundException;
|
|
||||||
import org.elasticsearch.action.ActionListener;
|
|
||||||
import org.elasticsearch.cluster.ClusterChangedEvent;
|
|
||||||
import org.elasticsearch.cluster.ClusterState;
|
|
||||||
import org.elasticsearch.cluster.ClusterStateListener;
|
|
||||||
import org.elasticsearch.cluster.ClusterStateUpdateTask;
|
|
||||||
import org.elasticsearch.cluster.metadata.MetaData;
|
|
||||||
import org.elasticsearch.cluster.node.DiscoveryNodes;
|
|
||||||
import org.elasticsearch.cluster.service.ClusterService;
|
|
||||||
import org.elasticsearch.common.Nullable;
|
|
||||||
import org.elasticsearch.common.component.AbstractComponent;
|
|
||||||
import org.elasticsearch.common.settings.Settings;
|
|
||||||
import org.elasticsearch.tasks.Task;
|
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksCustomMetaData.Assignment;
|
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksCustomMetaData.PersistentTask;
|
|
||||||
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Component that runs only on the master node and is responsible for assigning running tasks to nodes
|
|
||||||
*/
|
|
||||||
public class PersistentTasksClusterService extends AbstractComponent implements ClusterStateListener {
|
|
||||||
|
|
||||||
private final ClusterService clusterService;
|
|
||||||
private final PersistentTasksExecutorRegistry registry;
|
|
||||||
|
|
||||||
public PersistentTasksClusterService(Settings settings, PersistentTasksExecutorRegistry registry, ClusterService clusterService) {
|
|
||||||
super(settings);
|
|
||||||
this.clusterService = clusterService;
|
|
||||||
clusterService.addListener(this);
|
|
||||||
this.registry = registry;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new persistent task on master node
|
|
||||||
*
|
|
||||||
* @param action the action name
|
|
||||||
* @param params params
|
|
||||||
* @param listener the listener that will be called when task is started
|
|
||||||
*/
|
|
||||||
public <Params extends PersistentTaskParams> void createPersistentTask(String taskId, String action, @Nullable Params params,
|
|
||||||
ActionListener<PersistentTask<?>> listener) {
|
|
||||||
clusterService.submitStateUpdateTask("create persistent task", new ClusterStateUpdateTask() {
|
|
||||||
@Override
|
|
||||||
public ClusterState execute(ClusterState currentState) throws Exception {
|
|
||||||
PersistentTasksCustomMetaData.Builder builder = builder(currentState);
|
|
||||||
if (builder.hasTask(taskId)) {
|
|
||||||
throw new ResourceAlreadyExistsException("task with id {" + taskId + "} already exist");
|
|
||||||
}
|
|
||||||
validate(action, currentState, params);
|
|
||||||
final Assignment assignment;
|
|
||||||
assignment = getAssignement(action, currentState, params);
|
|
||||||
return update(currentState, builder.addTask(taskId, action, params, assignment));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(String source, Exception e) {
|
|
||||||
listener.onFailure(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
@Override
|
|
||||||
public void clusterStateProcessed(String source, ClusterState oldState, ClusterState newState) {
|
|
||||||
PersistentTasksCustomMetaData tasks = newState.getMetaData().custom(PersistentTasksCustomMetaData.TYPE);
|
|
||||||
if (tasks != null) {
|
|
||||||
listener.onResponse(tasks.getTask(taskId));
|
|
||||||
} else {
|
|
||||||
listener.onResponse(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Restarts a record about a running persistent task from cluster state
|
|
||||||
*
|
|
||||||
* @param id the id of the persistent task
|
|
||||||
* @param allocationId the allocation id of the persistent task
|
|
||||||
* @param failure the reason for restarting the task or null if the task completed successfully
|
|
||||||
* @param listener the listener that will be called when task is removed
|
|
||||||
*/
|
|
||||||
public void completePersistentTask(String id, long allocationId, Exception failure, ActionListener<PersistentTask<?>> listener) {
|
|
||||||
final String source;
|
|
||||||
if (failure != null) {
|
|
||||||
logger.warn("persistent task " + id + " failed", failure);
|
|
||||||
source = "finish persistent task (failed)";
|
|
||||||
} else {
|
|
||||||
source = "finish persistent task (success)";
|
|
||||||
}
|
|
||||||
clusterService.submitStateUpdateTask(source, new ClusterStateUpdateTask() {
|
|
||||||
@Override
|
|
||||||
public ClusterState execute(ClusterState currentState) throws Exception {
|
|
||||||
PersistentTasksCustomMetaData.Builder tasksInProgress = builder(currentState);
|
|
||||||
if (tasksInProgress.hasTask(id, allocationId)) {
|
|
||||||
tasksInProgress.finishTask(id);
|
|
||||||
return update(currentState, tasksInProgress);
|
|
||||||
} else {
|
|
||||||
if (tasksInProgress.hasTask(id)) {
|
|
||||||
logger.warn("The task [{}] with id [{}] was found but it has a different allocation id [{}], status is not updated",
|
|
||||||
PersistentTasksCustomMetaData.getTaskWithId(currentState, id).getTaskName(), id, allocationId);
|
|
||||||
} else {
|
|
||||||
logger.warn("The task [{}] wasn't found, status is not updated", id);
|
|
||||||
}
|
|
||||||
throw new ResourceNotFoundException("the task with id [" + id + "] and allocation id [" + allocationId + "] not found");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(String source, Exception e) {
|
|
||||||
listener.onFailure(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void clusterStateProcessed(String source, ClusterState oldState, ClusterState newState) {
|
|
||||||
// Using old state since in the new state the task is already gone
|
|
||||||
listener.onResponse(PersistentTasksCustomMetaData.getTaskWithId(oldState, id));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes the persistent task
|
|
||||||
*
|
|
||||||
* @param id the id of a persistent task
|
|
||||||
* @param listener the listener that will be called when task is removed
|
|
||||||
*/
|
|
||||||
public void removePersistentTask(String id, ActionListener<PersistentTask<?>> listener) {
|
|
||||||
clusterService.submitStateUpdateTask("remove persistent task", new ClusterStateUpdateTask() {
|
|
||||||
@Override
|
|
||||||
public ClusterState execute(ClusterState currentState) throws Exception {
|
|
||||||
PersistentTasksCustomMetaData.Builder tasksInProgress = builder(currentState);
|
|
||||||
if (tasksInProgress.hasTask(id)) {
|
|
||||||
return update(currentState, tasksInProgress.removeTask(id));
|
|
||||||
} else {
|
|
||||||
throw new ResourceNotFoundException("the task with id {} doesn't exist", id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(String source, Exception e) {
|
|
||||||
listener.onFailure(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void clusterStateProcessed(String source, ClusterState oldState, ClusterState newState) {
|
|
||||||
// Using old state since in the new state the task is already gone
|
|
||||||
listener.onResponse(PersistentTasksCustomMetaData.getTaskWithId(oldState, id));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update task status
|
|
||||||
*
|
|
||||||
* @param id the id of a persistent task
|
|
||||||
* @param allocationId the expected allocation id of the persistent task
|
|
||||||
* @param status new status
|
|
||||||
* @param listener the listener that will be called when task is removed
|
|
||||||
*/
|
|
||||||
public void updatePersistentTaskStatus(String id, long allocationId, Task.Status status, ActionListener<PersistentTask<?>> listener) {
|
|
||||||
clusterService.submitStateUpdateTask("update task status", new ClusterStateUpdateTask() {
|
|
||||||
@Override
|
|
||||||
public ClusterState execute(ClusterState currentState) throws Exception {
|
|
||||||
PersistentTasksCustomMetaData.Builder tasksInProgress = builder(currentState);
|
|
||||||
if (tasksInProgress.hasTask(id, allocationId)) {
|
|
||||||
return update(currentState, tasksInProgress.updateTaskStatus(id, status));
|
|
||||||
} else {
|
|
||||||
if (tasksInProgress.hasTask(id)) {
|
|
||||||
logger.warn("trying to update status on task {} with unexpected allocation id {}", id, allocationId);
|
|
||||||
} else {
|
|
||||||
logger.warn("trying to update status on non-existing task {}", id);
|
|
||||||
}
|
|
||||||
throw new ResourceNotFoundException("the task with id {} and allocation id {} doesn't exist", id, allocationId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(String source, Exception e) {
|
|
||||||
listener.onFailure(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void clusterStateProcessed(String source, ClusterState oldState, ClusterState newState) {
|
|
||||||
listener.onResponse(PersistentTasksCustomMetaData.getTaskWithId(newState, id));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private <Params extends PersistentTaskParams> Assignment getAssignement(String taskName, ClusterState currentState,
|
|
||||||
@Nullable Params params) {
|
|
||||||
PersistentTasksExecutor<Params> persistentTasksExecutor = registry.getPersistentTaskExecutorSafe(taskName);
|
|
||||||
return persistentTasksExecutor.getAssignment(params, currentState);
|
|
||||||
}
|
|
||||||
|
|
||||||
private <Params extends PersistentTaskParams> void validate(String taskName, ClusterState currentState, @Nullable Params params) {
|
|
||||||
PersistentTasksExecutor<Params> persistentTasksExecutor = registry.getPersistentTaskExecutorSafe(taskName);
|
|
||||||
persistentTasksExecutor.validate(params, currentState);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void clusterChanged(ClusterChangedEvent event) {
|
|
||||||
if (event.localNodeMaster()) {
|
|
||||||
logger.trace("checking task reassignment for cluster state {}", event.state().getVersion());
|
|
||||||
if (reassignmentRequired(event, this::getAssignement)) {
|
|
||||||
logger.trace("task reassignment is needed");
|
|
||||||
reassignTasks();
|
|
||||||
} else {
|
|
||||||
logger.trace("task reassignment is not needed");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ExecutorNodeDecider {
|
|
||||||
<Params extends PersistentTaskParams> Assignment getAssignment(String action, ClusterState currentState, Params params);
|
|
||||||
}
|
|
||||||
|
|
||||||
static boolean reassignmentRequired(ClusterChangedEvent event, ExecutorNodeDecider decider) {
|
|
||||||
PersistentTasksCustomMetaData tasks = event.state().getMetaData().custom(PersistentTasksCustomMetaData.TYPE);
|
|
||||||
PersistentTasksCustomMetaData prevTasks = event.previousState().getMetaData().custom(PersistentTasksCustomMetaData.TYPE);
|
|
||||||
if (tasks != null && (Objects.equals(tasks, prevTasks) == false ||
|
|
||||||
event.nodesChanged() ||
|
|
||||||
event.routingTableChanged() ||
|
|
||||||
event.previousState().nodes().isLocalNodeElectedMaster() == false)) {
|
|
||||||
// We need to check if removed nodes were running any of the tasks and reassign them
|
|
||||||
boolean reassignmentRequired = false;
|
|
||||||
for (PersistentTask<?> taskInProgress : tasks.tasks()) {
|
|
||||||
if (taskInProgress.needsReassignment(event.state().nodes())) {
|
|
||||||
// there is an unassigned task or task with a disappeared node - we need to try assigning it
|
|
||||||
if (Objects.equals(taskInProgress.getAssignment(),
|
|
||||||
decider.getAssignment(taskInProgress.getTaskName(), event.state(), taskInProgress.getParams())) == false) {
|
|
||||||
// it looks like a assignment for at least one task is possible - let's trigger reassignment
|
|
||||||
reassignmentRequired = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return reassignmentRequired;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Evaluates the cluster state and tries to assign tasks to nodes
|
|
||||||
*/
|
|
||||||
public void reassignTasks() {
|
|
||||||
clusterService.submitStateUpdateTask("reassign persistent tasks", new ClusterStateUpdateTask() {
|
|
||||||
@Override
|
|
||||||
public ClusterState execute(ClusterState currentState) throws Exception {
|
|
||||||
return reassignTasks(currentState, logger, PersistentTasksClusterService.this::getAssignement);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(String source, Exception e) {
|
|
||||||
logger.warn("Unsuccessful persistent task reassignment", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void clusterStateProcessed(String source, ClusterState oldState, ClusterState newState) {
|
|
||||||
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
static ClusterState reassignTasks(ClusterState currentState, Logger logger, ExecutorNodeDecider decider) {
|
|
||||||
PersistentTasksCustomMetaData tasks = currentState.getMetaData().custom(PersistentTasksCustomMetaData.TYPE);
|
|
||||||
ClusterState clusterState = currentState;
|
|
||||||
DiscoveryNodes nodes = currentState.nodes();
|
|
||||||
if (tasks != null) {
|
|
||||||
logger.trace("reassigning {} persistent tasks", tasks.tasks().size());
|
|
||||||
// We need to check if removed nodes were running any of the tasks and reassign them
|
|
||||||
for (PersistentTask<?> task : tasks.tasks()) {
|
|
||||||
if (task.needsReassignment(nodes)) {
|
|
||||||
// there is an unassigned task - we need to try assigning it
|
|
||||||
Assignment assignment = decider.getAssignment(task.getTaskName(), clusterState, task.getParams());
|
|
||||||
if (Objects.equals(assignment, task.getAssignment()) == false) {
|
|
||||||
logger.trace("reassigning task {} from node {} to node {}", task.getId(),
|
|
||||||
task.getAssignment().getExecutorNode(), assignment.getExecutorNode());
|
|
||||||
clusterState = update(clusterState, builder(clusterState).reassignTask(task.getId(), assignment));
|
|
||||||
} else {
|
|
||||||
logger.trace("ignoring task {} because assignment is the same {}", task.getId(), assignment);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
logger.trace("ignoring task {} because it is still running", task.getId());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return clusterState;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static PersistentTasksCustomMetaData.Builder builder(ClusterState currentState) {
|
|
||||||
return PersistentTasksCustomMetaData.builder(currentState.getMetaData().custom(PersistentTasksCustomMetaData.TYPE));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ClusterState update(ClusterState currentState, PersistentTasksCustomMetaData.Builder tasksInProgress) {
|
|
||||||
if (tasksInProgress.isChanged()) {
|
|
||||||
return ClusterState.builder(currentState).metaData(
|
|
||||||
MetaData.builder(currentState.metaData()).putCustom(PersistentTasksCustomMetaData.TYPE, tasksInProgress.build())
|
|
||||||
).build();
|
|
||||||
} else {
|
|
||||||
return currentState;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,679 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.xpack.core.persistent;
|
|
||||||
|
|
||||||
import org.elasticsearch.ResourceAlreadyExistsException;
|
|
||||||
import org.elasticsearch.ResourceNotFoundException;
|
|
||||||
import org.elasticsearch.Version;
|
|
||||||
import org.elasticsearch.cluster.AbstractNamedDiffable;
|
|
||||||
import org.elasticsearch.cluster.ClusterState;
|
|
||||||
import org.elasticsearch.cluster.NamedDiff;
|
|
||||||
import org.elasticsearch.cluster.metadata.MetaData;
|
|
||||||
import org.elasticsearch.cluster.node.DiscoveryNodes;
|
|
||||||
import org.elasticsearch.common.Nullable;
|
|
||||||
import org.elasticsearch.common.ParseField;
|
|
||||||
import org.elasticsearch.common.Strings;
|
|
||||||
import org.elasticsearch.common.io.stream.StreamInput;
|
|
||||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
|
||||||
import org.elasticsearch.common.io.stream.Writeable;
|
|
||||||
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
|
|
||||||
import org.elasticsearch.common.xcontent.ObjectParser;
|
|
||||||
import org.elasticsearch.common.xcontent.ObjectParser.NamedObjectParser;
|
|
||||||
import org.elasticsearch.common.xcontent.ToXContent;
|
|
||||||
import org.elasticsearch.common.xcontent.ToXContentObject;
|
|
||||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
|
||||||
import org.elasticsearch.common.xcontent.XContentParser;
|
|
||||||
import org.elasticsearch.tasks.Task;
|
|
||||||
import org.elasticsearch.tasks.Task.Status;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.EnumSet;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.function.Predicate;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import static org.elasticsearch.cluster.metadata.MetaData.ALL_CONTEXTS;
|
|
||||||
import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A cluster state record that contains a list of all running persistent tasks
|
|
||||||
*/
|
|
||||||
public final class PersistentTasksCustomMetaData extends AbstractNamedDiffable<MetaData.Custom> implements MetaData.Custom {
|
|
||||||
public static final String TYPE = "persistent_tasks";
|
|
||||||
|
|
||||||
private static final String API_CONTEXT = MetaData.XContentContext.API.toString();
|
|
||||||
|
|
||||||
// TODO: Implement custom Diff for tasks
|
|
||||||
private final Map<String, PersistentTask<?>> tasks;
|
|
||||||
|
|
||||||
private final long lastAllocationId;
|
|
||||||
|
|
||||||
public PersistentTasksCustomMetaData(long lastAllocationId, Map<String, PersistentTask<?>> tasks) {
|
|
||||||
this.lastAllocationId = lastAllocationId;
|
|
||||||
this.tasks = tasks;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final ObjectParser<Builder, Void> PERSISTENT_TASKS_PARSER = new ObjectParser<>(TYPE, Builder::new);
|
|
||||||
|
|
||||||
private static final ObjectParser<TaskBuilder<PersistentTaskParams>, Void> PERSISTENT_TASK_PARSER =
|
|
||||||
new ObjectParser<>("tasks", TaskBuilder::new);
|
|
||||||
|
|
||||||
public static final ConstructingObjectParser<Assignment, Void> ASSIGNMENT_PARSER =
|
|
||||||
new ConstructingObjectParser<>("assignment", objects -> new Assignment((String) objects[0], (String) objects[1]));
|
|
||||||
|
|
||||||
private static final NamedObjectParser<TaskDescriptionBuilder<PersistentTaskParams>, Void> TASK_DESCRIPTION_PARSER;
|
|
||||||
|
|
||||||
static {
|
|
||||||
// Tasks parser initialization
|
|
||||||
PERSISTENT_TASKS_PARSER.declareLong(Builder::setLastAllocationId, new ParseField("last_allocation_id"));
|
|
||||||
PERSISTENT_TASKS_PARSER.declareObjectArray(Builder::setTasks, PERSISTENT_TASK_PARSER, new ParseField("tasks"));
|
|
||||||
|
|
||||||
// Task description parser initialization
|
|
||||||
ObjectParser<TaskDescriptionBuilder<PersistentTaskParams>, String> parser = new ObjectParser<>("named");
|
|
||||||
parser.declareObject(TaskDescriptionBuilder::setParams,
|
|
||||||
(p, c) -> p.namedObject(PersistentTaskParams.class, c, null), new ParseField("params"));
|
|
||||||
parser.declareObject(TaskDescriptionBuilder::setStatus,
|
|
||||||
(p, c) -> p.namedObject(Status.class, c, null), new ParseField("status"));
|
|
||||||
TASK_DESCRIPTION_PARSER = (XContentParser p, Void c, String name) -> parser.parse(p, new TaskDescriptionBuilder<>(name), name);
|
|
||||||
|
|
||||||
// Assignment parser
|
|
||||||
ASSIGNMENT_PARSER.declareStringOrNull(constructorArg(), new ParseField("executor_node"));
|
|
||||||
ASSIGNMENT_PARSER.declareStringOrNull(constructorArg(), new ParseField("explanation"));
|
|
||||||
|
|
||||||
// Task parser initialization
|
|
||||||
PERSISTENT_TASK_PARSER.declareString(TaskBuilder::setId, new ParseField("id"));
|
|
||||||
PERSISTENT_TASK_PARSER.declareString(TaskBuilder::setTaskName, new ParseField("name"));
|
|
||||||
PERSISTENT_TASK_PARSER.declareLong(TaskBuilder::setAllocationId, new ParseField("allocation_id"));
|
|
||||||
|
|
||||||
PERSISTENT_TASK_PARSER.declareNamedObjects(
|
|
||||||
(TaskBuilder<PersistentTaskParams> taskBuilder, List<TaskDescriptionBuilder<PersistentTaskParams>> objects) -> {
|
|
||||||
if (objects.size() != 1) {
|
|
||||||
throw new IllegalArgumentException("only one task description per task is allowed");
|
|
||||||
}
|
|
||||||
TaskDescriptionBuilder<PersistentTaskParams> builder = objects.get(0);
|
|
||||||
taskBuilder.setTaskName(builder.taskName);
|
|
||||||
taskBuilder.setParams(builder.params);
|
|
||||||
taskBuilder.setStatus(builder.status);
|
|
||||||
}, TASK_DESCRIPTION_PARSER, new ParseField("task"));
|
|
||||||
PERSISTENT_TASK_PARSER.declareObject(TaskBuilder::setAssignment, ASSIGNMENT_PARSER, new ParseField("assignment"));
|
|
||||||
PERSISTENT_TASK_PARSER.declareLong(TaskBuilder::setAllocationIdOnLastStatusUpdate,
|
|
||||||
new ParseField("allocation_id_on_last_status_update"));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Private builder used in XContent parser to build task-specific portion (params and status)
|
|
||||||
*/
|
|
||||||
private static class TaskDescriptionBuilder<Params extends PersistentTaskParams> {
|
|
||||||
private final String taskName;
|
|
||||||
private Params params;
|
|
||||||
private Status status;
|
|
||||||
|
|
||||||
private TaskDescriptionBuilder(String taskName) {
|
|
||||||
this.taskName = taskName;
|
|
||||||
}
|
|
||||||
|
|
||||||
private TaskDescriptionBuilder setParams(Params params) {
|
|
||||||
this.params = params;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
private TaskDescriptionBuilder setStatus(Status status) {
|
|
||||||
this.status = status;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public Collection<PersistentTask<?>> tasks() {
|
|
||||||
return this.tasks.values();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map<String, PersistentTask<?>> taskMap() {
|
|
||||||
return this.tasks;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PersistentTask<?> getTask(String id) {
|
|
||||||
return this.tasks.get(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Collection<PersistentTask<?>> findTasks(String taskName, Predicate<PersistentTask<?>> predicate) {
|
|
||||||
return this.tasks().stream()
|
|
||||||
.filter(p -> taskName.equals(p.getTaskName()))
|
|
||||||
.filter(predicate)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean tasksExist(String taskName, Predicate<PersistentTask<?>> predicate) {
|
|
||||||
return this.tasks().stream()
|
|
||||||
.filter(p -> taskName.equals(p.getTaskName()))
|
|
||||||
.anyMatch(predicate);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) return true;
|
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
|
||||||
PersistentTasksCustomMetaData that = (PersistentTasksCustomMetaData) o;
|
|
||||||
return lastAllocationId == that.lastAllocationId &&
|
|
||||||
Objects.equals(tasks, that.tasks);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return Objects.hash(tasks, lastAllocationId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return Strings.toString(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getNumberOfTasksOnNode(String nodeId, String taskName) {
|
|
||||||
return tasks.values().stream().filter(
|
|
||||||
task -> taskName.equals(task.taskName) && nodeId.equals(task.assignment.executorNode)).count();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Version getMinimalSupportedVersion() {
|
|
||||||
return Version.V_5_4_0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public EnumSet<MetaData.XContentContext> context() {
|
|
||||||
return ALL_CONTEXTS;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static PersistentTasksCustomMetaData fromXContent(XContentParser parser) throws IOException {
|
|
||||||
return PERSISTENT_TASKS_PARSER.parse(parser, null).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public static <Params extends PersistentTaskParams> PersistentTask<Params> getTaskWithId(ClusterState clusterState, String taskId) {
|
|
||||||
PersistentTasksCustomMetaData tasks = clusterState.metaData().custom(PersistentTasksCustomMetaData.TYPE);
|
|
||||||
if (tasks != null) {
|
|
||||||
return (PersistentTask<Params>) tasks.getTask(taskId);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Assignment {
|
|
||||||
@Nullable
|
|
||||||
private final String executorNode;
|
|
||||||
private final String explanation;
|
|
||||||
|
|
||||||
public Assignment(String executorNode, String explanation) {
|
|
||||||
this.executorNode = executorNode;
|
|
||||||
assert explanation != null;
|
|
||||||
this.explanation = explanation;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public String getExecutorNode() {
|
|
||||||
return executorNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getExplanation() {
|
|
||||||
return explanation;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) return true;
|
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
|
||||||
Assignment that = (Assignment) o;
|
|
||||||
return Objects.equals(executorNode, that.executorNode) &&
|
|
||||||
Objects.equals(explanation, that.explanation);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return Objects.hash(executorNode, explanation);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isAssigned() {
|
|
||||||
return executorNode != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "node: [" + executorNode + "], explanation: [" + explanation + "]";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final Assignment INITIAL_ASSIGNMENT = new Assignment(null, "waiting for initial assignment");
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A record that represents a single running persistent task
|
|
||||||
*/
|
|
||||||
public static class PersistentTask<P extends PersistentTaskParams> implements Writeable, ToXContentObject {
|
|
||||||
private final String id;
|
|
||||||
private final long allocationId;
|
|
||||||
private final String taskName;
|
|
||||||
@Nullable
|
|
||||||
private final P params;
|
|
||||||
@Nullable
|
|
||||||
private final Status status;
|
|
||||||
private final Assignment assignment;
|
|
||||||
@Nullable
|
|
||||||
private final Long allocationIdOnLastStatusUpdate;
|
|
||||||
|
|
||||||
|
|
||||||
public PersistentTask(String id, String taskName, P params, long allocationId, Assignment assignment) {
|
|
||||||
this(id, allocationId, taskName, params, null, assignment, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public PersistentTask(PersistentTask<P> task, long allocationId, Assignment assignment) {
|
|
||||||
this(task.id, allocationId, task.taskName, task.params, task.status,
|
|
||||||
assignment, task.allocationId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public PersistentTask(PersistentTask<P> task, Status status) {
|
|
||||||
this(task.id, task.allocationId, task.taskName, task.params, status,
|
|
||||||
task.assignment, task.allocationId);
|
|
||||||
}
|
|
||||||
|
|
||||||
private PersistentTask(String id, long allocationId, String taskName, P params,
|
|
||||||
Status status, Assignment assignment, Long allocationIdOnLastStatusUpdate) {
|
|
||||||
this.id = id;
|
|
||||||
this.allocationId = allocationId;
|
|
||||||
this.taskName = taskName;
|
|
||||||
this.params = params;
|
|
||||||
this.status = status;
|
|
||||||
this.assignment = assignment;
|
|
||||||
this.allocationIdOnLastStatusUpdate = allocationIdOnLastStatusUpdate;
|
|
||||||
if (params != null) {
|
|
||||||
if (params.getWriteableName().equals(taskName) == false) {
|
|
||||||
throw new IllegalArgumentException("params have to have the same writeable name as task. params: " +
|
|
||||||
params.getWriteableName() + " task: " + taskName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (status != null) {
|
|
||||||
if (status.getWriteableName().equals(taskName) == false) {
|
|
||||||
throw new IllegalArgumentException("status has to have the same writeable name as task. status: " +
|
|
||||||
status.getWriteableName() + " task: " + taskName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public PersistentTask(StreamInput in) throws IOException {
|
|
||||||
id = in.readString();
|
|
||||||
allocationId = in.readLong();
|
|
||||||
taskName = in.readString();
|
|
||||||
params = (P) in.readOptionalNamedWriteable(PersistentTaskParams.class);
|
|
||||||
status = in.readOptionalNamedWriteable(Task.Status.class);
|
|
||||||
assignment = new Assignment(in.readOptionalString(), in.readString());
|
|
||||||
allocationIdOnLastStatusUpdate = in.readOptionalLong();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeTo(StreamOutput out) throws IOException {
|
|
||||||
out.writeString(id);
|
|
||||||
out.writeLong(allocationId);
|
|
||||||
out.writeString(taskName);
|
|
||||||
out.writeOptionalNamedWriteable(params);
|
|
||||||
out.writeOptionalNamedWriteable(status);
|
|
||||||
out.writeOptionalString(assignment.executorNode);
|
|
||||||
out.writeString(assignment.explanation);
|
|
||||||
out.writeOptionalLong(allocationIdOnLastStatusUpdate);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) return true;
|
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
|
||||||
PersistentTask<?> that = (PersistentTask<?>) o;
|
|
||||||
return Objects.equals(id, that.id) &&
|
|
||||||
allocationId == that.allocationId &&
|
|
||||||
Objects.equals(taskName, that.taskName) &&
|
|
||||||
Objects.equals(params, that.params) &&
|
|
||||||
Objects.equals(status, that.status) &&
|
|
||||||
Objects.equals(assignment, that.assignment) &&
|
|
||||||
Objects.equals(allocationIdOnLastStatusUpdate, that.allocationIdOnLastStatusUpdate);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return Objects.hash(id, allocationId, taskName, params, status, assignment,
|
|
||||||
allocationIdOnLastStatusUpdate);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return Strings.toString(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getId() {
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getAllocationId() {
|
|
||||||
return allocationId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getTaskName() {
|
|
||||||
return taskName;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public P getParams() {
|
|
||||||
return params;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public String getExecutorNode() {
|
|
||||||
return assignment.executorNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Assignment getAssignment() {
|
|
||||||
return assignment;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isAssigned() {
|
|
||||||
return assignment.isAssigned();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the tasks is not stopped and unassigned or assigned to a non-existing node.
|
|
||||||
*/
|
|
||||||
public boolean needsReassignment(DiscoveryNodes nodes) {
|
|
||||||
return (assignment.isAssigned() == false || nodes.nodeExists(assignment.getExecutorNode()) == false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public Status getStatus() {
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public XContentBuilder toXContent(XContentBuilder builder, ToXContent.Params xParams) throws IOException {
|
|
||||||
builder.startObject();
|
|
||||||
{
|
|
||||||
builder.field("id", id);
|
|
||||||
builder.startObject("task");
|
|
||||||
{
|
|
||||||
builder.startObject(taskName);
|
|
||||||
{
|
|
||||||
if (params != null) {
|
|
||||||
builder.field("params", params, xParams);
|
|
||||||
}
|
|
||||||
if (status != null) {
|
|
||||||
builder.field("status", status, xParams);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
builder.endObject();
|
|
||||||
}
|
|
||||||
builder.endObject();
|
|
||||||
|
|
||||||
if (API_CONTEXT.equals(xParams.param(MetaData.CONTEXT_MODE_PARAM, API_CONTEXT))) {
|
|
||||||
// These are transient values that shouldn't be persisted to gateway cluster state or snapshot
|
|
||||||
builder.field("allocation_id", allocationId);
|
|
||||||
builder.startObject("assignment");
|
|
||||||
{
|
|
||||||
builder.field("executor_node", assignment.executorNode);
|
|
||||||
builder.field("explanation", assignment.explanation);
|
|
||||||
}
|
|
||||||
builder.endObject();
|
|
||||||
if (allocationIdOnLastStatusUpdate != null) {
|
|
||||||
builder.field("allocation_id_on_last_status_update", allocationIdOnLastStatusUpdate);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
builder.endObject();
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isFragment() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class TaskBuilder<Params extends PersistentTaskParams> {
|
|
||||||
private String id;
|
|
||||||
private long allocationId;
|
|
||||||
private String taskName;
|
|
||||||
private Params params;
|
|
||||||
private Status status;
|
|
||||||
private Assignment assignment = INITIAL_ASSIGNMENT;
|
|
||||||
private Long allocationIdOnLastStatusUpdate;
|
|
||||||
|
|
||||||
public TaskBuilder<Params> setId(String id) {
|
|
||||||
this.id = id;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TaskBuilder<Params> setAllocationId(long allocationId) {
|
|
||||||
this.allocationId = allocationId;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TaskBuilder<Params> setTaskName(String taskName) {
|
|
||||||
this.taskName = taskName;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TaskBuilder<Params> setParams(Params params) {
|
|
||||||
this.params = params;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TaskBuilder<Params> setStatus(Status status) {
|
|
||||||
this.status = status;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public TaskBuilder<Params> setAssignment(Assignment assignment) {
|
|
||||||
this.assignment = assignment;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TaskBuilder<Params> setAllocationIdOnLastStatusUpdate(Long allocationIdOnLastStatusUpdate) {
|
|
||||||
this.allocationIdOnLastStatusUpdate = allocationIdOnLastStatusUpdate;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PersistentTask<Params> build() {
|
|
||||||
return new PersistentTask<>(id, allocationId, taskName, params, status,
|
|
||||||
assignment, allocationIdOnLastStatusUpdate);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getWriteableName() {
|
|
||||||
return TYPE;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PersistentTasksCustomMetaData(StreamInput in) throws IOException {
|
|
||||||
lastAllocationId = in.readLong();
|
|
||||||
tasks = in.readMap(StreamInput::readString, PersistentTask::new);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeTo(StreamOutput out) throws IOException {
|
|
||||||
out.writeLong(lastAllocationId);
|
|
||||||
out.writeMap(tasks, StreamOutput::writeString, (stream, value) -> value.writeTo(stream));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static NamedDiff<MetaData.Custom> readDiffFrom(StreamInput in) throws IOException {
|
|
||||||
return readDiffFrom(MetaData.Custom.class, TYPE, in);
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getLastAllocationId() {
|
|
||||||
return lastAllocationId;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
|
||||||
builder.field("last_allocation_id", lastAllocationId);
|
|
||||||
builder.startArray("tasks");
|
|
||||||
for (PersistentTask<?> entry : tasks.values()) {
|
|
||||||
entry.toXContent(builder, params);
|
|
||||||
}
|
|
||||||
builder.endArray();
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Builder builder() {
|
|
||||||
return new Builder();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Builder builder(PersistentTasksCustomMetaData tasks) {
|
|
||||||
return new Builder(tasks);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Builder {
|
|
||||||
private final Map<String, PersistentTask<?>> tasks = new HashMap<>();
|
|
||||||
private long lastAllocationId;
|
|
||||||
private boolean changed;
|
|
||||||
|
|
||||||
private Builder() {
|
|
||||||
}
|
|
||||||
|
|
||||||
private Builder(PersistentTasksCustomMetaData tasksInProgress) {
|
|
||||||
if (tasksInProgress != null) {
|
|
||||||
tasks.putAll(tasksInProgress.tasks);
|
|
||||||
lastAllocationId = tasksInProgress.lastAllocationId;
|
|
||||||
} else {
|
|
||||||
lastAllocationId = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getLastAllocationId() {
|
|
||||||
return lastAllocationId;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Builder setLastAllocationId(long currentId) {
|
|
||||||
this.lastAllocationId = currentId;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
private <Params extends PersistentTaskParams> Builder setTasks(List<TaskBuilder<Params>> tasks) {
|
|
||||||
for (TaskBuilder builder : tasks) {
|
|
||||||
PersistentTask<?> task = builder.build();
|
|
||||||
this.tasks.put(task.getId(), task);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
private long getNextAllocationId() {
|
|
||||||
lastAllocationId++;
|
|
||||||
return lastAllocationId;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a new task to the builder
|
|
||||||
* <p>
|
|
||||||
* After the task is added its id can be found by calling {{@link #getLastAllocationId()}} method.
|
|
||||||
*/
|
|
||||||
public <Params extends PersistentTaskParams> Builder addTask(String taskId, String taskName, Params params,
|
|
||||||
Assignment assignment) {
|
|
||||||
changed = true;
|
|
||||||
PersistentTask<?> previousTask = tasks.put(taskId, new PersistentTask<>(taskId, taskName, params,
|
|
||||||
getNextAllocationId(), assignment));
|
|
||||||
if (previousTask != null) {
|
|
||||||
throw new ResourceAlreadyExistsException("Trying to override task with id {" + taskId + "}");
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reassigns the task to another node
|
|
||||||
*/
|
|
||||||
public Builder reassignTask(String taskId, Assignment assignment) {
|
|
||||||
PersistentTask<?> taskInProgress = tasks.get(taskId);
|
|
||||||
if (taskInProgress != null) {
|
|
||||||
changed = true;
|
|
||||||
tasks.put(taskId, new PersistentTask<>(taskInProgress, getNextAllocationId(), assignment));
|
|
||||||
} else {
|
|
||||||
throw new ResourceNotFoundException("cannot reassign task with id {" + taskId + "}, the task no longer exits");
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the task status
|
|
||||||
*/
|
|
||||||
public Builder updateTaskStatus(String taskId, Status status) {
|
|
||||||
PersistentTask<?> taskInProgress = tasks.get(taskId);
|
|
||||||
if (taskInProgress != null) {
|
|
||||||
changed = true;
|
|
||||||
tasks.put(taskId, new PersistentTask<>(taskInProgress, status));
|
|
||||||
} else {
|
|
||||||
throw new ResourceNotFoundException("cannot update task with id {" + taskId + "}, the task no longer exits");
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes the task
|
|
||||||
*/
|
|
||||||
public Builder removeTask(String taskId) {
|
|
||||||
if (tasks.remove(taskId) != null) {
|
|
||||||
changed = true;
|
|
||||||
} else {
|
|
||||||
throw new ResourceNotFoundException("cannot remove task with id {" + taskId + "}, the task no longer exits");
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Finishes the task
|
|
||||||
* <p>
|
|
||||||
* If the task is marked with removeOnCompletion flag, it is removed from the list, otherwise it is stopped.
|
|
||||||
*/
|
|
||||||
public Builder finishTask(String taskId) {
|
|
||||||
PersistentTask<?> taskInProgress = tasks.get(taskId);
|
|
||||||
if (taskInProgress != null) {
|
|
||||||
changed = true;
|
|
||||||
tasks.remove(taskId);
|
|
||||||
} else {
|
|
||||||
throw new ResourceNotFoundException("cannot finish task with id {" + taskId + "}, the task no longer exits");
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the task is currently present in the list
|
|
||||||
*/
|
|
||||||
public boolean hasTask(String taskId) {
|
|
||||||
return tasks.containsKey(taskId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the task is currently present in the list and has the right allocation id
|
|
||||||
*/
|
|
||||||
public boolean hasTask(String taskId, long allocationId) {
|
|
||||||
PersistentTask<?> taskInProgress = tasks.get(taskId);
|
|
||||||
if (taskInProgress != null) {
|
|
||||||
return taskInProgress.getAllocationId() == allocationId;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Set<String> getCurrentTaskIds() {
|
|
||||||
return tasks.keySet();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if any the task list was changed since the builder was created
|
|
||||||
*/
|
|
||||||
public boolean isChanged() {
|
|
||||||
return changed;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PersistentTasksCustomMetaData build() {
|
|
||||||
return new PersistentTasksCustomMetaData(lastAllocationId, Collections.unmodifiableMap(tasks));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,114 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.xpack.core.persistent;
|
|
||||||
|
|
||||||
import org.elasticsearch.cluster.ClusterState;
|
|
||||||
import org.elasticsearch.cluster.node.DiscoveryNode;
|
|
||||||
import org.elasticsearch.common.Nullable;
|
|
||||||
import org.elasticsearch.common.component.AbstractComponent;
|
|
||||||
import org.elasticsearch.common.settings.Settings;
|
|
||||||
import org.elasticsearch.tasks.Task;
|
|
||||||
import org.elasticsearch.tasks.TaskId;
|
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksCustomMetaData.Assignment;
|
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksCustomMetaData.PersistentTask;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.function.Predicate;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An executor of tasks that can survive restart of requesting or executing node.
|
|
||||||
* These tasks are using cluster state rather than only transport service to send requests and responses.
|
|
||||||
*/
|
|
||||||
public abstract class PersistentTasksExecutor<Params extends PersistentTaskParams> extends AbstractComponent {
|
|
||||||
|
|
||||||
private final String executor;
|
|
||||||
private final String taskName;
|
|
||||||
|
|
||||||
protected PersistentTasksExecutor(Settings settings, String taskName, String executor) {
|
|
||||||
super(settings);
|
|
||||||
this.taskName = taskName;
|
|
||||||
this.executor = executor;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getTaskName() {
|
|
||||||
return taskName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final Assignment NO_NODE_FOUND = new Assignment(null, "no appropriate nodes found for the assignment");
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the node id where the params has to be executed,
|
|
||||||
* <p>
|
|
||||||
* The default implementation returns the least loaded data node
|
|
||||||
*/
|
|
||||||
public Assignment getAssignment(Params params, ClusterState clusterState) {
|
|
||||||
DiscoveryNode discoveryNode = selectLeastLoadedNode(clusterState, DiscoveryNode::isDataNode);
|
|
||||||
if (discoveryNode == null) {
|
|
||||||
return NO_NODE_FOUND;
|
|
||||||
} else {
|
|
||||||
return new Assignment(discoveryNode.getId(), "");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Finds the least loaded node that satisfies the selector criteria
|
|
||||||
*/
|
|
||||||
protected DiscoveryNode selectLeastLoadedNode(ClusterState clusterState, Predicate<DiscoveryNode> selector) {
|
|
||||||
long minLoad = Long.MAX_VALUE;
|
|
||||||
DiscoveryNode minLoadedNode = null;
|
|
||||||
PersistentTasksCustomMetaData persistentTasks = clusterState.getMetaData().custom(PersistentTasksCustomMetaData.TYPE);
|
|
||||||
for (DiscoveryNode node : clusterState.getNodes()) {
|
|
||||||
if (selector.test(node)) {
|
|
||||||
if (persistentTasks == null) {
|
|
||||||
// We don't have any task running yet, pick the first available node
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
long numberOfTasks = persistentTasks.getNumberOfTasksOnNode(node.getId(), taskName);
|
|
||||||
if (minLoad > numberOfTasks) {
|
|
||||||
minLoad = numberOfTasks;
|
|
||||||
minLoadedNode = node;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return minLoadedNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks the current cluster state for compatibility with the params
|
|
||||||
* <p>
|
|
||||||
* Throws an exception if the supplied params cannot be executed on the cluster in the current state.
|
|
||||||
*/
|
|
||||||
public void validate(Params params, ClusterState clusterState) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a AllocatedPersistentTask for communicating with task manager
|
|
||||||
*/
|
|
||||||
protected AllocatedPersistentTask createTask(long id, String type, String action, TaskId parentTaskId,
|
|
||||||
PersistentTask<Params> taskInProgress, Map<String, String> headers) {
|
|
||||||
return new AllocatedPersistentTask(id, type, action, getDescription(taskInProgress), parentTaskId, headers);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns task description that will be available via task manager
|
|
||||||
*/
|
|
||||||
protected String getDescription(PersistentTask<Params> taskInProgress) {
|
|
||||||
return "id=" + taskInProgress.getId();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This operation will be executed on the executor node.
|
|
||||||
* <p>
|
|
||||||
* NOTE: The nodeOperation has to throw an exception, trigger task.markAsCompleted() or task.completeAndNotifyIfNeeded() methods to
|
|
||||||
* indicate that the persistent task has finished.
|
|
||||||
*/
|
|
||||||
protected abstract void nodeOperation(AllocatedPersistentTask task, @Nullable Params params, @Nullable Task.Status status);
|
|
||||||
|
|
||||||
public String getExecutor() {
|
|
||||||
return executor;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,41 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.xpack.core.persistent;
|
|
||||||
|
|
||||||
import org.elasticsearch.common.component.AbstractComponent;
|
|
||||||
import org.elasticsearch.common.settings.Settings;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Components that registers all persistent task executors
|
|
||||||
*/
|
|
||||||
public class PersistentTasksExecutorRegistry extends AbstractComponent {
|
|
||||||
|
|
||||||
private final Map<String, PersistentTasksExecutor<?>> taskExecutors;
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public PersistentTasksExecutorRegistry(Settings settings, Collection<PersistentTasksExecutor<?>> taskExecutors) {
|
|
||||||
super(settings);
|
|
||||||
Map<String, PersistentTasksExecutor<?>> map = new HashMap<>();
|
|
||||||
for (PersistentTasksExecutor<?> executor : taskExecutors) {
|
|
||||||
map.put(executor.getTaskName(), executor);
|
|
||||||
}
|
|
||||||
this.taskExecutors = Collections.unmodifiableMap(map);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public <Params extends PersistentTaskParams> PersistentTasksExecutor<Params> getPersistentTaskExecutorSafe(String taskName) {
|
|
||||||
PersistentTasksExecutor<Params> executor = (PersistentTasksExecutor<Params>) taskExecutors.get(taskName);
|
|
||||||
if (executor == null) {
|
|
||||||
throw new IllegalStateException("Unknown persistent executor [" + taskName + "]");
|
|
||||||
}
|
|
||||||
return executor;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,265 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.xpack.core.persistent;
|
|
||||||
|
|
||||||
import org.apache.logging.log4j.message.ParameterizedMessage;
|
|
||||||
import org.apache.logging.log4j.util.Supplier;
|
|
||||||
import org.elasticsearch.action.ActionListener;
|
|
||||||
import org.elasticsearch.action.admin.cluster.node.tasks.cancel.CancelTasksResponse;
|
|
||||||
import org.elasticsearch.cluster.ClusterChangedEvent;
|
|
||||||
import org.elasticsearch.cluster.ClusterStateListener;
|
|
||||||
import org.elasticsearch.common.Strings;
|
|
||||||
import org.elasticsearch.common.component.AbstractComponent;
|
|
||||||
import org.elasticsearch.common.io.stream.StreamInput;
|
|
||||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
|
||||||
import org.elasticsearch.common.settings.Settings;
|
|
||||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
|
||||||
import org.elasticsearch.gateway.GatewayService;
|
|
||||||
import org.elasticsearch.tasks.Task;
|
|
||||||
import org.elasticsearch.tasks.TaskAwareRequest;
|
|
||||||
import org.elasticsearch.tasks.TaskId;
|
|
||||||
import org.elasticsearch.tasks.TaskManager;
|
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksCustomMetaData.PersistentTask;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import static java.util.Objects.requireNonNull;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This component is responsible for coordination of execution of persistent tasks on individual nodes. It runs on all
|
|
||||||
* non-transport client nodes in the cluster and monitors cluster state changes to detect started commands.
|
|
||||||
*/
|
|
||||||
public class PersistentTasksNodeService extends AbstractComponent implements ClusterStateListener {
|
|
||||||
private final Map<Long, AllocatedPersistentTask> runningTasks = new HashMap<>();
|
|
||||||
private final PersistentTasksService persistentTasksService;
|
|
||||||
private final PersistentTasksExecutorRegistry persistentTasksExecutorRegistry;
|
|
||||||
private final TaskManager taskManager;
|
|
||||||
private final NodePersistentTasksExecutor nodePersistentTasksExecutor;
|
|
||||||
|
|
||||||
|
|
||||||
public PersistentTasksNodeService(Settings settings,
|
|
||||||
PersistentTasksService persistentTasksService,
|
|
||||||
PersistentTasksExecutorRegistry persistentTasksExecutorRegistry,
|
|
||||||
TaskManager taskManager, NodePersistentTasksExecutor nodePersistentTasksExecutor) {
|
|
||||||
super(settings);
|
|
||||||
this.persistentTasksService = persistentTasksService;
|
|
||||||
this.persistentTasksExecutorRegistry = persistentTasksExecutorRegistry;
|
|
||||||
this.taskManager = taskManager;
|
|
||||||
this.nodePersistentTasksExecutor = nodePersistentTasksExecutor;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void clusterChanged(ClusterChangedEvent event) {
|
|
||||||
if (event.state().blocks().hasGlobalBlock(GatewayService.STATE_NOT_RECOVERED_BLOCK)) {
|
|
||||||
// wait until the gateway has recovered from disk, otherwise if the only master restarts
|
|
||||||
// we start cancelling all local tasks before cluster has a chance to recover.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
PersistentTasksCustomMetaData tasks = event.state().getMetaData().custom(PersistentTasksCustomMetaData.TYPE);
|
|
||||||
PersistentTasksCustomMetaData previousTasks = event.previousState().getMetaData().custom(PersistentTasksCustomMetaData.TYPE);
|
|
||||||
|
|
||||||
// Cluster State Local State Local Action
|
|
||||||
// STARTED NULL Create as STARTED, Start
|
|
||||||
// STARTED STARTED Noop - running
|
|
||||||
// STARTED COMPLETED Noop - waiting for notification ack
|
|
||||||
|
|
||||||
// NULL NULL Noop - nothing to do
|
|
||||||
// NULL STARTED Remove locally, Mark as PENDING_CANCEL, Cancel
|
|
||||||
// NULL COMPLETED Remove locally
|
|
||||||
|
|
||||||
// Master states:
|
|
||||||
// NULL - doesn't exist in the cluster state
|
|
||||||
// STARTED - exist in the cluster state
|
|
||||||
|
|
||||||
// Local state:
|
|
||||||
// NULL - we don't have task registered locally in runningTasks
|
|
||||||
// STARTED - registered in TaskManager, requires master notification when finishes
|
|
||||||
// PENDING_CANCEL - registered in TaskManager, doesn't require master notification when finishes
|
|
||||||
// COMPLETED - not registered in TaskManager, notified, waiting for master to remove it from CS so we can remove locally
|
|
||||||
|
|
||||||
// When task finishes if it is marked as STARTED or PENDING_CANCEL it is marked as COMPLETED and unregistered,
|
|
||||||
// If the task was STARTED, the master notification is also triggered (this is handled by unregisterTask() method, which is
|
|
||||||
// triggered by PersistentTaskListener
|
|
||||||
|
|
||||||
if (Objects.equals(tasks, previousTasks) == false || event.nodesChanged()) {
|
|
||||||
// We have some changes let's check if they are related to our node
|
|
||||||
String localNodeId = event.state().getNodes().getLocalNodeId();
|
|
||||||
Set<Long> notVisitedTasks = new HashSet<>(runningTasks.keySet());
|
|
||||||
if (tasks != null) {
|
|
||||||
for (PersistentTask<?> taskInProgress : tasks.tasks()) {
|
|
||||||
if (localNodeId.equals(taskInProgress.getExecutorNode())) {
|
|
||||||
Long allocationId = taskInProgress.getAllocationId();
|
|
||||||
AllocatedPersistentTask persistentTask = runningTasks.get(allocationId);
|
|
||||||
if (persistentTask == null) {
|
|
||||||
// New task - let's start it
|
|
||||||
startTask(taskInProgress);
|
|
||||||
} else {
|
|
||||||
// The task is still running
|
|
||||||
notVisitedTasks.remove(allocationId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (Long id : notVisitedTasks) {
|
|
||||||
AllocatedPersistentTask task = runningTasks.get(id);
|
|
||||||
if (task.getState() == AllocatedPersistentTask.State.COMPLETED) {
|
|
||||||
// Result was sent to the caller and the caller acknowledged acceptance of the result
|
|
||||||
logger.trace("Found completed persistent task [{}] with id [{}] and allocation id [{}] - removing",
|
|
||||||
task.getAction(), task.getPersistentTaskId(), task.getAllocationId());
|
|
||||||
runningTasks.remove(id);
|
|
||||||
} else {
|
|
||||||
// task is running locally, but master doesn't know about it - that means that the persistent task was removed
|
|
||||||
// cancel the task without notifying master
|
|
||||||
logger.trace("Found unregistered persistent task [{}] with id [{}] and allocation id [{}] - cancelling",
|
|
||||||
task.getAction(), task.getPersistentTaskId(), task.getAllocationId());
|
|
||||||
cancelTask(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private <Params extends PersistentTaskParams> void startTask(PersistentTask<Params> taskInProgress) {
|
|
||||||
PersistentTasksExecutor<Params> executor =
|
|
||||||
persistentTasksExecutorRegistry.getPersistentTaskExecutorSafe(taskInProgress.getTaskName());
|
|
||||||
|
|
||||||
TaskAwareRequest request = new TaskAwareRequest() {
|
|
||||||
TaskId parentTaskId = new TaskId("cluster", taskInProgress.getAllocationId());
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setParentTask(TaskId taskId) {
|
|
||||||
throw new UnsupportedOperationException("parent task if for persistent tasks shouldn't change");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public TaskId getParentTask() {
|
|
||||||
return parentTaskId;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Task createTask(long id, String type, String action, TaskId parentTaskId, Map<String, String> headers) {
|
|
||||||
return executor.createTask(id, type, action, parentTaskId, taskInProgress, headers);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
AllocatedPersistentTask task = (AllocatedPersistentTask) taskManager.register("persistent", taskInProgress.getTaskName() + "[c]",
|
|
||||||
request);
|
|
||||||
boolean processed = false;
|
|
||||||
try {
|
|
||||||
task.init(persistentTasksService, taskManager, logger, taskInProgress.getId(), taskInProgress.getAllocationId());
|
|
||||||
logger.trace("Persistent task [{}] with id [{}] and allocation id [{}] was created", task.getAction(),
|
|
||||||
task.getPersistentTaskId(), task.getAllocationId());
|
|
||||||
try {
|
|
||||||
runningTasks.put(taskInProgress.getAllocationId(), task);
|
|
||||||
nodePersistentTasksExecutor.executeTask(taskInProgress.getParams(), taskInProgress.getStatus(), task, executor);
|
|
||||||
} catch (Exception e) {
|
|
||||||
// Submit task failure
|
|
||||||
task.markAsFailed(e);
|
|
||||||
}
|
|
||||||
processed = true;
|
|
||||||
} finally {
|
|
||||||
if (processed == false) {
|
|
||||||
// something went wrong - unregistering task
|
|
||||||
logger.warn("Persistent task [{}] with id [{}] and allocation id [{}] failed to create", task.getAction(),
|
|
||||||
task.getPersistentTaskId(), task.getAllocationId());
|
|
||||||
taskManager.unregister(task);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unregisters and then cancels the locally running task using the task manager. No notification to master will be send upon
|
|
||||||
* cancellation.
|
|
||||||
*/
|
|
||||||
private void cancelTask(Long allocationId) {
|
|
||||||
AllocatedPersistentTask task = runningTasks.remove(allocationId);
|
|
||||||
if (task.markAsCancelled()) {
|
|
||||||
// Cancel the local task using the task manager
|
|
||||||
persistentTasksService.sendTaskManagerCancellation(task.getId(), new ActionListener<CancelTasksResponse>() {
|
|
||||||
@Override
|
|
||||||
public void onResponse(CancelTasksResponse cancelTasksResponse) {
|
|
||||||
logger.trace("Persistent task [{}] with id [{}] and allocation id [{}] was cancelled", task.getAction(),
|
|
||||||
task.getPersistentTaskId(), task.getAllocationId());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(Exception e) {
|
|
||||||
// There is really nothing we can do in case of failure here
|
|
||||||
logger.warn((Supplier<?>) () ->
|
|
||||||
new ParameterizedMessage("failed to cancel task [{}] with id [{}] and allocation id [{}]", task.getAction(),
|
|
||||||
task.getPersistentTaskId(), task.getAllocationId()), e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static class Status implements Task.Status {
|
|
||||||
public static final String NAME = "persistent_executor";
|
|
||||||
|
|
||||||
private final AllocatedPersistentTask.State state;
|
|
||||||
|
|
||||||
public Status(AllocatedPersistentTask.State state) {
|
|
||||||
this.state = requireNonNull(state, "State cannot be null");
|
|
||||||
}
|
|
||||||
|
|
||||||
public Status(StreamInput in) throws IOException {
|
|
||||||
state = AllocatedPersistentTask.State.valueOf(in.readString());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getWriteableName() {
|
|
||||||
return NAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
|
||||||
builder.startObject();
|
|
||||||
builder.field("state", state.toString());
|
|
||||||
builder.endObject();
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeTo(StreamOutput out) throws IOException {
|
|
||||||
out.writeString(state.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return Strings.toString(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public AllocatedPersistentTask.State getState() {
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isFragment() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) return true;
|
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
|
||||||
Status status = (Status) o;
|
|
||||||
return state == status.state;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return Objects.hash(state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,184 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.xpack.core.persistent;
|
|
||||||
|
|
||||||
import org.elasticsearch.action.ActionListener;
|
|
||||||
import org.elasticsearch.action.admin.cluster.node.tasks.cancel.CancelTasksRequest;
|
|
||||||
import org.elasticsearch.action.admin.cluster.node.tasks.cancel.CancelTasksResponse;
|
|
||||||
import org.elasticsearch.client.Client;
|
|
||||||
import org.elasticsearch.cluster.ClusterState;
|
|
||||||
import org.elasticsearch.cluster.ClusterStateObserver;
|
|
||||||
import org.elasticsearch.cluster.node.DiscoveryNode;
|
|
||||||
import org.elasticsearch.cluster.service.ClusterService;
|
|
||||||
import org.elasticsearch.common.Nullable;
|
|
||||||
import org.elasticsearch.common.component.AbstractComponent;
|
|
||||||
import org.elasticsearch.common.settings.Settings;
|
|
||||||
import org.elasticsearch.common.unit.TimeValue;
|
|
||||||
import org.elasticsearch.node.NodeClosedException;
|
|
||||||
import org.elasticsearch.tasks.Task;
|
|
||||||
import org.elasticsearch.tasks.TaskId;
|
|
||||||
import org.elasticsearch.threadpool.ThreadPool;
|
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksCustomMetaData.PersistentTask;
|
|
||||||
|
|
||||||
import java.util.function.Predicate;
|
|
||||||
|
|
||||||
import static org.elasticsearch.xpack.core.ClientHelper.PERSISTENT_TASK_ORIGIN;
|
|
||||||
import static org.elasticsearch.xpack.core.ClientHelper.executeAsyncWithOrigin;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This service is used by persistent actions to propagate changes in the action state and notify about completion
|
|
||||||
*/
|
|
||||||
public class PersistentTasksService extends AbstractComponent {
|
|
||||||
|
|
||||||
private final Client client;
|
|
||||||
private final ClusterService clusterService;
|
|
||||||
private final ThreadPool threadPool;
|
|
||||||
|
|
||||||
public PersistentTasksService(Settings settings, ClusterService clusterService, ThreadPool threadPool, Client client) {
|
|
||||||
super(settings);
|
|
||||||
this.client = client;
|
|
||||||
this.clusterService = clusterService;
|
|
||||||
this.threadPool = threadPool;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates the specified persistent task and attempts to assign it to a node.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public <Params extends PersistentTaskParams> void startPersistentTask(String taskId, String taskName, @Nullable Params params,
|
|
||||||
ActionListener<PersistentTask<Params>> listener) {
|
|
||||||
StartPersistentTaskAction.Request createPersistentActionRequest =
|
|
||||||
new StartPersistentTaskAction.Request(taskId, taskName, params);
|
|
||||||
try {
|
|
||||||
executeAsyncWithOrigin(client, PERSISTENT_TASK_ORIGIN, StartPersistentTaskAction.INSTANCE, createPersistentActionRequest,
|
|
||||||
ActionListener.wrap(o -> listener.onResponse((PersistentTask<Params>) o.getTask()), listener::onFailure));
|
|
||||||
} catch (Exception e) {
|
|
||||||
listener.onFailure(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Notifies the PersistentTasksClusterService about successful (failure == null) completion of a task or its failure
|
|
||||||
*/
|
|
||||||
public void sendCompletionNotification(String taskId, long allocationId, Exception failure,
|
|
||||||
ActionListener<PersistentTask<?>> listener) {
|
|
||||||
CompletionPersistentTaskAction.Request restartRequest = new CompletionPersistentTaskAction.Request(taskId, allocationId, failure);
|
|
||||||
try {
|
|
||||||
executeAsyncWithOrigin(client, PERSISTENT_TASK_ORIGIN, CompletionPersistentTaskAction.INSTANCE, restartRequest,
|
|
||||||
ActionListener.wrap(o -> listener.onResponse(o.getTask()), listener::onFailure));
|
|
||||||
} catch (Exception e) {
|
|
||||||
listener.onFailure(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cancels a locally running task using the task manager
|
|
||||||
*/
|
|
||||||
void sendTaskManagerCancellation(long taskId, ActionListener<CancelTasksResponse> listener) {
|
|
||||||
DiscoveryNode localNode = clusterService.localNode();
|
|
||||||
CancelTasksRequest cancelTasksRequest = new CancelTasksRequest();
|
|
||||||
cancelTasksRequest.setTaskId(new TaskId(localNode.getId(), taskId));
|
|
||||||
cancelTasksRequest.setReason("persistent action was removed");
|
|
||||||
try {
|
|
||||||
executeAsyncWithOrigin(client.threadPool().getThreadContext(), PERSISTENT_TASK_ORIGIN, cancelTasksRequest, listener,
|
|
||||||
client.admin().cluster()::cancelTasks);
|
|
||||||
} catch (Exception e) {
|
|
||||||
listener.onFailure(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates status of the persistent task.
|
|
||||||
* <p>
|
|
||||||
* Persistent task implementers shouldn't call this method directly and use
|
|
||||||
* {@link AllocatedPersistentTask#updatePersistentStatus} instead
|
|
||||||
*/
|
|
||||||
void updateStatus(String taskId, long allocationId, Task.Status status, ActionListener<PersistentTask<?>> listener) {
|
|
||||||
UpdatePersistentTaskStatusAction.Request updateStatusRequest =
|
|
||||||
new UpdatePersistentTaskStatusAction.Request(taskId, allocationId, status);
|
|
||||||
try {
|
|
||||||
executeAsyncWithOrigin(client, PERSISTENT_TASK_ORIGIN, UpdatePersistentTaskStatusAction.INSTANCE, updateStatusRequest,
|
|
||||||
ActionListener.wrap(o -> listener.onResponse(o.getTask()), listener::onFailure));
|
|
||||||
} catch (Exception e) {
|
|
||||||
listener.onFailure(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cancels if needed and removes a persistent task
|
|
||||||
*/
|
|
||||||
public void cancelPersistentTask(String taskId, ActionListener<PersistentTask<?>> listener) {
|
|
||||||
RemovePersistentTaskAction.Request removeRequest = new RemovePersistentTaskAction.Request(taskId);
|
|
||||||
try {
|
|
||||||
executeAsyncWithOrigin(client, PERSISTENT_TASK_ORIGIN, RemovePersistentTaskAction.INSTANCE, removeRequest,
|
|
||||||
ActionListener.wrap(o -> listener.onResponse(o.getTask()), listener::onFailure));
|
|
||||||
} catch (Exception e) {
|
|
||||||
listener.onFailure(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the persistent task with giving id (taskId) has the desired state and if it doesn't
|
|
||||||
* waits of it.
|
|
||||||
*/
|
|
||||||
public void waitForPersistentTaskStatus(String taskId, Predicate<PersistentTask<?>> predicate, @Nullable TimeValue timeout,
|
|
||||||
WaitForPersistentTaskStatusListener<?> listener) {
|
|
||||||
ClusterStateObserver stateObserver = new ClusterStateObserver(clusterService, timeout, logger, threadPool.getThreadContext());
|
|
||||||
if (predicate.test(PersistentTasksCustomMetaData.getTaskWithId(stateObserver.setAndGetObservedState(), taskId))) {
|
|
||||||
listener.onResponse(PersistentTasksCustomMetaData.getTaskWithId(stateObserver.setAndGetObservedState(), taskId));
|
|
||||||
} else {
|
|
||||||
stateObserver.waitForNextChange(new ClusterStateObserver.Listener() {
|
|
||||||
@Override
|
|
||||||
public void onNewClusterState(ClusterState state) {
|
|
||||||
listener.onResponse(PersistentTasksCustomMetaData.getTaskWithId(state, taskId));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onClusterServiceClose() {
|
|
||||||
listener.onFailure(new NodeClosedException(clusterService.localNode()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onTimeout(TimeValue timeout) {
|
|
||||||
listener.onTimeout(timeout);
|
|
||||||
}
|
|
||||||
}, clusterState -> predicate.test(PersistentTasksCustomMetaData.getTaskWithId(clusterState, taskId)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void waitForPersistentTasksStatus(Predicate<PersistentTasksCustomMetaData> predicate,
|
|
||||||
@Nullable TimeValue timeout, ActionListener<Boolean> listener) {
|
|
||||||
ClusterStateObserver stateObserver = new ClusterStateObserver(clusterService, timeout,
|
|
||||||
logger, threadPool.getThreadContext());
|
|
||||||
if (predicate.test(stateObserver.setAndGetObservedState().metaData().custom(PersistentTasksCustomMetaData.TYPE))) {
|
|
||||||
listener.onResponse(true);
|
|
||||||
} else {
|
|
||||||
stateObserver.waitForNextChange(new ClusterStateObserver.Listener() {
|
|
||||||
@Override
|
|
||||||
public void onNewClusterState(ClusterState state) {
|
|
||||||
listener.onResponse(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onClusterServiceClose() {
|
|
||||||
listener.onFailure(new NodeClosedException(clusterService.localNode()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onTimeout(TimeValue timeout) {
|
|
||||||
listener.onFailure(new IllegalStateException("timed out after " + timeout));
|
|
||||||
}
|
|
||||||
}, clusterState -> predicate.test(clusterState.metaData().custom(PersistentTasksCustomMetaData.TYPE)), timeout);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface WaitForPersistentTaskStatusListener<Params extends PersistentTaskParams>
|
|
||||||
extends ActionListener<PersistentTask<Params>> {
|
|
||||||
default void onTimeout(TimeValue timeout) {
|
|
||||||
onFailure(new IllegalStateException("timed out after " + timeout));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,162 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.xpack.core.persistent;
|
|
||||||
|
|
||||||
import org.elasticsearch.action.Action;
|
|
||||||
import org.elasticsearch.action.ActionListener;
|
|
||||||
import org.elasticsearch.action.ActionRequestValidationException;
|
|
||||||
import org.elasticsearch.action.support.ActionFilters;
|
|
||||||
import org.elasticsearch.action.support.master.MasterNodeOperationRequestBuilder;
|
|
||||||
import org.elasticsearch.action.support.master.MasterNodeRequest;
|
|
||||||
import org.elasticsearch.action.support.master.TransportMasterNodeAction;
|
|
||||||
import org.elasticsearch.client.ElasticsearchClient;
|
|
||||||
import org.elasticsearch.cluster.ClusterState;
|
|
||||||
import org.elasticsearch.cluster.block.ClusterBlockException;
|
|
||||||
import org.elasticsearch.cluster.block.ClusterBlockLevel;
|
|
||||||
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
|
|
||||||
import org.elasticsearch.cluster.service.ClusterService;
|
|
||||||
import org.elasticsearch.common.inject.Inject;
|
|
||||||
import org.elasticsearch.common.io.stream.StreamInput;
|
|
||||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
|
||||||
import org.elasticsearch.common.settings.Settings;
|
|
||||||
import org.elasticsearch.threadpool.ThreadPool;
|
|
||||||
import org.elasticsearch.transport.TransportService;
|
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksCustomMetaData.PersistentTask;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public class RemovePersistentTaskAction extends Action<RemovePersistentTaskAction.Request,
|
|
||||||
PersistentTaskResponse,
|
|
||||||
RemovePersistentTaskAction.RequestBuilder> {
|
|
||||||
|
|
||||||
public static final RemovePersistentTaskAction INSTANCE = new RemovePersistentTaskAction();
|
|
||||||
public static final String NAME = "cluster:admin/persistent/remove";
|
|
||||||
|
|
||||||
private RemovePersistentTaskAction() {
|
|
||||||
super(NAME);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public RequestBuilder newRequestBuilder(ElasticsearchClient client) {
|
|
||||||
return new RequestBuilder(client, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PersistentTaskResponse newResponse() {
|
|
||||||
return new PersistentTaskResponse();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Request extends MasterNodeRequest<Request> {
|
|
||||||
|
|
||||||
private String taskId;
|
|
||||||
|
|
||||||
public Request() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public Request(String taskId) {
|
|
||||||
this.taskId = taskId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTaskId(String taskId) {
|
|
||||||
this.taskId = taskId;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void readFrom(StreamInput in) throws IOException {
|
|
||||||
super.readFrom(in);
|
|
||||||
taskId = in.readString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeTo(StreamOutput out) throws IOException {
|
|
||||||
super.writeTo(out);
|
|
||||||
out.writeString(taskId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ActionRequestValidationException validate() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) return true;
|
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
|
||||||
Request request = (Request) o;
|
|
||||||
return Objects.equals(taskId, request.taskId);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return Objects.hash(taskId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class RequestBuilder extends MasterNodeOperationRequestBuilder<RemovePersistentTaskAction.Request,
|
|
||||||
PersistentTaskResponse, RemovePersistentTaskAction.RequestBuilder> {
|
|
||||||
|
|
||||||
protected RequestBuilder(ElasticsearchClient client, RemovePersistentTaskAction action) {
|
|
||||||
super(client, action, new Request());
|
|
||||||
}
|
|
||||||
|
|
||||||
public final RequestBuilder setTaskId(String taskId) {
|
|
||||||
request.setTaskId(taskId);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class TransportAction extends TransportMasterNodeAction<Request, PersistentTaskResponse> {
|
|
||||||
|
|
||||||
private final PersistentTasksClusterService persistentTasksClusterService;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public TransportAction(Settings settings, TransportService transportService, ClusterService clusterService,
|
|
||||||
ThreadPool threadPool, ActionFilters actionFilters,
|
|
||||||
PersistentTasksClusterService persistentTasksClusterService,
|
|
||||||
IndexNameExpressionResolver indexNameExpressionResolver) {
|
|
||||||
super(settings, RemovePersistentTaskAction.NAME, transportService, clusterService, threadPool, actionFilters,
|
|
||||||
indexNameExpressionResolver, Request::new);
|
|
||||||
this.persistentTasksClusterService = persistentTasksClusterService;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String executor() {
|
|
||||||
return ThreadPool.Names.MANAGEMENT;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected PersistentTaskResponse newResponse() {
|
|
||||||
return new PersistentTaskResponse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected ClusterBlockException checkBlock(Request request, ClusterState state) {
|
|
||||||
// Cluster is not affected but we look up repositories in metadata
|
|
||||||
return state.blocks().globalBlockedException(ClusterBlockLevel.METADATA_WRITE);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected final void masterOperation(final Request request, ClusterState state,
|
|
||||||
final ActionListener<PersistentTaskResponse> listener) {
|
|
||||||
persistentTasksClusterService.removePersistentTask(request.taskId, new ActionListener<PersistentTask<?>>() {
|
|
||||||
@Override
|
|
||||||
public void onResponse(PersistentTask<?> task) {
|
|
||||||
listener.onResponse(new PersistentTaskResponse(task));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(Exception e) {
|
|
||||||
listener.onFailure(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
@ -1,232 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.xpack.core.persistent;
|
|
||||||
|
|
||||||
import org.elasticsearch.action.Action;
|
|
||||||
import org.elasticsearch.action.ActionListener;
|
|
||||||
import org.elasticsearch.action.ActionRequestValidationException;
|
|
||||||
import org.elasticsearch.action.support.ActionFilters;
|
|
||||||
import org.elasticsearch.action.support.master.MasterNodeOperationRequestBuilder;
|
|
||||||
import org.elasticsearch.action.support.master.MasterNodeRequest;
|
|
||||||
import org.elasticsearch.action.support.master.TransportMasterNodeAction;
|
|
||||||
import org.elasticsearch.client.ElasticsearchClient;
|
|
||||||
import org.elasticsearch.cluster.ClusterState;
|
|
||||||
import org.elasticsearch.cluster.block.ClusterBlockException;
|
|
||||||
import org.elasticsearch.cluster.block.ClusterBlockLevel;
|
|
||||||
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
|
|
||||||
import org.elasticsearch.cluster.service.ClusterService;
|
|
||||||
import org.elasticsearch.common.Nullable;
|
|
||||||
import org.elasticsearch.common.inject.Inject;
|
|
||||||
import org.elasticsearch.common.io.stream.StreamInput;
|
|
||||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
|
||||||
import org.elasticsearch.common.settings.Settings;
|
|
||||||
import org.elasticsearch.threadpool.ThreadPool;
|
|
||||||
import org.elasticsearch.transport.TransportService;
|
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksCustomMetaData.PersistentTask;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
import static org.elasticsearch.action.ValidateActions.addValidationError;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This action can be used to add the record for the persistent action to the cluster state.
|
|
||||||
*/
|
|
||||||
public class StartPersistentTaskAction extends Action<StartPersistentTaskAction.Request,
|
|
||||||
PersistentTaskResponse,
|
|
||||||
StartPersistentTaskAction.RequestBuilder> {
|
|
||||||
|
|
||||||
public static final StartPersistentTaskAction INSTANCE = new StartPersistentTaskAction();
|
|
||||||
public static final String NAME = "cluster:admin/persistent/start";
|
|
||||||
|
|
||||||
private StartPersistentTaskAction() {
|
|
||||||
super(NAME);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public RequestBuilder newRequestBuilder(ElasticsearchClient client) {
|
|
||||||
return new RequestBuilder(client, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PersistentTaskResponse newResponse() {
|
|
||||||
return new PersistentTaskResponse();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Request extends MasterNodeRequest<Request> {
|
|
||||||
|
|
||||||
private String taskId;
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private String taskName;
|
|
||||||
|
|
||||||
private PersistentTaskParams params;
|
|
||||||
|
|
||||||
public Request() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public Request(String taskId, String taskName, PersistentTaskParams params) {
|
|
||||||
this.taskId = taskId;
|
|
||||||
this.taskName = taskName;
|
|
||||||
this.params = params;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void readFrom(StreamInput in) throws IOException {
|
|
||||||
super.readFrom(in);
|
|
||||||
taskId = in.readString();
|
|
||||||
taskName = in.readString();
|
|
||||||
params = in.readOptionalNamedWriteable(PersistentTaskParams.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeTo(StreamOutput out) throws IOException {
|
|
||||||
super.writeTo(out);
|
|
||||||
out.writeString(taskId);
|
|
||||||
out.writeString(taskName);
|
|
||||||
out.writeOptionalNamedWriteable(params);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ActionRequestValidationException validate() {
|
|
||||||
ActionRequestValidationException validationException = null;
|
|
||||||
if (this.taskId == null) {
|
|
||||||
validationException = addValidationError("task id must be specified", validationException);
|
|
||||||
}
|
|
||||||
if (this.taskName == null) {
|
|
||||||
validationException = addValidationError("action must be specified", validationException);
|
|
||||||
}
|
|
||||||
if (params != null) {
|
|
||||||
if (params.getWriteableName().equals(taskName) == false) {
|
|
||||||
validationException = addValidationError("params have to have the same writeable name as task. params: " +
|
|
||||||
params.getWriteableName() + " task: " + taskName, validationException);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return validationException;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) return true;
|
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
|
||||||
Request request1 = (Request) o;
|
|
||||||
return Objects.equals(taskId, request1.taskId) && Objects.equals(taskName, request1.taskName) &&
|
|
||||||
Objects.equals(params, request1.params);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return Objects.hash(taskId, taskName, params);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getTaskName() {
|
|
||||||
return taskName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTaskName(String taskName) {
|
|
||||||
this.taskName = taskName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getTaskId() {
|
|
||||||
return taskId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTaskId(String taskId) {
|
|
||||||
this.taskId = taskId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PersistentTaskParams getParams() {
|
|
||||||
return params;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public void setParams(PersistentTaskParams params) {
|
|
||||||
this.params = params;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class RequestBuilder extends MasterNodeOperationRequestBuilder<StartPersistentTaskAction.Request,
|
|
||||||
PersistentTaskResponse, StartPersistentTaskAction.RequestBuilder> {
|
|
||||||
|
|
||||||
protected RequestBuilder(ElasticsearchClient client, StartPersistentTaskAction action) {
|
|
||||||
super(client, action, new Request());
|
|
||||||
}
|
|
||||||
|
|
||||||
public RequestBuilder setTaskId(String taskId) {
|
|
||||||
request.setTaskId(taskId);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public RequestBuilder setAction(String action) {
|
|
||||||
request.setTaskName(action);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public RequestBuilder setRequest(PersistentTaskParams params) {
|
|
||||||
request.setParams(params);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class TransportAction extends TransportMasterNodeAction<Request, PersistentTaskResponse> {
|
|
||||||
|
|
||||||
private final PersistentTasksClusterService persistentTasksClusterService;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public TransportAction(Settings settings, TransportService transportService, ClusterService clusterService,
|
|
||||||
ThreadPool threadPool, ActionFilters actionFilters,
|
|
||||||
PersistentTasksClusterService persistentTasksClusterService,
|
|
||||||
PersistentTasksExecutorRegistry persistentTasksExecutorRegistry,
|
|
||||||
PersistentTasksService persistentTasksService,
|
|
||||||
IndexNameExpressionResolver indexNameExpressionResolver) {
|
|
||||||
super(settings, StartPersistentTaskAction.NAME, transportService, clusterService, threadPool, actionFilters,
|
|
||||||
indexNameExpressionResolver, Request::new);
|
|
||||||
this.persistentTasksClusterService = persistentTasksClusterService;
|
|
||||||
NodePersistentTasksExecutor executor = new NodePersistentTasksExecutor(threadPool);
|
|
||||||
clusterService.addListener(new PersistentTasksNodeService(settings, persistentTasksService, persistentTasksExecutorRegistry,
|
|
||||||
transportService.getTaskManager(), executor));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String executor() {
|
|
||||||
return ThreadPool.Names.GENERIC;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected PersistentTaskResponse newResponse() {
|
|
||||||
return new PersistentTaskResponse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected ClusterBlockException checkBlock(Request request, ClusterState state) {
|
|
||||||
// Cluster is not affected but we look up repositories in metadata
|
|
||||||
return state.blocks().globalBlockedException(ClusterBlockLevel.METADATA_WRITE);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected final void masterOperation(final Request request, ClusterState state,
|
|
||||||
final ActionListener<PersistentTaskResponse> listener) {
|
|
||||||
persistentTasksClusterService.createPersistentTask(request.taskId, request.taskName, request.params,
|
|
||||||
new ActionListener<PersistentTask<?>>() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onResponse(PersistentTask<?> task) {
|
|
||||||
listener.onResponse(new PersistentTaskResponse(task));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(Exception e) {
|
|
||||||
listener.onFailure(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
@ -1,197 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.xpack.core.persistent;
|
|
||||||
|
|
||||||
import org.elasticsearch.action.Action;
|
|
||||||
import org.elasticsearch.action.ActionListener;
|
|
||||||
import org.elasticsearch.action.ActionRequestValidationException;
|
|
||||||
import org.elasticsearch.action.support.ActionFilters;
|
|
||||||
import org.elasticsearch.action.support.master.MasterNodeOperationRequestBuilder;
|
|
||||||
import org.elasticsearch.action.support.master.MasterNodeRequest;
|
|
||||||
import org.elasticsearch.action.support.master.TransportMasterNodeAction;
|
|
||||||
import org.elasticsearch.client.ElasticsearchClient;
|
|
||||||
import org.elasticsearch.cluster.ClusterState;
|
|
||||||
import org.elasticsearch.cluster.block.ClusterBlockException;
|
|
||||||
import org.elasticsearch.cluster.block.ClusterBlockLevel;
|
|
||||||
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
|
|
||||||
import org.elasticsearch.cluster.service.ClusterService;
|
|
||||||
import org.elasticsearch.common.inject.Inject;
|
|
||||||
import org.elasticsearch.common.io.stream.StreamInput;
|
|
||||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
|
||||||
import org.elasticsearch.common.settings.Settings;
|
|
||||||
import org.elasticsearch.tasks.Task;
|
|
||||||
import org.elasticsearch.threadpool.ThreadPool;
|
|
||||||
import org.elasticsearch.transport.TransportService;
|
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksCustomMetaData.PersistentTask;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
import static org.elasticsearch.action.ValidateActions.addValidationError;
|
|
||||||
|
|
||||||
public class UpdatePersistentTaskStatusAction extends Action<UpdatePersistentTaskStatusAction.Request,
|
|
||||||
PersistentTaskResponse,
|
|
||||||
UpdatePersistentTaskStatusAction.RequestBuilder> {
|
|
||||||
|
|
||||||
public static final UpdatePersistentTaskStatusAction INSTANCE = new UpdatePersistentTaskStatusAction();
|
|
||||||
public static final String NAME = "cluster:admin/persistent/update_status";
|
|
||||||
|
|
||||||
private UpdatePersistentTaskStatusAction() {
|
|
||||||
super(NAME);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public RequestBuilder newRequestBuilder(ElasticsearchClient client) {
|
|
||||||
return new RequestBuilder(client, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PersistentTaskResponse newResponse() {
|
|
||||||
return new PersistentTaskResponse();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Request extends MasterNodeRequest<Request> {
|
|
||||||
|
|
||||||
private String taskId;
|
|
||||||
|
|
||||||
private long allocationId = -1L;
|
|
||||||
|
|
||||||
private Task.Status status;
|
|
||||||
|
|
||||||
public Request() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public Request(String taskId, long allocationId, Task.Status status) {
|
|
||||||
this.taskId = taskId;
|
|
||||||
this.allocationId = allocationId;
|
|
||||||
this.status = status;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTaskId(String taskId) {
|
|
||||||
this.taskId = taskId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAllocationId(long allocationId) {
|
|
||||||
this.allocationId = allocationId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setStatus(Task.Status status) {
|
|
||||||
this.status = status;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void readFrom(StreamInput in) throws IOException {
|
|
||||||
super.readFrom(in);
|
|
||||||
taskId = in.readString();
|
|
||||||
allocationId = in.readLong();
|
|
||||||
status = in.readOptionalNamedWriteable(Task.Status.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeTo(StreamOutput out) throws IOException {
|
|
||||||
super.writeTo(out);
|
|
||||||
out.writeString(taskId);
|
|
||||||
out.writeLong(allocationId);
|
|
||||||
out.writeOptionalNamedWriteable(status);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ActionRequestValidationException validate() {
|
|
||||||
ActionRequestValidationException validationException = null;
|
|
||||||
if (this.taskId == null) {
|
|
||||||
validationException = addValidationError("task id must be specified", validationException);
|
|
||||||
}
|
|
||||||
if (this.allocationId == -1L) {
|
|
||||||
validationException = addValidationError("allocationId must be specified", validationException);
|
|
||||||
}
|
|
||||||
// We cannot really check if status has the same type as task because we don't have access
|
|
||||||
// to the task here. We will check it when we try to update the task
|
|
||||||
return validationException;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) return true;
|
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
|
||||||
Request request = (Request) o;
|
|
||||||
return Objects.equals(taskId, request.taskId) && allocationId == request.allocationId &&
|
|
||||||
Objects.equals(status, request.status);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return Objects.hash(taskId, allocationId, status);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class RequestBuilder extends MasterNodeOperationRequestBuilder<UpdatePersistentTaskStatusAction.Request,
|
|
||||||
PersistentTaskResponse, UpdatePersistentTaskStatusAction.RequestBuilder> {
|
|
||||||
|
|
||||||
protected RequestBuilder(ElasticsearchClient client, UpdatePersistentTaskStatusAction action) {
|
|
||||||
super(client, action, new Request());
|
|
||||||
}
|
|
||||||
|
|
||||||
public final RequestBuilder setTaskId(String taskId) {
|
|
||||||
request.setTaskId(taskId);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public final RequestBuilder setStatus(Task.Status status) {
|
|
||||||
request.setStatus(status);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class TransportAction extends TransportMasterNodeAction<Request, PersistentTaskResponse> {
|
|
||||||
|
|
||||||
private final PersistentTasksClusterService persistentTasksClusterService;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public TransportAction(Settings settings, TransportService transportService, ClusterService clusterService,
|
|
||||||
ThreadPool threadPool, ActionFilters actionFilters,
|
|
||||||
PersistentTasksClusterService persistentTasksClusterService,
|
|
||||||
IndexNameExpressionResolver indexNameExpressionResolver) {
|
|
||||||
super(settings, UpdatePersistentTaskStatusAction.NAME, transportService, clusterService, threadPool, actionFilters,
|
|
||||||
indexNameExpressionResolver, Request::new);
|
|
||||||
this.persistentTasksClusterService = persistentTasksClusterService;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String executor() {
|
|
||||||
return ThreadPool.Names.MANAGEMENT;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected PersistentTaskResponse newResponse() {
|
|
||||||
return new PersistentTaskResponse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected ClusterBlockException checkBlock(Request request, ClusterState state) {
|
|
||||||
// Cluster is not affected but we look up repositories in metadata
|
|
||||||
return state.blocks().globalBlockedException(ClusterBlockLevel.METADATA_WRITE);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected final void masterOperation(final Request request, ClusterState state,
|
|
||||||
final ActionListener<PersistentTaskResponse> listener) {
|
|
||||||
persistentTasksClusterService.updatePersistentTaskStatus(request.taskId, request.allocationId, request.status,
|
|
||||||
new ActionListener<PersistentTask<?>>() {
|
|
||||||
@Override
|
|
||||||
public void onResponse(PersistentTask<?> task) {
|
|
||||||
listener.onResponse(new PersistentTaskResponse(task));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(Exception e) {
|
|
||||||
listener.onFailure(e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,35 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The Persistent Tasks Executors are responsible for executing restartable tasks that can survive disappearance of a
|
|
||||||
* coordinating and executor nodes.
|
|
||||||
* <p>
|
|
||||||
* In order to be resilient to node restarts, the persistent tasks are using the cluster state instead of a transport service to send
|
|
||||||
* requests and responses. The execution is done in six phases:
|
|
||||||
* <p>
|
|
||||||
* 1. The coordinating node sends an ordinary transport request to the master node to start a new persistent task. This task is handled
|
|
||||||
* by the {@link org.elasticsearch.xpack.core.persistent.PersistentTasksService}, which is using
|
|
||||||
* {@link org.elasticsearch.xpack.core.persistent.PersistentTasksClusterService} to update cluster state with the record about running
|
|
||||||
* persistent task.
|
|
||||||
* <p>
|
|
||||||
* 2. The master node updates the {@link org.elasticsearch.xpack.core.persistent.PersistentTasksCustomMetaData} in the cluster state to
|
|
||||||
* indicate that there is a new persistent task is running in the system.
|
|
||||||
* <p>
|
|
||||||
* 3. The {@link org.elasticsearch.xpack.core.persistent.PersistentTasksNodeService} running on every node in the cluster monitors changes
|
|
||||||
* in the cluster state and starts execution of all new tasks assigned to the node it is running on.
|
|
||||||
* <p>
|
|
||||||
* 4. If the task fails to start on the node, the {@link org.elasticsearch.xpack.core.persistent.PersistentTasksNodeService} uses the
|
|
||||||
* {@link org.elasticsearch.xpack.core.persistent.PersistentTasksCustomMetaData} to notify the
|
|
||||||
* {@link org.elasticsearch.xpack.core.persistent.PersistentTasksService}, which reassigns the action to another node in the cluster.
|
|
||||||
* <p>
|
|
||||||
* 5. If a task finishes successfully on the node and calls listener.onResponse(), the corresponding persistent action is removed from the
|
|
||||||
* cluster state unless removeOnCompletion flag for this task is set to false.
|
|
||||||
* <p>
|
|
||||||
* 6. The {@link org.elasticsearch.xpack.core.persistent.RemovePersistentTaskAction} action can be also used to remove the persistent task.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.elasticsearch.xpack.core.persistent;
|
|
|
@ -45,6 +45,7 @@ import org.elasticsearch.plugins.DiscoveryPlugin;
|
||||||
import org.elasticsearch.plugins.IngestPlugin;
|
import org.elasticsearch.plugins.IngestPlugin;
|
||||||
import org.elasticsearch.plugins.MapperPlugin;
|
import org.elasticsearch.plugins.MapperPlugin;
|
||||||
import org.elasticsearch.plugins.NetworkPlugin;
|
import org.elasticsearch.plugins.NetworkPlugin;
|
||||||
|
import org.elasticsearch.plugins.PersistentTaskPlugin;
|
||||||
import org.elasticsearch.plugins.Plugin;
|
import org.elasticsearch.plugins.Plugin;
|
||||||
import org.elasticsearch.plugins.ScriptPlugin;
|
import org.elasticsearch.plugins.ScriptPlugin;
|
||||||
import org.elasticsearch.rest.RestController;
|
import org.elasticsearch.rest.RestController;
|
||||||
|
@ -56,7 +57,7 @@ import org.elasticsearch.threadpool.ThreadPool;
|
||||||
import org.elasticsearch.transport.Transport;
|
import org.elasticsearch.transport.Transport;
|
||||||
import org.elasticsearch.transport.TransportInterceptor;
|
import org.elasticsearch.transport.TransportInterceptor;
|
||||||
import org.elasticsearch.watcher.ResourceWatcherService;
|
import org.elasticsearch.watcher.ResourceWatcherService;
|
||||||
import org.elasticsearch.xpack.core.XPackPlugin;
|
import org.elasticsearch.persistent.PersistentTasksExecutor;
|
||||||
import org.elasticsearch.xpack.core.ssl.SSLService;
|
import org.elasticsearch.xpack.core.ssl.SSLService;
|
||||||
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
@ -74,8 +75,10 @@ import java.util.function.Supplier;
|
||||||
import java.util.function.UnaryOperator;
|
import java.util.function.UnaryOperator;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import static java.util.stream.Collectors.toList;
|
||||||
|
|
||||||
public class LocalStateCompositeXPackPlugin extends XPackPlugin implements ScriptPlugin, ActionPlugin, IngestPlugin, NetworkPlugin,
|
public class LocalStateCompositeXPackPlugin extends XPackPlugin implements ScriptPlugin, ActionPlugin, IngestPlugin, NetworkPlugin,
|
||||||
ClusterPlugin, DiscoveryPlugin, MapperPlugin, AnalysisPlugin {
|
ClusterPlugin, DiscoveryPlugin, MapperPlugin, AnalysisPlugin, PersistentTaskPlugin {
|
||||||
|
|
||||||
private XPackLicenseState licenseState;
|
private XPackLicenseState licenseState;
|
||||||
private SSLService sslService;
|
private SSLService sslService;
|
||||||
|
@ -379,6 +382,14 @@ public class LocalStateCompositeXPackPlugin extends XPackPlugin implements Scrip
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<PersistentTasksExecutor<?>> getPersistentTasksExecutor(ClusterService clusterService) {
|
||||||
|
return filterPlugins(PersistentTaskPlugin.class).stream()
|
||||||
|
.map(p -> p.getPersistentTasksExecutor(clusterService))
|
||||||
|
.flatMap(List::stream)
|
||||||
|
.collect(toList());
|
||||||
|
}
|
||||||
|
|
||||||
private <T> List<T> filterPlugins(Class<T> type) {
|
private <T> List<T> filterPlugins(Class<T> type) {
|
||||||
return plugins.stream().filter(x -> type.isAssignableFrom(x.getClass())).map(p -> ((T)p))
|
return plugins.stream().filter(x -> type.isAssignableFrom(x.getClass())).map(p -> ((T)p))
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
|
|
|
@ -1,24 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.xpack.core.persistent;
|
|
||||||
|
|
||||||
import org.elasticsearch.test.AbstractStreamableTestCase;
|
|
||||||
import org.elasticsearch.xpack.core.persistent.RemovePersistentTaskAction.Request;
|
|
||||||
|
|
||||||
import static com.carrotsearch.randomizedtesting.RandomizedTest.randomAsciiOfLength;
|
|
||||||
|
|
||||||
public class CancelPersistentTaskRequestTests extends AbstractStreamableTestCase<Request> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Request createTestInstance() {
|
|
||||||
return new Request(randomAsciiOfLength(10));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Request createBlankInstance() {
|
|
||||||
return new Request();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,439 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.xpack.core.persistent;
|
|
||||||
|
|
||||||
import com.carrotsearch.hppc.cursors.ObjectCursor;
|
|
||||||
import org.elasticsearch.Version;
|
|
||||||
import org.elasticsearch.cluster.ClusterChangedEvent;
|
|
||||||
import org.elasticsearch.cluster.ClusterName;
|
|
||||||
import org.elasticsearch.cluster.ClusterState;
|
|
||||||
import org.elasticsearch.cluster.metadata.IndexMetaData;
|
|
||||||
import org.elasticsearch.cluster.metadata.MetaData;
|
|
||||||
import org.elasticsearch.cluster.node.DiscoveryNode;
|
|
||||||
import org.elasticsearch.cluster.node.DiscoveryNodes;
|
|
||||||
import org.elasticsearch.cluster.routing.RoutingTable;
|
|
||||||
import org.elasticsearch.common.UUIDs;
|
|
||||||
import org.elasticsearch.common.settings.Settings;
|
|
||||||
import org.elasticsearch.test.ESTestCase;
|
|
||||||
import org.elasticsearch.test.VersionUtils;
|
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksCustomMetaData.Assignment;
|
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksCustomMetaData.PersistentTask;
|
|
||||||
import org.elasticsearch.xpack.core.persistent.TestPersistentTasksPlugin.TestParams;
|
|
||||||
import org.elasticsearch.xpack.core.persistent.TestPersistentTasksPlugin.TestPersistentTasksExecutor;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import static java.util.Collections.emptyMap;
|
|
||||||
import static org.elasticsearch.xpack.core.persistent.PersistentTasksExecutor.NO_NODE_FOUND;
|
|
||||||
import static org.hamcrest.Matchers.equalTo;
|
|
||||||
import static org.hamcrest.Matchers.lessThanOrEqualTo;
|
|
||||||
import static org.hamcrest.Matchers.notNullValue;
|
|
||||||
import static org.hamcrest.Matchers.nullValue;
|
|
||||||
|
|
||||||
public class PersistentTasksClusterServiceTests extends ESTestCase {
|
|
||||||
|
|
||||||
public void testReassignmentRequired() {
|
|
||||||
int numberOfIterations = randomIntBetween(1, 30);
|
|
||||||
ClusterState clusterState = initialState();
|
|
||||||
for (int i = 0; i < numberOfIterations; i++) {
|
|
||||||
boolean significant = randomBoolean();
|
|
||||||
ClusterState previousState = clusterState;
|
|
||||||
logger.info("inter {} significant: {}", i, significant);
|
|
||||||
if (significant) {
|
|
||||||
clusterState = significantChange(clusterState);
|
|
||||||
} else {
|
|
||||||
clusterState = insignificantChange(clusterState);
|
|
||||||
}
|
|
||||||
ClusterChangedEvent event = new ClusterChangedEvent("test", clusterState, previousState);
|
|
||||||
assertThat(dumpEvent(event), PersistentTasksClusterService.reassignmentRequired(event,
|
|
||||||
new PersistentTasksClusterService.ExecutorNodeDecider() {
|
|
||||||
@Override
|
|
||||||
public <Params extends PersistentTaskParams> Assignment getAssignment(
|
|
||||||
String action, ClusterState currentState, Params params) {
|
|
||||||
if ("never_assign".equals(((TestParams) params).getTestParam())) {
|
|
||||||
return NO_NODE_FOUND;
|
|
||||||
}
|
|
||||||
return randomNodeAssignment(currentState.nodes());
|
|
||||||
}
|
|
||||||
}), equalTo(significant));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testReassignTasksWithNoTasks() {
|
|
||||||
ClusterState clusterState = initialState();
|
|
||||||
assertThat(reassign(clusterState).metaData().custom(PersistentTasksCustomMetaData.TYPE), nullValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testReassignConsidersClusterStateUpdates() {
|
|
||||||
ClusterState clusterState = initialState();
|
|
||||||
ClusterState.Builder builder = ClusterState.builder(clusterState);
|
|
||||||
PersistentTasksCustomMetaData.Builder tasks = PersistentTasksCustomMetaData.builder(
|
|
||||||
clusterState.metaData().custom(PersistentTasksCustomMetaData.TYPE));
|
|
||||||
DiscoveryNodes.Builder nodes = DiscoveryNodes.builder(clusterState.nodes());
|
|
||||||
addTestNodes(nodes, randomIntBetween(1, 10));
|
|
||||||
int numberOfTasks = randomIntBetween(2, 40);
|
|
||||||
for (int i = 0; i < numberOfTasks; i++) {
|
|
||||||
addTask(tasks, "assign_one", randomBoolean() ? null : "no_longer_exits");
|
|
||||||
}
|
|
||||||
|
|
||||||
MetaData.Builder metaData = MetaData.builder(clusterState.metaData()).putCustom(PersistentTasksCustomMetaData.TYPE, tasks.build());
|
|
||||||
clusterState = builder.metaData(metaData).nodes(nodes).build();
|
|
||||||
ClusterState newClusterState = reassign(clusterState);
|
|
||||||
|
|
||||||
PersistentTasksCustomMetaData tasksInProgress = newClusterState.getMetaData().custom(PersistentTasksCustomMetaData.TYPE);
|
|
||||||
assertThat(tasksInProgress, notNullValue());
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testReassignTasks() {
|
|
||||||
ClusterState clusterState = initialState();
|
|
||||||
ClusterState.Builder builder = ClusterState.builder(clusterState);
|
|
||||||
PersistentTasksCustomMetaData.Builder tasks = PersistentTasksCustomMetaData.builder(
|
|
||||||
clusterState.metaData().custom(PersistentTasksCustomMetaData.TYPE));
|
|
||||||
DiscoveryNodes.Builder nodes = DiscoveryNodes.builder(clusterState.nodes());
|
|
||||||
addTestNodes(nodes, randomIntBetween(1, 10));
|
|
||||||
int numberOfTasks = randomIntBetween(0, 40);
|
|
||||||
for (int i = 0; i < numberOfTasks; i++) {
|
|
||||||
switch (randomInt(2)) {
|
|
||||||
case 0:
|
|
||||||
// add an unassigned task that should get assigned because it's assigned to a non-existing node or unassigned
|
|
||||||
addTask(tasks, "assign_me", randomBoolean() ? null : "no_longer_exits");
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
// add a task assigned to non-existing node that should not get assigned
|
|
||||||
addTask(tasks, "dont_assign_me", randomBoolean() ? null : "no_longer_exits");
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
addTask(tasks, "assign_one", randomBoolean() ? null : "no_longer_exits");
|
|
||||||
break;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
MetaData.Builder metaData = MetaData.builder(clusterState.metaData()).putCustom(PersistentTasksCustomMetaData.TYPE, tasks.build());
|
|
||||||
clusterState = builder.metaData(metaData).nodes(nodes).build();
|
|
||||||
ClusterState newClusterState = reassign(clusterState);
|
|
||||||
|
|
||||||
PersistentTasksCustomMetaData tasksInProgress = newClusterState.getMetaData().custom(PersistentTasksCustomMetaData.TYPE);
|
|
||||||
assertThat(tasksInProgress, notNullValue());
|
|
||||||
|
|
||||||
assertThat("number of tasks shouldn't change as a result or reassignment",
|
|
||||||
numberOfTasks, equalTo(tasksInProgress.tasks().size()));
|
|
||||||
|
|
||||||
int assignOneCount = 0;
|
|
||||||
|
|
||||||
for (PersistentTask<?> task : tasksInProgress.tasks()) {
|
|
||||||
// explanation should correspond to the action name
|
|
||||||
switch (((TestParams) task.getParams()).getTestParam()) {
|
|
||||||
case "assign_me":
|
|
||||||
assertThat(task.getExecutorNode(), notNullValue());
|
|
||||||
assertThat(task.isAssigned(), equalTo(true));
|
|
||||||
if (clusterState.nodes().nodeExists(task.getExecutorNode()) == false) {
|
|
||||||
logger.info(clusterState.metaData().custom(PersistentTasksCustomMetaData.TYPE).toString());
|
|
||||||
}
|
|
||||||
assertThat("task should be assigned to a node that is in the cluster, was assigned to " + task.getExecutorNode(),
|
|
||||||
clusterState.nodes().nodeExists(task.getExecutorNode()), equalTo(true));
|
|
||||||
assertThat(task.getAssignment().getExplanation(), equalTo("test assignment"));
|
|
||||||
break;
|
|
||||||
case "dont_assign_me":
|
|
||||||
assertThat(task.getExecutorNode(), nullValue());
|
|
||||||
assertThat(task.isAssigned(), equalTo(false));
|
|
||||||
assertThat(task.getAssignment().getExplanation(), equalTo("no appropriate nodes found for the assignment"));
|
|
||||||
break;
|
|
||||||
case "assign_one":
|
|
||||||
if (task.isAssigned()) {
|
|
||||||
assignOneCount++;
|
|
||||||
assertThat("more than one assign_one tasks are assigned", assignOneCount, lessThanOrEqualTo(1));
|
|
||||||
assertThat(task.getAssignment().getExplanation(), equalTo("test assignment"));
|
|
||||||
} else {
|
|
||||||
assertThat(task.getAssignment().getExplanation(), equalTo("only one task can be assigned at a time"));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
fail("Unknown action " + task.getTaskName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void addTestNodes(DiscoveryNodes.Builder nodes, int nonLocalNodesCount) {
|
|
||||||
for (int i = 0; i < nonLocalNodesCount; i++) {
|
|
||||||
nodes.add(new DiscoveryNode("other_node_" + i, buildNewFakeTransportAddress(), Version.CURRENT));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private ClusterState reassign(ClusterState clusterState) {
|
|
||||||
return PersistentTasksClusterService.reassignTasks(clusterState, logger,
|
|
||||||
new PersistentTasksClusterService.ExecutorNodeDecider() {
|
|
||||||
@Override
|
|
||||||
public <Params extends PersistentTaskParams> Assignment getAssignment(
|
|
||||||
String action, ClusterState currentState, Params params) {
|
|
||||||
TestParams testParams = (TestParams) params;
|
|
||||||
switch (testParams.getTestParam()) {
|
|
||||||
case "assign_me":
|
|
||||||
return randomNodeAssignment(currentState.nodes());
|
|
||||||
case "dont_assign_me":
|
|
||||||
return NO_NODE_FOUND;
|
|
||||||
case "fail_me_if_called":
|
|
||||||
fail("the decision decider shouldn't be called on this task");
|
|
||||||
return null;
|
|
||||||
case "assign_one":
|
|
||||||
return assignOnlyOneTaskAtATime(currentState);
|
|
||||||
default:
|
|
||||||
fail("unknown param " + testParams.getTestParam());
|
|
||||||
}
|
|
||||||
return NO_NODE_FOUND;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private Assignment assignOnlyOneTaskAtATime(ClusterState clusterState) {
|
|
||||||
DiscoveryNodes nodes = clusterState.nodes();
|
|
||||||
PersistentTasksCustomMetaData tasksInProgress = clusterState.getMetaData().custom(PersistentTasksCustomMetaData.TYPE);
|
|
||||||
if (tasksInProgress.findTasks(TestPersistentTasksExecutor.NAME, task ->
|
|
||||||
"assign_one".equals(((TestParams) task.getParams()).getTestParam()) &&
|
|
||||||
nodes.nodeExists(task.getExecutorNode())).isEmpty()) {
|
|
||||||
return randomNodeAssignment(clusterState.nodes());
|
|
||||||
} else {
|
|
||||||
return new Assignment(null, "only one task can be assigned at a time");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Assignment randomNodeAssignment(DiscoveryNodes nodes) {
|
|
||||||
if (nodes.getNodes().isEmpty()) {
|
|
||||||
return NO_NODE_FOUND;
|
|
||||||
}
|
|
||||||
List<String> nodeList = new ArrayList<>();
|
|
||||||
for (ObjectCursor<String> node : nodes.getNodes().keys()) {
|
|
||||||
nodeList.add(node.value);
|
|
||||||
}
|
|
||||||
String node = randomFrom(nodeList);
|
|
||||||
if (node != null) {
|
|
||||||
return new Assignment(node, "test assignment");
|
|
||||||
} else {
|
|
||||||
return NO_NODE_FOUND;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String dumpEvent(ClusterChangedEvent event) {
|
|
||||||
return "nodes_changed: " + event.nodesChanged() +
|
|
||||||
" nodes_removed:" + event.nodesRemoved() +
|
|
||||||
" routing_table_changed:" + event.routingTableChanged() +
|
|
||||||
" tasks: " + event.state().metaData().custom(PersistentTasksCustomMetaData.TYPE);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ClusterState significantChange(ClusterState clusterState) {
|
|
||||||
ClusterState.Builder builder = ClusterState.builder(clusterState);
|
|
||||||
PersistentTasksCustomMetaData tasks = clusterState.getMetaData().custom(PersistentTasksCustomMetaData.TYPE);
|
|
||||||
if (tasks != null) {
|
|
||||||
if (randomBoolean()) {
|
|
||||||
for (PersistentTask<?> task : tasks.tasks()) {
|
|
||||||
if (task.isAssigned() && clusterState.nodes().nodeExists(task.getExecutorNode())) {
|
|
||||||
logger.info("removed node {}", task.getExecutorNode());
|
|
||||||
builder.nodes(DiscoveryNodes.builder(clusterState.nodes()).remove(task.getExecutorNode()));
|
|
||||||
return builder.build();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
boolean tasksOrNodesChanged = false;
|
|
||||||
// add a new unassigned task
|
|
||||||
if (hasAssignableTasks(tasks, clusterState.nodes()) == false) {
|
|
||||||
// we don't have any unassigned tasks - add some
|
|
||||||
if (randomBoolean()) {
|
|
||||||
logger.info("added random task");
|
|
||||||
addRandomTask(builder, MetaData.builder(clusterState.metaData()), PersistentTasksCustomMetaData.builder(tasks), null);
|
|
||||||
tasksOrNodesChanged = true;
|
|
||||||
} else {
|
|
||||||
logger.info("added unassignable task with custom assignment message");
|
|
||||||
addRandomTask(builder, MetaData.builder(clusterState.metaData()), PersistentTasksCustomMetaData.builder(tasks),
|
|
||||||
new Assignment(null, "change me"), "never_assign");
|
|
||||||
tasksOrNodesChanged = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// add a node if there are unassigned tasks
|
|
||||||
if (clusterState.nodes().getNodes().isEmpty()) {
|
|
||||||
logger.info("added random node");
|
|
||||||
builder.nodes(DiscoveryNodes.builder(clusterState.nodes()).add(newNode(randomAlphaOfLength(10))));
|
|
||||||
tasksOrNodesChanged = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tasksOrNodesChanged == false) {
|
|
||||||
// change routing table to simulate a change
|
|
||||||
logger.info("changed routing table");
|
|
||||||
MetaData.Builder metaData = MetaData.builder(clusterState.metaData());
|
|
||||||
RoutingTable.Builder routingTable = RoutingTable.builder(clusterState.routingTable());
|
|
||||||
changeRoutingTable(metaData, routingTable);
|
|
||||||
builder.metaData(metaData).routingTable(routingTable.build());
|
|
||||||
}
|
|
||||||
return builder.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private PersistentTasksCustomMetaData removeTasksWithChangingAssignment(PersistentTasksCustomMetaData tasks) {
|
|
||||||
if (tasks != null) {
|
|
||||||
boolean changed = false;
|
|
||||||
PersistentTasksCustomMetaData.Builder tasksBuilder = PersistentTasksCustomMetaData.builder(tasks);
|
|
||||||
for (PersistentTask<?> task : tasks.tasks()) {
|
|
||||||
// Remove all unassigned tasks that cause changing assignments they might trigger a significant change
|
|
||||||
if ("never_assign".equals(((TestParams) task.getParams()).getTestParam()) &&
|
|
||||||
"change me".equals(task.getAssignment().getExplanation())) {
|
|
||||||
logger.info("removed task with changing assignment {}", task.getId());
|
|
||||||
tasksBuilder.removeTask(task.getId());
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (changed) {
|
|
||||||
return tasksBuilder.build();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return tasks;
|
|
||||||
}
|
|
||||||
|
|
||||||
private ClusterState insignificantChange(ClusterState clusterState) {
|
|
||||||
ClusterState.Builder builder = ClusterState.builder(clusterState);
|
|
||||||
PersistentTasksCustomMetaData tasks = clusterState.getMetaData().custom(PersistentTasksCustomMetaData.TYPE);
|
|
||||||
tasks = removeTasksWithChangingAssignment(tasks);
|
|
||||||
PersistentTasksCustomMetaData.Builder tasksBuilder = PersistentTasksCustomMetaData.builder(tasks);
|
|
||||||
|
|
||||||
if (randomBoolean()) {
|
|
||||||
if (hasAssignableTasks(tasks, clusterState.nodes()) == false) {
|
|
||||||
// we don't have any unassigned tasks - adding a node or changing a routing table shouldn't affect anything
|
|
||||||
if (randomBoolean()) {
|
|
||||||
logger.info("added random node");
|
|
||||||
builder.nodes(DiscoveryNodes.builder(clusterState.nodes()).add(newNode(randomAlphaOfLength(10))));
|
|
||||||
}
|
|
||||||
if (randomBoolean()) {
|
|
||||||
logger.info("added random unassignable task");
|
|
||||||
addRandomTask(builder, MetaData.builder(clusterState.metaData()), tasksBuilder, NO_NODE_FOUND, "never_assign");
|
|
||||||
return builder.build();
|
|
||||||
}
|
|
||||||
logger.info("changed routing table");
|
|
||||||
MetaData.Builder metaData = MetaData.builder(clusterState.metaData());
|
|
||||||
metaData.putCustom(PersistentTasksCustomMetaData.TYPE, tasksBuilder.build());
|
|
||||||
RoutingTable.Builder routingTable = RoutingTable.builder(clusterState.routingTable());
|
|
||||||
changeRoutingTable(metaData, routingTable);
|
|
||||||
builder.metaData(metaData).routingTable(routingTable.build());
|
|
||||||
return builder.build();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (randomBoolean()) {
|
|
||||||
// remove a node that doesn't have any tasks assigned to it and it's not the master node
|
|
||||||
for (DiscoveryNode node : clusterState.nodes()) {
|
|
||||||
if (hasTasksAssignedTo(tasks, node.getId()) == false && "this_node".equals(node.getId()) == false) {
|
|
||||||
logger.info("removed unassigned node {}", node.getId());
|
|
||||||
return builder.nodes(DiscoveryNodes.builder(clusterState.nodes()).remove(node.getId())).build();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (randomBoolean()) {
|
|
||||||
// clear the task
|
|
||||||
if (randomBoolean()) {
|
|
||||||
logger.info("removed all tasks");
|
|
||||||
MetaData.Builder metaData = MetaData.builder(clusterState.metaData()).putCustom(PersistentTasksCustomMetaData.TYPE,
|
|
||||||
PersistentTasksCustomMetaData.builder().build());
|
|
||||||
return builder.metaData(metaData).build();
|
|
||||||
} else {
|
|
||||||
logger.info("set task custom to null");
|
|
||||||
MetaData.Builder metaData = MetaData.builder(clusterState.metaData()).removeCustom(PersistentTasksCustomMetaData.TYPE);
|
|
||||||
return builder.metaData(metaData).build();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
logger.info("removed all unassigned tasks and changed routing table");
|
|
||||||
if (tasks != null) {
|
|
||||||
for (PersistentTask<?> task : tasks.tasks()) {
|
|
||||||
if (task.getExecutorNode() == null || "never_assign".equals(((TestParams) task.getParams()).getTestParam())) {
|
|
||||||
tasksBuilder.removeTask(task.getId());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Just add a random index - that shouldn't change anything
|
|
||||||
IndexMetaData indexMetaData = IndexMetaData.builder(randomAlphaOfLength(10))
|
|
||||||
.settings(Settings.builder().put("index.version.created", VersionUtils.randomVersion(random())))
|
|
||||||
.numberOfShards(1)
|
|
||||||
.numberOfReplicas(1)
|
|
||||||
.build();
|
|
||||||
MetaData.Builder metaData = MetaData.builder(clusterState.metaData()).put(indexMetaData, false)
|
|
||||||
.putCustom(PersistentTasksCustomMetaData.TYPE, tasksBuilder.build());
|
|
||||||
return builder.metaData(metaData).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean hasAssignableTasks(PersistentTasksCustomMetaData tasks, DiscoveryNodes discoveryNodes) {
|
|
||||||
if (tasks == null || tasks.tasks().isEmpty()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return tasks.tasks().stream().anyMatch(task -> {
|
|
||||||
if (task.getExecutorNode() == null || discoveryNodes.nodeExists(task.getExecutorNode())) {
|
|
||||||
return "never_assign".equals(((TestParams) task.getParams()).getTestParam()) == false;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean hasTasksAssignedTo(PersistentTasksCustomMetaData tasks, String nodeId) {
|
|
||||||
return tasks != null && tasks.tasks().stream().anyMatch(
|
|
||||||
task -> nodeId.equals(task.getExecutorNode())) == false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private ClusterState.Builder addRandomTask(ClusterState.Builder clusterStateBuilder,
|
|
||||||
MetaData.Builder metaData, PersistentTasksCustomMetaData.Builder tasks,
|
|
||||||
String node) {
|
|
||||||
return addRandomTask(clusterStateBuilder, metaData, tasks, new Assignment(node, randomAlphaOfLength(10)),
|
|
||||||
randomAlphaOfLength(10));
|
|
||||||
}
|
|
||||||
|
|
||||||
private ClusterState.Builder addRandomTask(ClusterState.Builder clusterStateBuilder,
|
|
||||||
MetaData.Builder metaData, PersistentTasksCustomMetaData.Builder tasks,
|
|
||||||
Assignment assignment, String param) {
|
|
||||||
return clusterStateBuilder.metaData(metaData.putCustom(PersistentTasksCustomMetaData.TYPE,
|
|
||||||
tasks.addTask(UUIDs.base64UUID(), TestPersistentTasksExecutor.NAME, new TestParams(param), assignment).build()));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addTask(PersistentTasksCustomMetaData.Builder tasks, String param, String node) {
|
|
||||||
tasks.addTask(UUIDs.base64UUID(), TestPersistentTasksExecutor.NAME, new TestParams(param),
|
|
||||||
new Assignment(node, "explanation: " + param));
|
|
||||||
}
|
|
||||||
|
|
||||||
private DiscoveryNode newNode(String nodeId) {
|
|
||||||
return new DiscoveryNode(nodeId, buildNewFakeTransportAddress(), emptyMap(),
|
|
||||||
Collections.unmodifiableSet(new HashSet<>(Arrays.asList(DiscoveryNode.Role.MASTER, DiscoveryNode.Role.DATA))),
|
|
||||||
Version.CURRENT);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private ClusterState initialState() {
|
|
||||||
MetaData.Builder metaData = MetaData.builder();
|
|
||||||
RoutingTable.Builder routingTable = RoutingTable.builder();
|
|
||||||
int randomIndices = randomIntBetween(0, 5);
|
|
||||||
for (int i = 0; i < randomIndices; i++) {
|
|
||||||
changeRoutingTable(metaData, routingTable);
|
|
||||||
}
|
|
||||||
|
|
||||||
DiscoveryNodes.Builder nodes = DiscoveryNodes.builder();
|
|
||||||
nodes.add(DiscoveryNode.createLocal(Settings.EMPTY, buildNewFakeTransportAddress(), "this_node"));
|
|
||||||
nodes.localNodeId("this_node");
|
|
||||||
nodes.masterNodeId("this_node");
|
|
||||||
|
|
||||||
return ClusterState.builder(ClusterName.DEFAULT)
|
|
||||||
.metaData(metaData)
|
|
||||||
.routingTable(routingTable.build())
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void changeRoutingTable(MetaData.Builder metaData, RoutingTable.Builder routingTable) {
|
|
||||||
IndexMetaData indexMetaData = IndexMetaData.builder(randomAlphaOfLength(10))
|
|
||||||
.settings(Settings.builder().put("index.version.created", VersionUtils.randomVersion(random())))
|
|
||||||
.numberOfShards(1)
|
|
||||||
.numberOfReplicas(1)
|
|
||||||
.build();
|
|
||||||
metaData.put(indexMetaData, false);
|
|
||||||
routingTable.addAsNew(indexMetaData);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,249 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.xpack.core.persistent;
|
|
||||||
|
|
||||||
import org.elasticsearch.ResourceNotFoundException;
|
|
||||||
import org.elasticsearch.cluster.Diff;
|
|
||||||
import org.elasticsearch.cluster.NamedDiff;
|
|
||||||
import org.elasticsearch.cluster.metadata.MetaData;
|
|
||||||
import org.elasticsearch.cluster.metadata.MetaData.Custom;
|
|
||||||
import org.elasticsearch.common.ParseField;
|
|
||||||
import org.elasticsearch.common.UUIDs;
|
|
||||||
import org.elasticsearch.common.bytes.BytesReference;
|
|
||||||
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
|
|
||||||
import org.elasticsearch.common.io.stream.NamedWriteableRegistry.Entry;
|
|
||||||
import org.elasticsearch.common.io.stream.Writeable;
|
|
||||||
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
|
|
||||||
import org.elasticsearch.common.xcontent.ToXContent;
|
|
||||||
import org.elasticsearch.common.xcontent.XContentFactory;
|
|
||||||
import org.elasticsearch.common.xcontent.XContentParser;
|
|
||||||
import org.elasticsearch.common.xcontent.XContentType;
|
|
||||||
import org.elasticsearch.tasks.Task;
|
|
||||||
import org.elasticsearch.test.AbstractDiffableSerializationTestCase;
|
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksCustomMetaData.Assignment;
|
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksCustomMetaData.Builder;
|
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksCustomMetaData.PersistentTask;
|
|
||||||
import org.elasticsearch.xpack.core.persistent.TestPersistentTasksPlugin.Status;
|
|
||||||
import org.elasticsearch.xpack.core.persistent.TestPersistentTasksPlugin.TestParams;
|
|
||||||
import org.elasticsearch.xpack.core.persistent.TestPersistentTasksPlugin.TestPersistentTasksExecutor;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
|
||||||
|
|
||||||
import static org.elasticsearch.cluster.metadata.MetaData.CONTEXT_MODE_GATEWAY;
|
|
||||||
import static org.elasticsearch.cluster.metadata.MetaData.CONTEXT_MODE_SNAPSHOT;
|
|
||||||
import static org.elasticsearch.xpack.core.persistent.PersistentTasksExecutor.NO_NODE_FOUND;
|
|
||||||
|
|
||||||
public class PersistentTasksCustomMetaDataTests extends AbstractDiffableSerializationTestCase<Custom> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected PersistentTasksCustomMetaData createTestInstance() {
|
|
||||||
int numberOfTasks = randomInt(10);
|
|
||||||
PersistentTasksCustomMetaData.Builder tasks = PersistentTasksCustomMetaData.builder();
|
|
||||||
for (int i = 0; i < numberOfTasks; i++) {
|
|
||||||
String taskId = UUIDs.base64UUID();
|
|
||||||
tasks.addTask(taskId, TestPersistentTasksExecutor.NAME, new TestParams(randomAlphaOfLength(10)),
|
|
||||||
randomAssignment());
|
|
||||||
if (randomBoolean()) {
|
|
||||||
// From time to time update status
|
|
||||||
tasks.updateTaskStatus(taskId, new Status(randomAlphaOfLength(10)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return tasks.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Writeable.Reader<Custom> instanceReader() {
|
|
||||||
return PersistentTasksCustomMetaData::new;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected NamedWriteableRegistry getNamedWriteableRegistry() {
|
|
||||||
return new NamedWriteableRegistry(Arrays.asList(
|
|
||||||
new Entry(MetaData.Custom.class, PersistentTasksCustomMetaData.TYPE, PersistentTasksCustomMetaData::new),
|
|
||||||
new Entry(NamedDiff.class, PersistentTasksCustomMetaData.TYPE, PersistentTasksCustomMetaData::readDiffFrom),
|
|
||||||
new Entry(PersistentTaskParams.class, TestPersistentTasksExecutor.NAME, TestParams::new),
|
|
||||||
new Entry(Task.Status.class, TestPersistentTasksExecutor.NAME, Status::new)
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Custom makeTestChanges(Custom testInstance) {
|
|
||||||
Builder builder = PersistentTasksCustomMetaData.builder((PersistentTasksCustomMetaData) testInstance);
|
|
||||||
switch (randomInt(3)) {
|
|
||||||
case 0:
|
|
||||||
addRandomTask(builder);
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
if (builder.getCurrentTaskIds().isEmpty()) {
|
|
||||||
addRandomTask(builder);
|
|
||||||
} else {
|
|
||||||
builder.reassignTask(pickRandomTask(builder), randomAssignment());
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
if (builder.getCurrentTaskIds().isEmpty()) {
|
|
||||||
addRandomTask(builder);
|
|
||||||
} else {
|
|
||||||
builder.updateTaskStatus(pickRandomTask(builder), randomBoolean() ? new Status(randomAlphaOfLength(10)) : null);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
if (builder.getCurrentTaskIds().isEmpty()) {
|
|
||||||
addRandomTask(builder);
|
|
||||||
} else {
|
|
||||||
builder.removeTask(pickRandomTask(builder));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return builder.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Writeable.Reader<Diff<Custom>> diffReader() {
|
|
||||||
return PersistentTasksCustomMetaData::readDiffFrom;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected PersistentTasksCustomMetaData doParseInstance(XContentParser parser) throws IOException {
|
|
||||||
return PersistentTasksCustomMetaData.fromXContent(parser);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
@Override
|
|
||||||
protected XContentBuilder toXContent(Custom instance, XContentType contentType) throws IOException {
|
|
||||||
return toXContent(instance, contentType, new ToXContent.MapParams(
|
|
||||||
Collections.singletonMap(MetaData.CONTEXT_MODE_PARAM, MetaData.XContentContext.API.toString())));
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
private String addRandomTask(Builder builder) {
|
|
||||||
String taskId = UUIDs.base64UUID();
|
|
||||||
builder.addTask(taskId, TestPersistentTasksExecutor.NAME, new TestParams(randomAlphaOfLength(10)), randomAssignment());
|
|
||||||
return taskId;
|
|
||||||
}
|
|
||||||
|
|
||||||
private String pickRandomTask(PersistentTasksCustomMetaData.Builder testInstance) {
|
|
||||||
return randomFrom(new ArrayList<>(testInstance.getCurrentTaskIds()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected NamedXContentRegistry xContentRegistry() {
|
|
||||||
return new NamedXContentRegistry(Arrays.asList(
|
|
||||||
new NamedXContentRegistry.Entry(PersistentTaskParams.class, new ParseField(TestPersistentTasksExecutor.NAME),
|
|
||||||
TestParams::fromXContent),
|
|
||||||
new NamedXContentRegistry.Entry(Task.Status.class, new ParseField(TestPersistentTasksExecutor.NAME), Status::fromXContent)
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public void testSerializationContext() throws Exception {
|
|
||||||
PersistentTasksCustomMetaData testInstance = createTestInstance();
|
|
||||||
for (int i = 0; i < randomInt(10); i++) {
|
|
||||||
testInstance = (PersistentTasksCustomMetaData) makeTestChanges(testInstance);
|
|
||||||
}
|
|
||||||
|
|
||||||
ToXContent.MapParams params = new ToXContent.MapParams(
|
|
||||||
Collections.singletonMap(MetaData.CONTEXT_MODE_PARAM, randomFrom(CONTEXT_MODE_SNAPSHOT, CONTEXT_MODE_GATEWAY)));
|
|
||||||
|
|
||||||
XContentType xContentType = randomFrom(XContentType.values());
|
|
||||||
BytesReference shuffled = toShuffledXContent(testInstance, xContentType, params, false);
|
|
||||||
|
|
||||||
XContentParser parser = createParser(XContentFactory.xContent(xContentType), shuffled);
|
|
||||||
PersistentTasksCustomMetaData newInstance = doParseInstance(parser);
|
|
||||||
assertNotSame(newInstance, testInstance);
|
|
||||||
|
|
||||||
assertEquals(testInstance.tasks().size(), newInstance.tasks().size());
|
|
||||||
for (PersistentTask<?> testTask : testInstance.tasks()) {
|
|
||||||
PersistentTask<TestParams> newTask = (PersistentTask<TestParams>) newInstance.getTask(testTask.getId());
|
|
||||||
assertNotNull(newTask);
|
|
||||||
|
|
||||||
// Things that should be serialized
|
|
||||||
assertEquals(testTask.getTaskName(), newTask.getTaskName());
|
|
||||||
assertEquals(testTask.getId(), newTask.getId());
|
|
||||||
assertEquals(testTask.getStatus(), newTask.getStatus());
|
|
||||||
assertEquals(testTask.getParams(), newTask.getParams());
|
|
||||||
|
|
||||||
// Things that shouldn't be serialized
|
|
||||||
assertEquals(0, newTask.getAllocationId());
|
|
||||||
assertNull(newTask.getExecutorNode());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testBuilder() {
|
|
||||||
PersistentTasksCustomMetaData persistentTasks = null;
|
|
||||||
String lastKnownTask = "";
|
|
||||||
for (int i = 0; i < randomIntBetween(10, 100); i++) {
|
|
||||||
final Builder builder;
|
|
||||||
if (randomBoolean()) {
|
|
||||||
builder = PersistentTasksCustomMetaData.builder();
|
|
||||||
} else {
|
|
||||||
builder = PersistentTasksCustomMetaData.builder(persistentTasks);
|
|
||||||
}
|
|
||||||
boolean changed = false;
|
|
||||||
for (int j = 0; j < randomIntBetween(1, 10); j++) {
|
|
||||||
switch (randomInt(4)) {
|
|
||||||
case 0:
|
|
||||||
lastKnownTask = addRandomTask(builder);
|
|
||||||
changed = true;
|
|
||||||
break;
|
|
||||||
case 1:
|
|
||||||
if (builder.hasTask(lastKnownTask)) {
|
|
||||||
changed = true;
|
|
||||||
builder.reassignTask(lastKnownTask, randomAssignment());
|
|
||||||
} else {
|
|
||||||
String fLastKnownTask = lastKnownTask;
|
|
||||||
expectThrows(ResourceNotFoundException.class, () -> builder.reassignTask(fLastKnownTask, randomAssignment()));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
if (builder.hasTask(lastKnownTask)) {
|
|
||||||
changed = true;
|
|
||||||
builder.updateTaskStatus(lastKnownTask, randomBoolean() ? new Status(randomAlphaOfLength(10)) : null);
|
|
||||||
} else {
|
|
||||||
String fLastKnownTask = lastKnownTask;
|
|
||||||
expectThrows(ResourceNotFoundException.class, () -> builder.updateTaskStatus(fLastKnownTask, null));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
if (builder.hasTask(lastKnownTask)) {
|
|
||||||
changed = true;
|
|
||||||
builder.removeTask(lastKnownTask);
|
|
||||||
} else {
|
|
||||||
String fLastKnownTask = lastKnownTask;
|
|
||||||
expectThrows(ResourceNotFoundException.class, () -> builder.removeTask(fLastKnownTask));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
if (builder.hasTask(lastKnownTask)) {
|
|
||||||
changed = true;
|
|
||||||
builder.finishTask(lastKnownTask);
|
|
||||||
} else {
|
|
||||||
String fLastKnownTask = lastKnownTask;
|
|
||||||
expectThrows(ResourceNotFoundException.class, () -> builder.finishTask(fLastKnownTask));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
assertEquals(changed, builder.isChanged());
|
|
||||||
persistentTasks = builder.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private Assignment randomAssignment() {
|
|
||||||
if (randomBoolean()) {
|
|
||||||
if (randomBoolean()) {
|
|
||||||
return NO_NODE_FOUND;
|
|
||||||
} else {
|
|
||||||
return new Assignment(null, randomAlphaOfLength(10));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return new Assignment(randomAlphaOfLength(10), randomAlphaOfLength(10));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,103 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.xpack.core.persistent;
|
|
||||||
|
|
||||||
import org.elasticsearch.action.support.PlainActionFuture;
|
|
||||||
import org.elasticsearch.common.UUIDs;
|
|
||||||
import org.elasticsearch.plugins.Plugin;
|
|
||||||
import org.elasticsearch.test.ESIntegTestCase;
|
|
||||||
import org.elasticsearch.test.junit.annotations.TestLogging;
|
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksCustomMetaData.PersistentTask;
|
|
||||||
import org.elasticsearch.xpack.core.persistent.TestPersistentTasksPlugin.TestParams;
|
|
||||||
import org.elasticsearch.xpack.core.persistent.TestPersistentTasksPlugin.TestPersistentTasksExecutor;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.empty;
|
|
||||||
import static org.hamcrest.Matchers.equalTo;
|
|
||||||
import static org.hamcrest.Matchers.greaterThan;
|
|
||||||
|
|
||||||
@ESIntegTestCase.ClusterScope(scope = ESIntegTestCase.Scope.TEST, minNumDataNodes = 1)
|
|
||||||
public class PersistentTasksExecutorFullRestartIT extends ESIntegTestCase {
|
|
||||||
@Override
|
|
||||||
protected Collection<Class<? extends Plugin>> nodePlugins() {
|
|
||||||
return Collections.singletonList(TestPersistentTasksPlugin.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Collection<Class<? extends Plugin>> transportClientPlugins() {
|
|
||||||
return nodePlugins();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected boolean ignoreExternalCluster() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@TestLogging("org.elasticsearch.xpack.persistent:TRACE,org.elasticsearch.cluster.service:DEBUG")
|
|
||||||
public void testFullClusterRestart() throws Exception {
|
|
||||||
PersistentTasksService service = internalCluster().getInstance(PersistentTasksService.class);
|
|
||||||
int numberOfTasks = randomIntBetween(1, 10);
|
|
||||||
String[] taskIds = new String[numberOfTasks];
|
|
||||||
List<PlainActionFuture<PersistentTask<TestParams>>> futures = new ArrayList<>(numberOfTasks);
|
|
||||||
|
|
||||||
for (int i = 0; i < numberOfTasks; i++) {
|
|
||||||
PlainActionFuture<PersistentTask<TestParams>> future = new PlainActionFuture<>();
|
|
||||||
futures.add(future);
|
|
||||||
taskIds[i] = UUIDs.base64UUID();
|
|
||||||
service.startPersistentTask(taskIds[i], TestPersistentTasksExecutor.NAME, randomBoolean() ? null : new TestParams("Blah"),
|
|
||||||
future);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < numberOfTasks; i++) {
|
|
||||||
assertThat(futures.get(i).get().getId(), equalTo(taskIds[i]));
|
|
||||||
}
|
|
||||||
|
|
||||||
PersistentTasksCustomMetaData tasksInProgress = internalCluster().clusterService().state().getMetaData()
|
|
||||||
.custom(PersistentTasksCustomMetaData.TYPE);
|
|
||||||
assertThat(tasksInProgress.tasks().size(), equalTo(numberOfTasks));
|
|
||||||
|
|
||||||
// Make sure that at least one of the tasks is running
|
|
||||||
assertBusy(() -> {
|
|
||||||
// Wait for the task to start
|
|
||||||
assertThat(client().admin().cluster().prepareListTasks().setActions(TestPersistentTasksExecutor.NAME + "[c]").get()
|
|
||||||
.getTasks().size(), greaterThan(0));
|
|
||||||
});
|
|
||||||
|
|
||||||
// Restart cluster
|
|
||||||
internalCluster().fullRestart();
|
|
||||||
ensureYellow();
|
|
||||||
|
|
||||||
tasksInProgress = internalCluster().clusterService().state().getMetaData().custom(PersistentTasksCustomMetaData.TYPE);
|
|
||||||
assertThat(tasksInProgress.tasks().size(), equalTo(numberOfTasks));
|
|
||||||
// Check that cluster state is correct
|
|
||||||
for (int i = 0; i < numberOfTasks; i++) {
|
|
||||||
PersistentTask<?> task = tasksInProgress.getTask(taskIds[i]);
|
|
||||||
assertNotNull(task);
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info("Waiting for {} tasks to start", numberOfTasks);
|
|
||||||
assertBusy(() -> {
|
|
||||||
// Wait for all tasks to start
|
|
||||||
assertThat(client().admin().cluster().prepareListTasks().setActions(TestPersistentTasksExecutor.NAME + "[c]").get()
|
|
||||||
.getTasks().size(), equalTo(numberOfTasks));
|
|
||||||
});
|
|
||||||
|
|
||||||
logger.info("Complete all tasks");
|
|
||||||
// Complete the running task and make sure it finishes properly
|
|
||||||
assertThat(new TestPersistentTasksPlugin.TestTasksRequestBuilder(client()).setOperation("finish").get().getTasks().size(),
|
|
||||||
equalTo(numberOfTasks));
|
|
||||||
|
|
||||||
assertBusy(() -> {
|
|
||||||
// Make sure the task is removed from the cluster state
|
|
||||||
assertThat(((PersistentTasksCustomMetaData) internalCluster().clusterService().state().getMetaData()
|
|
||||||
.custom(PersistentTasksCustomMetaData.TYPE)).tasks(), empty());
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,284 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.xpack.core.persistent;
|
|
||||||
|
|
||||||
import org.elasticsearch.ResourceAlreadyExistsException;
|
|
||||||
import org.elasticsearch.ResourceNotFoundException;
|
|
||||||
import org.elasticsearch.action.support.PlainActionFuture;
|
|
||||||
import org.elasticsearch.common.UUIDs;
|
|
||||||
import org.elasticsearch.common.settings.Settings;
|
|
||||||
import org.elasticsearch.common.unit.TimeValue;
|
|
||||||
import org.elasticsearch.plugins.Plugin;
|
|
||||||
import org.elasticsearch.tasks.TaskId;
|
|
||||||
import org.elasticsearch.tasks.TaskInfo;
|
|
||||||
import org.elasticsearch.test.ESIntegTestCase;
|
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksCustomMetaData.PersistentTask;
|
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksService.WaitForPersistentTaskStatusListener;
|
|
||||||
import org.elasticsearch.xpack.core.persistent.TestPersistentTasksPlugin.Status;
|
|
||||||
import org.elasticsearch.xpack.core.persistent.TestPersistentTasksPlugin.TestParams;
|
|
||||||
import org.elasticsearch.xpack.core.persistent.TestPersistentTasksPlugin.TestPersistentTasksExecutor;
|
|
||||||
import org.elasticsearch.xpack.core.persistent.TestPersistentTasksPlugin.TestTasksRequestBuilder;
|
|
||||||
import org.junit.After;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertThrows;
|
|
||||||
import static org.hamcrest.Matchers.empty;
|
|
||||||
import static org.hamcrest.Matchers.equalTo;
|
|
||||||
import static org.hamcrest.Matchers.nullValue;
|
|
||||||
|
|
||||||
@ESIntegTestCase.ClusterScope(scope = ESIntegTestCase.Scope.SUITE, minNumDataNodes = 2)
|
|
||||||
public class PersistentTasksExecutorIT extends ESIntegTestCase {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Collection<Class<? extends Plugin>> nodePlugins() {
|
|
||||||
return Collections.singletonList(TestPersistentTasksPlugin.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Collection<Class<? extends Plugin>> transportClientPlugins() {
|
|
||||||
return nodePlugins();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected boolean ignoreExternalCluster() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@After
|
|
||||||
public void cleanup() throws Exception {
|
|
||||||
assertNoRunningTasks();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class WaitForPersistentTaskStatusFuture<Params extends PersistentTaskParams>
|
|
||||||
extends PlainActionFuture<PersistentTask<Params>>
|
|
||||||
implements WaitForPersistentTaskStatusListener<Params> {
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testPersistentActionFailure() throws Exception {
|
|
||||||
PersistentTasksService persistentTasksService = internalCluster().getInstance(PersistentTasksService.class);
|
|
||||||
PlainActionFuture<PersistentTask<TestParams>> future = new PlainActionFuture<>();
|
|
||||||
persistentTasksService.startPersistentTask(UUIDs.base64UUID(), TestPersistentTasksExecutor.NAME, new TestParams("Blah"), future);
|
|
||||||
long allocationId = future.get().getAllocationId();
|
|
||||||
assertBusy(() -> {
|
|
||||||
// Wait for the task to start
|
|
||||||
assertThat(client().admin().cluster().prepareListTasks().setActions(TestPersistentTasksExecutor.NAME + "[c]").get()
|
|
||||||
.getTasks().size(), equalTo(1));
|
|
||||||
});
|
|
||||||
TaskInfo firstRunningTask = client().admin().cluster().prepareListTasks().setActions(TestPersistentTasksExecutor.NAME + "[c]")
|
|
||||||
.get().getTasks().get(0);
|
|
||||||
logger.info("Found running task with id {} and parent {}", firstRunningTask.getId(), firstRunningTask.getParentTaskId());
|
|
||||||
// Verifying parent
|
|
||||||
assertThat(firstRunningTask.getParentTaskId().getId(), equalTo(allocationId));
|
|
||||||
assertThat(firstRunningTask.getParentTaskId().getNodeId(), equalTo("cluster"));
|
|
||||||
|
|
||||||
logger.info("Failing the running task");
|
|
||||||
// Fail the running task and make sure it restarts properly
|
|
||||||
assertThat(new TestTasksRequestBuilder(client()).setOperation("fail").setTaskId(firstRunningTask.getTaskId())
|
|
||||||
.get().getTasks().size(), equalTo(1));
|
|
||||||
|
|
||||||
logger.info("Waiting for persistent task with id {} to disappear", firstRunningTask.getId());
|
|
||||||
assertBusy(() -> {
|
|
||||||
// Wait for the task to disappear completely
|
|
||||||
assertThat(client().admin().cluster().prepareListTasks().setActions(TestPersistentTasksExecutor.NAME + "[c]").get().getTasks(),
|
|
||||||
empty());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testPersistentActionCompletion() throws Exception {
|
|
||||||
PersistentTasksService persistentTasksService = internalCluster().getInstance(PersistentTasksService.class);
|
|
||||||
PlainActionFuture<PersistentTask<TestParams>> future = new PlainActionFuture<>();
|
|
||||||
String taskId = UUIDs.base64UUID();
|
|
||||||
persistentTasksService.startPersistentTask(taskId, TestPersistentTasksExecutor.NAME, new TestParams("Blah"), future);
|
|
||||||
long allocationId = future.get().getAllocationId();
|
|
||||||
assertBusy(() -> {
|
|
||||||
// Wait for the task to start
|
|
||||||
assertThat(client().admin().cluster().prepareListTasks().setActions(TestPersistentTasksExecutor.NAME + "[c]").get()
|
|
||||||
.getTasks().size(), equalTo(1));
|
|
||||||
});
|
|
||||||
TaskInfo firstRunningTask = client().admin().cluster().prepareListTasks().setActions(TestPersistentTasksExecutor.NAME + "[c]")
|
|
||||||
.setDetailed(true).get().getTasks().get(0);
|
|
||||||
logger.info("Found running task with id {} and parent {}", firstRunningTask.getId(), firstRunningTask.getParentTaskId());
|
|
||||||
// Verifying parent and description
|
|
||||||
assertThat(firstRunningTask.getParentTaskId().getId(), equalTo(allocationId));
|
|
||||||
assertThat(firstRunningTask.getParentTaskId().getNodeId(), equalTo("cluster"));
|
|
||||||
assertThat(firstRunningTask.getDescription(), equalTo("id=" + taskId));
|
|
||||||
|
|
||||||
if (randomBoolean()) {
|
|
||||||
logger.info("Simulating errant completion notification");
|
|
||||||
//try sending completion request with incorrect allocation id
|
|
||||||
PlainActionFuture<PersistentTask<?>> failedCompletionNotificationFuture = new PlainActionFuture<>();
|
|
||||||
persistentTasksService.sendCompletionNotification(taskId, Long.MAX_VALUE, null, failedCompletionNotificationFuture);
|
|
||||||
assertThrows(failedCompletionNotificationFuture, ResourceNotFoundException.class);
|
|
||||||
// Make sure that the task is still running
|
|
||||||
assertThat(client().admin().cluster().prepareListTasks().setActions(TestPersistentTasksExecutor.NAME + "[c]")
|
|
||||||
.setDetailed(true).get().getTasks().size(), equalTo(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
stopOrCancelTask(firstRunningTask.getTaskId());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testPersistentActionWithNoAvailableNode() throws Exception {
|
|
||||||
PersistentTasksService persistentTasksService = internalCluster().getInstance(PersistentTasksService.class);
|
|
||||||
PlainActionFuture<PersistentTask<TestParams>> future = new PlainActionFuture<>();
|
|
||||||
TestParams testParams = new TestParams("Blah");
|
|
||||||
testParams.setExecutorNodeAttr("test");
|
|
||||||
persistentTasksService.startPersistentTask(UUIDs.base64UUID(), TestPersistentTasksExecutor.NAME, testParams, future);
|
|
||||||
String taskId = future.get().getId();
|
|
||||||
|
|
||||||
Settings nodeSettings = Settings.builder().put(nodeSettings(0)).put("node.attr.test_attr", "test").build();
|
|
||||||
String newNode = internalCluster().startNode(nodeSettings);
|
|
||||||
String newNodeId = internalCluster().clusterService(newNode).localNode().getId();
|
|
||||||
assertBusy(() -> {
|
|
||||||
// Wait for the task to start
|
|
||||||
assertThat(client().admin().cluster().prepareListTasks().setActions(TestPersistentTasksExecutor.NAME + "[c]").get().getTasks()
|
|
||||||
.size(), equalTo(1));
|
|
||||||
});
|
|
||||||
TaskInfo taskInfo = client().admin().cluster().prepareListTasks().setActions(TestPersistentTasksExecutor.NAME + "[c]")
|
|
||||||
.get().getTasks().get(0);
|
|
||||||
|
|
||||||
// Verifying the the task runs on the new node
|
|
||||||
assertThat(taskInfo.getTaskId().getNodeId(), equalTo(newNodeId));
|
|
||||||
|
|
||||||
internalCluster().stopRandomNode(settings -> "test".equals(settings.get("node.attr.test_attr")));
|
|
||||||
|
|
||||||
assertBusy(() -> {
|
|
||||||
// Wait for the task to disappear completely
|
|
||||||
assertThat(client().admin().cluster().prepareListTasks().setActions(TestPersistentTasksExecutor.NAME + "[c]").get().getTasks(),
|
|
||||||
empty());
|
|
||||||
});
|
|
||||||
|
|
||||||
// Remove the persistent task
|
|
||||||
PlainActionFuture<PersistentTask<?>> removeFuture = new PlainActionFuture<>();
|
|
||||||
persistentTasksService.cancelPersistentTask(taskId, removeFuture);
|
|
||||||
assertEquals(removeFuture.get().getId(), taskId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testPersistentActionStatusUpdate() throws Exception {
|
|
||||||
PersistentTasksService persistentTasksService = internalCluster().getInstance(PersistentTasksService.class);
|
|
||||||
PlainActionFuture<PersistentTask<TestParams>> future = new PlainActionFuture<>();
|
|
||||||
persistentTasksService.startPersistentTask(UUIDs.base64UUID(), TestPersistentTasksExecutor.NAME, new TestParams("Blah"), future);
|
|
||||||
String taskId = future.get().getId();
|
|
||||||
|
|
||||||
assertBusy(() -> {
|
|
||||||
// Wait for the task to start
|
|
||||||
assertThat(client().admin().cluster().prepareListTasks().setActions(TestPersistentTasksExecutor.NAME + "[c]").get().getTasks()
|
|
||||||
.size(), equalTo(1));
|
|
||||||
});
|
|
||||||
TaskInfo firstRunningTask = client().admin().cluster().prepareListTasks().setActions(TestPersistentTasksExecutor.NAME + "[c]")
|
|
||||||
.get().getTasks().get(0);
|
|
||||||
|
|
||||||
PersistentTasksCustomMetaData tasksInProgress = internalCluster().clusterService().state().getMetaData()
|
|
||||||
.custom(PersistentTasksCustomMetaData.TYPE);
|
|
||||||
assertThat(tasksInProgress.tasks().size(), equalTo(1));
|
|
||||||
assertThat(tasksInProgress.tasks().iterator().next().getStatus(), nullValue());
|
|
||||||
|
|
||||||
int numberOfUpdates = randomIntBetween(1, 10);
|
|
||||||
for (int i = 0; i < numberOfUpdates; i++) {
|
|
||||||
logger.info("Updating the task status");
|
|
||||||
// Complete the running task and make sure it finishes properly
|
|
||||||
assertThat(new TestTasksRequestBuilder(client()).setOperation("update_status").setTaskId(firstRunningTask.getTaskId())
|
|
||||||
.get().getTasks().size(), equalTo(1));
|
|
||||||
|
|
||||||
int finalI = i;
|
|
||||||
WaitForPersistentTaskStatusFuture<?> future1 = new WaitForPersistentTaskStatusFuture<>();
|
|
||||||
persistentTasksService.waitForPersistentTaskStatus(taskId,
|
|
||||||
task -> task != null && task.getStatus() != null && task.getStatus().toString() != null &&
|
|
||||||
task.getStatus().toString().equals("{\"phase\":\"phase " + (finalI + 1) + "\"}"),
|
|
||||||
TimeValue.timeValueSeconds(10), future1);
|
|
||||||
assertThat(future1.get().getId(), equalTo(taskId));
|
|
||||||
}
|
|
||||||
|
|
||||||
WaitForPersistentTaskStatusFuture<?> future1 = new WaitForPersistentTaskStatusFuture<>();
|
|
||||||
persistentTasksService.waitForPersistentTaskStatus(taskId,
|
|
||||||
task -> false, TimeValue.timeValueMillis(10), future1);
|
|
||||||
|
|
||||||
assertThrows(future1, IllegalStateException.class, "timed out after 10ms");
|
|
||||||
|
|
||||||
PlainActionFuture<PersistentTask<?>> failedUpdateFuture = new PlainActionFuture<>();
|
|
||||||
persistentTasksService.updateStatus(taskId, -2, new Status("should fail"), failedUpdateFuture);
|
|
||||||
assertThrows(failedUpdateFuture, ResourceNotFoundException.class, "the task with id " + taskId +
|
|
||||||
" and allocation id -2 doesn't exist");
|
|
||||||
|
|
||||||
// Wait for the task to disappear
|
|
||||||
WaitForPersistentTaskStatusFuture<?> future2 = new WaitForPersistentTaskStatusFuture<>();
|
|
||||||
persistentTasksService.waitForPersistentTaskStatus(taskId, Objects::isNull, TimeValue.timeValueSeconds(10), future2);
|
|
||||||
|
|
||||||
logger.info("Completing the running task");
|
|
||||||
// Complete the running task and make sure it finishes properly
|
|
||||||
assertThat(new TestTasksRequestBuilder(client()).setOperation("finish").setTaskId(firstRunningTask.getTaskId())
|
|
||||||
.get().getTasks().size(), equalTo(1));
|
|
||||||
|
|
||||||
assertThat(future2.get(), nullValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testCreatePersistentTaskWithDuplicateId() throws Exception {
|
|
||||||
PersistentTasksService persistentTasksService = internalCluster().getInstance(PersistentTasksService.class);
|
|
||||||
PlainActionFuture<PersistentTask<TestParams>> future = new PlainActionFuture<>();
|
|
||||||
String taskId = UUIDs.base64UUID();
|
|
||||||
persistentTasksService.startPersistentTask(taskId, TestPersistentTasksExecutor.NAME, new TestParams("Blah"), future);
|
|
||||||
future.get();
|
|
||||||
|
|
||||||
PlainActionFuture<PersistentTask<TestParams>> future2 = new PlainActionFuture<>();
|
|
||||||
persistentTasksService.startPersistentTask(taskId, TestPersistentTasksExecutor.NAME, new TestParams("Blah"), future2);
|
|
||||||
assertThrows(future2, ResourceAlreadyExistsException.class);
|
|
||||||
|
|
||||||
assertBusy(() -> {
|
|
||||||
// Wait for the task to start
|
|
||||||
assertThat(client().admin().cluster().prepareListTasks().setActions(TestPersistentTasksExecutor.NAME + "[c]").get()
|
|
||||||
.getTasks().size(), equalTo(1));
|
|
||||||
});
|
|
||||||
|
|
||||||
TaskInfo firstRunningTask = client().admin().cluster().prepareListTasks().setActions(TestPersistentTasksExecutor.NAME + "[c]")
|
|
||||||
.get().getTasks().get(0);
|
|
||||||
|
|
||||||
logger.info("Completing the running task");
|
|
||||||
// Fail the running task and make sure it restarts properly
|
|
||||||
assertThat(new TestTasksRequestBuilder(client()).setOperation("finish").setTaskId(firstRunningTask.getTaskId())
|
|
||||||
.get().getTasks().size(), equalTo(1));
|
|
||||||
|
|
||||||
logger.info("Waiting for persistent task with id {} to disappear", firstRunningTask.getId());
|
|
||||||
assertBusy(() -> {
|
|
||||||
// Wait for the task to disappear completely
|
|
||||||
assertThat(client().admin().cluster().prepareListTasks().setActions(TestPersistentTasksExecutor.NAME + "[c]").get().getTasks(),
|
|
||||||
empty());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void stopOrCancelTask(TaskId taskId) {
|
|
||||||
if (randomBoolean()) {
|
|
||||||
logger.info("Completing the running task");
|
|
||||||
// Complete the running task and make sure it finishes properly
|
|
||||||
assertThat(new TestTasksRequestBuilder(client()).setOperation("finish").setTaskId(taskId)
|
|
||||||
.get().getTasks().size(), equalTo(1));
|
|
||||||
|
|
||||||
} else {
|
|
||||||
logger.info("Cancelling the running task");
|
|
||||||
// Cancel the running task and make sure it finishes properly
|
|
||||||
assertThat(client().admin().cluster().prepareCancelTasks().setTaskId(taskId)
|
|
||||||
.get().getTasks().size(), equalTo(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private void assertNoRunningTasks() throws Exception {
|
|
||||||
assertBusy(() -> {
|
|
||||||
// Wait for the task to finish
|
|
||||||
List<TaskInfo> tasks = client().admin().cluster().prepareListTasks().setActions(TestPersistentTasksExecutor.NAME + "[c]").get()
|
|
||||||
.getTasks();
|
|
||||||
logger.info("Found {} tasks", tasks.size());
|
|
||||||
assertThat(tasks.size(), equalTo(0));
|
|
||||||
|
|
||||||
// Make sure the task is removed from the cluster state
|
|
||||||
assertThat(((PersistentTasksCustomMetaData) internalCluster().clusterService().state().getMetaData()
|
|
||||||
.custom(PersistentTasksCustomMetaData.TYPE)).tasks(), empty());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,42 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.xpack.core.persistent;
|
|
||||||
|
|
||||||
import org.elasticsearch.common.UUIDs;
|
|
||||||
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
|
|
||||||
import org.elasticsearch.test.AbstractStreamableTestCase;
|
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksCustomMetaData.PersistentTask;
|
|
||||||
import org.elasticsearch.xpack.core.persistent.TestPersistentTasksPlugin.TestPersistentTasksExecutor;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
|
|
||||||
public class PersistentTasksExecutorResponseTests extends AbstractStreamableTestCase<PersistentTaskResponse> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected PersistentTaskResponse createTestInstance() {
|
|
||||||
if (randomBoolean()) {
|
|
||||||
return new PersistentTaskResponse(
|
|
||||||
new PersistentTask<PersistentTaskParams>(UUIDs.base64UUID(), TestPersistentTasksExecutor.NAME,
|
|
||||||
new TestPersistentTasksPlugin.TestParams("test"),
|
|
||||||
randomLong(), PersistentTasksCustomMetaData.INITIAL_ASSIGNMENT));
|
|
||||||
} else {
|
|
||||||
return new PersistentTaskResponse(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected PersistentTaskResponse createBlankInstance() {
|
|
||||||
return new PersistentTaskResponse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected NamedWriteableRegistry getNamedWriteableRegistry() {
|
|
||||||
return new NamedWriteableRegistry(Collections.singletonList(
|
|
||||||
new NamedWriteableRegistry.Entry(PersistentTaskParams.class,
|
|
||||||
TestPersistentTasksExecutor.NAME, TestPersistentTasksPlugin.TestParams::new)
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,29 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.xpack.core.persistent;
|
|
||||||
|
|
||||||
import org.elasticsearch.common.io.stream.Writeable;
|
|
||||||
import org.elasticsearch.test.AbstractWireSerializingTestCase;
|
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksNodeService.Status;
|
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.containsString;
|
|
||||||
|
|
||||||
public class PersistentTasksNodeServiceStatusTests extends AbstractWireSerializingTestCase<Status> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Status createTestInstance() {
|
|
||||||
return new Status(randomFrom(AllocatedPersistentTask.State.values()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Writeable.Reader<Status> instanceReader() {
|
|
||||||
return Status::new;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testToString() {
|
|
||||||
assertThat(createTestInstance().toString(), containsString("state"));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,357 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.xpack.core.persistent;
|
|
||||||
|
|
||||||
import org.elasticsearch.Version;
|
|
||||||
import org.elasticsearch.action.ActionListener;
|
|
||||||
import org.elasticsearch.action.admin.cluster.node.tasks.cancel.CancelTasksResponse;
|
|
||||||
import org.elasticsearch.cluster.ClusterChangedEvent;
|
|
||||||
import org.elasticsearch.cluster.ClusterName;
|
|
||||||
import org.elasticsearch.cluster.ClusterState;
|
|
||||||
import org.elasticsearch.cluster.metadata.MetaData;
|
|
||||||
import org.elasticsearch.cluster.node.DiscoveryNode;
|
|
||||||
import org.elasticsearch.cluster.node.DiscoveryNodes;
|
|
||||||
import org.elasticsearch.cluster.routing.RoutingTable;
|
|
||||||
import org.elasticsearch.common.UUIDs;
|
|
||||||
import org.elasticsearch.common.settings.Settings;
|
|
||||||
import org.elasticsearch.tasks.Task;
|
|
||||||
import org.elasticsearch.tasks.TaskId;
|
|
||||||
import org.elasticsearch.tasks.TaskManager;
|
|
||||||
import org.elasticsearch.test.ESTestCase;
|
|
||||||
import org.elasticsearch.threadpool.TestThreadPool;
|
|
||||||
import org.elasticsearch.threadpool.ThreadPool;
|
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksCustomMetaData.Assignment;
|
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksCustomMetaData.PersistentTask;
|
|
||||||
import org.elasticsearch.xpack.core.persistent.TestPersistentTasksPlugin.TestParams;
|
|
||||||
import org.elasticsearch.xpack.core.persistent.TestPersistentTasksPlugin.TestPersistentTasksExecutor;
|
|
||||||
import org.junit.After;
|
|
||||||
import org.junit.Before;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.empty;
|
|
||||||
import static org.hamcrest.Matchers.sameInstance;
|
|
||||||
import static org.hamcrest.core.IsEqual.equalTo;
|
|
||||||
import static org.mockito.Matchers.any;
|
|
||||||
import static org.mockito.Matchers.anyLong;
|
|
||||||
import static org.mockito.Matchers.anyString;
|
|
||||||
import static org.mockito.Matchers.eq;
|
|
||||||
import static org.mockito.Mockito.mock;
|
|
||||||
import static org.mockito.Mockito.when;
|
|
||||||
|
|
||||||
public class PersistentTasksNodeServiceTests extends ESTestCase {
|
|
||||||
|
|
||||||
private ThreadPool threadPool;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Before
|
|
||||||
public void setUp() throws Exception {
|
|
||||||
super.setUp();
|
|
||||||
threadPool = new TestThreadPool(getClass().getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@After
|
|
||||||
public void tearDown() throws Exception {
|
|
||||||
terminate(threadPool);
|
|
||||||
super.tearDown();
|
|
||||||
}
|
|
||||||
|
|
||||||
private ClusterState createInitialClusterState(int nonLocalNodesCount, Settings settings) {
|
|
||||||
ClusterState.Builder state = ClusterState.builder(new ClusterName("PersistentActionExecutorTests"));
|
|
||||||
state.metaData(MetaData.builder().generateClusterUuidIfNeeded());
|
|
||||||
state.routingTable(RoutingTable.builder().build());
|
|
||||||
DiscoveryNodes.Builder nodes = DiscoveryNodes.builder();
|
|
||||||
nodes.add(DiscoveryNode.createLocal(settings, buildNewFakeTransportAddress(), "this_node"));
|
|
||||||
for (int i = 0; i < nonLocalNodesCount; i++) {
|
|
||||||
nodes.add(new DiscoveryNode("other_node_" + i, buildNewFakeTransportAddress(), Version.CURRENT));
|
|
||||||
}
|
|
||||||
nodes.localNodeId("this_node");
|
|
||||||
state.nodes(nodes);
|
|
||||||
return state.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testStartTask() throws Exception {
|
|
||||||
PersistentTasksService persistentTasksService = mock(PersistentTasksService.class);
|
|
||||||
@SuppressWarnings("unchecked") PersistentTasksExecutor<TestParams> action = mock(PersistentTasksExecutor.class);
|
|
||||||
when(action.getExecutor()).thenReturn(ThreadPool.Names.SAME);
|
|
||||||
when(action.getTaskName()).thenReturn(TestPersistentTasksExecutor.NAME);
|
|
||||||
int nonLocalNodesCount = randomInt(10);
|
|
||||||
// need to account for 5 original tasks on each node and their relocations
|
|
||||||
for (int i = 0; i < (nonLocalNodesCount + 1) * 10; i++) {
|
|
||||||
TaskId parentId = new TaskId("cluster", i);
|
|
||||||
when(action.createTask(anyLong(), anyString(), anyString(), eq(parentId), any(), any())).thenReturn(
|
|
||||||
new TestPersistentTasksPlugin.TestTask(i, "persistent", "test", "", parentId, Collections.emptyMap()));
|
|
||||||
}
|
|
||||||
PersistentTasksExecutorRegistry registry = new PersistentTasksExecutorRegistry(Settings.EMPTY, Collections.singletonList(action));
|
|
||||||
|
|
||||||
MockExecutor executor = new MockExecutor();
|
|
||||||
PersistentTasksNodeService coordinator = new PersistentTasksNodeService(Settings.EMPTY, persistentTasksService,
|
|
||||||
registry, new TaskManager(Settings.EMPTY, threadPool, Collections.emptySet()), executor);
|
|
||||||
|
|
||||||
ClusterState state = createInitialClusterState(nonLocalNodesCount, Settings.EMPTY);
|
|
||||||
|
|
||||||
PersistentTasksCustomMetaData.Builder tasks = PersistentTasksCustomMetaData.builder();
|
|
||||||
boolean added = false;
|
|
||||||
if (nonLocalNodesCount > 0) {
|
|
||||||
for (int i = 0; i < randomInt(5); i++) {
|
|
||||||
tasks.addTask(UUIDs.base64UUID(), TestPersistentTasksExecutor.NAME, new TestParams("other_" + i),
|
|
||||||
new Assignment("other_node_" + randomInt(nonLocalNodesCount), "test assignment on other node"));
|
|
||||||
if (added == false && randomBoolean()) {
|
|
||||||
added = true;
|
|
||||||
tasks.addTask(UUIDs.base64UUID(), TestPersistentTasksExecutor.NAME, new TestParams("this_param"),
|
|
||||||
new Assignment("this_node", "test assignment on this node"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (added == false) {
|
|
||||||
logger.info("No local node action was added");
|
|
||||||
|
|
||||||
}
|
|
||||||
MetaData.Builder metaData = MetaData.builder(state.metaData());
|
|
||||||
metaData.putCustom(PersistentTasksCustomMetaData.TYPE, tasks.build());
|
|
||||||
ClusterState newClusterState = ClusterState.builder(state).metaData(metaData).build();
|
|
||||||
|
|
||||||
coordinator.clusterChanged(new ClusterChangedEvent("test", newClusterState, state));
|
|
||||||
if (added) {
|
|
||||||
// Action for this node was added, let's make sure it was invoked
|
|
||||||
assertThat(executor.executions.size(), equalTo(1));
|
|
||||||
|
|
||||||
// Add task on some other node
|
|
||||||
state = newClusterState;
|
|
||||||
newClusterState = addTask(state, TestPersistentTasksExecutor.NAME, null, "some_other_node");
|
|
||||||
coordinator.clusterChanged(new ClusterChangedEvent("test", newClusterState, state));
|
|
||||||
|
|
||||||
// Make sure action wasn't called again
|
|
||||||
assertThat(executor.executions.size(), equalTo(1));
|
|
||||||
|
|
||||||
// Start another task on this node
|
|
||||||
state = newClusterState;
|
|
||||||
newClusterState = addTask(state, TestPersistentTasksExecutor.NAME, new TestParams("this_param"), "this_node");
|
|
||||||
coordinator.clusterChanged(new ClusterChangedEvent("test", newClusterState, state));
|
|
||||||
|
|
||||||
// Make sure action was called this time
|
|
||||||
assertThat(executor.size(), equalTo(2));
|
|
||||||
|
|
||||||
// Finish both tasks
|
|
||||||
executor.get(0).task.markAsFailed(new RuntimeException());
|
|
||||||
executor.get(1).task.markAsCompleted();
|
|
||||||
String failedTaskId = executor.get(0).task.getPersistentTaskId();
|
|
||||||
String finishedTaskId = executor.get(1).task.getPersistentTaskId();
|
|
||||||
executor.clear();
|
|
||||||
|
|
||||||
// Add task on some other node
|
|
||||||
state = newClusterState;
|
|
||||||
newClusterState = addTask(state, TestPersistentTasksExecutor.NAME, null, "some_other_node");
|
|
||||||
coordinator.clusterChanged(new ClusterChangedEvent("test", newClusterState, state));
|
|
||||||
|
|
||||||
// Make sure action wasn't called again
|
|
||||||
assertThat(executor.size(), equalTo(0));
|
|
||||||
|
|
||||||
// Simulate reallocation of the failed task on the same node
|
|
||||||
state = newClusterState;
|
|
||||||
newClusterState = reallocateTask(state, failedTaskId, "this_node");
|
|
||||||
coordinator.clusterChanged(new ClusterChangedEvent("test", newClusterState, state));
|
|
||||||
|
|
||||||
// Simulate removal of the finished task
|
|
||||||
state = newClusterState;
|
|
||||||
newClusterState = removeTask(state, finishedTaskId);
|
|
||||||
coordinator.clusterChanged(new ClusterChangedEvent("test", newClusterState, state));
|
|
||||||
|
|
||||||
// Make sure action was only allocated on this node once
|
|
||||||
assertThat(executor.size(), equalTo(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testParamsStatusAndNodeTaskAreDelegated() throws Exception {
|
|
||||||
PersistentTasksService persistentTasksService = mock(PersistentTasksService.class);
|
|
||||||
@SuppressWarnings("unchecked") PersistentTasksExecutor<TestParams> action = mock(PersistentTasksExecutor.class);
|
|
||||||
when(action.getExecutor()).thenReturn(ThreadPool.Names.SAME);
|
|
||||||
when(action.getTaskName()).thenReturn(TestPersistentTasksExecutor.NAME);
|
|
||||||
TaskId parentId = new TaskId("cluster", 1);
|
|
||||||
AllocatedPersistentTask nodeTask =
|
|
||||||
new TestPersistentTasksPlugin.TestTask(0, "persistent", "test", "", parentId, Collections.emptyMap());
|
|
||||||
when(action.createTask(anyLong(), anyString(), anyString(), eq(parentId), any(), any())).thenReturn(nodeTask);
|
|
||||||
PersistentTasksExecutorRegistry registry = new PersistentTasksExecutorRegistry(Settings.EMPTY, Collections.singletonList(action));
|
|
||||||
|
|
||||||
MockExecutor executor = new MockExecutor();
|
|
||||||
PersistentTasksNodeService coordinator = new PersistentTasksNodeService(Settings.EMPTY, persistentTasksService,
|
|
||||||
registry, new TaskManager(Settings.EMPTY, threadPool, Collections.emptySet()), executor);
|
|
||||||
|
|
||||||
ClusterState state = createInitialClusterState(1, Settings.EMPTY);
|
|
||||||
|
|
||||||
Task.Status status = new TestPersistentTasksPlugin.Status("_test_phase");
|
|
||||||
PersistentTasksCustomMetaData.Builder tasks = PersistentTasksCustomMetaData.builder();
|
|
||||||
String taskId = UUIDs.base64UUID();
|
|
||||||
TestParams taskParams = new TestParams("other_0");
|
|
||||||
tasks.addTask(taskId, TestPersistentTasksExecutor.NAME, taskParams,
|
|
||||||
new Assignment("this_node", "test assignment on other node"));
|
|
||||||
tasks.updateTaskStatus(taskId, status);
|
|
||||||
MetaData.Builder metaData = MetaData.builder(state.metaData());
|
|
||||||
metaData.putCustom(PersistentTasksCustomMetaData.TYPE, tasks.build());
|
|
||||||
ClusterState newClusterState = ClusterState.builder(state).metaData(metaData).build();
|
|
||||||
|
|
||||||
coordinator.clusterChanged(new ClusterChangedEvent("test", newClusterState, state));
|
|
||||||
|
|
||||||
assertThat(executor.size(), equalTo(1));
|
|
||||||
assertThat(executor.get(0).params, sameInstance(taskParams));
|
|
||||||
assertThat(executor.get(0).status, sameInstance(status));
|
|
||||||
assertThat(executor.get(0).task, sameInstance(nodeTask));
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testTaskCancellation() {
|
|
||||||
AtomicLong capturedTaskId = new AtomicLong();
|
|
||||||
AtomicReference<ActionListener<CancelTasksResponse>> capturedListener = new AtomicReference<>();
|
|
||||||
PersistentTasksService persistentTasksService = new PersistentTasksService(Settings.EMPTY, null, null, null) {
|
|
||||||
@Override
|
|
||||||
public void sendTaskManagerCancellation(long taskId, ActionListener<CancelTasksResponse> listener) {
|
|
||||||
capturedTaskId.set(taskId);
|
|
||||||
capturedListener.set(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void sendCompletionNotification(String taskId, long allocationId, Exception failure,
|
|
||||||
ActionListener<PersistentTask<?>> listener) {
|
|
||||||
fail("Shouldn't be called during Cluster State cancellation");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@SuppressWarnings("unchecked") PersistentTasksExecutor<TestParams> action = mock(PersistentTasksExecutor.class);
|
|
||||||
when(action.getExecutor()).thenReturn(ThreadPool.Names.SAME);
|
|
||||||
when(action.getTaskName()).thenReturn("test");
|
|
||||||
when(action.createTask(anyLong(), anyString(), anyString(), any(), any(), any()))
|
|
||||||
.thenReturn(new TestPersistentTasksPlugin.TestTask(1, "persistent", "test", "", new TaskId("cluster", 1),
|
|
||||||
Collections.emptyMap()));
|
|
||||||
PersistentTasksExecutorRegistry registry = new PersistentTasksExecutorRegistry(Settings.EMPTY, Collections.singletonList(action));
|
|
||||||
|
|
||||||
int nonLocalNodesCount = randomInt(10);
|
|
||||||
MockExecutor executor = new MockExecutor();
|
|
||||||
TaskManager taskManager = new TaskManager(Settings.EMPTY, threadPool, Collections.emptySet());
|
|
||||||
PersistentTasksNodeService coordinator = new PersistentTasksNodeService(Settings.EMPTY, persistentTasksService,
|
|
||||||
registry, taskManager, executor);
|
|
||||||
|
|
||||||
ClusterState state = createInitialClusterState(nonLocalNodesCount, Settings.EMPTY);
|
|
||||||
|
|
||||||
ClusterState newClusterState = state;
|
|
||||||
// Allocate first task
|
|
||||||
state = newClusterState;
|
|
||||||
newClusterState = addTask(state, "test", null, "this_node");
|
|
||||||
coordinator.clusterChanged(new ClusterChangedEvent("test", newClusterState, state));
|
|
||||||
|
|
||||||
// Check the the task is know to the task manager
|
|
||||||
assertThat(taskManager.getTasks().size(), equalTo(1));
|
|
||||||
AllocatedPersistentTask runningTask = (AllocatedPersistentTask)taskManager.getTasks().values().iterator().next();
|
|
||||||
String persistentId = runningTask.getPersistentTaskId();
|
|
||||||
long localId = runningTask.getId();
|
|
||||||
// Make sure it returns correct status
|
|
||||||
Task.Status status = runningTask.getStatus();
|
|
||||||
assertThat(status.toString(), equalTo("{\"state\":\"STARTED\"}"));
|
|
||||||
|
|
||||||
state = newClusterState;
|
|
||||||
// Relocate the task to some other node or remove it completely
|
|
||||||
if (randomBoolean()) {
|
|
||||||
newClusterState = reallocateTask(state, persistentId, "some_other_node");
|
|
||||||
} else {
|
|
||||||
newClusterState = removeTask(state, persistentId);
|
|
||||||
}
|
|
||||||
coordinator.clusterChanged(new ClusterChangedEvent("test", newClusterState, state));
|
|
||||||
|
|
||||||
// Make sure it returns correct status
|
|
||||||
assertThat(taskManager.getTasks().size(), equalTo(1));
|
|
||||||
assertThat(taskManager.getTasks().values().iterator().next().getStatus().toString(), equalTo("{\"state\":\"PENDING_CANCEL\"}"));
|
|
||||||
|
|
||||||
|
|
||||||
// That should trigger cancellation request
|
|
||||||
assertThat(capturedTaskId.get(), equalTo(localId));
|
|
||||||
// Notify successful cancellation
|
|
||||||
capturedListener.get().onResponse(new CancelTasksResponse());
|
|
||||||
|
|
||||||
// finish or fail task
|
|
||||||
if (randomBoolean()) {
|
|
||||||
executor.get(0).task.markAsCompleted();
|
|
||||||
} else {
|
|
||||||
executor.get(0).task.markAsFailed(new IOException("test"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check the the task is now removed from task manager
|
|
||||||
assertThat(taskManager.getTasks().values(), empty());
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private <Params extends PersistentTaskParams> ClusterState addTask(ClusterState state, String action, Params params,
|
|
||||||
String node) {
|
|
||||||
PersistentTasksCustomMetaData.Builder builder =
|
|
||||||
PersistentTasksCustomMetaData.builder(state.getMetaData().custom(PersistentTasksCustomMetaData.TYPE));
|
|
||||||
return ClusterState.builder(state).metaData(MetaData.builder(state.metaData()).putCustom(PersistentTasksCustomMetaData.TYPE,
|
|
||||||
builder.addTask(UUIDs.base64UUID(), action, params, new Assignment(node, "test assignment")).build())).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private ClusterState reallocateTask(ClusterState state, String taskId, String node) {
|
|
||||||
PersistentTasksCustomMetaData.Builder builder =
|
|
||||||
PersistentTasksCustomMetaData.builder(state.getMetaData().custom(PersistentTasksCustomMetaData.TYPE));
|
|
||||||
assertTrue(builder.hasTask(taskId));
|
|
||||||
return ClusterState.builder(state).metaData(MetaData.builder(state.metaData()).putCustom(PersistentTasksCustomMetaData.TYPE,
|
|
||||||
builder.reassignTask(taskId, new Assignment(node, "test assignment")).build())).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private ClusterState removeTask(ClusterState state, String taskId) {
|
|
||||||
PersistentTasksCustomMetaData.Builder builder =
|
|
||||||
PersistentTasksCustomMetaData.builder(state.getMetaData().custom(PersistentTasksCustomMetaData.TYPE));
|
|
||||||
assertTrue(builder.hasTask(taskId));
|
|
||||||
return ClusterState.builder(state).metaData(MetaData.builder(state.metaData()).putCustom(PersistentTasksCustomMetaData.TYPE,
|
|
||||||
builder.removeTask(taskId).build())).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private class Execution {
|
|
||||||
private final PersistentTaskParams params;
|
|
||||||
private final AllocatedPersistentTask task;
|
|
||||||
private final Task.Status status;
|
|
||||||
private final PersistentTasksExecutor<?> holder;
|
|
||||||
|
|
||||||
Execution(PersistentTaskParams params, AllocatedPersistentTask task, Task.Status status, PersistentTasksExecutor<?> holder) {
|
|
||||||
this.params = params;
|
|
||||||
this.task = task;
|
|
||||||
this.status = status;
|
|
||||||
this.holder = holder;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class MockExecutor extends NodePersistentTasksExecutor {
|
|
||||||
private List<Execution> executions = new ArrayList<>();
|
|
||||||
|
|
||||||
MockExecutor() {
|
|
||||||
super(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <Params extends PersistentTaskParams> void executeTask(Params params,
|
|
||||||
Task.Status status,
|
|
||||||
AllocatedPersistentTask task,
|
|
||||||
PersistentTasksExecutor<Params> executor) {
|
|
||||||
executions.add(new Execution(params, task, status, executor));
|
|
||||||
}
|
|
||||||
|
|
||||||
public Execution get(int i) {
|
|
||||||
return executions.get(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int size() {
|
|
||||||
return executions.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void clear() {
|
|
||||||
executions.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.xpack.core.persistent;
|
|
||||||
|
|
||||||
import org.elasticsearch.test.AbstractStreamableTestCase;
|
|
||||||
import org.elasticsearch.xpack.core.persistent.CompletionPersistentTaskAction.Request;
|
|
||||||
|
|
||||||
public class RestartPersistentTaskRequestTests extends AbstractStreamableTestCase<Request> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Request createTestInstance() {
|
|
||||||
return new Request(randomAlphaOfLength(10), randomLong(), null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Request createBlankInstance() {
|
|
||||||
return new Request();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,48 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.xpack.core.persistent;
|
|
||||||
|
|
||||||
import org.elasticsearch.common.UUIDs;
|
|
||||||
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
|
|
||||||
import org.elasticsearch.common.io.stream.NamedWriteableRegistry.Entry;
|
|
||||||
import org.elasticsearch.test.AbstractStreamableTestCase;
|
|
||||||
import org.elasticsearch.xpack.core.persistent.StartPersistentTaskAction.Request;
|
|
||||||
import org.elasticsearch.xpack.core.persistent.TestPersistentTasksPlugin.TestParams;
|
|
||||||
import org.elasticsearch.xpack.core.persistent.TestPersistentTasksPlugin.TestPersistentTasksExecutor;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
|
|
||||||
public class StartPersistentActionRequestTests extends AbstractStreamableTestCase<Request> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Request createTestInstance() {
|
|
||||||
TestParams testParams;
|
|
||||||
if (randomBoolean()) {
|
|
||||||
testParams = new TestParams();
|
|
||||||
if (randomBoolean()) {
|
|
||||||
testParams.setTestParam(randomAlphaOfLengthBetween(1, 20));
|
|
||||||
}
|
|
||||||
if (randomBoolean()) {
|
|
||||||
testParams.setExecutorNodeAttr(randomAlphaOfLengthBetween(1, 20));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
testParams = null;
|
|
||||||
}
|
|
||||||
return new Request(UUIDs.base64UUID(), randomAlphaOfLengthBetween(1, 20), testParams);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Request createBlankInstance() {
|
|
||||||
return new Request();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected NamedWriteableRegistry getNamedWriteableRegistry() {
|
|
||||||
return new NamedWriteableRegistry(Collections.singletonList(
|
|
||||||
new Entry(PersistentTaskParams.class, TestPersistentTasksExecutor.NAME, TestParams::new)
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,540 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.xpack.core.persistent;
|
|
||||||
|
|
||||||
import org.elasticsearch.action.Action;
|
|
||||||
import org.elasticsearch.action.ActionListener;
|
|
||||||
import org.elasticsearch.action.ActionRequest;
|
|
||||||
import org.elasticsearch.action.ActionResponse;
|
|
||||||
import org.elasticsearch.action.FailedNodeException;
|
|
||||||
import org.elasticsearch.action.TaskOperationFailure;
|
|
||||||
import org.elasticsearch.action.support.ActionFilters;
|
|
||||||
import org.elasticsearch.action.support.tasks.BaseTasksRequest;
|
|
||||||
import org.elasticsearch.action.support.tasks.BaseTasksResponse;
|
|
||||||
import org.elasticsearch.action.support.tasks.TasksRequestBuilder;
|
|
||||||
import org.elasticsearch.action.support.tasks.TransportTasksAction;
|
|
||||||
import org.elasticsearch.client.Client;
|
|
||||||
import org.elasticsearch.client.ElasticsearchClient;
|
|
||||||
import org.elasticsearch.cluster.ClusterState;
|
|
||||||
import org.elasticsearch.cluster.NamedDiff;
|
|
||||||
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
|
|
||||||
import org.elasticsearch.cluster.metadata.MetaData;
|
|
||||||
import org.elasticsearch.cluster.node.DiscoveryNode;
|
|
||||||
import org.elasticsearch.cluster.service.ClusterService;
|
|
||||||
import org.elasticsearch.common.ParseField;
|
|
||||||
import org.elasticsearch.common.Strings;
|
|
||||||
import org.elasticsearch.common.component.Lifecycle;
|
|
||||||
import org.elasticsearch.common.inject.Inject;
|
|
||||||
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
|
|
||||||
import org.elasticsearch.common.io.stream.StreamInput;
|
|
||||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
|
||||||
import org.elasticsearch.common.io.stream.Writeable;
|
|
||||||
import org.elasticsearch.common.settings.Settings;
|
|
||||||
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
|
|
||||||
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
|
|
||||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
|
||||||
import org.elasticsearch.common.xcontent.XContentParser;
|
|
||||||
import org.elasticsearch.env.Environment;
|
|
||||||
import org.elasticsearch.env.NodeEnvironment;
|
|
||||||
import org.elasticsearch.plugins.ActionPlugin;
|
|
||||||
import org.elasticsearch.plugins.Plugin;
|
|
||||||
import org.elasticsearch.script.ScriptService;
|
|
||||||
import org.elasticsearch.tasks.Task;
|
|
||||||
import org.elasticsearch.tasks.TaskCancelledException;
|
|
||||||
import org.elasticsearch.tasks.TaskId;
|
|
||||||
import org.elasticsearch.threadpool.ThreadPool;
|
|
||||||
import org.elasticsearch.transport.TransportService;
|
|
||||||
import org.elasticsearch.watcher.ResourceWatcherService;
|
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksCustomMetaData.Assignment;
|
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksCustomMetaData.PersistentTask;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.concurrent.CountDownLatch;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
|
|
||||||
import static java.util.Objects.requireNonNull;
|
|
||||||
import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg;
|
|
||||||
import static org.elasticsearch.test.ESTestCase.awaitBusy;
|
|
||||||
import static org.elasticsearch.test.ESTestCase.randomBoolean;
|
|
||||||
import static org.junit.Assert.assertTrue;
|
|
||||||
import static org.junit.Assert.fail;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A plugin that adds a test persistent task.
|
|
||||||
*/
|
|
||||||
public class TestPersistentTasksPlugin extends Plugin implements ActionPlugin {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<ActionHandler<? extends ActionRequest, ? extends ActionResponse>> getActions() {
|
|
||||||
return Arrays.asList(
|
|
||||||
new ActionHandler<>(TestTaskAction.INSTANCE, TransportTestTaskAction.class),
|
|
||||||
new ActionHandler<>(StartPersistentTaskAction.INSTANCE, StartPersistentTaskAction.TransportAction.class),
|
|
||||||
new ActionHandler<>(UpdatePersistentTaskStatusAction.INSTANCE, UpdatePersistentTaskStatusAction.TransportAction.class),
|
|
||||||
new ActionHandler<>(CompletionPersistentTaskAction.INSTANCE, CompletionPersistentTaskAction.TransportAction.class),
|
|
||||||
new ActionHandler<>(RemovePersistentTaskAction.INSTANCE, RemovePersistentTaskAction.TransportAction.class)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<Object> createComponents(Client client, ClusterService clusterService, ThreadPool threadPool,
|
|
||||||
ResourceWatcherService resourceWatcherService, ScriptService scriptService,
|
|
||||||
NamedXContentRegistry xContentRegistry, Environment environment,
|
|
||||||
NodeEnvironment nodeEnvironment, NamedWriteableRegistry namedWriteableRegistry) {
|
|
||||||
PersistentTasksService persistentTasksService = new PersistentTasksService(Settings.EMPTY, clusterService, threadPool, client);
|
|
||||||
TestPersistentTasksExecutor testPersistentAction = new TestPersistentTasksExecutor(Settings.EMPTY, clusterService);
|
|
||||||
PersistentTasksExecutorRegistry persistentTasksExecutorRegistry = new PersistentTasksExecutorRegistry(Settings.EMPTY,
|
|
||||||
Collections.singletonList(testPersistentAction));
|
|
||||||
return Arrays.asList(
|
|
||||||
persistentTasksService,
|
|
||||||
persistentTasksExecutorRegistry,
|
|
||||||
new PersistentTasksClusterService(Settings.EMPTY, persistentTasksExecutorRegistry, clusterService)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<NamedWriteableRegistry.Entry> getNamedWriteables() {
|
|
||||||
return Arrays.asList(
|
|
||||||
new NamedWriteableRegistry.Entry(PersistentTaskParams.class, TestPersistentTasksExecutor.NAME, TestParams::new),
|
|
||||||
new NamedWriteableRegistry.Entry(Task.Status.class,
|
|
||||||
PersistentTasksNodeService.Status.NAME, PersistentTasksNodeService.Status::new),
|
|
||||||
new NamedWriteableRegistry.Entry(MetaData.Custom.class, PersistentTasksCustomMetaData.TYPE,
|
|
||||||
PersistentTasksCustomMetaData::new),
|
|
||||||
new NamedWriteableRegistry.Entry(NamedDiff.class, PersistentTasksCustomMetaData.TYPE,
|
|
||||||
PersistentTasksCustomMetaData::readDiffFrom),
|
|
||||||
new NamedWriteableRegistry.Entry(Task.Status.class, TestPersistentTasksExecutor.NAME, Status::new)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<NamedXContentRegistry.Entry> getNamedXContent() {
|
|
||||||
return Arrays.asList(
|
|
||||||
new NamedXContentRegistry.Entry(MetaData.Custom.class, new ParseField(PersistentTasksCustomMetaData.TYPE),
|
|
||||||
PersistentTasksCustomMetaData::fromXContent),
|
|
||||||
new NamedXContentRegistry.Entry(PersistentTaskParams.class, new ParseField(TestPersistentTasksExecutor.NAME),
|
|
||||||
TestParams::fromXContent),
|
|
||||||
new NamedXContentRegistry.Entry(Task.Status.class, new ParseField(TestPersistentTasksExecutor.NAME), Status::fromXContent)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class TestParams implements PersistentTaskParams {
|
|
||||||
|
|
||||||
public static final ConstructingObjectParser<TestParams, Void> REQUEST_PARSER =
|
|
||||||
new ConstructingObjectParser<>(TestPersistentTasksExecutor.NAME, args -> new TestParams((String) args[0]));
|
|
||||||
|
|
||||||
static {
|
|
||||||
REQUEST_PARSER.declareString(constructorArg(), new ParseField("param"));
|
|
||||||
}
|
|
||||||
|
|
||||||
private String executorNodeAttr = null;
|
|
||||||
|
|
||||||
private String responseNode = null;
|
|
||||||
|
|
||||||
private String testParam = null;
|
|
||||||
|
|
||||||
public TestParams() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public TestParams(String testParam) {
|
|
||||||
this.testParam = testParam;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TestParams(StreamInput in) throws IOException {
|
|
||||||
executorNodeAttr = in.readOptionalString();
|
|
||||||
responseNode = in.readOptionalString();
|
|
||||||
testParam = in.readOptionalString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getWriteableName() {
|
|
||||||
return TestPersistentTasksExecutor.NAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setExecutorNodeAttr(String executorNodeAttr) {
|
|
||||||
this.executorNodeAttr = executorNodeAttr;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setTestParam(String testParam) {
|
|
||||||
this.testParam = testParam;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getExecutorNodeAttr() {
|
|
||||||
return executorNodeAttr;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getTestParam() {
|
|
||||||
return testParam;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeTo(StreamOutput out) throws IOException {
|
|
||||||
out.writeOptionalString(executorNodeAttr);
|
|
||||||
out.writeOptionalString(responseNode);
|
|
||||||
out.writeOptionalString(testParam);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
|
||||||
builder.startObject();
|
|
||||||
builder.field("param", testParam);
|
|
||||||
builder.endObject();
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static TestParams fromXContent(XContentParser parser) throws IOException {
|
|
||||||
return REQUEST_PARSER.parse(parser, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) return true;
|
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
|
||||||
TestParams that = (TestParams) o;
|
|
||||||
return Objects.equals(executorNodeAttr, that.executorNodeAttr) &&
|
|
||||||
Objects.equals(responseNode, that.responseNode) &&
|
|
||||||
Objects.equals(testParam, that.testParam);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return Objects.hash(executorNodeAttr, responseNode, testParam);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Status implements Task.Status {
|
|
||||||
|
|
||||||
private final String phase;
|
|
||||||
|
|
||||||
public static final ConstructingObjectParser<Status, Void> STATUS_PARSER =
|
|
||||||
new ConstructingObjectParser<>(TestPersistentTasksExecutor.NAME, args -> new Status((String) args[0]));
|
|
||||||
|
|
||||||
static {
|
|
||||||
STATUS_PARSER.declareString(constructorArg(), new ParseField("phase"));
|
|
||||||
}
|
|
||||||
|
|
||||||
public Status(String phase) {
|
|
||||||
this.phase = requireNonNull(phase, "Phase cannot be null");
|
|
||||||
}
|
|
||||||
|
|
||||||
public Status(StreamInput in) throws IOException {
|
|
||||||
phase = in.readString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getWriteableName() {
|
|
||||||
return TestPersistentTasksExecutor.NAME;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
|
||||||
builder.startObject();
|
|
||||||
builder.field("phase", phase);
|
|
||||||
builder.endObject();
|
|
||||||
return builder;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Task.Status fromXContent(XContentParser parser) throws IOException {
|
|
||||||
return STATUS_PARSER.parse(parser, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isFragment() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeTo(StreamOutput out) throws IOException {
|
|
||||||
out.writeString(phase);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return Strings.toString(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Implements equals and hashcode for testing
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object obj) {
|
|
||||||
if (obj == null || obj.getClass() != Status.class) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
Status other = (Status) obj;
|
|
||||||
return phase.equals(other.phase);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return phase.hashCode();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static class TestPersistentTasksExecutor extends PersistentTasksExecutor<TestParams> {
|
|
||||||
|
|
||||||
public static final String NAME = "cluster:admin/persistent/test";
|
|
||||||
private final ClusterService clusterService;
|
|
||||||
|
|
||||||
public TestPersistentTasksExecutor(Settings settings, ClusterService clusterService) {
|
|
||||||
super(settings, NAME, ThreadPool.Names.GENERIC);
|
|
||||||
this.clusterService = clusterService;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Assignment getAssignment(TestParams params, ClusterState clusterState) {
|
|
||||||
if (params == null || params.getExecutorNodeAttr() == null) {
|
|
||||||
return super.getAssignment(params, clusterState);
|
|
||||||
} else {
|
|
||||||
DiscoveryNode executorNode = selectLeastLoadedNode(clusterState,
|
|
||||||
discoveryNode -> params.getExecutorNodeAttr().equals(discoveryNode.getAttributes().get("test_attr")));
|
|
||||||
if (executorNode != null) {
|
|
||||||
return new Assignment(executorNode.getId(), "test assignment");
|
|
||||||
} else {
|
|
||||||
return NO_NODE_FOUND;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void nodeOperation(AllocatedPersistentTask task, TestParams params, Task.Status status) {
|
|
||||||
logger.info("started node operation for the task {}", task);
|
|
||||||
try {
|
|
||||||
TestTask testTask = (TestTask) task;
|
|
||||||
AtomicInteger phase = new AtomicInteger();
|
|
||||||
while (true) {
|
|
||||||
// wait for something to happen
|
|
||||||
assertTrue(awaitBusy(() -> testTask.isCancelled() ||
|
|
||||||
testTask.getOperation() != null ||
|
|
||||||
clusterService.lifecycleState() != Lifecycle.State.STARTED, // speedup finishing on closed nodes
|
|
||||||
30, TimeUnit.SECONDS)); // This can take a while during large cluster restart
|
|
||||||
if (clusterService.lifecycleState() != Lifecycle.State.STARTED) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if ("finish".equals(testTask.getOperation())) {
|
|
||||||
task.markAsCompleted();
|
|
||||||
return;
|
|
||||||
} else if ("fail".equals(testTask.getOperation())) {
|
|
||||||
task.markAsFailed(new RuntimeException("Simulating failure"));
|
|
||||||
return;
|
|
||||||
} else if ("update_status".equals(testTask.getOperation())) {
|
|
||||||
testTask.setOperation(null);
|
|
||||||
CountDownLatch latch = new CountDownLatch(1);
|
|
||||||
Status newStatus = new Status("phase " + phase.incrementAndGet());
|
|
||||||
logger.info("updating the task status to {}", newStatus);
|
|
||||||
task.updatePersistentStatus(newStatus, new ActionListener<PersistentTask<?>>() {
|
|
||||||
@Override
|
|
||||||
public void onResponse(PersistentTask<?> persistentTask) {
|
|
||||||
logger.info("updating was successful");
|
|
||||||
latch.countDown();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFailure(Exception e) {
|
|
||||||
logger.info("updating failed", e);
|
|
||||||
latch.countDown();
|
|
||||||
fail(e.toString());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
assertTrue(latch.await(10, TimeUnit.SECONDS));
|
|
||||||
} else if (testTask.isCancelled()) {
|
|
||||||
// Cancellation make cause different ways for the task to finish
|
|
||||||
if (randomBoolean()) {
|
|
||||||
if (randomBoolean()) {
|
|
||||||
task.markAsFailed(new TaskCancelledException(testTask.getReasonCancelled()));
|
|
||||||
} else {
|
|
||||||
task.markAsCompleted();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
task.markAsFailed(new RuntimeException(testTask.getReasonCancelled()));
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
fail("We really shouldn't be here");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
task.markAsFailed(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected AllocatedPersistentTask createTask(long id, String type, String action, TaskId parentTaskId,
|
|
||||||
PersistentTask<TestParams> task, Map<String, String> headers) {
|
|
||||||
return new TestTask(id, type, action, getDescription(task), parentTaskId, headers);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class TestTaskAction extends Action<TestTasksRequest, TestTasksResponse, TestTasksRequestBuilder> {
|
|
||||||
|
|
||||||
public static final TestTaskAction INSTANCE = new TestTaskAction();
|
|
||||||
public static final String NAME = "cluster:admin/persistent/task_test";
|
|
||||||
|
|
||||||
private TestTaskAction() {
|
|
||||||
super(NAME);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public TestTasksResponse newResponse() {
|
|
||||||
return new TestTasksResponse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public TestTasksRequestBuilder newRequestBuilder(ElasticsearchClient client) {
|
|
||||||
return new TestTasksRequestBuilder(client);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static class TestTask extends AllocatedPersistentTask {
|
|
||||||
private volatile String operation;
|
|
||||||
|
|
||||||
public TestTask(long id, String type, String action, String description, TaskId parentTask, Map<String, String> headers) {
|
|
||||||
super(id, type, action, description, parentTask, headers);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getOperation() {
|
|
||||||
return operation;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setOperation(String operation) {
|
|
||||||
this.operation = operation;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "TestTask[" + this.getId() + ", " + this.getParentTaskId() + ", " + this.getOperation() + "]";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static class TestTaskResponse implements Writeable {
|
|
||||||
|
|
||||||
TestTaskResponse() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
TestTaskResponse(StreamInput in) throws IOException {
|
|
||||||
in.readBoolean();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeTo(StreamOutput out) throws IOException {
|
|
||||||
out.writeBoolean(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class TestTasksRequest extends BaseTasksRequest<TestTasksRequest> {
|
|
||||||
private String operation;
|
|
||||||
|
|
||||||
public TestTasksRequest() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void readFrom(StreamInput in) throws IOException {
|
|
||||||
super.readFrom(in);
|
|
||||||
operation = in.readOptionalString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeTo(StreamOutput out) throws IOException {
|
|
||||||
super.writeTo(out);
|
|
||||||
out.writeOptionalString(operation);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setOperation(String operation) {
|
|
||||||
this.operation = operation;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getOperation() {
|
|
||||||
return operation;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class TestTasksRequestBuilder extends TasksRequestBuilder<TestTasksRequest, TestTasksResponse, TestTasksRequestBuilder> {
|
|
||||||
|
|
||||||
protected TestTasksRequestBuilder(ElasticsearchClient client) {
|
|
||||||
super(client, TestTaskAction.INSTANCE, new TestTasksRequest());
|
|
||||||
}
|
|
||||||
|
|
||||||
public TestTasksRequestBuilder setOperation(String operation) {
|
|
||||||
request.setOperation(operation);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class TestTasksResponse extends BaseTasksResponse {
|
|
||||||
|
|
||||||
private List<TestTaskResponse> tasks;
|
|
||||||
|
|
||||||
public TestTasksResponse() {
|
|
||||||
super(null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public TestTasksResponse(List<TestTaskResponse> tasks, List<TaskOperationFailure> taskFailures,
|
|
||||||
List<? extends FailedNodeException> nodeFailures) {
|
|
||||||
super(taskFailures, nodeFailures);
|
|
||||||
this.tasks = tasks == null ? Collections.emptyList() : Collections.unmodifiableList(new ArrayList<>(tasks));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void readFrom(StreamInput in) throws IOException {
|
|
||||||
super.readFrom(in);
|
|
||||||
tasks = in.readList(TestTaskResponse::new);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeTo(StreamOutput out) throws IOException {
|
|
||||||
super.writeTo(out);
|
|
||||||
out.writeList(tasks);
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<TestTaskResponse> getTasks() {
|
|
||||||
return tasks;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class TransportTestTaskAction extends TransportTasksAction<TestTask,
|
|
||||||
TestTasksRequest, TestTasksResponse, TestTaskResponse> {
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public TransportTestTaskAction(Settings settings, ThreadPool threadPool, ClusterService clusterService,
|
|
||||||
TransportService transportService, ActionFilters actionFilters,
|
|
||||||
IndexNameExpressionResolver indexNameExpressionResolver, String nodeExecutor) {
|
|
||||||
super(settings, TestTaskAction.NAME, threadPool, clusterService, transportService, actionFilters, indexNameExpressionResolver,
|
|
||||||
TestTasksRequest::new, TestTasksResponse::new, ThreadPool.Names.MANAGEMENT);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected TestTasksResponse newResponse(TestTasksRequest request, List<TestTaskResponse> tasks,
|
|
||||||
List<TaskOperationFailure> taskOperationFailures,
|
|
||||||
List<FailedNodeException> failedNodeExceptions) {
|
|
||||||
return new TestTasksResponse(tasks, taskOperationFailures, failedNodeExceptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected TestTaskResponse readTaskResponse(StreamInput in) throws IOException {
|
|
||||||
return new TestTaskResponse(in);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void taskOperation(TestTasksRequest request, TestTask task, ActionListener<TestTaskResponse> listener) {
|
|
||||||
task.setOperation(request.operation);
|
|
||||||
listener.onResponse(new TestTaskResponse());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,36 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.xpack.core.persistent;
|
|
||||||
|
|
||||||
import org.elasticsearch.common.UUIDs;
|
|
||||||
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
|
|
||||||
import org.elasticsearch.tasks.Task;
|
|
||||||
import org.elasticsearch.test.AbstractStreamableTestCase;
|
|
||||||
import org.elasticsearch.xpack.core.persistent.TestPersistentTasksPlugin.Status;
|
|
||||||
import org.elasticsearch.xpack.core.persistent.TestPersistentTasksPlugin.TestPersistentTasksExecutor;
|
|
||||||
import org.elasticsearch.xpack.core.persistent.UpdatePersistentTaskStatusAction.Request;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
|
|
||||||
public class UpdatePersistentTaskRequestTests extends AbstractStreamableTestCase<Request> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Request createTestInstance() {
|
|
||||||
return new Request(UUIDs.base64UUID(), randomLong(), new Status(randomAlphaOfLength(10)));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Request createBlankInstance() {
|
|
||||||
return new Request();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected NamedWriteableRegistry getNamedWriteableRegistry() {
|
|
||||||
return new NamedWriteableRegistry(Collections.singletonList(
|
|
||||||
new NamedWriteableRegistry.Entry(Task.Status.class, TestPersistentTasksExecutor.NAME, Status::new)
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -41,6 +41,7 @@ import org.elasticsearch.monitor.os.OsProbe;
|
||||||
import org.elasticsearch.monitor.os.OsStats;
|
import org.elasticsearch.monitor.os.OsStats;
|
||||||
import org.elasticsearch.plugins.ActionPlugin;
|
import org.elasticsearch.plugins.ActionPlugin;
|
||||||
import org.elasticsearch.plugins.AnalysisPlugin;
|
import org.elasticsearch.plugins.AnalysisPlugin;
|
||||||
|
import org.elasticsearch.plugins.PersistentTaskPlugin;
|
||||||
import org.elasticsearch.plugins.Plugin;
|
import org.elasticsearch.plugins.Plugin;
|
||||||
import org.elasticsearch.rest.RestController;
|
import org.elasticsearch.rest.RestController;
|
||||||
import org.elasticsearch.rest.RestHandler;
|
import org.elasticsearch.rest.RestHandler;
|
||||||
|
@ -101,14 +102,14 @@ import org.elasticsearch.xpack.core.ml.job.persistence.AnomalyDetectorsIndex;
|
||||||
import org.elasticsearch.xpack.core.ml.job.persistence.ElasticsearchMappings;
|
import org.elasticsearch.xpack.core.ml.job.persistence.ElasticsearchMappings;
|
||||||
import org.elasticsearch.xpack.core.ml.notifications.AuditMessage;
|
import org.elasticsearch.xpack.core.ml.notifications.AuditMessage;
|
||||||
import org.elasticsearch.xpack.core.ml.notifications.AuditorField;
|
import org.elasticsearch.xpack.core.ml.notifications.AuditorField;
|
||||||
import org.elasticsearch.xpack.core.persistent.CompletionPersistentTaskAction;
|
import org.elasticsearch.persistent.CompletionPersistentTaskAction;
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksClusterService;
|
import org.elasticsearch.persistent.PersistentTasksClusterService;
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksExecutor;
|
import org.elasticsearch.persistent.PersistentTasksExecutor;
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksExecutorRegistry;
|
import org.elasticsearch.persistent.PersistentTasksExecutorRegistry;
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksService;
|
import org.elasticsearch.persistent.PersistentTasksService;
|
||||||
import org.elasticsearch.xpack.core.persistent.RemovePersistentTaskAction;
|
import org.elasticsearch.persistent.RemovePersistentTaskAction;
|
||||||
import org.elasticsearch.xpack.core.persistent.StartPersistentTaskAction;
|
import org.elasticsearch.persistent.StartPersistentTaskAction;
|
||||||
import org.elasticsearch.xpack.core.persistent.UpdatePersistentTaskStatusAction;
|
import org.elasticsearch.persistent.UpdatePersistentTaskStatusAction;
|
||||||
import org.elasticsearch.xpack.core.template.TemplateUtils;
|
import org.elasticsearch.xpack.core.template.TemplateUtils;
|
||||||
import org.elasticsearch.xpack.ml.action.TransportCloseJobAction;
|
import org.elasticsearch.xpack.ml.action.TransportCloseJobAction;
|
||||||
import org.elasticsearch.xpack.ml.action.TransportDeleteCalendarAction;
|
import org.elasticsearch.xpack.ml.action.TransportDeleteCalendarAction;
|
||||||
|
@ -233,7 +234,7 @@ import java.util.function.UnaryOperator;
|
||||||
|
|
||||||
import static java.util.Collections.emptyList;
|
import static java.util.Collections.emptyList;
|
||||||
|
|
||||||
public class MachineLearning extends Plugin implements ActionPlugin, AnalysisPlugin {
|
public class MachineLearning extends Plugin implements ActionPlugin, AnalysisPlugin, PersistentTaskPlugin {
|
||||||
public static final String NAME = "ml";
|
public static final String NAME = "ml";
|
||||||
public static final String BASE_PATH = "/_xpack/ml/";
|
public static final String BASE_PATH = "/_xpack/ml/";
|
||||||
public static final String DATAFEED_THREAD_POOL_NAME = NAME + "_datafeed";
|
public static final String DATAFEED_THREAD_POOL_NAME = NAME + "_datafeed";
|
||||||
|
@ -352,33 +353,6 @@ public class MachineLearning extends Plugin implements ActionPlugin, AnalysisPlu
|
||||||
ResourceWatcherService resourceWatcherService, ScriptService scriptService,
|
ResourceWatcherService resourceWatcherService, ScriptService scriptService,
|
||||||
NamedXContentRegistry xContentRegistry, Environment environment,
|
NamedXContentRegistry xContentRegistry, Environment environment,
|
||||||
NodeEnvironment nodeEnvironment, NamedWriteableRegistry namedWriteableRegistry) {
|
NodeEnvironment nodeEnvironment, NamedWriteableRegistry namedWriteableRegistry) {
|
||||||
List<Object> components = new ArrayList<>();
|
|
||||||
|
|
||||||
PersistentTasksService persistentTasksService = new PersistentTasksService(settings, clusterService, threadPool, client);
|
|
||||||
|
|
||||||
components.addAll(createComponents(client, clusterService, threadPool, xContentRegistry, environment));
|
|
||||||
|
|
||||||
// This was lifted from the XPackPlugins createComponents when it got split
|
|
||||||
// This is not extensible and anyone copying this code needs to instead make this work
|
|
||||||
// using the same single service (at the time of this writing XPackPlugin was the place these common things got created)
|
|
||||||
// and do not just copy this whole thing and drop it in your service.
|
|
||||||
// The Actions for this service will also have to be moved back into XPackPlugin
|
|
||||||
List<PersistentTasksExecutor<?>> tasksExecutors = new ArrayList<>();
|
|
||||||
tasksExecutors.addAll(createPersistentTasksExecutors(clusterService));
|
|
||||||
|
|
||||||
PersistentTasksExecutorRegistry registry = new PersistentTasksExecutorRegistry(settings, tasksExecutors);
|
|
||||||
PersistentTasksClusterService persistentTasksClusterService = new PersistentTasksClusterService(settings, registry, clusterService);
|
|
||||||
components.add(persistentTasksClusterService);
|
|
||||||
components.add(persistentTasksService);
|
|
||||||
components.add(registry);
|
|
||||||
return components;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: once initialization of the PersistentTasksClusterService, PersistentTasksService
|
|
||||||
// and PersistentTasksExecutorRegistry has been moved somewhere else the entire contents of
|
|
||||||
// this method can replace the entire contents of the overridden createComponents() method
|
|
||||||
private Collection<Object> createComponents(Client client, ClusterService clusterService, ThreadPool threadPool,
|
|
||||||
NamedXContentRegistry xContentRegistry, Environment environment) {
|
|
||||||
if (enabled == false || transportClientMode) {
|
if (enabled == false || transportClientMode) {
|
||||||
return emptyList();
|
return emptyList();
|
||||||
}
|
}
|
||||||
|
@ -443,7 +417,7 @@ public class MachineLearning extends Plugin implements ActionPlugin, AnalysisPlu
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<PersistentTasksExecutor<?>> createPersistentTasksExecutors(ClusterService clusterService) {
|
public List<PersistentTasksExecutor<?>> getPersistentTasksExecutor(ClusterService clusterService) {
|
||||||
if (enabled == false || transportClientMode) {
|
if (enabled == false || transportClientMode) {
|
||||||
return emptyList();
|
return emptyList();
|
||||||
}
|
}
|
||||||
|
@ -570,14 +544,7 @@ public class MachineLearning extends Plugin implements ActionPlugin, AnalysisPlu
|
||||||
new ActionHandler<>(DeleteCalendarEventAction.INSTANCE, TransportDeleteCalendarEventAction.class),
|
new ActionHandler<>(DeleteCalendarEventAction.INSTANCE, TransportDeleteCalendarEventAction.class),
|
||||||
new ActionHandler<>(UpdateCalendarJobAction.INSTANCE, TransportUpdateCalendarJobAction.class),
|
new ActionHandler<>(UpdateCalendarJobAction.INSTANCE, TransportUpdateCalendarJobAction.class),
|
||||||
new ActionHandler<>(GetCalendarEventsAction.INSTANCE, TransportGetCalendarEventsAction.class),
|
new ActionHandler<>(GetCalendarEventsAction.INSTANCE, TransportGetCalendarEventsAction.class),
|
||||||
new ActionHandler<>(PostCalendarEventsAction.INSTANCE, TransportPostCalendarEventsAction.class),
|
new ActionHandler<>(PostCalendarEventsAction.INSTANCE, TransportPostCalendarEventsAction.class)
|
||||||
// These actions reside here because ML is the only user of the Persistence service currently.
|
|
||||||
// Once another project uses this service, these actions will need to be moved out to a common place
|
|
||||||
// where they are registered.
|
|
||||||
new ActionHandler<>(StartPersistentTaskAction.INSTANCE, StartPersistentTaskAction.TransportAction.class),
|
|
||||||
new ActionHandler<>(UpdatePersistentTaskStatusAction.INSTANCE, UpdatePersistentTaskStatusAction.TransportAction.class),
|
|
||||||
new ActionHandler<>(RemovePersistentTaskAction.INSTANCE, RemovePersistentTaskAction.TransportAction.class),
|
|
||||||
new ActionHandler<>(CompletionPersistentTaskAction.INSTANCE, CompletionPersistentTaskAction.TransportAction.class)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -18,9 +18,9 @@ import org.elasticsearch.xpack.core.ml.MlMetadata;
|
||||||
import org.elasticsearch.xpack.core.ml.action.OpenJobAction;
|
import org.elasticsearch.xpack.core.ml.action.OpenJobAction;
|
||||||
import org.elasticsearch.xpack.core.ml.action.StartDatafeedAction;
|
import org.elasticsearch.xpack.core.ml.action.StartDatafeedAction;
|
||||||
import org.elasticsearch.xpack.core.ml.datafeed.DatafeedConfig;
|
import org.elasticsearch.xpack.core.ml.datafeed.DatafeedConfig;
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksCustomMetaData;
|
import org.elasticsearch.persistent.PersistentTasksCustomMetaData;
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksCustomMetaData.Assignment;
|
import org.elasticsearch.persistent.PersistentTasksCustomMetaData.Assignment;
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksCustomMetaData.PersistentTask;
|
import org.elasticsearch.persistent.PersistentTasksCustomMetaData.PersistentTask;
|
||||||
import org.elasticsearch.xpack.ml.notifications.Auditor;
|
import org.elasticsearch.xpack.ml.notifications.Auditor;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
|
@ -38,8 +38,8 @@ import org.elasticsearch.xpack.core.ml.job.config.JobState;
|
||||||
import org.elasticsearch.xpack.core.ml.job.config.JobTaskStatus;
|
import org.elasticsearch.xpack.core.ml.job.config.JobTaskStatus;
|
||||||
import org.elasticsearch.xpack.core.ml.job.messages.Messages;
|
import org.elasticsearch.xpack.core.ml.job.messages.Messages;
|
||||||
import org.elasticsearch.xpack.core.ml.utils.ExceptionsHelper;
|
import org.elasticsearch.xpack.core.ml.utils.ExceptionsHelper;
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksCustomMetaData;
|
import org.elasticsearch.persistent.PersistentTasksCustomMetaData;
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksService;
|
import org.elasticsearch.persistent.PersistentTasksService;
|
||||||
import org.elasticsearch.xpack.ml.MachineLearning;
|
import org.elasticsearch.xpack.ml.MachineLearning;
|
||||||
import org.elasticsearch.xpack.ml.notifications.Auditor;
|
import org.elasticsearch.xpack.ml.notifications.Auditor;
|
||||||
|
|
||||||
|
|
|
@ -25,8 +25,8 @@ import org.elasticsearch.xpack.core.ml.MLMetadataField;
|
||||||
import org.elasticsearch.xpack.core.ml.MlMetadata;
|
import org.elasticsearch.xpack.core.ml.MlMetadata;
|
||||||
import org.elasticsearch.xpack.core.ml.action.DeleteDatafeedAction;
|
import org.elasticsearch.xpack.core.ml.action.DeleteDatafeedAction;
|
||||||
import org.elasticsearch.xpack.core.ml.action.IsolateDatafeedAction;
|
import org.elasticsearch.xpack.core.ml.action.IsolateDatafeedAction;
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksCustomMetaData;
|
import org.elasticsearch.persistent.PersistentTasksCustomMetaData;
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksService;
|
import org.elasticsearch.persistent.PersistentTasksService;
|
||||||
|
|
||||||
import static org.elasticsearch.xpack.core.ClientHelper.ML_ORIGIN;
|
import static org.elasticsearch.xpack.core.ClientHelper.ML_ORIGIN;
|
||||||
import static org.elasticsearch.xpack.core.ClientHelper.executeAsyncWithOrigin;
|
import static org.elasticsearch.xpack.core.ClientHelper.executeAsyncWithOrigin;
|
||||||
|
|
|
@ -33,8 +33,8 @@ import org.elasticsearch.xpack.core.ml.MlMetadata;
|
||||||
import org.elasticsearch.xpack.core.ml.action.DeleteJobAction;
|
import org.elasticsearch.xpack.core.ml.action.DeleteJobAction;
|
||||||
import org.elasticsearch.xpack.core.ml.action.KillProcessAction;
|
import org.elasticsearch.xpack.core.ml.action.KillProcessAction;
|
||||||
import org.elasticsearch.xpack.core.ml.job.persistence.JobStorageDeletionTask;
|
import org.elasticsearch.xpack.core.ml.job.persistence.JobStorageDeletionTask;
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksCustomMetaData;
|
import org.elasticsearch.persistent.PersistentTasksCustomMetaData;
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksService;
|
import org.elasticsearch.persistent.PersistentTasksService;
|
||||||
import org.elasticsearch.xpack.ml.job.JobManager;
|
import org.elasticsearch.xpack.ml.job.JobManager;
|
||||||
|
|
||||||
import java.util.concurrent.TimeoutException;
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
|
@ -24,7 +24,7 @@ import org.elasticsearch.xpack.core.ml.action.GetDatafeedsStatsAction;
|
||||||
import org.elasticsearch.xpack.core.ml.action.util.QueryPage;
|
import org.elasticsearch.xpack.core.ml.action.util.QueryPage;
|
||||||
import org.elasticsearch.xpack.core.ml.datafeed.DatafeedConfig;
|
import org.elasticsearch.xpack.core.ml.datafeed.DatafeedConfig;
|
||||||
import org.elasticsearch.xpack.core.ml.datafeed.DatafeedState;
|
import org.elasticsearch.xpack.core.ml.datafeed.DatafeedState;
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksCustomMetaData;
|
import org.elasticsearch.persistent.PersistentTasksCustomMetaData;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
|
@ -31,7 +31,7 @@ import org.elasticsearch.xpack.core.ml.job.config.Job;
|
||||||
import org.elasticsearch.xpack.core.ml.job.config.JobState;
|
import org.elasticsearch.xpack.core.ml.job.config.JobState;
|
||||||
import org.elasticsearch.xpack.core.ml.job.process.autodetect.state.DataCounts;
|
import org.elasticsearch.xpack.core.ml.job.process.autodetect.state.DataCounts;
|
||||||
import org.elasticsearch.xpack.core.ml.job.process.autodetect.state.ModelSizeStats;
|
import org.elasticsearch.xpack.core.ml.job.process.autodetect.state.ModelSizeStats;
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksCustomMetaData;
|
import org.elasticsearch.persistent.PersistentTasksCustomMetaData;
|
||||||
import org.elasticsearch.xpack.ml.job.persistence.JobProvider;
|
import org.elasticsearch.xpack.ml.job.persistence.JobProvider;
|
||||||
import org.elasticsearch.xpack.ml.job.process.autodetect.AutodetectProcessManager;
|
import org.elasticsearch.xpack.ml.job.process.autodetect.AutodetectProcessManager;
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,7 @@ import org.elasticsearch.threadpool.ThreadPool;
|
||||||
import org.elasticsearch.transport.TransportService;
|
import org.elasticsearch.transport.TransportService;
|
||||||
import org.elasticsearch.xpack.core.ml.MlMetadata;
|
import org.elasticsearch.xpack.core.ml.MlMetadata;
|
||||||
import org.elasticsearch.xpack.core.ml.action.IsolateDatafeedAction;
|
import org.elasticsearch.xpack.core.ml.action.IsolateDatafeedAction;
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksCustomMetaData;
|
import org.elasticsearch.persistent.PersistentTasksCustomMetaData;
|
||||||
import org.elasticsearch.xpack.ml.MachineLearning;
|
import org.elasticsearch.xpack.ml.MachineLearning;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
|
@ -22,7 +22,7 @@ import org.elasticsearch.transport.TransportService;
|
||||||
import org.elasticsearch.xpack.core.ml.MlMetadata;
|
import org.elasticsearch.xpack.core.ml.MlMetadata;
|
||||||
import org.elasticsearch.xpack.core.ml.action.JobTaskRequest;
|
import org.elasticsearch.xpack.core.ml.action.JobTaskRequest;
|
||||||
import org.elasticsearch.xpack.core.ml.utils.ExceptionsHelper;
|
import org.elasticsearch.xpack.core.ml.utils.ExceptionsHelper;
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksCustomMetaData;
|
import org.elasticsearch.persistent.PersistentTasksCustomMetaData;
|
||||||
import org.elasticsearch.xpack.ml.job.JobManager;
|
import org.elasticsearch.xpack.ml.job.JobManager;
|
||||||
import org.elasticsearch.xpack.ml.job.process.autodetect.AutodetectProcessManager;
|
import org.elasticsearch.xpack.ml.job.process.autodetect.AutodetectProcessManager;
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ import org.elasticsearch.xpack.core.ml.MlMetadata;
|
||||||
import org.elasticsearch.xpack.core.ml.action.KillProcessAction;
|
import org.elasticsearch.xpack.core.ml.action.KillProcessAction;
|
||||||
import org.elasticsearch.xpack.core.ml.job.messages.Messages;
|
import org.elasticsearch.xpack.core.ml.job.messages.Messages;
|
||||||
import org.elasticsearch.xpack.core.ml.utils.ExceptionsHelper;
|
import org.elasticsearch.xpack.core.ml.utils.ExceptionsHelper;
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksCustomMetaData;
|
import org.elasticsearch.persistent.PersistentTasksCustomMetaData;
|
||||||
import org.elasticsearch.xpack.ml.MachineLearning;
|
import org.elasticsearch.xpack.ml.MachineLearning;
|
||||||
import org.elasticsearch.xpack.ml.job.process.autodetect.AutodetectProcessManager;
|
import org.elasticsearch.xpack.ml.job.process.autodetect.AutodetectProcessManager;
|
||||||
import org.elasticsearch.xpack.ml.notifications.Auditor;
|
import org.elasticsearch.xpack.ml.notifications.Auditor;
|
||||||
|
|
|
@ -56,10 +56,10 @@ import org.elasticsearch.xpack.core.ml.job.config.JobUpdate;
|
||||||
import org.elasticsearch.xpack.core.ml.job.persistence.AnomalyDetectorsIndex;
|
import org.elasticsearch.xpack.core.ml.job.persistence.AnomalyDetectorsIndex;
|
||||||
import org.elasticsearch.xpack.core.ml.job.persistence.ElasticsearchMappings;
|
import org.elasticsearch.xpack.core.ml.job.persistence.ElasticsearchMappings;
|
||||||
import org.elasticsearch.xpack.core.ml.utils.ExceptionsHelper;
|
import org.elasticsearch.xpack.core.ml.utils.ExceptionsHelper;
|
||||||
import org.elasticsearch.xpack.core.persistent.AllocatedPersistentTask;
|
import org.elasticsearch.persistent.AllocatedPersistentTask;
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksCustomMetaData;
|
import org.elasticsearch.persistent.PersistentTasksCustomMetaData;
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksExecutor;
|
import org.elasticsearch.persistent.PersistentTasksExecutor;
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksService;
|
import org.elasticsearch.persistent.PersistentTasksService;
|
||||||
import org.elasticsearch.xpack.ml.MachineLearning;
|
import org.elasticsearch.xpack.ml.MachineLearning;
|
||||||
import org.elasticsearch.xpack.ml.job.persistence.JobProvider;
|
import org.elasticsearch.xpack.ml.job.persistence.JobProvider;
|
||||||
import org.elasticsearch.xpack.ml.job.process.autodetect.AutodetectProcessManager;
|
import org.elasticsearch.xpack.ml.job.process.autodetect.AutodetectProcessManager;
|
||||||
|
|
|
@ -38,10 +38,10 @@ import org.elasticsearch.xpack.core.ml.datafeed.DatafeedState;
|
||||||
import org.elasticsearch.xpack.core.ml.job.config.Job;
|
import org.elasticsearch.xpack.core.ml.job.config.Job;
|
||||||
import org.elasticsearch.xpack.core.ml.job.config.JobState;
|
import org.elasticsearch.xpack.core.ml.job.config.JobState;
|
||||||
import org.elasticsearch.xpack.core.ml.utils.ExceptionsHelper;
|
import org.elasticsearch.xpack.core.ml.utils.ExceptionsHelper;
|
||||||
import org.elasticsearch.xpack.core.persistent.AllocatedPersistentTask;
|
import org.elasticsearch.persistent.AllocatedPersistentTask;
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksCustomMetaData;
|
import org.elasticsearch.persistent.PersistentTasksCustomMetaData;
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksExecutor;
|
import org.elasticsearch.persistent.PersistentTasksExecutor;
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksService;
|
import org.elasticsearch.persistent.PersistentTasksService;
|
||||||
import org.elasticsearch.xpack.ml.MachineLearning;
|
import org.elasticsearch.xpack.ml.MachineLearning;
|
||||||
import org.elasticsearch.xpack.ml.datafeed.DatafeedManager;
|
import org.elasticsearch.xpack.ml.datafeed.DatafeedManager;
|
||||||
import org.elasticsearch.xpack.ml.datafeed.DatafeedNodeSelector;
|
import org.elasticsearch.xpack.ml.datafeed.DatafeedNodeSelector;
|
||||||
|
|
|
@ -33,8 +33,8 @@ import org.elasticsearch.xpack.core.ml.datafeed.DatafeedConfig;
|
||||||
import org.elasticsearch.xpack.core.ml.datafeed.DatafeedState;
|
import org.elasticsearch.xpack.core.ml.datafeed.DatafeedState;
|
||||||
import org.elasticsearch.xpack.core.ml.job.messages.Messages;
|
import org.elasticsearch.xpack.core.ml.job.messages.Messages;
|
||||||
import org.elasticsearch.xpack.core.ml.utils.ExceptionsHelper;
|
import org.elasticsearch.xpack.core.ml.utils.ExceptionsHelper;
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksCustomMetaData;
|
import org.elasticsearch.persistent.PersistentTasksCustomMetaData;
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksService;
|
import org.elasticsearch.persistent.PersistentTasksService;
|
||||||
import org.elasticsearch.xpack.ml.MachineLearning;
|
import org.elasticsearch.xpack.ml.MachineLearning;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
|
@ -25,7 +25,7 @@ import org.elasticsearch.xpack.core.ml.action.PutDatafeedAction;
|
||||||
import org.elasticsearch.xpack.core.ml.action.UpdateDatafeedAction;
|
import org.elasticsearch.xpack.core.ml.action.UpdateDatafeedAction;
|
||||||
import org.elasticsearch.xpack.core.ml.datafeed.DatafeedConfig;
|
import org.elasticsearch.xpack.core.ml.datafeed.DatafeedConfig;
|
||||||
import org.elasticsearch.xpack.core.ml.datafeed.DatafeedUpdate;
|
import org.elasticsearch.xpack.core.ml.datafeed.DatafeedUpdate;
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksCustomMetaData;
|
import org.elasticsearch.persistent.PersistentTasksCustomMetaData;
|
||||||
|
|
||||||
public class TransportUpdateDatafeedAction extends TransportMasterNodeAction<UpdateDatafeedAction.Request, PutDatafeedAction.Response> {
|
public class TransportUpdateDatafeedAction extends TransportMasterNodeAction<UpdateDatafeedAction.Request, PutDatafeedAction.Response> {
|
||||||
|
|
||||||
|
|
|
@ -29,14 +29,11 @@ import org.elasticsearch.xpack.core.ml.datafeed.DatafeedState;
|
||||||
import org.elasticsearch.xpack.core.ml.job.config.Job;
|
import org.elasticsearch.xpack.core.ml.job.config.Job;
|
||||||
import org.elasticsearch.xpack.core.ml.job.config.JobState;
|
import org.elasticsearch.xpack.core.ml.job.config.JobState;
|
||||||
import org.elasticsearch.xpack.core.ml.job.messages.Messages;
|
import org.elasticsearch.xpack.core.ml.job.messages.Messages;
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksCustomMetaData;
|
import org.elasticsearch.persistent.PersistentTasksCustomMetaData;
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksCustomMetaData.PersistentTask;
|
import org.elasticsearch.persistent.PersistentTasksCustomMetaData.PersistentTask;
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksService;
|
|
||||||
import org.elasticsearch.xpack.ml.MachineLearning;
|
import org.elasticsearch.xpack.ml.MachineLearning;
|
||||||
import org.elasticsearch.xpack.ml.action.TransportStartDatafeedAction;
|
import org.elasticsearch.xpack.ml.action.TransportStartDatafeedAction;
|
||||||
import org.elasticsearch.xpack.ml.notifications.Auditor;
|
import org.elasticsearch.xpack.ml.notifications.Auditor;
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksCustomMetaData;
|
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksCustomMetaData.PersistentTask;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
|
@ -53,7 +50,7 @@ import java.util.function.Supplier;
|
||||||
|
|
||||||
import static org.elasticsearch.xpack.core.ClientHelper.ML_ORIGIN;
|
import static org.elasticsearch.xpack.core.ClientHelper.ML_ORIGIN;
|
||||||
import static org.elasticsearch.xpack.core.ClientHelper.executeAsyncWithOrigin;
|
import static org.elasticsearch.xpack.core.ClientHelper.executeAsyncWithOrigin;
|
||||||
import static org.elasticsearch.xpack.core.persistent.PersistentTasksService.WaitForPersistentTaskStatusListener;
|
import static org.elasticsearch.persistent.PersistentTasksService.WaitForPersistentTaskStatusListener;
|
||||||
|
|
||||||
public class DatafeedManager extends AbstractComponent {
|
public class DatafeedManager extends AbstractComponent {
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ import org.elasticsearch.xpack.core.ml.datafeed.DatafeedConfig;
|
||||||
import org.elasticsearch.xpack.core.ml.job.config.JobState;
|
import org.elasticsearch.xpack.core.ml.job.config.JobState;
|
||||||
import org.elasticsearch.xpack.core.ml.job.config.JobTaskStatus;
|
import org.elasticsearch.xpack.core.ml.job.config.JobTaskStatus;
|
||||||
import org.elasticsearch.xpack.core.ml.utils.ExceptionsHelper;
|
import org.elasticsearch.xpack.core.ml.utils.ExceptionsHelper;
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksCustomMetaData;
|
import org.elasticsearch.persistent.PersistentTasksCustomMetaData;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
|
@ -43,7 +43,7 @@ import org.elasticsearch.xpack.core.ml.job.persistence.JobStorageDeletionTask;
|
||||||
import org.elasticsearch.xpack.core.ml.job.process.autodetect.state.ModelSizeStats;
|
import org.elasticsearch.xpack.core.ml.job.process.autodetect.state.ModelSizeStats;
|
||||||
import org.elasticsearch.xpack.core.ml.job.process.autodetect.state.ModelSnapshot;
|
import org.elasticsearch.xpack.core.ml.job.process.autodetect.state.ModelSnapshot;
|
||||||
import org.elasticsearch.xpack.core.ml.utils.ExceptionsHelper;
|
import org.elasticsearch.xpack.core.ml.utils.ExceptionsHelper;
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksCustomMetaData;
|
import org.elasticsearch.persistent.PersistentTasksCustomMetaData;
|
||||||
import org.elasticsearch.xpack.ml.job.persistence.JobProvider;
|
import org.elasticsearch.xpack.ml.job.persistence.JobProvider;
|
||||||
import org.elasticsearch.xpack.ml.job.persistence.JobResultsPersister;
|
import org.elasticsearch.xpack.ml.job.persistence.JobResultsPersister;
|
||||||
import org.elasticsearch.xpack.ml.job.process.autodetect.UpdateParams;
|
import org.elasticsearch.xpack.ml.job.process.autodetect.UpdateParams;
|
||||||
|
|
|
@ -36,7 +36,7 @@ import org.elasticsearch.xpack.core.ml.job.process.autodetect.state.DataCounts;
|
||||||
import org.elasticsearch.xpack.core.ml.job.process.autodetect.state.ModelSizeStats;
|
import org.elasticsearch.xpack.core.ml.job.process.autodetect.state.ModelSizeStats;
|
||||||
import org.elasticsearch.xpack.core.ml.job.process.autodetect.state.ModelSnapshot;
|
import org.elasticsearch.xpack.core.ml.job.process.autodetect.state.ModelSnapshot;
|
||||||
import org.elasticsearch.xpack.core.ml.utils.ExceptionsHelper;
|
import org.elasticsearch.xpack.core.ml.utils.ExceptionsHelper;
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksCustomMetaData.PersistentTask;
|
import org.elasticsearch.persistent.PersistentTasksCustomMetaData.PersistentTask;
|
||||||
import org.elasticsearch.xpack.ml.MachineLearning;
|
import org.elasticsearch.xpack.ml.MachineLearning;
|
||||||
import org.elasticsearch.xpack.ml.action.TransportOpenJobAction.JobTask;
|
import org.elasticsearch.xpack.ml.action.TransportOpenJobAction.JobTask;
|
||||||
import org.elasticsearch.xpack.ml.job.JobManager;
|
import org.elasticsearch.xpack.ml.job.JobManager;
|
||||||
|
|
|
@ -31,7 +31,7 @@ import org.elasticsearch.xpack.core.ml.action.StopDatafeedAction;
|
||||||
import org.elasticsearch.xpack.core.ml.client.MachineLearningClient;
|
import org.elasticsearch.xpack.core.ml.client.MachineLearningClient;
|
||||||
import org.elasticsearch.xpack.core.ml.datafeed.DatafeedState;
|
import org.elasticsearch.xpack.core.ml.datafeed.DatafeedState;
|
||||||
import org.elasticsearch.xpack.core.ml.job.config.JobState;
|
import org.elasticsearch.xpack.core.ml.job.config.JobState;
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksCustomMetaData;
|
import org.elasticsearch.persistent.PersistentTasksCustomMetaData;
|
||||||
import org.elasticsearch.xpack.ml.LocalStateMachineLearning;
|
import org.elasticsearch.xpack.ml.LocalStateMachineLearning;
|
||||||
import org.elasticsearch.xpack.ml.support.BaseMlIntegTestCase;
|
import org.elasticsearch.xpack.ml.support.BaseMlIntegTestCase;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
|
|
@ -16,7 +16,7 @@ import org.elasticsearch.cluster.service.ClusterService;
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
import org.elasticsearch.common.transport.TransportAddress;
|
import org.elasticsearch.common.transport.TransportAddress;
|
||||||
import org.elasticsearch.test.ESTestCase;
|
import org.elasticsearch.test.ESTestCase;
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksCustomMetaData;
|
import org.elasticsearch.persistent.PersistentTasksCustomMetaData;
|
||||||
import org.elasticsearch.xpack.ml.notifications.Auditor;
|
import org.elasticsearch.xpack.ml.notifications.Auditor;
|
||||||
|
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
|
|
|
@ -46,7 +46,7 @@ public class MlDailyManagementServiceTests extends ESTestCase {
|
||||||
CountDownLatch latch = new CountDownLatch(triggerCount);
|
CountDownLatch latch = new CountDownLatch(triggerCount);
|
||||||
try (MlDailyMaintenanceService service = createService(latch, client)) {
|
try (MlDailyMaintenanceService service = createService(latch, client)) {
|
||||||
service.start();
|
service.start();
|
||||||
latch.await(1, TimeUnit.SECONDS);
|
latch.await(5, TimeUnit.SECONDS);
|
||||||
}
|
}
|
||||||
|
|
||||||
verify(client, Mockito.atLeast(triggerCount - 1)).execute(same(DeleteExpiredDataAction.INSTANCE), any(), any());
|
verify(client, Mockito.atLeast(triggerCount - 1)).execute(same(DeleteExpiredDataAction.INSTANCE), any(), any());
|
||||||
|
|
|
@ -29,7 +29,7 @@ import org.elasticsearch.xpack.core.ml.job.config.Job;
|
||||||
import org.elasticsearch.xpack.core.ml.job.config.JobState;
|
import org.elasticsearch.xpack.core.ml.job.config.JobState;
|
||||||
import org.elasticsearch.xpack.core.ml.job.config.JobTaskStatus;
|
import org.elasticsearch.xpack.core.ml.job.config.JobTaskStatus;
|
||||||
import org.elasticsearch.xpack.core.ml.job.config.JobTests;
|
import org.elasticsearch.xpack.core.ml.job.config.JobTests;
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksCustomMetaData;
|
import org.elasticsearch.persistent.PersistentTasksCustomMetaData;
|
||||||
import org.elasticsearch.xpack.ml.datafeed.DatafeedManagerTests;
|
import org.elasticsearch.xpack.ml.datafeed.DatafeedManagerTests;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
@ -37,7 +37,7 @@ import java.util.Date;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import static org.elasticsearch.xpack.core.ml.job.config.JobTests.buildJobBuilder;
|
import static org.elasticsearch.xpack.core.ml.job.config.JobTests.buildJobBuilder;
|
||||||
import static org.elasticsearch.xpack.core.persistent.PersistentTasksCustomMetaData.INITIAL_ASSIGNMENT;
|
import static org.elasticsearch.persistent.PersistentTasksCustomMetaData.INITIAL_ASSIGNMENT;
|
||||||
import static org.elasticsearch.xpack.ml.action.TransportOpenJobActionTests.addJobTask;
|
import static org.elasticsearch.xpack.ml.action.TransportOpenJobActionTests.addJobTask;
|
||||||
import static org.elasticsearch.xpack.ml.datafeed.DatafeedManagerTests.createDatafeedConfig;
|
import static org.elasticsearch.xpack.ml.datafeed.DatafeedManagerTests.createDatafeedConfig;
|
||||||
import static org.elasticsearch.xpack.ml.datafeed.DatafeedManagerTests.createDatafeedJob;
|
import static org.elasticsearch.xpack.ml.datafeed.DatafeedManagerTests.createDatafeedJob;
|
||||||
|
|
|
@ -28,9 +28,9 @@ import org.elasticsearch.xpack.core.ml.action.CloseJobAction.Request;
|
||||||
import org.elasticsearch.xpack.core.ml.action.StartDatafeedAction;
|
import org.elasticsearch.xpack.core.ml.action.StartDatafeedAction;
|
||||||
import org.elasticsearch.xpack.core.ml.datafeed.DatafeedState;
|
import org.elasticsearch.xpack.core.ml.datafeed.DatafeedState;
|
||||||
import org.elasticsearch.xpack.core.ml.job.config.JobState;
|
import org.elasticsearch.xpack.core.ml.job.config.JobState;
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksCustomMetaData;
|
import org.elasticsearch.persistent.PersistentTasksCustomMetaData;
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksCustomMetaData.Assignment;
|
import org.elasticsearch.persistent.PersistentTasksCustomMetaData.Assignment;
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksService;
|
import org.elasticsearch.persistent.PersistentTasksService;
|
||||||
import org.elasticsearch.xpack.ml.notifications.Auditor;
|
import org.elasticsearch.xpack.ml.notifications.Auditor;
|
||||||
import org.elasticsearch.xpack.ml.support.BaseMlIntegTestCase;
|
import org.elasticsearch.xpack.ml.support.BaseMlIntegTestCase;
|
||||||
|
|
||||||
|
|
|
@ -41,8 +41,8 @@ import org.elasticsearch.xpack.core.ml.job.persistence.AnomalyDetectorsIndex;
|
||||||
import org.elasticsearch.xpack.core.ml.job.persistence.AnomalyDetectorsIndexFields;
|
import org.elasticsearch.xpack.core.ml.job.persistence.AnomalyDetectorsIndexFields;
|
||||||
import org.elasticsearch.xpack.core.ml.job.persistence.ElasticsearchMappings;
|
import org.elasticsearch.xpack.core.ml.job.persistence.ElasticsearchMappings;
|
||||||
import org.elasticsearch.xpack.core.ml.notifications.AuditorField;
|
import org.elasticsearch.xpack.core.ml.notifications.AuditorField;
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksCustomMetaData;
|
import org.elasticsearch.persistent.PersistentTasksCustomMetaData;
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksCustomMetaData.Assignment;
|
import org.elasticsearch.persistent.PersistentTasksCustomMetaData.Assignment;
|
||||||
import org.elasticsearch.xpack.ml.MachineLearning;
|
import org.elasticsearch.xpack.ml.MachineLearning;
|
||||||
import org.elasticsearch.xpack.ml.support.BaseMlIntegTestCase;
|
import org.elasticsearch.xpack.ml.support.BaseMlIntegTestCase;
|
||||||
|
|
||||||
|
|
|
@ -14,14 +14,14 @@ import org.elasticsearch.xpack.core.ml.action.StartDatafeedAction;
|
||||||
import org.elasticsearch.xpack.core.ml.datafeed.DatafeedConfig;
|
import org.elasticsearch.xpack.core.ml.datafeed.DatafeedConfig;
|
||||||
import org.elasticsearch.xpack.core.ml.job.config.Job;
|
import org.elasticsearch.xpack.core.ml.job.config.Job;
|
||||||
import org.elasticsearch.xpack.core.ml.job.config.JobState;
|
import org.elasticsearch.xpack.core.ml.job.config.JobState;
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksCustomMetaData;
|
import org.elasticsearch.persistent.PersistentTasksCustomMetaData;
|
||||||
import org.elasticsearch.xpack.ml.datafeed.DatafeedManager;
|
import org.elasticsearch.xpack.ml.datafeed.DatafeedManager;
|
||||||
import org.elasticsearch.xpack.ml.datafeed.DatafeedManagerTests;
|
import org.elasticsearch.xpack.ml.datafeed.DatafeedManagerTests;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
import static org.elasticsearch.xpack.core.persistent.PersistentTasksCustomMetaData.INITIAL_ASSIGNMENT;
|
import static org.elasticsearch.persistent.PersistentTasksCustomMetaData.INITIAL_ASSIGNMENT;
|
||||||
import static org.elasticsearch.xpack.ml.action.TransportOpenJobActionTests.addJobTask;
|
import static org.elasticsearch.xpack.ml.action.TransportOpenJobActionTests.addJobTask;
|
||||||
import static org.hamcrest.Matchers.equalTo;
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ import org.elasticsearch.xpack.core.ml.action.StopDatafeedAction;
|
||||||
import org.elasticsearch.xpack.core.ml.datafeed.DatafeedConfig;
|
import org.elasticsearch.xpack.core.ml.datafeed.DatafeedConfig;
|
||||||
import org.elasticsearch.xpack.core.ml.datafeed.DatafeedState;
|
import org.elasticsearch.xpack.core.ml.datafeed.DatafeedState;
|
||||||
import org.elasticsearch.xpack.core.ml.job.config.Job;
|
import org.elasticsearch.xpack.core.ml.job.config.Job;
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksCustomMetaData;
|
import org.elasticsearch.persistent.PersistentTasksCustomMetaData;
|
||||||
import org.elasticsearch.xpack.ml.support.BaseMlIntegTestCase;
|
import org.elasticsearch.xpack.ml.support.BaseMlIntegTestCase;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
|
@ -35,8 +35,8 @@ import org.elasticsearch.xpack.core.ml.job.config.Job;
|
||||||
import org.elasticsearch.xpack.core.ml.job.config.JobState;
|
import org.elasticsearch.xpack.core.ml.job.config.JobState;
|
||||||
import org.elasticsearch.xpack.core.ml.notifications.AuditMessage;
|
import org.elasticsearch.xpack.core.ml.notifications.AuditMessage;
|
||||||
import org.elasticsearch.xpack.core.ml.notifications.AuditorField;
|
import org.elasticsearch.xpack.core.ml.notifications.AuditorField;
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksCustomMetaData;
|
import org.elasticsearch.persistent.PersistentTasksCustomMetaData;
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksCustomMetaData.PersistentTask;
|
import org.elasticsearch.persistent.PersistentTasksCustomMetaData.PersistentTask;
|
||||||
import org.elasticsearch.xpack.ml.MachineLearning;
|
import org.elasticsearch.xpack.ml.MachineLearning;
|
||||||
import org.elasticsearch.xpack.ml.action.TransportStartDatafeedActionTests;
|
import org.elasticsearch.xpack.ml.action.TransportStartDatafeedActionTests;
|
||||||
import org.elasticsearch.xpack.ml.action.TransportStartDatafeedAction.DatafeedTask;
|
import org.elasticsearch.xpack.ml.action.TransportStartDatafeedAction.DatafeedTask;
|
||||||
|
|
|
@ -32,7 +32,7 @@ import org.elasticsearch.xpack.core.ml.MlMetadata;
|
||||||
import org.elasticsearch.xpack.core.ml.job.config.Job;
|
import org.elasticsearch.xpack.core.ml.job.config.Job;
|
||||||
import org.elasticsearch.xpack.core.ml.job.config.JobState;
|
import org.elasticsearch.xpack.core.ml.job.config.JobState;
|
||||||
import org.elasticsearch.xpack.core.ml.job.config.JobTaskStatus;
|
import org.elasticsearch.xpack.core.ml.job.config.JobTaskStatus;
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksCustomMetaData;
|
import org.elasticsearch.persistent.PersistentTasksCustomMetaData;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
|
|
|
@ -38,8 +38,8 @@ import org.elasticsearch.xpack.core.ml.job.config.Detector;
|
||||||
import org.elasticsearch.xpack.core.ml.job.config.Job;
|
import org.elasticsearch.xpack.core.ml.job.config.Job;
|
||||||
import org.elasticsearch.xpack.core.ml.job.config.JobState;
|
import org.elasticsearch.xpack.core.ml.job.config.JobState;
|
||||||
import org.elasticsearch.xpack.core.ml.job.config.JobTaskStatus;
|
import org.elasticsearch.xpack.core.ml.job.config.JobTaskStatus;
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksCustomMetaData;
|
import org.elasticsearch.persistent.PersistentTasksCustomMetaData;
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksCustomMetaData.PersistentTask;
|
import org.elasticsearch.persistent.PersistentTasksCustomMetaData.PersistentTask;
|
||||||
import org.elasticsearch.xpack.ml.MachineLearning;
|
import org.elasticsearch.xpack.ml.MachineLearning;
|
||||||
import org.elasticsearch.xpack.ml.support.BaseMlIntegTestCase;
|
import org.elasticsearch.xpack.ml.support.BaseMlIntegTestCase;
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ import org.elasticsearch.xpack.core.ml.MlMetadata;
|
||||||
import org.elasticsearch.xpack.core.ml.action.DeleteJobAction;
|
import org.elasticsearch.xpack.core.ml.action.DeleteJobAction;
|
||||||
import org.elasticsearch.xpack.core.ml.action.PutJobAction;
|
import org.elasticsearch.xpack.core.ml.action.PutJobAction;
|
||||||
import org.elasticsearch.xpack.core.ml.job.config.Job;
|
import org.elasticsearch.xpack.core.ml.job.config.Job;
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksCustomMetaData;
|
import org.elasticsearch.persistent.PersistentTasksCustomMetaData;
|
||||||
import org.elasticsearch.xpack.ml.support.BaseMlIntegTestCase;
|
import org.elasticsearch.xpack.ml.support.BaseMlIntegTestCase;
|
||||||
|
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
|
|
@ -33,8 +33,8 @@ import org.elasticsearch.xpack.core.ml.datafeed.DatafeedState;
|
||||||
import org.elasticsearch.xpack.core.ml.job.config.Job;
|
import org.elasticsearch.xpack.core.ml.job.config.Job;
|
||||||
import org.elasticsearch.xpack.core.ml.job.config.JobState;
|
import org.elasticsearch.xpack.core.ml.job.config.JobState;
|
||||||
import org.elasticsearch.xpack.core.ml.job.process.autodetect.state.DataCounts;
|
import org.elasticsearch.xpack.core.ml.job.process.autodetect.state.DataCounts;
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksCustomMetaData;
|
import org.elasticsearch.persistent.PersistentTasksCustomMetaData;
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksCustomMetaData.PersistentTask;
|
import org.elasticsearch.persistent.PersistentTasksCustomMetaData.PersistentTask;
|
||||||
import org.elasticsearch.xpack.ml.support.BaseMlIntegTestCase;
|
import org.elasticsearch.xpack.ml.support.BaseMlIntegTestCase;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
|
@ -21,7 +21,7 @@ import org.elasticsearch.xpack.core.ml.action.PutJobAction;
|
||||||
import org.elasticsearch.xpack.core.ml.job.config.Job;
|
import org.elasticsearch.xpack.core.ml.job.config.Job;
|
||||||
import org.elasticsearch.xpack.core.ml.job.config.JobState;
|
import org.elasticsearch.xpack.core.ml.job.config.JobState;
|
||||||
import org.elasticsearch.xpack.core.ml.job.config.JobTaskStatus;
|
import org.elasticsearch.xpack.core.ml.job.config.JobTaskStatus;
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksCustomMetaData;
|
import org.elasticsearch.persistent.PersistentTasksCustomMetaData;
|
||||||
import org.elasticsearch.xpack.ml.MachineLearning;
|
import org.elasticsearch.xpack.ml.MachineLearning;
|
||||||
import org.elasticsearch.xpack.ml.job.process.autodetect.AutodetectProcessManager;
|
import org.elasticsearch.xpack.ml.job.process.autodetect.AutodetectProcessManager;
|
||||||
import org.elasticsearch.xpack.ml.support.BaseMlIntegTestCase;
|
import org.elasticsearch.xpack.ml.support.BaseMlIntegTestCase;
|
||||||
|
|
|
@ -33,7 +33,7 @@ import org.elasticsearch.xpack.core.ml.job.config.Job;
|
||||||
import org.elasticsearch.xpack.core.ml.job.config.JobState;
|
import org.elasticsearch.xpack.core.ml.job.config.JobState;
|
||||||
import org.elasticsearch.xpack.core.ml.job.config.MlFilter;
|
import org.elasticsearch.xpack.core.ml.job.config.MlFilter;
|
||||||
import org.elasticsearch.xpack.core.ml.job.config.RuleCondition;
|
import org.elasticsearch.xpack.core.ml.job.config.RuleCondition;
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksCustomMetaData;
|
import org.elasticsearch.persistent.PersistentTasksCustomMetaData;
|
||||||
import org.elasticsearch.xpack.ml.job.categorization.CategorizationAnalyzerTests;
|
import org.elasticsearch.xpack.ml.job.categorization.CategorizationAnalyzerTests;
|
||||||
import org.elasticsearch.xpack.ml.job.persistence.JobProvider;
|
import org.elasticsearch.xpack.ml.job.persistence.JobProvider;
|
||||||
import org.elasticsearch.xpack.ml.job.process.autodetect.UpdateParams;
|
import org.elasticsearch.xpack.ml.job.process.autodetect.UpdateParams;
|
||||||
|
|
|
@ -424,6 +424,9 @@ public class Security extends Plugin implements ActionPlugin, IngestPlugin, Netw
|
||||||
components.add(realms);
|
components.add(realms);
|
||||||
components.add(reservedRealm);
|
components.add(reservedRealm);
|
||||||
|
|
||||||
|
securityLifecycleService.addSecurityIndexHealthChangeListener(nativeRoleMappingStore::onSecurityIndexHealthChange);
|
||||||
|
securityLifecycleService.addSecurityIndexOutOfDateListener(nativeRoleMappingStore::onSecurityIndexOutOfDateChange);
|
||||||
|
|
||||||
AuthenticationFailureHandler failureHandler = null;
|
AuthenticationFailureHandler failureHandler = null;
|
||||||
String extensionName = null;
|
String extensionName = null;
|
||||||
for (SecurityExtension extension : securityExtensions) {
|
for (SecurityExtension extension : securityExtensions) {
|
||||||
|
|
|
@ -11,6 +11,7 @@ import org.elasticsearch.client.Client;
|
||||||
import org.elasticsearch.cluster.ClusterChangedEvent;
|
import org.elasticsearch.cluster.ClusterChangedEvent;
|
||||||
import org.elasticsearch.cluster.ClusterState;
|
import org.elasticsearch.cluster.ClusterState;
|
||||||
import org.elasticsearch.cluster.ClusterStateListener;
|
import org.elasticsearch.cluster.ClusterStateListener;
|
||||||
|
import org.elasticsearch.cluster.health.ClusterHealthStatus;
|
||||||
import org.elasticsearch.cluster.health.ClusterIndexHealth;
|
import org.elasticsearch.cluster.health.ClusterIndexHealth;
|
||||||
import org.elasticsearch.cluster.service.ClusterService;
|
import org.elasticsearch.cluster.service.ClusterService;
|
||||||
import org.elasticsearch.common.component.AbstractComponent;
|
import org.elasticsearch.common.component.AbstractComponent;
|
||||||
|
@ -214,4 +215,21 @@ public class SecurityLifecycleService extends AbstractComponent implements Clust
|
||||||
public boolean isSecurityIndexOutOfDate() {
|
public boolean isSecurityIndexOutOfDate() {
|
||||||
return securityIndex.isIndexUpToDate() == false;
|
return securityIndex.isIndexUpToDate() == false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is the move from {@code previousHealth} to {@code currentHealth} a move from an unhealthy ("RED") index state to a healthy
|
||||||
|
* ("non-RED") state.
|
||||||
|
*/
|
||||||
|
public static boolean isMoveFromRedToNonRed(ClusterIndexHealth previousHealth, ClusterIndexHealth currentHealth) {
|
||||||
|
return (previousHealth == null || previousHealth.getStatus() == ClusterHealthStatus.RED)
|
||||||
|
&& currentHealth != null && currentHealth.getStatus() != ClusterHealthStatus.RED;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is the move from {@code previousHealth} to {@code currentHealth} a move from index-exists to index-deleted
|
||||||
|
*/
|
||||||
|
public static boolean isIndexDeleted(ClusterIndexHealth previousHealth, ClusterIndexHealth currentHealth) {
|
||||||
|
return previousHealth != null && currentHealth == null;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ import org.elasticsearch.action.index.IndexResponse;
|
||||||
import org.elasticsearch.action.search.SearchRequest;
|
import org.elasticsearch.action.search.SearchRequest;
|
||||||
import org.elasticsearch.action.support.ContextPreservingActionListener;
|
import org.elasticsearch.action.support.ContextPreservingActionListener;
|
||||||
import org.elasticsearch.client.Client;
|
import org.elasticsearch.client.Client;
|
||||||
|
import org.elasticsearch.cluster.health.ClusterIndexHealth;
|
||||||
import org.elasticsearch.common.CheckedBiConsumer;
|
import org.elasticsearch.common.CheckedBiConsumer;
|
||||||
import org.elasticsearch.common.bytes.BytesReference;
|
import org.elasticsearch.common.bytes.BytesReference;
|
||||||
import org.elasticsearch.common.component.AbstractComponent;
|
import org.elasticsearch.common.component.AbstractComponent;
|
||||||
|
@ -58,6 +59,8 @@ import static org.elasticsearch.xpack.core.ClientHelper.SECURITY_ORIGIN;
|
||||||
import static org.elasticsearch.xpack.core.ClientHelper.executeAsyncWithOrigin;
|
import static org.elasticsearch.xpack.core.ClientHelper.executeAsyncWithOrigin;
|
||||||
import static org.elasticsearch.xpack.core.ClientHelper.stashWithOrigin;
|
import static org.elasticsearch.xpack.core.ClientHelper.stashWithOrigin;
|
||||||
import static org.elasticsearch.xpack.security.SecurityLifecycleService.SECURITY_INDEX_NAME;
|
import static org.elasticsearch.xpack.security.SecurityLifecycleService.SECURITY_INDEX_NAME;
|
||||||
|
import static org.elasticsearch.xpack.security.SecurityLifecycleService.isIndexDeleted;
|
||||||
|
import static org.elasticsearch.xpack.security.SecurityLifecycleService.isMoveFromRedToNonRed;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This store reads + writes {@link ExpressionRoleMapping role mappings} in an Elasticsearch
|
* This store reads + writes {@link ExpressionRoleMapping role mappings} in an Elasticsearch
|
||||||
|
@ -79,6 +82,18 @@ public class NativeRoleMappingStore extends AbstractComponent implements UserRol
|
||||||
|
|
||||||
private static final String SECURITY_GENERIC_TYPE = "doc";
|
private static final String SECURITY_GENERIC_TYPE = "doc";
|
||||||
|
|
||||||
|
private static final ActionListener<Object> NO_OP_ACTION_LISTENER = new ActionListener<Object>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(Object o) {
|
||||||
|
// nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Exception e) {
|
||||||
|
// nothing
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
private final Client client;
|
private final Client client;
|
||||||
private final SecurityLifecycleService securityLifecycleService;
|
private final SecurityLifecycleService securityLifecycleService;
|
||||||
private final List<String> realmsToRefresh = new CopyOnWriteArrayList<>();
|
private final List<String> realmsToRefresh = new CopyOnWriteArrayList<>();
|
||||||
|
@ -301,6 +316,17 @@ public class NativeRoleMappingStore extends AbstractComponent implements UserRol
|
||||||
listener.onResponse(usageStats);
|
listener.onResponse(usageStats);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void onSecurityIndexHealthChange(ClusterIndexHealth previousHealth, ClusterIndexHealth currentHealth) {
|
||||||
|
if (isMoveFromRedToNonRed(previousHealth, currentHealth) || isIndexDeleted(previousHealth, currentHealth)) {
|
||||||
|
refreshRealms(NO_OP_ACTION_LISTENER, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onSecurityIndexOutOfDateChange(boolean prevOutOfDate, boolean outOfDate) {
|
||||||
|
assert prevOutOfDate != outOfDate : "this method should only be called if the two values are different";
|
||||||
|
refreshRealms(NO_OP_ACTION_LISTENER, null);
|
||||||
|
}
|
||||||
|
|
||||||
private <Result> void refreshRealms(ActionListener<Result> listener, Result result) {
|
private <Result> void refreshRealms(ActionListener<Result> listener, Result result) {
|
||||||
String[] realmNames = this.realmsToRefresh.toArray(new String[realmsToRefresh.size()]);
|
String[] realmNames = this.realmsToRefresh.toArray(new String[realmsToRefresh.size()]);
|
||||||
final SecurityClient securityClient = new SecurityClient(client);
|
final SecurityClient securityClient = new SecurityClient(client);
|
||||||
|
|
|
@ -7,7 +7,6 @@ package org.elasticsearch.xpack.security.authz.store;
|
||||||
|
|
||||||
import org.apache.logging.log4j.message.ParameterizedMessage;
|
import org.apache.logging.log4j.message.ParameterizedMessage;
|
||||||
import org.elasticsearch.action.ActionListener;
|
import org.elasticsearch.action.ActionListener;
|
||||||
import org.elasticsearch.cluster.health.ClusterHealthStatus;
|
|
||||||
import org.elasticsearch.cluster.health.ClusterIndexHealth;
|
import org.elasticsearch.cluster.health.ClusterIndexHealth;
|
||||||
import org.elasticsearch.common.Strings;
|
import org.elasticsearch.common.Strings;
|
||||||
import org.elasticsearch.common.bytes.BytesReference;
|
import org.elasticsearch.common.bytes.BytesReference;
|
||||||
|
@ -34,6 +33,7 @@ import org.elasticsearch.xpack.core.security.authz.privilege.ClusterPrivilege;
|
||||||
import org.elasticsearch.xpack.core.security.authz.privilege.IndexPrivilege;
|
import org.elasticsearch.xpack.core.security.authz.privilege.IndexPrivilege;
|
||||||
import org.elasticsearch.xpack.core.security.authz.privilege.Privilege;
|
import org.elasticsearch.xpack.core.security.authz.privilege.Privilege;
|
||||||
import org.elasticsearch.xpack.core.security.authz.store.ReservedRolesStore;
|
import org.elasticsearch.xpack.core.security.authz.store.ReservedRolesStore;
|
||||||
|
import org.elasticsearch.xpack.security.SecurityLifecycleService;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
@ -53,6 +53,8 @@ import java.util.function.BiConsumer;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static org.elasticsearch.xpack.core.security.SecurityField.setting;
|
import static org.elasticsearch.xpack.core.security.SecurityField.setting;
|
||||||
|
import static org.elasticsearch.xpack.security.SecurityLifecycleService.isIndexDeleted;
|
||||||
|
import static org.elasticsearch.xpack.security.SecurityLifecycleService.isMoveFromRedToNonRed;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A composite roles store that combines built in roles, file-based roles, and index-based roles. Checks the built in roles first, then the
|
* A composite roles store that combines built in roles, file-based roles, and index-based roles. Checks the built in roles first, then the
|
||||||
|
@ -322,11 +324,7 @@ public class CompositeRolesStore extends AbstractComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onSecurityIndexHealthChange(ClusterIndexHealth previousHealth, ClusterIndexHealth currentHealth) {
|
public void onSecurityIndexHealthChange(ClusterIndexHealth previousHealth, ClusterIndexHealth currentHealth) {
|
||||||
final boolean movedFromRedToNonRed = (previousHealth == null || previousHealth.getStatus() == ClusterHealthStatus.RED)
|
if (isMoveFromRedToNonRed(previousHealth, currentHealth) || isIndexDeleted(previousHealth, currentHealth)) {
|
||||||
&& currentHealth != null && currentHealth.getStatus() != ClusterHealthStatus.RED;
|
|
||||||
final boolean indexDeleted = previousHealth != null && currentHealth == null;
|
|
||||||
|
|
||||||
if (movedFromRedToNonRed || indexDeleted) {
|
|
||||||
invalidateAll();
|
invalidateAll();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,211 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License;
|
||||||
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
|
*/
|
||||||
|
package org.elasticsearch.xpack.security.authc.support.mapper;
|
||||||
|
|
||||||
|
import org.elasticsearch.action.ActionListener;
|
||||||
|
import org.elasticsearch.action.support.PlainActionFuture;
|
||||||
|
import org.elasticsearch.client.Client;
|
||||||
|
import org.elasticsearch.cluster.ClusterName;
|
||||||
|
import org.elasticsearch.cluster.health.ClusterHealthStatus;
|
||||||
|
import org.elasticsearch.cluster.health.ClusterIndexHealth;
|
||||||
|
import org.elasticsearch.common.settings.Settings;
|
||||||
|
import org.elasticsearch.common.util.concurrent.ThreadContext;
|
||||||
|
import org.elasticsearch.env.Environment;
|
||||||
|
import org.elasticsearch.env.TestEnvironment;
|
||||||
|
import org.elasticsearch.test.ESTestCase;
|
||||||
|
import org.elasticsearch.threadpool.ThreadPool;
|
||||||
|
import org.elasticsearch.xpack.core.security.action.realm.ClearRealmCacheAction;
|
||||||
|
import org.elasticsearch.xpack.core.security.action.realm.ClearRealmCacheRequest;
|
||||||
|
import org.elasticsearch.xpack.core.security.action.realm.ClearRealmCacheResponse;
|
||||||
|
import org.elasticsearch.xpack.core.security.authc.AuthenticationResult;
|
||||||
|
import org.elasticsearch.xpack.core.security.authc.RealmConfig;
|
||||||
|
import org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken;
|
||||||
|
import org.elasticsearch.xpack.core.security.authc.support.mapper.ExpressionRoleMapping;
|
||||||
|
import org.elasticsearch.xpack.core.security.authc.support.mapper.expressiondsl.FieldExpression;
|
||||||
|
import org.elasticsearch.xpack.core.security.authc.support.mapper.expressiondsl.FieldExpression.FieldValue;
|
||||||
|
import org.elasticsearch.xpack.core.security.user.User;
|
||||||
|
import org.elasticsearch.xpack.security.SecurityLifecycleService;
|
||||||
|
import org.elasticsearch.xpack.security.authc.support.CachingUsernamePasswordRealm;
|
||||||
|
import org.elasticsearch.xpack.security.authc.support.UserRoleMapper;
|
||||||
|
import org.hamcrest.Matchers;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import static org.elasticsearch.xpack.security.test.SecurityTestUtils.getClusterIndexHealth;
|
||||||
|
import static org.mockito.Matchers.any;
|
||||||
|
import static org.mockito.Matchers.eq;
|
||||||
|
import static org.mockito.Mockito.doAnswer;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
public class NativeRoleMappingStoreTests extends ESTestCase {
|
||||||
|
|
||||||
|
public void testResolveRoles() throws Exception {
|
||||||
|
// Does match DN
|
||||||
|
final ExpressionRoleMapping mapping1 = new ExpressionRoleMapping("dept_h",
|
||||||
|
new FieldExpression("dn", Collections.singletonList(new FieldValue("*,ou=dept_h,o=forces,dc=gc,dc=ca"))),
|
||||||
|
Arrays.asList("dept_h", "defence"), Collections.emptyMap(), true);
|
||||||
|
// Does not match - user is not in this group
|
||||||
|
final ExpressionRoleMapping mapping2 = new ExpressionRoleMapping("admin",
|
||||||
|
new FieldExpression("groups", Collections.singletonList(
|
||||||
|
new FieldValue(randomiseDn("cn=esadmin,ou=groups,ou=dept_h,o=forces,dc=gc,dc=ca")))),
|
||||||
|
Arrays.asList("admin"), Collections.emptyMap(), true);
|
||||||
|
// Does match - user is one of these groups
|
||||||
|
final ExpressionRoleMapping mapping3 = new ExpressionRoleMapping("flight",
|
||||||
|
new FieldExpression("groups", Arrays.asList(
|
||||||
|
new FieldValue(randomiseDn("cn=alphaflight,ou=groups,ou=dept_h,o=forces,dc=gc,dc=ca")),
|
||||||
|
new FieldValue(randomiseDn("cn=betaflight,ou=groups,ou=dept_h,o=forces,dc=gc,dc=ca")),
|
||||||
|
new FieldValue(randomiseDn("cn=gammaflight,ou=groups,ou=dept_h,o=forces,dc=gc,dc=ca"))
|
||||||
|
)),
|
||||||
|
Arrays.asList("flight"), Collections.emptyMap(), true);
|
||||||
|
// Does not match - mapping is not enabled
|
||||||
|
final ExpressionRoleMapping mapping4 = new ExpressionRoleMapping("mutants",
|
||||||
|
new FieldExpression("groups", Collections.singletonList(
|
||||||
|
new FieldValue(randomiseDn("cn=mutants,ou=groups,ou=dept_h,o=forces,dc=gc,dc=ca")))),
|
||||||
|
Arrays.asList("mutants"), Collections.emptyMap(), false);
|
||||||
|
|
||||||
|
final Client client = mock(Client.class);
|
||||||
|
final SecurityLifecycleService lifecycleService = mock(SecurityLifecycleService.class);
|
||||||
|
when(lifecycleService.isSecurityIndexAvailable()).thenReturn(true);
|
||||||
|
|
||||||
|
final NativeRoleMappingStore store = new NativeRoleMappingStore(Settings.EMPTY, client, lifecycleService) {
|
||||||
|
@Override
|
||||||
|
protected void loadMappings(ActionListener<List<ExpressionRoleMapping>> listener) {
|
||||||
|
final List<ExpressionRoleMapping> mappings = Arrays.asList(mapping1, mapping2, mapping3, mapping4);
|
||||||
|
logger.info("Role mappings are: [{}]", mappings);
|
||||||
|
listener.onResponse(mappings);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
final RealmConfig realm = new RealmConfig("ldap1", Settings.EMPTY, Settings.EMPTY, mock(Environment.class),
|
||||||
|
new ThreadContext(Settings.EMPTY));
|
||||||
|
|
||||||
|
final PlainActionFuture<Set<String>> future = new PlainActionFuture<>();
|
||||||
|
final UserRoleMapper.UserData user = new UserRoleMapper.UserData("sasquatch",
|
||||||
|
randomiseDn("cn=walter.langowski,ou=people,ou=dept_h,o=forces,dc=gc,dc=ca"),
|
||||||
|
Arrays.asList(
|
||||||
|
randomiseDn("cn=alphaflight,ou=groups,ou=dept_h,o=forces,dc=gc,dc=ca"),
|
||||||
|
randomiseDn("cn=mutants,ou=groups,ou=dept_h,o=forces,dc=gc,dc=ca")
|
||||||
|
), Collections.emptyMap(), realm);
|
||||||
|
|
||||||
|
logger.info("UserData is [{}]", user);
|
||||||
|
store.resolveRoles(user, future);
|
||||||
|
final Set<String> roles = future.get();
|
||||||
|
assertThat(roles, Matchers.containsInAnyOrder("dept_h", "defence", "flight"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String randomiseDn(String dn) {
|
||||||
|
// Randomly transform the dn into another valid form that is logically identical,
|
||||||
|
// but (potentially) textually different
|
||||||
|
switch (randomIntBetween(0, 3)) {
|
||||||
|
case 0:
|
||||||
|
// do nothing
|
||||||
|
return dn;
|
||||||
|
case 1:
|
||||||
|
return dn.toUpperCase(Locale.ROOT);
|
||||||
|
case 2:
|
||||||
|
// Upper case just the attribute name for each RDN
|
||||||
|
return Arrays.stream(dn.split(",")).map(s -> {
|
||||||
|
final String[] arr = s.split("=");
|
||||||
|
arr[0] = arr[0].toUpperCase(Locale.ROOT);
|
||||||
|
return String.join("=", arr);
|
||||||
|
}).collect(Collectors.joining(","));
|
||||||
|
case 3:
|
||||||
|
return dn.replaceAll(",", ", ");
|
||||||
|
}
|
||||||
|
return dn;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void testCacheClearOnIndexHealthChange() {
|
||||||
|
final AtomicInteger numInvalidation = new AtomicInteger(0);
|
||||||
|
final NativeRoleMappingStore store = buildRoleMappingStoreForInvalidationTesting(numInvalidation);
|
||||||
|
|
||||||
|
int expectedInvalidation = 0;
|
||||||
|
// existing to no longer present
|
||||||
|
ClusterIndexHealth previousHealth = getClusterIndexHealth(randomFrom(ClusterHealthStatus.GREEN, ClusterHealthStatus.YELLOW));
|
||||||
|
ClusterIndexHealth currentHealth = null;
|
||||||
|
store.onSecurityIndexHealthChange(previousHealth, currentHealth);
|
||||||
|
assertEquals(++expectedInvalidation, numInvalidation.get());
|
||||||
|
|
||||||
|
// doesn't exist to exists
|
||||||
|
previousHealth = null;
|
||||||
|
currentHealth = getClusterIndexHealth(randomFrom(ClusterHealthStatus.GREEN, ClusterHealthStatus.YELLOW));
|
||||||
|
store.onSecurityIndexHealthChange(previousHealth, currentHealth);
|
||||||
|
assertEquals(++expectedInvalidation, numInvalidation.get());
|
||||||
|
|
||||||
|
// green or yellow to red
|
||||||
|
previousHealth = getClusterIndexHealth(randomFrom(ClusterHealthStatus.GREEN, ClusterHealthStatus.YELLOW));
|
||||||
|
currentHealth = getClusterIndexHealth(ClusterHealthStatus.RED);
|
||||||
|
store.onSecurityIndexHealthChange(previousHealth, currentHealth);
|
||||||
|
assertEquals(expectedInvalidation, numInvalidation.get());
|
||||||
|
|
||||||
|
// red to non red
|
||||||
|
previousHealth = getClusterIndexHealth(ClusterHealthStatus.RED);
|
||||||
|
currentHealth = getClusterIndexHealth(randomFrom(ClusterHealthStatus.GREEN, ClusterHealthStatus.YELLOW));
|
||||||
|
store.onSecurityIndexHealthChange(previousHealth, currentHealth);
|
||||||
|
assertEquals(++expectedInvalidation, numInvalidation.get());
|
||||||
|
|
||||||
|
// green to yellow or yellow to green
|
||||||
|
previousHealth = getClusterIndexHealth(randomFrom(ClusterHealthStatus.GREEN, ClusterHealthStatus.YELLOW));
|
||||||
|
currentHealth = getClusterIndexHealth(
|
||||||
|
previousHealth.getStatus() == ClusterHealthStatus.GREEN ? ClusterHealthStatus.YELLOW : ClusterHealthStatus.GREEN);
|
||||||
|
store.onSecurityIndexHealthChange(previousHealth, currentHealth);
|
||||||
|
assertEquals(expectedInvalidation, numInvalidation.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testCacheClearOnIndexOutOfDateChange() {
|
||||||
|
final AtomicInteger numInvalidation = new AtomicInteger(0);
|
||||||
|
final NativeRoleMappingStore store = buildRoleMappingStoreForInvalidationTesting(numInvalidation);
|
||||||
|
|
||||||
|
store.onSecurityIndexOutOfDateChange(false, true);
|
||||||
|
assertEquals(1, numInvalidation.get());
|
||||||
|
|
||||||
|
store.onSecurityIndexOutOfDateChange(true, false);
|
||||||
|
assertEquals(2, numInvalidation.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
private NativeRoleMappingStore buildRoleMappingStoreForInvalidationTesting(AtomicInteger invalidationCounter) {
|
||||||
|
final Settings settings = Settings.builder().put("path.home", createTempDir()).build();
|
||||||
|
|
||||||
|
final ThreadPool threadPool = mock(ThreadPool.class);
|
||||||
|
final ThreadContext threadContext = new ThreadContext(settings);
|
||||||
|
when(threadPool.getThreadContext()).thenReturn(threadContext);
|
||||||
|
|
||||||
|
final Client client = mock(Client.class);
|
||||||
|
when(client.threadPool()).thenReturn(threadPool);
|
||||||
|
when(client.settings()).thenReturn(settings);
|
||||||
|
doAnswer(invocationOnMock -> {
|
||||||
|
ActionListener<ClearRealmCacheResponse> listener = (ActionListener<ClearRealmCacheResponse>) invocationOnMock.getArguments()[2];
|
||||||
|
invalidationCounter.incrementAndGet();
|
||||||
|
listener.onResponse(new ClearRealmCacheResponse(new ClusterName("cluster"), Collections.emptyList(), Collections.emptyList()));
|
||||||
|
return null;
|
||||||
|
}).when(client).execute(eq(ClearRealmCacheAction.INSTANCE), any(ClearRealmCacheRequest.class), any(ActionListener.class));
|
||||||
|
|
||||||
|
final Environment env = TestEnvironment.newEnvironment(settings);
|
||||||
|
final RealmConfig realmConfig = new RealmConfig(getTestName(), Settings.EMPTY, settings, env, threadContext);
|
||||||
|
final CachingUsernamePasswordRealm mockRealm = new CachingUsernamePasswordRealm("test", realmConfig) {
|
||||||
|
@Override
|
||||||
|
protected void doAuthenticate(UsernamePasswordToken token, ActionListener<AuthenticationResult> listener) {
|
||||||
|
listener.onResponse(AuthenticationResult.notHandled());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void doLookupUser(String username, ActionListener<User> listener) {
|
||||||
|
listener.onResponse(null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
final NativeRoleMappingStore store = new NativeRoleMappingStore(Settings.EMPTY, client, mock(SecurityLifecycleService.class));
|
||||||
|
store.refreshRealmOnChange(mockRealm);
|
||||||
|
return store;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,111 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.xpack.security.authc.support.mapper;
|
|
||||||
|
|
||||||
import org.elasticsearch.action.ActionListener;
|
|
||||||
import org.elasticsearch.action.support.PlainActionFuture;
|
|
||||||
import org.elasticsearch.client.Client;
|
|
||||||
import org.elasticsearch.common.settings.Settings;
|
|
||||||
import org.elasticsearch.common.util.concurrent.ThreadContext;
|
|
||||||
import org.elasticsearch.env.Environment;
|
|
||||||
import org.elasticsearch.test.ESTestCase;
|
|
||||||
import org.elasticsearch.xpack.core.security.authc.RealmConfig;
|
|
||||||
import org.elasticsearch.xpack.core.security.authc.support.mapper.ExpressionRoleMapping;
|
|
||||||
import org.elasticsearch.xpack.core.security.authc.support.mapper.expressiondsl.FieldExpression;
|
|
||||||
import org.elasticsearch.xpack.core.security.authc.support.mapper.expressiondsl.FieldExpression.FieldValue;
|
|
||||||
import org.elasticsearch.xpack.security.SecurityLifecycleService;
|
|
||||||
import org.elasticsearch.xpack.security.authc.support.UserRoleMapper;
|
|
||||||
import org.hamcrest.Matchers;
|
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
import static org.mockito.Mockito.mock;
|
|
||||||
import static org.mockito.Mockito.when;
|
|
||||||
|
|
||||||
public class NativeUserRoleMapperTests extends ESTestCase {
|
|
||||||
|
|
||||||
public void testResolveRoles() throws Exception {
|
|
||||||
// Does match DN
|
|
||||||
final ExpressionRoleMapping mapping1 = new ExpressionRoleMapping("dept_h",
|
|
||||||
new FieldExpression("dn", Collections.singletonList(new FieldValue("*,ou=dept_h,o=forces,dc=gc,dc=ca"))),
|
|
||||||
Arrays.asList("dept_h", "defence"), Collections.emptyMap(), true);
|
|
||||||
// Does not match - user is not in this group
|
|
||||||
final ExpressionRoleMapping mapping2 = new ExpressionRoleMapping("admin",
|
|
||||||
new FieldExpression("groups", Collections.singletonList(
|
|
||||||
new FieldValue(randomiseDn("cn=esadmin,ou=groups,ou=dept_h,o=forces,dc=gc,dc=ca")))),
|
|
||||||
Arrays.asList("admin"), Collections.emptyMap(), true);
|
|
||||||
// Does match - user is one of these groups
|
|
||||||
final ExpressionRoleMapping mapping3 = new ExpressionRoleMapping("flight",
|
|
||||||
new FieldExpression("groups", Arrays.asList(
|
|
||||||
new FieldValue(randomiseDn("cn=alphaflight,ou=groups,ou=dept_h,o=forces,dc=gc,dc=ca")),
|
|
||||||
new FieldValue(randomiseDn("cn=betaflight,ou=groups,ou=dept_h,o=forces,dc=gc,dc=ca")),
|
|
||||||
new FieldValue(randomiseDn("cn=gammaflight,ou=groups,ou=dept_h,o=forces,dc=gc,dc=ca"))
|
|
||||||
)),
|
|
||||||
Arrays.asList("flight"), Collections.emptyMap(), true);
|
|
||||||
// Does not match - mapping is not enabled
|
|
||||||
final ExpressionRoleMapping mapping4 = new ExpressionRoleMapping("mutants",
|
|
||||||
new FieldExpression("groups", Collections.singletonList(
|
|
||||||
new FieldValue(randomiseDn("cn=mutants,ou=groups,ou=dept_h,o=forces,dc=gc,dc=ca")))),
|
|
||||||
Arrays.asList("mutants"), Collections.emptyMap(), false);
|
|
||||||
|
|
||||||
final Client client = mock(Client.class);
|
|
||||||
final SecurityLifecycleService lifecycleService = mock(SecurityLifecycleService.class);
|
|
||||||
when(lifecycleService.isSecurityIndexAvailable()).thenReturn(true);
|
|
||||||
|
|
||||||
final NativeRoleMappingStore store = new NativeRoleMappingStore(Settings.EMPTY, client, lifecycleService) {
|
|
||||||
@Override
|
|
||||||
protected void loadMappings(ActionListener<List<ExpressionRoleMapping>> listener) {
|
|
||||||
final List<ExpressionRoleMapping> mappings = Arrays.asList(mapping1, mapping2, mapping3, mapping4);
|
|
||||||
logger.info("Role mappings are: [{}]", mappings);
|
|
||||||
listener.onResponse(mappings);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
final RealmConfig realm = new RealmConfig("ldap1", Settings.EMPTY, Settings.EMPTY, mock(Environment.class),
|
|
||||||
new ThreadContext(Settings.EMPTY));
|
|
||||||
|
|
||||||
final PlainActionFuture<Set<String>> future = new PlainActionFuture<>();
|
|
||||||
final UserRoleMapper.UserData user = new UserRoleMapper.UserData("sasquatch",
|
|
||||||
randomiseDn("cn=walter.langowski,ou=people,ou=dept_h,o=forces,dc=gc,dc=ca"),
|
|
||||||
Arrays.asList(
|
|
||||||
randomiseDn("cn=alphaflight,ou=groups,ou=dept_h,o=forces,dc=gc,dc=ca"),
|
|
||||||
randomiseDn("cn=mutants,ou=groups,ou=dept_h,o=forces,dc=gc,dc=ca")
|
|
||||||
), Collections.emptyMap(), realm);
|
|
||||||
|
|
||||||
logger.info("UserData is [{}]", user);
|
|
||||||
store.resolveRoles(user, future);
|
|
||||||
final Set<String> roles = future.get();
|
|
||||||
assertThat(roles, Matchers.containsInAnyOrder("dept_h", "defence", "flight"));
|
|
||||||
}
|
|
||||||
|
|
||||||
private String randomiseDn(String dn) {
|
|
||||||
// Randomly transform the dn into another valid form that is logically identical,
|
|
||||||
// but (potentially) textually different
|
|
||||||
switch (randomIntBetween(0, 3)) {
|
|
||||||
case 0:
|
|
||||||
// do nothing
|
|
||||||
return dn;
|
|
||||||
case 1:
|
|
||||||
return dn.toUpperCase(Locale.ROOT);
|
|
||||||
case 2:
|
|
||||||
// Upper case just the attribute name for each RDN
|
|
||||||
return Arrays.stream(dn.split(",")).map(s -> {
|
|
||||||
final String[] arr = s.split("=");
|
|
||||||
arr[0] = arr[0].toUpperCase(Locale.ROOT);
|
|
||||||
return String.join("=", arr);
|
|
||||||
}).collect(Collectors.joining(","));
|
|
||||||
case 3:
|
|
||||||
return dn.replaceAll(",", ", ");
|
|
||||||
}
|
|
||||||
return dn;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -78,9 +78,9 @@ import org.elasticsearch.xpack.core.ml.job.results.CategoryDefinition;
|
||||||
import org.elasticsearch.xpack.core.ml.job.results.Forecast;
|
import org.elasticsearch.xpack.core.ml.job.results.Forecast;
|
||||||
import org.elasticsearch.xpack.core.ml.job.results.ForecastRequestStats;
|
import org.elasticsearch.xpack.core.ml.job.results.ForecastRequestStats;
|
||||||
import org.elasticsearch.xpack.core.ml.job.results.Result;
|
import org.elasticsearch.xpack.core.ml.job.results.Result;
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTaskParams;
|
import org.elasticsearch.persistent.PersistentTaskParams;
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksCustomMetaData;
|
import org.elasticsearch.persistent.PersistentTasksCustomMetaData;
|
||||||
import org.elasticsearch.xpack.core.persistent.PersistentTasksNodeService;
|
import org.elasticsearch.persistent.PersistentTasksNodeService;
|
||||||
import org.elasticsearch.xpack.core.security.SecurityField;
|
import org.elasticsearch.xpack.core.security.SecurityField;
|
||||||
import org.elasticsearch.xpack.core.security.authc.TokenMetaData;
|
import org.elasticsearch.xpack.core.security.authc.TokenMetaData;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue