[7.x][Transform] decouple task and indexer (#48812)

decouple TransformTask and ClientTransformIndexer. Interaction between the 2 classes are
now moved into a context class which holds shared information.

relates #45369
This commit is contained in:
Hendrik Muhs 2019-11-01 19:39:35 +01:00 committed by GitHub
parent 6ab4645f4e
commit 5ecde37a68
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 1759 additions and 1363 deletions

View File

@ -258,6 +258,10 @@ public class TransformCheckpoint implements Writeable, ToXContentObject {
return NAME + "-" + transformId + "-" + checkpoint; return NAME + "-" + transformId + "-" + checkpoint;
} }
public static boolean isNullOrEmpty (TransformCheckpoint checkpoint) {
return checkpoint == null || checkpoint.isEmpty();
}
/** /**
* Calculate the diff of 2 checkpoints * Calculate the diff of 2 checkpoints
* *

View File

@ -98,7 +98,6 @@ import org.elasticsearch.xpack.transform.rest.action.compat.RestStartTransformAc
import org.elasticsearch.xpack.transform.rest.action.compat.RestStopTransformActionDeprecated; import org.elasticsearch.xpack.transform.rest.action.compat.RestStopTransformActionDeprecated;
import org.elasticsearch.xpack.transform.rest.action.compat.RestUpdateTransformActionDeprecated; import org.elasticsearch.xpack.transform.rest.action.compat.RestUpdateTransformActionDeprecated;
import org.elasticsearch.xpack.transform.transforms.TransformPersistentTasksExecutor; import org.elasticsearch.xpack.transform.transforms.TransformPersistentTasksExecutor;
import org.elasticsearch.xpack.transform.transforms.TransformTask;
import java.io.IOException; import java.io.IOException;
import java.time.Clock; import java.time.Clock;
@ -128,6 +127,19 @@ public class Transform extends Plugin implements ActionPlugin, PersistentTaskPlu
private final SetOnce<TransformCheckpointService> transformCheckpointService = new SetOnce<>(); private final SetOnce<TransformCheckpointService> transformCheckpointService = new SetOnce<>();
private final SetOnce<SchedulerEngine> schedulerEngine = new SetOnce<>(); private final SetOnce<SchedulerEngine> schedulerEngine = new SetOnce<>();
public static final int DEFAULT_FAILURE_RETRIES = 10;
// How many times the transform task can retry on an non-critical failure
public static final Setting<Integer> NUM_FAILURE_RETRIES_SETTING = Setting
.intSetting(
"xpack.transform.num_transform_failure_retries",
DEFAULT_FAILURE_RETRIES,
0,
100,
Setting.Property.NodeScope,
Setting.Property.Dynamic
);
public Transform(Settings settings) { public Transform(Settings settings) {
this.settings = settings; this.settings = settings;
this.enabled = XPackSettings.TRANSFORM_ENABLED.get(settings); this.enabled = XPackSettings.TRANSFORM_ENABLED.get(settings);
@ -146,18 +158,27 @@ public class Transform extends Plugin implements ActionPlugin, PersistentTaskPlu
return modules; return modules;
} }
protected XPackLicenseState getLicenseState() { return XPackPlugin.getSharedLicenseState(); } protected XPackLicenseState getLicenseState() {
return XPackPlugin.getSharedLicenseState();
}
@Override @Override
public List<RestHandler> getRestHandlers(final Settings settings, final RestController restController, public List<RestHandler> getRestHandlers(
final ClusterSettings clusterSettings, final IndexScopedSettings indexScopedSettings, final SettingsFilter settingsFilter, final Settings settings,
final IndexNameExpressionResolver indexNameExpressionResolver, final Supplier<DiscoveryNodes> nodesInCluster) { final RestController restController,
final ClusterSettings clusterSettings,
final IndexScopedSettings indexScopedSettings,
final SettingsFilter settingsFilter,
final IndexNameExpressionResolver indexNameExpressionResolver,
final Supplier<DiscoveryNodes> nodesInCluster
) {
if (!enabled) { if (!enabled) {
return emptyList(); return emptyList();
} }
return Arrays.asList( return Arrays
.asList(
new RestPutTransformAction(restController), new RestPutTransformAction(restController),
new RestStartTransformAction(restController), new RestStartTransformAction(restController),
new RestStopTransformAction(restController), new RestStopTransformAction(restController),
@ -185,7 +206,8 @@ public class Transform extends Plugin implements ActionPlugin, PersistentTaskPlu
return emptyList(); return emptyList();
} }
return Arrays.asList( return Arrays
.asList(
new ActionHandler<>(PutTransformAction.INSTANCE, TransportPutTransformAction.class), new ActionHandler<>(PutTransformAction.INSTANCE, TransportPutTransformAction.class),
new ActionHandler<>(StartTransformAction.INSTANCE, TransportStartTransformAction.class), new ActionHandler<>(StartTransformAction.INSTANCE, TransportStartTransformAction.class),
new ActionHandler<>(StopTransformAction.INSTANCE, TransportStopTransformAction.class), new ActionHandler<>(StopTransformAction.INSTANCE, TransportStopTransformAction.class),
@ -213,35 +235,45 @@ public class Transform extends Plugin implements ActionPlugin, PersistentTaskPlu
return emptyList(); return emptyList();
} }
FixedExecutorBuilder indexing = new FixedExecutorBuilder(settings, TASK_THREAD_POOL_NAME, 4, 4, FixedExecutorBuilder indexing = new FixedExecutorBuilder(settings, TASK_THREAD_POOL_NAME, 4, 4, "transform.task_thread_pool");
"transform.task_thread_pool");
return Collections.singletonList(indexing); return Collections.singletonList(indexing);
} }
@Override @Override
public Collection<Object> createComponents(Client client, ClusterService clusterService, ThreadPool threadPool, public Collection<Object> createComponents(
ResourceWatcherService resourceWatcherService, ScriptService scriptService, NamedXContentRegistry xContentRegistry, Client client,
Environment environment, NodeEnvironment nodeEnvironment, NamedWriteableRegistry namedWriteableRegistry) { ClusterService clusterService,
ThreadPool threadPool,
ResourceWatcherService resourceWatcherService,
ScriptService scriptService,
NamedXContentRegistry xContentRegistry,
Environment environment,
NodeEnvironment nodeEnvironment,
NamedWriteableRegistry namedWriteableRegistry
) {
if (enabled == false || transportClientMode) { if (enabled == false || transportClientMode) {
return emptyList(); return emptyList();
} }
transformAuditor.set(new TransformAuditor(client, clusterService.getNodeName())); transformAuditor.set(new TransformAuditor(client, clusterService.getNodeName()));
transformConfigManager.set(new TransformConfigManager(client, xContentRegistry)); transformConfigManager.set(new TransformConfigManager(client, xContentRegistry));
transformCheckpointService.set(new TransformCheckpointService(client, transformCheckpointService.set(new TransformCheckpointService(client, transformConfigManager.get(), transformAuditor.get()));
transformConfigManager.get(),
transformAuditor.get()));
return Arrays.asList(transformConfigManager.get(), transformAuditor.get(), transformCheckpointService.get(), return Arrays
new TransformClusterStateListener(clusterService, client)); .asList(
transformConfigManager.get(),
transformAuditor.get(),
transformCheckpointService.get(),
new TransformClusterStateListener(clusterService, client)
);
} }
@Override @Override
public UnaryOperator<Map<String, IndexTemplateMetaData>> getIndexTemplateMetaDataUpgrader() { public UnaryOperator<Map<String, IndexTemplateMetaData>> getIndexTemplateMetaDataUpgrader() {
return templates -> { return templates -> {
try { try {
templates.put(TransformInternalIndexConstants.LATEST_INDEX_VERSIONED_NAME, templates
TransformInternalIndex.getIndexTemplateMetaData()); .put(TransformInternalIndexConstants.LATEST_INDEX_VERSIONED_NAME, TransformInternalIndex.getIndexTemplateMetaData());
} catch (IOException e) { } catch (IOException e) {
logger.error("Error creating data frame index template", e); logger.error("Error creating data frame index template", e);
} }
@ -255,8 +287,12 @@ public class Transform extends Plugin implements ActionPlugin, PersistentTaskPlu
} }
@Override @Override
public List<PersistentTasksExecutor<?>> getPersistentTasksExecutor(ClusterService clusterService, ThreadPool threadPool, public List<PersistentTasksExecutor<?>> getPersistentTasksExecutor(
Client client, SettingsModule settingsModule) { ClusterService clusterService,
ThreadPool threadPool,
Client client,
SettingsModule settingsModule
) {
if (enabled == false || transportClientMode) { if (enabled == false || transportClientMode) {
return emptyList(); return emptyList();
} }
@ -269,20 +305,24 @@ public class Transform extends Plugin implements ActionPlugin, PersistentTaskPlu
assert transformAuditor.get() != null; assert transformAuditor.get() != null;
assert transformCheckpointService.get() != null; assert transformCheckpointService.get() != null;
return Collections.singletonList( return Collections
new TransformPersistentTasksExecutor(client, .singletonList(
new TransformPersistentTasksExecutor(
client,
transformConfigManager.get(), transformConfigManager.get(),
transformCheckpointService.get(), transformCheckpointService.get(),
schedulerEngine.get(), schedulerEngine.get(),
transformAuditor.get(), transformAuditor.get(),
threadPool, threadPool,
clusterService, clusterService,
settingsModule.getSettings())); settingsModule.getSettings()
)
);
} }
@Override @Override
public List<Setting<?>> getSettings() { public List<Setting<?>> getSettings() {
return Collections.singletonList(TransformTask.NUM_FAILURE_RETRIES_SETTING); return Collections.singletonList(NUM_FAILURE_RETRIES_SETTING);
} }
@Override @Override

View File

@ -51,8 +51,7 @@ public class DefaultCheckpointProvider implements CheckpointProvider {
private TransformCheckpoint nextCheckpoint; private TransformCheckpoint nextCheckpoint;
private TransformCheckpoint sourceCheckpoint; private TransformCheckpoint sourceCheckpoint;
TransformCheckpointingInfoBuilder() { TransformCheckpointingInfoBuilder() {}
}
TransformCheckpointingInfo build() { TransformCheckpointingInfo build() {
if (lastCheckpoint == null) { if (lastCheckpoint == null) {
@ -70,11 +69,22 @@ public class DefaultCheckpointProvider implements CheckpointProvider {
long nextCheckpointNumber = nextCheckpoint.getCheckpoint() > 0 ? nextCheckpoint.getCheckpoint() : 0; long nextCheckpointNumber = nextCheckpoint.getCheckpoint() > 0 ? nextCheckpoint.getCheckpoint() : 0;
return new TransformCheckpointingInfo( return new TransformCheckpointingInfo(
new TransformCheckpointStats(lastCheckpointNumber, null, null, new TransformCheckpointStats(
lastCheckpoint.getTimestamp(), lastCheckpoint.getTimeUpperBound()), lastCheckpointNumber,
new TransformCheckpointStats(nextCheckpointNumber, nextCheckpointPosition, null,
nextCheckpointProgress, nextCheckpoint.getTimestamp(), nextCheckpoint.getTimeUpperBound()), null,
TransformCheckpoint.getBehind(lastCheckpoint, sourceCheckpoint)); lastCheckpoint.getTimestamp(),
lastCheckpoint.getTimeUpperBound()
),
new TransformCheckpointStats(
nextCheckpointNumber,
nextCheckpointPosition,
nextCheckpointProgress,
nextCheckpoint.getTimestamp(),
nextCheckpoint.getTimeUpperBound()
),
TransformCheckpoint.getBehind(lastCheckpoint, sourceCheckpoint)
);
} }
public TransformCheckpointingInfoBuilder setLastCheckpoint(TransformCheckpoint lastCheckpoint) { public TransformCheckpointingInfoBuilder setLastCheckpoint(TransformCheckpoint lastCheckpoint) {
@ -110,10 +120,12 @@ public class DefaultCheckpointProvider implements CheckpointProvider {
protected final TransformAuditor transformAuditor; protected final TransformAuditor transformAuditor;
protected final TransformConfig transformConfig; protected final TransformConfig transformConfig;
public DefaultCheckpointProvider(final Client client, public DefaultCheckpointProvider(
final Client client,
final TransformConfigManager transformConfigManager, final TransformConfigManager transformConfigManager,
final TransformAuditor transformAuditor, final TransformAuditor transformAuditor,
final TransformConfig transformConfig) { final TransformConfig transformConfig
) {
this.client = client; this.client = client;
this.transformConfigManager = transformConfigManager; this.transformConfigManager = transformConfigManager;
this.transformAuditor = transformAuditor; this.transformAuditor = transformAuditor;
@ -126,14 +138,17 @@ public class DefaultCheckpointProvider implements CheckpointProvider {
} }
@Override @Override
public void createNextCheckpoint(final TransformCheckpoint lastCheckpoint, public void createNextCheckpoint(final TransformCheckpoint lastCheckpoint, final ActionListener<TransformCheckpoint> listener) {
final ActionListener<TransformCheckpoint> listener) {
final long timestamp = System.currentTimeMillis(); final long timestamp = System.currentTimeMillis();
final long checkpoint = lastCheckpoint != null ? lastCheckpoint.getCheckpoint() + 1 : 1; final long checkpoint = TransformCheckpoint.isNullOrEmpty(lastCheckpoint) ? 1 : lastCheckpoint.getCheckpoint() + 1;
getIndexCheckpoints(ActionListener.wrap(checkpointsByIndex -> { getIndexCheckpoints(ActionListener.wrap(checkpointsByIndex -> {
reportSourceIndexChanges(lastCheckpoint != null ? lastCheckpoint.getIndicesCheckpoints().keySet() : Collections.emptySet(), reportSourceIndexChanges(
checkpointsByIndex.keySet()); TransformCheckpoint.isNullOrEmpty(lastCheckpoint)
? Collections.emptySet()
: lastCheckpoint.getIndicesCheckpoints().keySet(),
checkpointsByIndex.keySet()
);
listener.onResponse(new TransformCheckpoint(transformConfig.getId(), timestamp, checkpoint, checkpointsByIndex, 0L)); listener.onResponse(new TransformCheckpoint(transformConfig.getId(), timestamp, checkpoint, checkpointsByIndex, 0L));
}, listener::onFailure)); }, listener::onFailure));
@ -146,34 +161,41 @@ public class DefaultCheckpointProvider implements CheckpointProvider {
.features(new GetIndexRequest.Feature[0]) .features(new GetIndexRequest.Feature[0])
.indicesOptions(IndicesOptions.LENIENT_EXPAND_OPEN); .indicesOptions(IndicesOptions.LENIENT_EXPAND_OPEN);
ClientHelper.executeWithHeadersAsync(transformConfig.getHeaders(), ClientHelper.TRANSFORM_ORIGIN, client, GetIndexAction.INSTANCE, ClientHelper
getIndexRequest, ActionListener.wrap(getIndexResponse -> { .executeWithHeadersAsync(
transformConfig.getHeaders(),
ClientHelper.TRANSFORM_ORIGIN,
client,
GetIndexAction.INSTANCE,
getIndexRequest,
ActionListener.wrap(getIndexResponse -> {
Set<String> userIndices = getIndexResponse.getIndices() != null Set<String> userIndices = getIndexResponse.getIndices() != null
? new HashSet<>(Arrays.asList(getIndexResponse.getIndices())) ? new HashSet<>(Arrays.asList(getIndexResponse.getIndices()))
: Collections.emptySet(); : Collections.emptySet();
// 2nd get stats request // 2nd get stats request
ClientHelper.executeAsyncWithOrigin(client, ClientHelper
.executeAsyncWithOrigin(
client,
ClientHelper.TRANSFORM_ORIGIN, ClientHelper.TRANSFORM_ORIGIN,
IndicesStatsAction.INSTANCE, IndicesStatsAction.INSTANCE,
new IndicesStatsRequest() new IndicesStatsRequest()
.indices(transformConfig.getSource().getIndex()) .indices(transformConfig.getSource().getIndex())
.clear() .clear()
.indicesOptions(IndicesOptions.LENIENT_EXPAND_OPEN), .indicesOptions(IndicesOptions.LENIENT_EXPAND_OPEN),
ActionListener.wrap( ActionListener.wrap(response -> {
response -> {
if (response.getFailedShards() != 0) { if (response.getFailedShards() != 0) {
listener.onFailure( listener
new CheckpointException("Source has [" + response.getFailedShards() + "] failed shards")); .onFailure(
new CheckpointException("Source has [" + response.getFailedShards() + "] failed shards")
);
return; return;
} }
listener.onResponse(extractIndexCheckPoints(response.getShards(), userIndices)); listener.onResponse(extractIndexCheckPoints(response.getShards(), userIndices));
}, }, e -> listener.onFailure(new CheckpointException("Failed to create checkpoint", e)))
e-> listener.onFailure(new CheckpointException("Failed to create checkpoint", e)) );
)); }, e -> listener.onFailure(new CheckpointException("Failed to create checkpoint", e)))
}, );
e -> listener.onFailure(new CheckpointException("Failed to create checkpoint", e))
));
} }
static Map<String, long[]> extractIndexCheckPoints(ShardStats[] shards, Set<String> userIndices) { static Map<String, long[]> extractIndexCheckPoints(ShardStats[] shards, Set<String> userIndices) {
@ -220,23 +242,29 @@ public class DefaultCheckpointProvider implements CheckpointProvider {
// create the final structure // create the final structure
Map<String, long[]> checkpointsByIndexReduced = new TreeMap<>(); Map<String, long[]> checkpointsByIndexReduced = new TreeMap<>();
checkpointsByIndex.forEach((indexName, checkpoints) -> { checkpointsByIndex
.forEach(
(indexName, checkpoints) -> {
checkpointsByIndexReduced.put(indexName, checkpoints.values().stream().mapToLong(l -> l).toArray()); checkpointsByIndexReduced.put(indexName, checkpoints.values().stream().mapToLong(l -> l).toArray());
}); }
);
return checkpointsByIndexReduced; return checkpointsByIndexReduced;
} }
@Override @Override
public void getCheckpointingInfo(TransformCheckpoint lastCheckpoint, public void getCheckpointingInfo(
TransformCheckpoint lastCheckpoint,
TransformCheckpoint nextCheckpoint, TransformCheckpoint nextCheckpoint,
TransformIndexerPosition nextCheckpointPosition, TransformIndexerPosition nextCheckpointPosition,
TransformProgress nextCheckpointProgress, TransformProgress nextCheckpointProgress,
ActionListener<TransformCheckpointingInfo> listener) { ActionListener<TransformCheckpointingInfo> listener
) {
TransformCheckpointingInfoBuilder checkpointingInfoBuilder = new TransformCheckpointingInfoBuilder(); TransformCheckpointingInfoBuilder checkpointingInfoBuilder = new TransformCheckpointingInfoBuilder();
checkpointingInfoBuilder.setLastCheckpoint(lastCheckpoint) checkpointingInfoBuilder
.setLastCheckpoint(lastCheckpoint)
.setNextCheckpoint(nextCheckpoint) .setNextCheckpoint(nextCheckpoint)
.setNextCheckpointPosition(nextCheckpointPosition) .setNextCheckpointPosition(nextCheckpointPosition)
.setNextCheckpointProgress(nextCheckpointProgress); .setNextCheckpointProgress(nextCheckpointProgress);
@ -244,16 +272,19 @@ public class DefaultCheckpointProvider implements CheckpointProvider {
long timestamp = System.currentTimeMillis(); long timestamp = System.currentTimeMillis();
getIndexCheckpoints(ActionListener.wrap(checkpointsByIndex -> { getIndexCheckpoints(ActionListener.wrap(checkpointsByIndex -> {
checkpointingInfoBuilder.setSourceCheckpoint( checkpointingInfoBuilder
new TransformCheckpoint(transformConfig.getId(), timestamp, -1L, checkpointsByIndex, 0L)); .setSourceCheckpoint(new TransformCheckpoint(transformConfig.getId(), timestamp, -1L, checkpointsByIndex, 0L));
listener.onResponse(checkpointingInfoBuilder.build()); listener.onResponse(checkpointingInfoBuilder.build());
}, listener::onFailure)); }, listener::onFailure));
} }
@Override @Override
public void getCheckpointingInfo(long lastCheckpointNumber, TransformIndexerPosition nextCheckpointPosition, public void getCheckpointingInfo(
long lastCheckpointNumber,
TransformIndexerPosition nextCheckpointPosition,
TransformProgress nextCheckpointProgress, TransformProgress nextCheckpointProgress,
ActionListener<TransformCheckpointingInfo> listener) { ActionListener<TransformCheckpointingInfo> listener
) {
TransformCheckpointingInfoBuilder checkpointingInfoBuilder = new TransformCheckpointingInfoBuilder(); TransformCheckpointingInfoBuilder checkpointingInfoBuilder = new TransformCheckpointingInfoBuilder();
@ -262,47 +293,56 @@ public class DefaultCheckpointProvider implements CheckpointProvider {
long timestamp = System.currentTimeMillis(); long timestamp = System.currentTimeMillis();
// <3> got the source checkpoint, notify the user // <3> got the source checkpoint, notify the user
ActionListener<Map<String, long[]>> checkpointsByIndexListener = ActionListener.wrap( ActionListener<Map<String, long[]>> checkpointsByIndexListener = ActionListener.wrap(checkpointsByIndex -> {
checkpointsByIndex -> { checkpointingInfoBuilder
checkpointingInfoBuilder.setSourceCheckpoint( .setSourceCheckpoint(new TransformCheckpoint(transformConfig.getId(), timestamp, -1L, checkpointsByIndex, 0L));
new TransformCheckpoint(transformConfig.getId(), timestamp, -1L, checkpointsByIndex, 0L));
listener.onResponse(checkpointingInfoBuilder.build()); listener.onResponse(checkpointingInfoBuilder.build());
}, }, e -> {
e -> { logger
logger.debug((Supplier<?>) () -> new ParameterizedMessage( .debug(
"Failed to retrieve source checkpoint for transform [{}]", transformConfig.getId()), e); (Supplier<?>) () -> new ParameterizedMessage(
listener.onFailure(new CheckpointException("Failure during source checkpoint info retrieval", e)); "[{}] failed to retrieve source checkpoint for transform",
} transformConfig.getId()
),
e
); );
listener.onFailure(new CheckpointException("Failure during source checkpoint info retrieval", e));
});
// <2> got the next checkpoint, get the source checkpoint // <2> got the next checkpoint, get the source checkpoint
ActionListener<TransformCheckpoint> nextCheckpointListener = ActionListener.wrap( ActionListener<TransformCheckpoint> nextCheckpointListener = ActionListener.wrap(nextCheckpointObj -> {
nextCheckpointObj -> {
checkpointingInfoBuilder.setNextCheckpoint(nextCheckpointObj); checkpointingInfoBuilder.setNextCheckpoint(nextCheckpointObj);
getIndexCheckpoints(checkpointsByIndexListener); getIndexCheckpoints(checkpointsByIndexListener);
}, }, e -> {
e -> { logger
logger.debug((Supplier<?>) () -> new ParameterizedMessage( .debug(
"Failed to retrieve next checkpoint [{}] for transform [{}]", lastCheckpointNumber + 1, (Supplier<?>) () -> new ParameterizedMessage(
transformConfig.getId()), e); "[{}] failed to retrieve next checkpoint [{}]",
listener.onFailure(new CheckpointException("Failure during next checkpoint info retrieval", e)); transformConfig.getId(),
} lastCheckpointNumber + 1
),
e
); );
listener.onFailure(new CheckpointException("Failure during next checkpoint info retrieval", e));
});
// <1> got last checkpoint, get the next checkpoint // <1> got last checkpoint, get the next checkpoint
ActionListener<TransformCheckpoint> lastCheckpointListener = ActionListener.wrap( ActionListener<TransformCheckpoint> lastCheckpointListener = ActionListener.wrap(lastCheckpointObj -> {
lastCheckpointObj -> {
checkpointingInfoBuilder.lastCheckpoint = lastCheckpointObj; checkpointingInfoBuilder.lastCheckpoint = lastCheckpointObj;
transformConfigManager.getTransformCheckpoint(transformConfig.getId(), lastCheckpointNumber + 1, transformConfigManager.getTransformCheckpoint(transformConfig.getId(), lastCheckpointNumber + 1, nextCheckpointListener);
nextCheckpointListener); }, e -> {
}, logger
e -> { .debug(
logger.debug((Supplier<?>) () -> new ParameterizedMessage( (Supplier<?>) () -> new ParameterizedMessage(
"Failed to retrieve last checkpoint [{}] for transform [{}]", lastCheckpointNumber, "[{}] failed to retrieve last checkpoint [{}]",
transformConfig.getId()), e); transformConfig.getId(),
listener.onFailure(new CheckpointException("Failure during last checkpoint info retrieval", e)); lastCheckpointNumber
} ),
e
); );
listener.onFailure(new CheckpointException("Failure during last checkpoint info retrieval", e));
});
if (lastCheckpointNumber != 0) { if (lastCheckpointNumber != 0) {
transformConfigManager.getTransformCheckpoint(transformConfig.getId(), lastCheckpointNumber, lastCheckpointListener); transformConfigManager.getTransformCheckpoint(transformConfig.getId(), lastCheckpointNumber, lastCheckpointListener);
@ -321,20 +361,25 @@ public class DefaultCheckpointProvider implements CheckpointProvider {
// spam protection: only warn the first time // spam protection: only warn the first time
if (newSourceIndexes.isEmpty() && lastSourceIndexes.isEmpty() == false) { if (newSourceIndexes.isEmpty() && lastSourceIndexes.isEmpty() == false) {
String message = "Source did not resolve to any open indexes"; String message = "Source did not resolve to any open indexes";
logger.warn("{} for transform [{}]", message, transformConfig.getId()); logger.warn("[{}] {}", transformConfig.getId(), message);
transformAuditor.warning(transformConfig.getId(), message); transformAuditor.warning(transformConfig.getId(), message);
} else { } else {
Set<String> removedIndexes = Sets.difference(lastSourceIndexes, newSourceIndexes); Set<String> removedIndexes = Sets.difference(lastSourceIndexes, newSourceIndexes);
Set<String> addedIndexes = Sets.difference(newSourceIndexes, lastSourceIndexes); Set<String> addedIndexes = Sets.difference(newSourceIndexes, lastSourceIndexes);
if (removedIndexes.size() + addedIndexes.size() > AUDIT_CONCRETED_SOURCE_INDEX_CHANGES) { if (removedIndexes.size() + addedIndexes.size() > AUDIT_CONCRETED_SOURCE_INDEX_CHANGES) {
String message = "Source index resolve found more than " + AUDIT_CONCRETED_SOURCE_INDEX_CHANGES + " changes, [" String message = "Source index resolve found more than "
+ removedIndexes.size() + "] removed indexes, [" + addedIndexes.size() + "] new indexes"; + AUDIT_CONCRETED_SOURCE_INDEX_CHANGES
logger.debug("{} for transform [{}]", message, transformConfig.getId()); + " changes, ["
+ removedIndexes.size()
+ "] removed indexes, ["
+ addedIndexes.size()
+ "] new indexes";
logger.debug("[{}] {}", transformConfig.getId(), message);
transformAuditor.info(transformConfig.getId(), message); transformAuditor.info(transformConfig.getId(), message);
} else if (removedIndexes.size() + addedIndexes.size() > 0) { } else if (removedIndexes.size() + addedIndexes.size() > 0) {
String message = "Source index resolve found changes, removedIndexes: " + removedIndexes + ", new indexes: " + addedIndexes; String message = "Source index resolve found changes, removedIndexes: " + removedIndexes + ", new indexes: " + addedIndexes;
logger.debug("{} for transform [{}]", message, transformConfig.getId()); logger.debug("[{}] {}", transformConfig.getId(), message);
transformAuditor.info(transformConfig.getId(), message); transformAuditor.info(transformConfig.getId(), message);
} }
} }

View File

@ -18,9 +18,9 @@ import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.RangeQueryBuilder; import org.elasticsearch.index.query.RangeQueryBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder; import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.xpack.core.ClientHelper; import org.elasticsearch.xpack.core.ClientHelper;
import org.elasticsearch.xpack.core.transform.transforms.TimeSyncConfig;
import org.elasticsearch.xpack.core.transform.transforms.TransformCheckpoint; import org.elasticsearch.xpack.core.transform.transforms.TransformCheckpoint;
import org.elasticsearch.xpack.core.transform.transforms.TransformConfig; import org.elasticsearch.xpack.core.transform.transforms.TransformConfig;
import org.elasticsearch.xpack.core.transform.transforms.TimeSyncConfig;
import org.elasticsearch.xpack.transform.notifications.TransformAuditor; import org.elasticsearch.xpack.transform.notifications.TransformAuditor;
import org.elasticsearch.xpack.transform.persistence.TransformConfigManager; import org.elasticsearch.xpack.transform.persistence.TransformConfigManager;
@ -30,17 +30,18 @@ public class TimeBasedCheckpointProvider extends DefaultCheckpointProvider {
private final TimeSyncConfig timeSyncConfig; private final TimeSyncConfig timeSyncConfig;
TimeBasedCheckpointProvider(final Client client, TimeBasedCheckpointProvider(
final Client client,
final TransformConfigManager transformConfigManager, final TransformConfigManager transformConfigManager,
final TransformAuditor transformAuditor, final TransformAuditor transformAuditor,
final TransformConfig transformConfig) { final TransformConfig transformConfig
) {
super(client, transformConfigManager, transformAuditor, transformConfig); super(client, transformConfigManager, transformAuditor, transformConfig);
timeSyncConfig = (TimeSyncConfig) transformConfig.getSyncConfig(); timeSyncConfig = (TimeSyncConfig) transformConfig.getSyncConfig();
} }
@Override @Override
public void sourceHasChanged(TransformCheckpoint lastCheckpoint, public void sourceHasChanged(TransformCheckpoint lastCheckpoint, ActionListener<Boolean> listener) {
ActionListener<Boolean> listener) {
final long timestamp = getTime(); final long timestamp = getTime();
@ -53,36 +54,51 @@ public class TimeBasedCheckpointProvider extends DefaultCheckpointProvider {
.trackTotalHitsUpTo(1); .trackTotalHitsUpTo(1);
QueryBuilder queryBuilder = transformConfig.getSource().getQueryConfig().getQuery(); QueryBuilder queryBuilder = transformConfig.getSource().getQueryConfig().getQuery();
BoolQueryBuilder filteredQuery = new BoolQueryBuilder(). BoolQueryBuilder filteredQuery = new BoolQueryBuilder()
filter(queryBuilder). .filter(queryBuilder)
filter(new RangeQueryBuilder(timeSyncConfig.getField()). .filter(
gte(lastCheckpoint.getTimeUpperBound()). new RangeQueryBuilder(timeSyncConfig.getField())
lt(timestamp - timeSyncConfig.getDelay().millis()).format("epoch_millis")); .gte(lastCheckpoint.getTimeUpperBound())
.lt(timestamp - timeSyncConfig.getDelay().millis())
.format("epoch_millis")
);
sourceBuilder.query(filteredQuery); sourceBuilder.query(filteredQuery);
searchRequest.source(sourceBuilder); searchRequest.source(sourceBuilder);
logger.trace("query for changes based on time: {}", sourceBuilder); logger.trace("query for changes based on time: {}", sourceBuilder);
ClientHelper.executeWithHeadersAsync(transformConfig.getHeaders(), ClientHelper.TRANSFORM_ORIGIN, client, SearchAction.INSTANCE, ClientHelper
searchRequest, ActionListener.wrap(r -> { .executeWithHeadersAsync(
listener.onResponse(r.getHits().getTotalHits().value > 0L); transformConfig.getHeaders(),
}, listener::onFailure)); ClientHelper.TRANSFORM_ORIGIN,
client,
SearchAction.INSTANCE,
searchRequest,
ActionListener.wrap(r -> { listener.onResponse(r.getHits().getTotalHits().value > 0L); }, listener::onFailure)
);
} }
@Override @Override
public void createNextCheckpoint(final TransformCheckpoint lastCheckpoint, public void createNextCheckpoint(final TransformCheckpoint lastCheckpoint, final ActionListener<TransformCheckpoint> listener) {
final ActionListener<TransformCheckpoint> listener) {
final long timestamp = getTime(); final long timestamp = getTime();
final long checkpoint = lastCheckpoint != null ? lastCheckpoint.getCheckpoint() + 1 : 1; final long checkpoint = TransformCheckpoint.isNullOrEmpty(lastCheckpoint) ? 1 : lastCheckpoint.getCheckpoint() + 1;
// for time based synchronization // for time based synchronization
long timeUpperBound = timestamp - timeSyncConfig.getDelay().millis(); long timeUpperBound = timestamp - timeSyncConfig.getDelay().millis();
getIndexCheckpoints(ActionListener.wrap(checkpointsByIndex -> { getIndexCheckpoints(
listener.onResponse( ActionListener
new TransformCheckpoint(transformConfig.getId(), timestamp, checkpoint, checkpointsByIndex, timeUpperBound)); .wrap(
}, listener::onFailure)); checkpointsByIndex -> {
listener
.onResponse(
new TransformCheckpoint(transformConfig.getId(), timestamp, checkpoint, checkpointsByIndex, timeUpperBound)
);
},
listener::onFailure
)
);
} }
// for the purpose of testing // for the purpose of testing

View File

@ -27,7 +27,7 @@ public class SeqNoPrimaryTermAndIndex {
return new SeqNoPrimaryTermAndIndex(response.getSeqNo(), response.getPrimaryTerm(), response.getIndex()); return new SeqNoPrimaryTermAndIndex(response.getSeqNo(), response.getPrimaryTerm(), response.getIndex());
} }
SeqNoPrimaryTermAndIndex(long seqNo, long primaryTerm, String index) { public SeqNoPrimaryTermAndIndex(long seqNo, long primaryTerm, String index) {
this.seqNo = seqNo; this.seqNo = seqNo;
this.primaryTerm = primaryTerm; this.primaryTerm = primaryTerm;
this.index = index; this.index = index;
@ -61,9 +61,7 @@ public class SeqNoPrimaryTermAndIndex {
} }
SeqNoPrimaryTermAndIndex other = (SeqNoPrimaryTermAndIndex) obj; SeqNoPrimaryTermAndIndex other = (SeqNoPrimaryTermAndIndex) obj;
return Objects.equals(seqNo, other.seqNo) return Objects.equals(seqNo, other.seqNo) && Objects.equals(primaryTerm, other.primaryTerm) && Objects.equals(index, other.index);
&& Objects.equals(primaryTerm, other.primaryTerm)
&& Objects.equals(index, other.index);
} }
@Override @Override

View File

@ -10,7 +10,6 @@ import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.message.ParameterizedMessage; import org.apache.logging.log4j.message.ParameterizedMessage;
import org.elasticsearch.ElasticsearchException; import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.ResourceNotFoundException;
import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.bulk.BulkAction; import org.elasticsearch.action.bulk.BulkAction;
import org.elasticsearch.action.bulk.BulkItemResponse; import org.elasticsearch.action.bulk.BulkItemResponse;
@ -20,16 +19,14 @@ import org.elasticsearch.action.search.SearchAction;
import org.elasticsearch.action.search.SearchRequest; import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.Client; import org.elasticsearch.client.Client;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.logging.LoggerMessageFormat; import org.elasticsearch.common.logging.LoggerMessageFormat;
import org.elasticsearch.index.IndexNotFoundException;
import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.xpack.core.ClientHelper; import org.elasticsearch.xpack.core.ClientHelper;
import org.elasticsearch.xpack.core.indexing.IndexerState; import org.elasticsearch.xpack.core.indexing.IndexerState;
import org.elasticsearch.xpack.core.transform.TransformMessages;
import org.elasticsearch.xpack.core.transform.transforms.TransformIndexerPosition;
import org.elasticsearch.xpack.core.transform.transforms.TransformIndexerStats;
import org.elasticsearch.xpack.core.transform.transforms.TransformCheckpoint; import org.elasticsearch.xpack.core.transform.transforms.TransformCheckpoint;
import org.elasticsearch.xpack.core.transform.transforms.TransformConfig; import org.elasticsearch.xpack.core.transform.transforms.TransformConfig;
import org.elasticsearch.xpack.core.transform.transforms.TransformIndexerPosition;
import org.elasticsearch.xpack.core.transform.transforms.TransformIndexerStats;
import org.elasticsearch.xpack.core.transform.transforms.TransformProgress; import org.elasticsearch.xpack.core.transform.transforms.TransformProgress;
import org.elasticsearch.xpack.core.transform.transforms.TransformState; import org.elasticsearch.xpack.core.transform.transforms.TransformState;
import org.elasticsearch.xpack.core.transform.transforms.TransformStoredDoc; import org.elasticsearch.xpack.core.transform.transforms.TransformStoredDoc;
@ -37,38 +34,28 @@ import org.elasticsearch.xpack.core.transform.transforms.TransformTaskState;
import org.elasticsearch.xpack.core.transform.utils.ExceptionsHelper; import org.elasticsearch.xpack.core.transform.utils.ExceptionsHelper;
import org.elasticsearch.xpack.transform.checkpoint.CheckpointProvider; import org.elasticsearch.xpack.transform.checkpoint.CheckpointProvider;
import org.elasticsearch.xpack.transform.notifications.TransformAuditor; import org.elasticsearch.xpack.transform.notifications.TransformAuditor;
import org.elasticsearch.xpack.transform.persistence.TransformConfigManager;
import org.elasticsearch.xpack.transform.persistence.SeqNoPrimaryTermAndIndex; import org.elasticsearch.xpack.transform.persistence.SeqNoPrimaryTermAndIndex;
import org.elasticsearch.xpack.transform.transforms.pivot.AggregationResultUtils; import org.elasticsearch.xpack.transform.persistence.TransformConfigManager;
import java.time.Instant;
import java.util.Map; import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
class ClientTransformIndexer extends TransformIndexer { class ClientTransformIndexer extends TransformIndexer {
private static final Logger logger = LogManager.getLogger(ClientTransformIndexer.class); private static final Logger logger = LogManager.getLogger(ClientTransformIndexer.class);
private long logEvery = 1;
private long logCount = 0;
private final Client client; private final Client client;
private final TransformConfigManager transformsConfigManager;
private final CheckpointProvider checkpointProvider;
private final TransformTask transformTask;
private final AtomicInteger failureCount;
private volatile boolean auditBulkFailures = true;
// Indicates that the source has changed for the current run
private volatile boolean hasSourceChanged = true;
// Keeps track of the last exception that was written to our audit, keeps us from spamming the audit index
private volatile String lastAuditedExceptionMessage = null;
private final AtomicBoolean oldStatsCleanedUp = new AtomicBoolean(false); private final AtomicBoolean oldStatsCleanedUp = new AtomicBoolean(false);
private volatile boolean shouldStopAtCheckpoint = false;
private volatile Instant changesLastDetectedAt;
ClientTransformIndexer(TransformConfigManager transformsConfigManager, private final AtomicReference<SeqNoPrimaryTermAndIndex> seqNoPrimaryTermAndIndex;
ClientTransformIndexer(
Executor executor,
TransformConfigManager transformsConfigManager,
CheckpointProvider checkpointProvider, CheckpointProvider checkpointProvider,
TransformProgressGatherer progressGatherer,
AtomicReference<IndexerState> initialState, AtomicReference<IndexerState> initialState,
TransformIndexerPosition initialPosition, TransformIndexerPosition initialPosition,
Client client, Client client,
@ -79,12 +66,16 @@ class ClientTransformIndexer extends TransformIndexer {
TransformProgress transformProgress, TransformProgress transformProgress,
TransformCheckpoint lastCheckpoint, TransformCheckpoint lastCheckpoint,
TransformCheckpoint nextCheckpoint, TransformCheckpoint nextCheckpoint,
TransformTask parentTask, SeqNoPrimaryTermAndIndex seqNoPrimaryTermAndIndex,
boolean shouldStopAtCheckpoint) { TransformContext context,
super(ExceptionsHelper.requireNonNull(parentTask, "parentTask") boolean shouldStopAtCheckpoint
.getThreadPool() ) {
.executor(ThreadPool.Names.GENERIC), super(
ExceptionsHelper.requireNonNull(auditor, "auditor"), ExceptionsHelper.requireNonNull(executor, "executor"),
transformsConfigManager,
checkpointProvider,
progressGatherer,
auditor,
transformConfig, transformConfig,
fieldMappings, fieldMappings,
ExceptionsHelper.requireNonNull(initialState, "initialState"), ExceptionsHelper.requireNonNull(initialState, "initialState"),
@ -92,210 +83,72 @@ class ClientTransformIndexer extends TransformIndexer {
initialStats == null ? new TransformIndexerStats() : initialStats, initialStats == null ? new TransformIndexerStats() : initialStats,
transformProgress, transformProgress,
lastCheckpoint, lastCheckpoint,
nextCheckpoint); nextCheckpoint,
this.transformsConfigManager = ExceptionsHelper.requireNonNull(transformsConfigManager, "transformsConfigManager"); context
this.checkpointProvider = ExceptionsHelper.requireNonNull(checkpointProvider, "checkpointProvider"); );
this.client = ExceptionsHelper.requireNonNull(client, "client"); this.client = ExceptionsHelper.requireNonNull(client, "client");
this.transformTask = parentTask; this.seqNoPrimaryTermAndIndex = new AtomicReference<>(seqNoPrimaryTermAndIndex);
this.failureCount = new AtomicInteger(0);
this.shouldStopAtCheckpoint = shouldStopAtCheckpoint;
}
boolean shouldStopAtCheckpoint() { // TODO: move into context constructor
return shouldStopAtCheckpoint; context.setShouldStopAtCheckpoint(shouldStopAtCheckpoint);
}
void setShouldStopAtCheckpoint(boolean shouldStopAtCheckpoint) {
this.shouldStopAtCheckpoint = shouldStopAtCheckpoint;
} }
void persistShouldStopAtCheckpoint(boolean shouldStopAtCheckpoint, ActionListener<Void> shouldStopAtCheckpointListener) { void persistShouldStopAtCheckpoint(boolean shouldStopAtCheckpoint, ActionListener<Void> shouldStopAtCheckpointListener) {
if (this.shouldStopAtCheckpoint == shouldStopAtCheckpoint || if (context.shouldStopAtCheckpoint() == shouldStopAtCheckpoint
getState() == IndexerState.STOPPED || || getState() == IndexerState.STOPPED
getState() == IndexerState.STOPPING) { || getState() == IndexerState.STOPPING) {
shouldStopAtCheckpointListener.onResponse(null); shouldStopAtCheckpointListener.onResponse(null);
return; return;
} }
TransformState state = new TransformState( TransformState state = new TransformState(
transformTask.getTaskState(), context.getTaskState(),
getState(), getState(),
getPosition(), getPosition(),
transformTask.getCheckpoint(), context.getCheckpoint(),
transformTask.getStateReason(), context.getStateReason(),
getProgress(), getProgress(),
null, // Node attributes null, // Node attributes
shouldStopAtCheckpoint); shouldStopAtCheckpoint
doSaveState(state, );
ActionListener.wrap( doSaveState(state, ActionListener.wrap(r -> {
r -> {
// We only want to update this internal value if it is persisted as such // We only want to update this internal value if it is persisted as such
this.shouldStopAtCheckpoint = shouldStopAtCheckpoint; context.setShouldStopAtCheckpoint(shouldStopAtCheckpoint);
logger.debug("[{}] successfully persisted should_stop_at_checkpoint update [{}]", logger.debug("[{}] successfully persisted should_stop_at_checkpoint update [{}]", getJobId(), shouldStopAtCheckpoint);
getJobId(),
shouldStopAtCheckpoint);
shouldStopAtCheckpointListener.onResponse(null); shouldStopAtCheckpointListener.onResponse(null);
}, }, statsExc -> {
statsExc -> { logger.warn("[{}] failed to persist should_stop_at_checkpoint update [{}]", getJobId(), shouldStopAtCheckpoint);
logger.warn("[{}] failed to persist should_stop_at_checkpoint update [{}]",
getJobId(),
shouldStopAtCheckpoint);
shouldStopAtCheckpointListener.onFailure(statsExc); shouldStopAtCheckpointListener.onFailure(statsExc);
} }));
));
}
@Override
protected void onStart(long now, ActionListener<Boolean> listener) {
if (transformTask.getTaskState() == TransformTaskState.FAILED) {
logger.debug("[{}] attempted to start while failed.", getJobId());
listener.onFailure(new ElasticsearchException("Attempted to start a failed transform [{}].", getJobId()));
return;
}
// On each run, we need to get the total number of docs and reset the count of processed docs
// Since multiple checkpoints can be executed in the task while it is running on the same node, we need to gather
// the progress here, and not in the executor.
ActionListener<Void> updateConfigListener = ActionListener.wrap(
updateConfigResponse -> {
if (initialRun()) {
createCheckpoint(ActionListener.wrap(cp -> {
nextCheckpoint = cp;
// If nextCheckpoint > 1, this means that we are now on the checkpoint AFTER the batch checkpoint
// Consequently, the idea of percent complete no longer makes sense.
if (nextCheckpoint.getCheckpoint() > 1) {
progress = new TransformProgress(null, 0L, 0L);
super.onStart(now, listener);
return;
}
TransformProgressGatherer.getInitialProgress(this.client, buildFilterQuery(), getConfig(), ActionListener.wrap(
newProgress -> {
logger.trace("[{}] reset the progress from [{}] to [{}].", getJobId(), progress, newProgress);
progress = newProgress;
super.onStart(now, listener);
},
failure -> {
progress = null;
logger.warn(new ParameterizedMessage("[{}] unable to load progress information for task.",
getJobId()),
failure);
super.onStart(now, listener);
}
));
}, listener::onFailure));
} else {
super.onStart(now, listener);
}
},
listener::onFailure
);
// If we are continuous, we will want to verify we have the latest stored configuration
ActionListener<Void> changedSourceListener = ActionListener.wrap(
r -> {
if (isContinuous()) {
transformsConfigManager.getTransformConfiguration(getJobId(), ActionListener.wrap(
config -> {
transformConfig = config;
logger.debug("[{}] successfully refreshed transform config from index.", getJobId());
updateConfigListener.onResponse(null);
},
failure -> {
String msg = TransformMessages.getMessage(
TransformMessages.FAILED_TO_RELOAD_TRANSFORM_CONFIGURATION,
getJobId());
logger.error(msg, failure);
// If the transform config index or the transform config is gone, something serious occurred
// We are in an unknown state and should fail out
if (failure instanceof ResourceNotFoundException) {
updateConfigListener.onFailure(new TransformConfigReloadingException(msg, failure));
} else {
auditor.warning(getJobId(), msg);
updateConfigListener.onResponse(null);
}
}
));
} else {
updateConfigListener.onResponse(null);
}
},
listener::onFailure
);
// If we are not on the initial batch checkpoint and its the first pass of whatever continuous checkpoint we are on,
// we should verify if there are local changes based on the sync config. If not, do not proceed further and exit.
if (transformTask.getCheckpoint() > 0 && initialRun()) {
sourceHasChanged(ActionListener.wrap(
hasChanged -> {
hasSourceChanged = hasChanged;
if (hasChanged) {
changesLastDetectedAt = Instant.now();
logger.debug("[{}] source has changed, triggering new indexer run.", getJobId());
changedSourceListener.onResponse(null);
} else {
logger.trace("[{}] source has not changed, finish indexer early.", getJobId());
// No changes, stop executing
listener.onResponse(false);
}
},
failure -> {
// If we failed determining if the source changed, it's safer to assume there were changes.
// We should allow the failure path to complete as normal
hasSourceChanged = true;
listener.onFailure(failure);
}
));
} else {
hasSourceChanged = true;
changedSourceListener.onResponse(null);
}
}
public CheckpointProvider getCheckpointProvider() {
return checkpointProvider;
}
Instant getChangesLastDetectedAt() {
return changesLastDetectedAt;
}
@Override
public synchronized boolean maybeTriggerAsyncJob(long now) {
if (transformTask.getTaskState() == TransformTaskState.FAILED) {
logger.debug("[{}] schedule was triggered for transform but task is failed. Ignoring trigger.", getJobId());
return false;
}
// ignore trigger if indexer is running, prevents log spam in A2P indexer
IndexerState indexerState = getState();
if (IndexerState.INDEXING.equals(indexerState) || IndexerState.STOPPING.equals(indexerState)) {
logger.debug("[{}] indexer for transform has state [{}]. Ignoring trigger.", getJobId(), indexerState);
return false;
}
return super.maybeTriggerAsyncJob(now);
} }
@Override @Override
protected void doNextSearch(SearchRequest request, ActionListener<SearchResponse> nextPhase) { protected void doNextSearch(SearchRequest request, ActionListener<SearchResponse> nextPhase) {
if (transformTask.getTaskState() == TransformTaskState.FAILED) { if (context.getTaskState() == TransformTaskState.FAILED) {
logger.debug("[{}] attempted to search while failed.", getJobId()); logger.debug("[{}] attempted to search while failed.", getJobId());
nextPhase.onFailure(new ElasticsearchException("Attempted to do a search request for failed transform [{}].", nextPhase.onFailure(new ElasticsearchException("Attempted to do a search request for failed transform [{}].", getJobId()));
getJobId()));
return; return;
} }
ClientHelper.executeWithHeadersAsync(transformConfig.getHeaders(), ClientHelper.TRANSFORM_ORIGIN, client, ClientHelper
SearchAction.INSTANCE, request, nextPhase); .executeWithHeadersAsync(
transformConfig.getHeaders(),
ClientHelper.TRANSFORM_ORIGIN,
client,
SearchAction.INSTANCE,
request,
nextPhase
);
} }
@Override @Override
protected void doNextBulk(BulkRequest request, ActionListener<BulkResponse> nextPhase) { protected void doNextBulk(BulkRequest request, ActionListener<BulkResponse> nextPhase) {
if (transformTask.getTaskState() == TransformTaskState.FAILED) { if (context.getTaskState() == TransformTaskState.FAILED) {
logger.debug("[{}] attempted to bulk index while failed.", getJobId()); logger.debug("[{}] attempted to bulk index while failed.", getJobId());
nextPhase.onFailure(new ElasticsearchException("Attempted to do a bulk index request for failed transform [{}].", nextPhase.onFailure(new ElasticsearchException("Attempted to do a bulk index request for failed transform [{}].", getJobId()));
getJobId()));
return; return;
} }
ClientHelper.executeWithHeadersAsync(transformConfig.getHeaders(), ClientHelper
.executeWithHeadersAsync(
transformConfig.getHeaders(),
ClientHelper.TRANSFORM_ORIGIN, ClientHelper.TRANSFORM_ORIGIN,
client, client,
BulkAction.INSTANCE, BulkAction.INSTANCE,
@ -310,28 +163,37 @@ class ClientTransformIndexer extends TransformIndexer {
// TODO gather information on irrecoverable failures and update isIrrecoverableFailure // TODO gather information on irrecoverable failures and update isIrrecoverableFailure
} }
if (auditBulkFailures) { if (auditBulkFailures) {
auditor.warning(getJobId(), String failureMessage = bulkResponse.buildFailureMessage();
"Experienced at least [" + logger.debug("[{}] Bulk index failure encountered: {}", getJobId(), failureMessage);
failureCount + auditor
"] bulk index failures. See the logs of the node running the transform for details. " + .warning(
bulkResponse.buildFailureMessage()); getJobId(),
"Experienced at least ["
+ failureCount
+ "] bulk index failures. See the logs of the node running the transform for details. "
+ failureMessage
);
auditBulkFailures = false; auditBulkFailures = false;
} }
// This calls AsyncTwoPhaseIndexer#finishWithIndexingFailure // This calls AsyncTwoPhaseIndexer#finishWithIndexingFailure
// It increments the indexing failure, and then calls the `onFailure` logic // It increments the indexing failure, and then calls the `onFailure` logic
nextPhase.onFailure( nextPhase
new BulkIndexingException("Bulk index experienced failures. " + .onFailure(
"See the logs of the node running the transform for details.")); new BulkIndexingException(
"Bulk index experienced failures. " + "See the logs of the node running the transform for details."
)
);
} else { } else {
auditBulkFailures = true; auditBulkFailures = true;
nextPhase.onResponse(bulkResponse); nextPhase.onResponse(bulkResponse);
} }
}, nextPhase::onFailure)); }, nextPhase::onFailure)
);
} }
@Override @Override
protected void doSaveState(IndexerState indexerState, TransformIndexerPosition position, Runnable next) { protected void doSaveState(IndexerState indexerState, TransformIndexerPosition position, Runnable next) {
if (transformTask.getTaskState() == TransformTaskState.FAILED) { if (context.getTaskState() == TransformTaskState.FAILED) {
logger.debug("[{}] attempted to save state and stats while failed.", getJobId()); logger.debug("[{}] attempted to save state and stats while failed.", getJobId());
// If we are failed, we should call next to allow failure handling to occur if necessary. // If we are failed, we should call next to allow failure handling to occur if necessary.
next.run(); next.run();
@ -343,7 +205,7 @@ class ClientTransformIndexer extends TransformIndexer {
return; return;
} }
boolean shouldStopAtCheckpoint = shouldStopAtCheckpoint(); boolean shouldStopAtCheckpoint = context.shouldStopAtCheckpoint();
// If we should stop at the next checkpoint, are STARTED, and with `initialRun()` we are in one of two states // If we should stop at the next checkpoint, are STARTED, and with `initialRun()` we are in one of two states
// 1. We have just called `onFinish` completing our request, but `shouldStopAtCheckpoint` was set to `true` before our check // 1. We have just called `onFinish` completing our request, but `shouldStopAtCheckpoint` was set to `true` before our check
@ -354,8 +216,7 @@ class ClientTransformIndexer extends TransformIndexer {
if (shouldStopAtCheckpoint && initialRun() && indexerState.equals(IndexerState.STARTED)) { if (shouldStopAtCheckpoint && initialRun() && indexerState.equals(IndexerState.STARTED)) {
indexerState = IndexerState.STOPPED; indexerState = IndexerState.STOPPED;
auditor.info(transformConfig.getId(), "Transform is no longer in the middle of a checkpoint, initiating stop."); auditor.info(transformConfig.getId(), "Transform is no longer in the middle of a checkpoint, initiating stop.");
logger.info("[{}] transform is no longer in the middle of a checkpoint, initiating stop.", logger.info("[{}] transform is no longer in the middle of a checkpoint, initiating stop.", transformConfig.getId());
transformConfig.getId());
} }
// This means that the indexer was triggered to discover changes, found none, and exited early. // This means that the indexer was triggered to discover changes, found none, and exited early.
@ -366,11 +227,9 @@ class ClientTransformIndexer extends TransformIndexer {
return; return;
} }
TransformTaskState taskState = transformTask.getTaskState(); TransformTaskState taskState = context.getTaskState();
if (indexerState.equals(IndexerState.STARTED) if (indexerState.equals(IndexerState.STARTED) && context.getCheckpoint() == 1 && this.isContinuous() == false) {
&& transformTask.getCheckpoint() == 1
&& this.isContinuous() == false) {
// set both to stopped so they are persisted as such // set both to stopped so they are persisted as such
indexerState = IndexerState.STOPPED; indexerState = IndexerState.STOPPED;
@ -397,266 +256,73 @@ class ClientTransformIndexer extends TransformIndexer {
taskState, taskState,
indexerState, indexerState,
position, position,
transformTask.getCheckpoint(), context.getCheckpoint(),
transformTask.getStateReason(), context.getStateReason(),
getProgress(), getProgress(),
null, null,
shouldStopAtCheckpoint); shouldStopAtCheckpoint
);
logger.debug("[{}] updating persistent state of transform to [{}].", transformConfig.getId(), state.toString()); logger.debug("[{}] updating persistent state of transform to [{}].", transformConfig.getId(), state.toString());
doSaveState(state, ActionListener.wrap( doSaveState(state, ActionListener.wrap(r -> next.run(), e -> next.run()));
r -> next.run(),
e -> next.run()
));
} }
private void doSaveState(TransformState state, ActionListener<Void> listener) { private void doSaveState(TransformState state, ActionListener<Void> listener) {
// This could be `null` but the putOrUpdateTransformStoredDoc handles that case just fine // This could be `null` but the putOrUpdateTransformStoredDoc handles that case just fine
SeqNoPrimaryTermAndIndex seqNoPrimaryTermAndIndex = transformTask.getSeqNoPrimaryTermAndIndex(); SeqNoPrimaryTermAndIndex seqNoPrimaryTermAndIndex = getSeqNoPrimaryTermAndIndex();
// Persist the current state and stats in the internal index. The interval of this method being // Persist the current state and stats in the internal index. The interval of this method being
// called is controlled by AsyncTwoPhaseIndexer#onBulkResponse which calls doSaveState every so // called is controlled by AsyncTwoPhaseIndexer#onBulkResponse which calls doSaveState every so
// often when doing bulk indexing calls or at the end of one indexing run. // often when doing bulk indexing calls or at the end of one indexing run.
transformsConfigManager.putOrUpdateTransformStoredDoc( transformsConfigManager
.putOrUpdateTransformStoredDoc(
new TransformStoredDoc(getJobId(), state, getStats()), new TransformStoredDoc(getJobId(), state, getStats()),
seqNoPrimaryTermAndIndex, seqNoPrimaryTermAndIndex,
ActionListener.wrap( ActionListener.wrap(r -> {
r -> { updateSeqNoPrimaryTermAndIndex(seqNoPrimaryTermAndIndex, r);
transformTask.updateSeqNoPrimaryTermAndIndex(seqNoPrimaryTermAndIndex, r);
// for auto stop shutdown the task // for auto stop shutdown the task
if (state.getTaskState().equals(TransformTaskState.STOPPED)) { if (state.getTaskState().equals(TransformTaskState.STOPPED)) {
transformTask.shutdown(); context.shutdown();
} }
// Only do this clean up once, if it succeeded, no reason to do the query again. // Only do this clean up once, if it succeeded, no reason to do the query again.
if (oldStatsCleanedUp.compareAndSet(false, true)) { if (oldStatsCleanedUp.compareAndSet(false, true)) {
transformsConfigManager.deleteOldTransformStoredDocuments(getJobId(), ActionListener.wrap( transformsConfigManager.deleteOldTransformStoredDocuments(getJobId(), ActionListener.wrap(nil -> {
nil -> {
logger.trace("[{}] deleted old transform stats and state document", getJobId()); logger.trace("[{}] deleted old transform stats and state document", getJobId());
listener.onResponse(null); listener.onResponse(null);
}, }, e -> {
e -> { String msg = LoggerMessageFormat.format("[{}] failed deleting old transform configurations.", getJobId());
String msg = LoggerMessageFormat.format("[{}] failed deleting old transform configurations.",
getJobId());
logger.warn(msg, e); logger.warn(msg, e);
// If we have failed, we should attempt the clean up again later // If we have failed, we should attempt the clean up again later
oldStatsCleanedUp.set(false); oldStatsCleanedUp.set(false);
listener.onResponse(null); listener.onResponse(null);
} }));
));
} else { } else {
listener.onResponse(null); listener.onResponse(null);
} }
}, }, statsExc -> {
statsExc -> { logger.error(new ParameterizedMessage("[{}] updating stats of transform failed.", transformConfig.getId()), statsExc);
logger.error(new ParameterizedMessage("[{}] updating stats of transform failed.", auditor.warning(getJobId(), "Failure updating stats of transform: " + statsExc.getMessage());
transformConfig.getId()),
statsExc);
auditor.warning(getJobId(),
"Failure updating stats of transform: " + statsExc.getMessage());
// for auto stop shutdown the task // for auto stop shutdown the task
if (state.getTaskState().equals(TransformTaskState.STOPPED)) { if (state.getTaskState().equals(TransformTaskState.STOPPED)) {
transformTask.shutdown(); context.shutdown();
} }
listener.onFailure(statsExc); listener.onFailure(statsExc);
} })
)); );
} }
@Override void updateSeqNoPrimaryTermAndIndex(SeqNoPrimaryTermAndIndex expectedValue, SeqNoPrimaryTermAndIndex newValue) {
protected void onFailure(Exception exc) { boolean updated = seqNoPrimaryTermAndIndex.compareAndSet(expectedValue, newValue);
// the failure handler must not throw an exception due to internal problems // This should never happen. We ONLY ever update this value if at initialization or we just finished updating the document
try { // famous last words...
handleFailure(exc); assert updated : "[" + getJobId() + "] unexpected change to seqNoPrimaryTermAndIndex.";
} catch (Exception e) {
logger.error(
new ParameterizedMessage("[{}] transform encountered an unexpected internal exception: ", getJobId()),
e);
}
} }
@Override @Nullable
protected void onFinish(ActionListener<Void> listener) { SeqNoPrimaryTermAndIndex getSeqNoPrimaryTermAndIndex() {
try { return seqNoPrimaryTermAndIndex.get();
// This indicates an early exit since no changes were found.
// So, don't treat this like a checkpoint being completed, as no work was done.
if (hasSourceChanged == false) {
if (shouldStopAtCheckpoint) {
stop();
}
listener.onResponse(null);
return;
}
// TODO: needs cleanup super is called with a listener, but listener.onResponse is called below
// super.onFinish() fortunately ignores the listener
super.onFinish(listener);
long checkpoint = transformTask.incrementCheckpoint();
lastCheckpoint = getNextCheckpoint();
nextCheckpoint = null;
// Reset our failure count as we have finished and may start again with a new checkpoint
failureCount.set(0);
transformTask.setStateReason(null);
// With bucket_selector we could have read all the buckets and completed the transform
// but not "see" all the buckets since they were filtered out. Consequently, progress would
// show less than 100% even though we are done.
// NOTE: this method is called in the same thread as the processing thread.
// Theoretically, there should not be a race condition with updating progress here.
// NOTE 2: getPercentComplete should only NOT be null on the first (batch) checkpoint
if (progress != null && progress.getPercentComplete() != null && progress.getPercentComplete() < 100.0) {
progress.incrementDocsProcessed(progress.getTotalDocs() - progress.getDocumentsProcessed());
}
// If the last checkpoint is now greater than 1, that means that we have just processed the first
// continuous checkpoint and should start recording the exponential averages
if (lastCheckpoint != null && lastCheckpoint.getCheckpoint() > 1) {
long docsIndexed = 0;
long docsProcessed = 0;
// This should not happen as we simply create a new one when we reach continuous checkpoints
// but this is a paranoid `null` check
if (progress != null) {
docsIndexed = progress.getDocumentsIndexed();
docsProcessed = progress.getDocumentsProcessed();
}
long durationMs = System.currentTimeMillis() - lastCheckpoint.getTimestamp();
getStats().incrementCheckpointExponentialAverages(durationMs < 0 ? 0 : durationMs, docsIndexed, docsProcessed);
}
if (shouldAuditOnFinish(checkpoint)) {
auditor.info(getJobId(),
"Finished indexing for transform checkpoint [" + checkpoint + "].");
}
logger.debug(
"[{}] finished indexing for transform checkpoint [{}].", getJobId(), checkpoint);
auditBulkFailures = true;
if (shouldStopAtCheckpoint) {
stop();
}
listener.onResponse(null);
} catch (Exception e) {
listener.onFailure(e);
}
}
/**
* Indicates if an audit message should be written when onFinish is called for the given checkpoint
* We audit the first checkpoint, and then every 10 checkpoints until completedCheckpoint == 99
* Then we audit every 100, until completedCheckpoint == 999
*
* Then we always audit every 1_000 checkpoints
*
* @param completedCheckpoint The checkpoint that was just completed
* @return {@code true} if an audit message should be written
*/
protected boolean shouldAuditOnFinish(long completedCheckpoint) {
if (++logCount % logEvery != 0) {
return false;
}
if (completedCheckpoint == 0) {
return true;
}
int log10Checkpoint = (int) Math.floor(Math.log10(completedCheckpoint));
logEvery = log10Checkpoint >= 3 ? 1_000 : (int)Math.pow(10.0, log10Checkpoint);
logCount = 0;
return true;
}
@Override
protected void onStop() {
auditor.info(transformConfig.getId(), "Transform has stopped.");
logger.info("[{}] transform has stopped.", transformConfig.getId());
}
@Override
protected void onAbort() {
auditor.info(transformConfig.getId(), "Received abort request, stopping transform.");
logger.info("[{}] transform received abort request. Stopping indexer.", transformConfig.getId());
transformTask.shutdown();
}
@Override
protected void createCheckpoint(ActionListener<TransformCheckpoint> listener) {
checkpointProvider.createNextCheckpoint(getLastCheckpoint(), ActionListener.wrap(
checkpoint -> transformsConfigManager.putTransformCheckpoint(checkpoint,
ActionListener.wrap(
putCheckPointResponse -> listener.onResponse(checkpoint),
createCheckpointException -> {
logger.warn(new ParameterizedMessage("[{}] failed to create checkpoint.", getJobId()),
createCheckpointException);
listener.onFailure(
new RuntimeException("Failed to create checkpoint due to " + createCheckpointException.getMessage(),
createCheckpointException));
}
)),
getCheckPointException -> {
logger.warn(new ParameterizedMessage("[{}] failed to retrieve checkpoint.", getJobId()),
getCheckPointException);
listener.onFailure(
new RuntimeException("Failed to retrieve checkpoint due to " + getCheckPointException.getMessage(),
getCheckPointException));
}
));
}
@Override
protected void sourceHasChanged(ActionListener<Boolean> hasChangedListener) {
checkpointProvider.sourceHasChanged(getLastCheckpoint(),
ActionListener.wrap(
hasChanged -> {
logger.trace("[{}] change detected [{}].", getJobId(), hasChanged);
hasChangedListener.onResponse(hasChanged);
},
e -> {
logger.warn(
new ParameterizedMessage(
"[{}] failed to detect changes for transform. Skipping update till next check.",
getJobId()),
e);
auditor.warning(getJobId(),
"Failed to detect changes for transform, skipping update till next check. Exception: "
+ e.getMessage());
hasChangedListener.onResponse(false);
}));
}
private boolean isIrrecoverableFailure(Exception e) {
return e instanceof IndexNotFoundException
|| e instanceof AggregationResultUtils.AggregationExtractionException
|| e instanceof TransformConfigReloadingException;
}
synchronized void handleFailure(Exception e) {
logger.warn(new ParameterizedMessage("[{}] transform encountered an exception: ",
getJobId()),
e);
if (handleCircuitBreakingException(e)) {
return;
}
if (isIrrecoverableFailure(e) || failureCount.incrementAndGet() > transformTask.getNumFailureRetries()) {
String failureMessage = isIrrecoverableFailure(e) ?
"task encountered irrecoverable failure: " + e.getMessage() :
"task encountered more than " + transformTask.getNumFailureRetries() + " failures; latest failure: " + e.getMessage();
failIndexer(failureMessage);
} else {
// Since our schedule fires again very quickly after failures it is possible to run into the same failure numerous
// times in a row, very quickly. We do not want to spam the audit log with repeated failures, so only record the first one
if (e.getMessage().equals(lastAuditedExceptionMessage) == false) {
auditor.warning(getJobId(),
"Transform encountered an exception: " + e.getMessage() +
" Will attempt again at next scheduled trigger.");
lastAuditedExceptionMessage = e.getMessage();
}
}
}
@Override
protected void failIndexer(String failureMessage) {
logger.error("[{}] transform has failed; experienced: [{}].", getJobId(), failureMessage);
auditor.error(getJobId(), failureMessage);
transformTask.markAsFailed(failureMessage, ActionListener.wrap(
r -> {
// Successfully marked as failed, reset counter so that task can be restarted
failureCount.set(0);
}, e -> {}));
} }
// Considered a recoverable indexing failure // Considered a recoverable indexing failure
@ -666,9 +332,4 @@ class ClientTransformIndexer extends TransformIndexer {
} }
} }
private static class TransformConfigReloadingException extends ElasticsearchException {
TransformConfigReloadingException(String msg, Throwable cause, Object... args) {
super(msg, cause, args);
}
}
} }

View File

@ -8,17 +8,19 @@ package org.elasticsearch.xpack.transform.transforms;
import org.elasticsearch.client.Client; import org.elasticsearch.client.Client;
import org.elasticsearch.xpack.core.indexing.IndexerState; import org.elasticsearch.xpack.core.indexing.IndexerState;
import org.elasticsearch.xpack.core.transform.transforms.TransformIndexerPosition;
import org.elasticsearch.xpack.core.transform.transforms.TransformIndexerStats;
import org.elasticsearch.xpack.core.transform.transforms.TransformCheckpoint; import org.elasticsearch.xpack.core.transform.transforms.TransformCheckpoint;
import org.elasticsearch.xpack.core.transform.transforms.TransformConfig; import org.elasticsearch.xpack.core.transform.transforms.TransformConfig;
import org.elasticsearch.xpack.core.transform.transforms.TransformIndexerPosition;
import org.elasticsearch.xpack.core.transform.transforms.TransformIndexerStats;
import org.elasticsearch.xpack.core.transform.transforms.TransformProgress; import org.elasticsearch.xpack.core.transform.transforms.TransformProgress;
import org.elasticsearch.xpack.transform.checkpoint.CheckpointProvider; import org.elasticsearch.xpack.transform.checkpoint.CheckpointProvider;
import org.elasticsearch.xpack.transform.checkpoint.TransformCheckpointService; import org.elasticsearch.xpack.transform.checkpoint.TransformCheckpointService;
import org.elasticsearch.xpack.transform.notifications.TransformAuditor; import org.elasticsearch.xpack.transform.notifications.TransformAuditor;
import org.elasticsearch.xpack.transform.persistence.SeqNoPrimaryTermAndIndex;
import org.elasticsearch.xpack.transform.persistence.TransformConfigManager; import org.elasticsearch.xpack.transform.persistence.TransformConfigManager;
import java.util.Map; import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
class ClientTransformIndexerBuilder { class ClientTransformIndexerBuilder {
@ -34,29 +36,35 @@ class ClientTransformIndexerBuilder {
private TransformProgress progress; private TransformProgress progress;
private TransformCheckpoint lastCheckpoint; private TransformCheckpoint lastCheckpoint;
private TransformCheckpoint nextCheckpoint; private TransformCheckpoint nextCheckpoint;
private SeqNoPrimaryTermAndIndex seqNoPrimaryTermAndIndex;
private boolean shouldStopAtCheckpoint; private boolean shouldStopAtCheckpoint;
ClientTransformIndexerBuilder() { ClientTransformIndexerBuilder() {
this.initialStats = new TransformIndexerStats(); this.initialStats = new TransformIndexerStats();
} }
ClientTransformIndexer build(TransformTask parentTask) { ClientTransformIndexer build(Executor executor, TransformContext context) {
CheckpointProvider checkpointProvider = transformsCheckpointService.getCheckpointProvider(transformConfig); CheckpointProvider checkpointProvider = transformsCheckpointService.getCheckpointProvider(transformConfig);
return new ClientTransformIndexer(this.transformsConfigManager, return new ClientTransformIndexer(
executor,
transformsConfigManager,
checkpointProvider, checkpointProvider,
new TransformProgressGatherer(client),
new AtomicReference<>(this.indexerState), new AtomicReference<>(this.indexerState),
this.initialPosition, initialPosition,
this.client, client,
this.auditor, auditor,
this.initialStats, initialStats,
this.transformConfig, transformConfig,
this.fieldMappings, fieldMappings,
this.progress, progress,
this.lastCheckpoint, TransformCheckpoint.isNullOrEmpty(lastCheckpoint) ? TransformCheckpoint.EMPTY : lastCheckpoint,
this.nextCheckpoint, TransformCheckpoint.isNullOrEmpty(nextCheckpoint) ? TransformCheckpoint.EMPTY : nextCheckpoint,
parentTask, seqNoPrimaryTermAndIndex,
this.shouldStopAtCheckpoint); context,
shouldStopAtCheckpoint
);
} }
ClientTransformIndexerBuilder setShouldStopAtCheckpoint(boolean shouldStopAtCheckpoint) { ClientTransformIndexerBuilder setShouldStopAtCheckpoint(boolean shouldStopAtCheckpoint) {
@ -127,4 +135,10 @@ class ClientTransformIndexerBuilder {
this.nextCheckpoint = nextCheckpoint; this.nextCheckpoint = nextCheckpoint;
return this; return this;
} }
ClientTransformIndexerBuilder setSeqNoPrimaryTermAndIndex(SeqNoPrimaryTermAndIndex seqNoPrimaryTermAndIndex) {
this.seqNoPrimaryTermAndIndex = seqNoPrimaryTermAndIndex;
return this;
}
} }

View File

@ -0,0 +1,137 @@
/*
* 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.transform.transforms;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.xpack.core.transform.transforms.TransformTaskState;
import org.elasticsearch.xpack.transform.Transform;
import java.time.Instant;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
class TransformContext {
public interface Listener {
void shutdown();
void fail(String failureMessage, ActionListener<Void> listener);
}
private final AtomicReference<TransformTaskState> taskState;
private final AtomicReference<String> stateReason;
private final Listener taskListener;
private volatile int numFailureRetries = Transform.DEFAULT_FAILURE_RETRIES;
private final AtomicInteger failureCount;
private volatile Instant changesLastDetectedAt;
private volatile boolean shouldStopAtCheckpoint;
// the checkpoint of this transform, storing the checkpoint until data indexing from source to dest is _complete_
// Note: Each indexer run creates a new future checkpoint which becomes the current checkpoint only after the indexer run finished
private final AtomicLong currentCheckpoint;
TransformContext(final TransformTaskState taskState, String stateReason, long currentCheckpoint, Listener taskListener) {
this.taskState = new AtomicReference<>(taskState);
this.stateReason = new AtomicReference<>(stateReason);
this.currentCheckpoint = new AtomicLong(currentCheckpoint);
this.taskListener = taskListener;
this.failureCount = new AtomicInteger(0);
this.shouldStopAtCheckpoint = shouldStopAtCheckpoint;
}
TransformTaskState getTaskState() {
return taskState.get();
}
void setTaskState(TransformTaskState newState) {
taskState.set(newState);
}
boolean setTaskState(TransformTaskState oldState, TransformTaskState newState) {
return taskState.compareAndSet(oldState, newState);
}
void resetTaskState() {
taskState.set(TransformTaskState.STARTED);
stateReason.set(null);
}
void setTaskStateToFailed(String reason) {
taskState.set(TransformTaskState.FAILED);
stateReason.set(reason);
}
void resetReasonAndFailureCounter() {
stateReason.set(null);
failureCount.set(0);
}
String getStateReason() {
return stateReason.get();
}
void setCheckpoint(long newValue) {
currentCheckpoint.set(newValue);
}
long getCheckpoint() {
return currentCheckpoint.get();
}
long getAndIncrementCheckpoint() {
return currentCheckpoint.getAndIncrement();
}
void setNumFailureRetries(int numFailureRetries) {
this.numFailureRetries = numFailureRetries;
}
int getNumFailureRetries() {
return numFailureRetries;
}
int getAndIncrementFailureCount() {
return failureCount.getAndIncrement();
}
void setChangesLastDetectedAt(Instant time) {
changesLastDetectedAt = time;
}
Instant getChangesLastDetectedAt() {
return changesLastDetectedAt;
}
public boolean shouldStopAtCheckpoint() {
return shouldStopAtCheckpoint;
}
public void setShouldStopAtCheckpoint(boolean shouldStopAtCheckpoint) {
this.shouldStopAtCheckpoint = shouldStopAtCheckpoint;
}
void shutdown() {
taskListener.shutdown();
}
void markAsFailed(String failureMessage) {
taskListener
.fail(
failureMessage,
ActionListener
.wrap(
r -> {
// Successfully marked as failed, reset counter so that task can be restarted
failureCount.set(0);
},
e -> {}
)
);
}
}

View File

@ -8,6 +8,9 @@ package org.elasticsearch.xpack.transform.transforms;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.message.ParameterizedMessage;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.ResourceNotFoundException;
import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.index.IndexRequest; import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.SearchPhaseExecutionException; import org.elasticsearch.action.search.SearchPhaseExecutionException;
@ -18,6 +21,7 @@ import org.elasticsearch.action.support.IndicesOptions;
import org.elasticsearch.common.Strings; import org.elasticsearch.common.Strings;
import org.elasticsearch.common.breaker.CircuitBreakingException; import org.elasticsearch.common.breaker.CircuitBreakingException;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.index.IndexNotFoundException;
import org.elasticsearch.index.query.BoolQueryBuilder; import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.search.aggregations.Aggregations; import org.elasticsearch.search.aggregations.Aggregations;
@ -29,20 +33,24 @@ import org.elasticsearch.xpack.core.indexing.IndexerState;
import org.elasticsearch.xpack.core.indexing.IterationResult; import org.elasticsearch.xpack.core.indexing.IterationResult;
import org.elasticsearch.xpack.core.transform.TransformField; import org.elasticsearch.xpack.core.transform.TransformField;
import org.elasticsearch.xpack.core.transform.TransformMessages; import org.elasticsearch.xpack.core.transform.TransformMessages;
import org.elasticsearch.xpack.core.transform.transforms.TransformIndexerPosition;
import org.elasticsearch.xpack.core.transform.transforms.TransformIndexerStats;
import org.elasticsearch.xpack.core.transform.transforms.TransformCheckpoint; import org.elasticsearch.xpack.core.transform.transforms.TransformCheckpoint;
import org.elasticsearch.xpack.core.transform.transforms.TransformConfig; import org.elasticsearch.xpack.core.transform.transforms.TransformConfig;
import org.elasticsearch.xpack.core.transform.transforms.TransformIndexerPosition;
import org.elasticsearch.xpack.core.transform.transforms.TransformIndexerStats;
import org.elasticsearch.xpack.core.transform.transforms.TransformProgress; import org.elasticsearch.xpack.core.transform.transforms.TransformProgress;
import org.elasticsearch.xpack.core.transform.transforms.TransformTaskState;
import org.elasticsearch.xpack.core.transform.utils.ExceptionsHelper; import org.elasticsearch.xpack.core.transform.utils.ExceptionsHelper;
import org.elasticsearch.xpack.transform.checkpoint.CheckpointProvider;
import org.elasticsearch.xpack.transform.notifications.TransformAuditor; import org.elasticsearch.xpack.transform.notifications.TransformAuditor;
import org.elasticsearch.xpack.transform.persistence.TransformConfigManager;
import org.elasticsearch.xpack.transform.transforms.pivot.AggregationResultUtils;
import org.elasticsearch.xpack.transform.transforms.pivot.Pivot; import org.elasticsearch.xpack.transform.transforms.pivot.Pivot;
import java.io.IOException; import java.io.IOException;
import java.io.UncheckedIOException; import java.io.UncheckedIOException;
import java.time.Instant;
import java.util.Collections; import java.util.Collections;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
@ -73,24 +81,41 @@ public abstract class TransformIndexer extends AsyncTwoPhaseIndexer<TransformInd
public static final String COMPOSITE_AGGREGATION_NAME = "_transform"; public static final String COMPOSITE_AGGREGATION_NAME = "_transform";
private static final Logger logger = LogManager.getLogger(TransformIndexer.class); private static final Logger logger = LogManager.getLogger(TransformIndexer.class);
protected final TransformConfigManager transformsConfigManager;
private final CheckpointProvider checkpointProvider;
private final TransformProgressGatherer progressGatherer;
protected final TransformAuditor auditor; protected final TransformAuditor auditor;
protected final TransformContext context;
protected volatile TransformConfig transformConfig; protected volatile TransformConfig transformConfig;
protected volatile TransformProgress progress; private volatile TransformProgress progress;
protected volatile boolean auditBulkFailures = true;
// Indicates that the source has changed for the current run
protected volatile boolean hasSourceChanged = true;
private final Map<String, String> fieldMappings; private final Map<String, String> fieldMappings;
private Pivot pivot; private Pivot pivot;
private int pageSize = 0; private int pageSize = 0;
protected volatile TransformCheckpoint lastCheckpoint; private long logEvery = 1;
protected volatile TransformCheckpoint nextCheckpoint; private long logCount = 0;
private volatile TransformCheckpoint lastCheckpoint;
private volatile TransformCheckpoint nextCheckpoint;
// Keeps track of the last exception that was written to our audit, keeps us from spamming the audit index
private volatile String lastAuditedExceptionMessage = null;
private volatile RunState runState; private volatile RunState runState;
// hold information for continuous mode (partial updates) // hold information for continuous mode (partial updates)
private volatile Map<String, Set<String>> changedBuckets; private volatile Map<String, Set<String>> changedBuckets;
private volatile Map<String, Object> changedBucketsAfterKey; private volatile Map<String, Object> changedBucketsAfterKey;
public TransformIndexer(Executor executor, public TransformIndexer(
Executor executor,
TransformConfigManager transformsConfigManager,
CheckpointProvider checkpointProvider,
TransformProgressGatherer progressGatherer,
TransformAuditor auditor, TransformAuditor auditor,
TransformConfig transformConfig, TransformConfig transformConfig,
Map<String, String> fieldMappings, Map<String, String> fieldMappings,
@ -99,20 +124,25 @@ public abstract class TransformIndexer extends AsyncTwoPhaseIndexer<TransformInd
TransformIndexerStats jobStats, TransformIndexerStats jobStats,
TransformProgress transformProgress, TransformProgress transformProgress,
TransformCheckpoint lastCheckpoint, TransformCheckpoint lastCheckpoint,
TransformCheckpoint nextCheckpoint) { TransformCheckpoint nextCheckpoint,
TransformContext context
) {
super(executor, initialState, initialPosition, jobStats); super(executor, initialState, initialPosition, jobStats);
this.auditor = Objects.requireNonNull(auditor); this.transformsConfigManager = ExceptionsHelper.requireNonNull(transformsConfigManager, "transformsConfigManager");
this.checkpointProvider = ExceptionsHelper.requireNonNull(checkpointProvider, "checkpointProvider");
this.progressGatherer = ExceptionsHelper.requireNonNull(progressGatherer, "progressGatherer");
this.auditor = ExceptionsHelper.requireNonNull(auditor, "auditor");
this.transformConfig = ExceptionsHelper.requireNonNull(transformConfig, "transformConfig"); this.transformConfig = ExceptionsHelper.requireNonNull(transformConfig, "transformConfig");
this.fieldMappings = ExceptionsHelper.requireNonNull(fieldMappings, "fieldMappings"); this.fieldMappings = ExceptionsHelper.requireNonNull(fieldMappings, "fieldMappings");
this.progress = transformProgress; this.progress = transformProgress;
this.lastCheckpoint = lastCheckpoint; this.lastCheckpoint = ExceptionsHelper.requireNonNull(lastCheckpoint, "lastCheckpoint");
this.nextCheckpoint = nextCheckpoint; this.nextCheckpoint = ExceptionsHelper.requireNonNull(nextCheckpoint, "nextCheckpoint");
this.context = ExceptionsHelper.requireNonNull(context, "context");
// give runState a default // give runState a default
this.runState = RunState.FULL_RUN; this.runState = RunState.FULL_RUN;
} }
protected abstract void failIndexer(String message);
public int getPageSize() { public int getPageSize() {
return pageSize; return pageSize;
} }
@ -146,13 +176,61 @@ public abstract class TransformIndexer extends AsyncTwoPhaseIndexer<TransformInd
return nextCheckpoint; return nextCheckpoint;
} }
public CheckpointProvider getCheckpointProvider() {
return checkpointProvider;
}
/** /**
* Request a checkpoint * Request a checkpoint
*/ */
protected abstract void createCheckpoint(ActionListener<TransformCheckpoint> listener); protected void createCheckpoint(ActionListener<TransformCheckpoint> listener) {
checkpointProvider
.createNextCheckpoint(
getLastCheckpoint(),
ActionListener
.wrap(
checkpoint -> transformsConfigManager
.putTransformCheckpoint(
checkpoint,
ActionListener.wrap(putCheckPointResponse -> listener.onResponse(checkpoint), createCheckpointException -> {
logger
.warn(
new ParameterizedMessage("[{}] failed to create checkpoint.", getJobId()),
createCheckpointException
);
listener
.onFailure(
new RuntimeException(
"Failed to create checkpoint due to " + createCheckpointException.getMessage(),
createCheckpointException
)
);
})
),
getCheckPointException -> {
logger
.warn(new ParameterizedMessage("[{}] failed to retrieve checkpoint.", getJobId()), getCheckPointException);
listener
.onFailure(
new RuntimeException(
"Failed to retrieve checkpoint due to " + getCheckPointException.getMessage(),
getCheckPointException
)
);
}
)
);
}
@Override @Override
protected void onStart(long now, ActionListener<Boolean> listener) { protected void onStart(long now, ActionListener<Boolean> listener) {
if (context.getTaskState() == TransformTaskState.FAILED) {
logger.debug("[{}] attempted to start while failed.", getJobId());
listener.onFailure(new ElasticsearchException("Attempted to start a failed transform [{}].", getJobId()));
return;
}
ActionListener<Void> finalListener = ActionListener.wrap(r -> {
try { try {
pivot = new Pivot(getConfig().getPivotConfig()); pivot = new Pivot(getConfig().getPivotConfig());
@ -165,6 +243,86 @@ public abstract class TransformIndexer extends AsyncTwoPhaseIndexer<TransformInd
listener.onResponse(true); listener.onResponse(true);
} catch (Exception e) { } catch (Exception e) {
listener.onFailure(e); listener.onFailure(e);
return;
}
}, listener::onFailure);
// On each run, we need to get the total number of docs and reset the count of processed docs
// Since multiple checkpoints can be executed in the task while it is running on the same node, we need to gather
// the progress here, and not in the executor.
ActionListener<Void> updateConfigListener = ActionListener.wrap(updateConfigResponse -> {
if (initialRun()) {
createCheckpoint(ActionListener.wrap(cp -> {
nextCheckpoint = cp;
// If nextCheckpoint > 1, this means that we are now on the checkpoint AFTER the batch checkpoint
// Consequently, the idea of percent complete no longer makes sense.
if (nextCheckpoint.getCheckpoint() > 1) {
progress = new TransformProgress(null, 0L, 0L);
finalListener.onResponse(null);
return;
}
progressGatherer.getInitialProgress(buildFilterQuery(), getConfig(), ActionListener.wrap(newProgress -> {
logger.trace("[{}] reset the progress from [{}] to [{}].", getJobId(), progress, newProgress);
progress = newProgress;
finalListener.onResponse(null);
}, failure -> {
progress = null;
logger.warn(new ParameterizedMessage("[{}] unable to load progress information for task.", getJobId()), failure);
finalListener.onResponse(null);
}));
}, listener::onFailure));
} else {
finalListener.onResponse(null);
}
}, listener::onFailure);
// If we are continuous, we will want to verify we have the latest stored configuration
ActionListener<Void> changedSourceListener = ActionListener.wrap(r -> {
if (isContinuous()) {
transformsConfigManager.getTransformConfiguration(getJobId(), ActionListener.wrap(config -> {
transformConfig = config;
logger.debug("[{}] successfully refreshed transform config from index.", getJobId());
updateConfigListener.onResponse(null);
}, failure -> {
String msg = TransformMessages.getMessage(TransformMessages.FAILED_TO_RELOAD_TRANSFORM_CONFIGURATION, getJobId());
logger.error(msg, failure);
// If the transform config index or the transform config is gone, something serious occurred
// We are in an unknown state and should fail out
if (failure instanceof ResourceNotFoundException) {
updateConfigListener.onFailure(new TransformConfigReloadingException(msg, failure));
} else {
auditor.warning(getJobId(), msg);
updateConfigListener.onResponse(null);
}
}));
} else {
updateConfigListener.onResponse(null);
}
}, listener::onFailure);
// If we are not on the initial batch checkpoint and its the first pass of whatever continuous checkpoint we are on,
// we should verify if there are local changes based on the sync config. If not, do not proceed further and exit.
if (context.getCheckpoint() > 0 && initialRun()) {
sourceHasChanged(ActionListener.wrap(hasChanged -> {
hasSourceChanged = hasChanged;
if (hasChanged) {
context.setChangesLastDetectedAt(Instant.now());
logger.debug("[{}] source has changed, triggering new indexer run.", getJobId());
changedSourceListener.onResponse(null);
} else {
logger.trace("[{}] source has not changed, finish indexer early.", getJobId());
// No changes, stop executing
listener.onResponse(false);
}
}, failure -> {
// If we failed determining if the source changed, it's safer to assume there were changes.
// We should allow the failure path to complete as normal
hasSourceChanged = true;
listener.onFailure(failure);
}));
} else {
hasSourceChanged = true;
changedSourceListener.onResponse(null);
} }
} }
@ -174,10 +332,63 @@ public abstract class TransformIndexer extends AsyncTwoPhaseIndexer<TransformInd
@Override @Override
protected void onFinish(ActionListener<Void> listener) { protected void onFinish(ActionListener<Void> listener) {
try {
// This indicates an early exit since no changes were found.
// So, don't treat this like a checkpoint being completed, as no work was done.
if (hasSourceChanged == false) {
if (context.shouldStopAtCheckpoint()) {
stop();
}
listener.onResponse(null);
return;
}
// reset the page size, so we do not memorize a low page size forever // reset the page size, so we do not memorize a low page size forever
pageSize = pivot.getInitialPageSize(); pageSize = pivot.getInitialPageSize();
// reset the changed bucket to free memory // reset the changed bucket to free memory
changedBuckets = null; changedBuckets = null;
long checkpoint = context.getAndIncrementCheckpoint();
lastCheckpoint = getNextCheckpoint();
nextCheckpoint = null;
// Reset our failure count as we have finished and may start again with a new checkpoint
context.resetReasonAndFailureCounter();
// With bucket_selector we could have read all the buckets and completed the transform
// but not "see" all the buckets since they were filtered out. Consequently, progress would
// show less than 100% even though we are done.
// NOTE: this method is called in the same thread as the processing thread.
// Theoretically, there should not be a race condition with updating progress here.
// NOTE 2: getPercentComplete should only NOT be null on the first (batch) checkpoint
if (progress != null && progress.getPercentComplete() != null && progress.getPercentComplete() < 100.0) {
progress.incrementDocsProcessed(progress.getTotalDocs() - progress.getDocumentsProcessed());
}
// If the last checkpoint is now greater than 1, that means that we have just processed the first
// continuous checkpoint and should start recording the exponential averages
if (lastCheckpoint != null && lastCheckpoint.getCheckpoint() > 1) {
long docsIndexed = 0;
long docsProcessed = 0;
// This should not happen as we simply create a new one when we reach continuous checkpoints
// but this is a paranoid `null` check
if (progress != null) {
docsIndexed = progress.getDocumentsIndexed();
docsProcessed = progress.getDocumentsProcessed();
}
long durationMs = System.currentTimeMillis() - lastCheckpoint.getTimestamp();
getStats().incrementCheckpointExponentialAverages(durationMs < 0 ? 0 : durationMs, docsIndexed, docsProcessed);
}
if (shouldAuditOnFinish(checkpoint)) {
auditor.info(getJobId(), "Finished indexing for transform checkpoint [" + checkpoint + "].");
}
logger.debug("[{}] finished indexing for transform checkpoint [{}].", getJobId(), checkpoint);
auditBulkFailures = true;
if (context.shouldStopAtCheckpoint()) {
stop();
}
listener.onResponse(null);
} catch (Exception e) {
listener.onFailure(e);
}
} }
@Override @Override
@ -186,13 +397,16 @@ public abstract class TransformIndexer extends AsyncTwoPhaseIndexer<TransformInd
// Treat this as a "we reached the end". // Treat this as a "we reached the end".
// This should only happen when all underlying indices have gone away. Consequently, there is no more data to read. // This should only happen when all underlying indices have gone away. Consequently, there is no more data to read.
if (aggregations == null) { if (aggregations == null) {
logger.info("[" + getJobId() + "] unexpected null aggregations in search response. " + logger
"Source indices have been deleted or closed."); .info("[{}] unexpected null aggregations in search response. " + "Source indices have been deleted or closed.", getJobId());
auditor.info(getJobId(), auditor
"Source indices have been deleted or closed. " + .info(
"Please verify that these indices exist and are open [" + getJobId(),
Strings.arrayToCommaDelimitedString(getConfig().getSource().getIndex()) + "Source indices have been deleted or closed. "
"]."); + "Please verify that these indices exist and are open ["
+ Strings.arrayToCommaDelimitedString(getConfig().getSource().getIndex())
+ "]."
);
return new IterationResult<>(Collections.emptyList(), null, true); return new IterationResult<>(Collections.emptyList(), null, true);
} }
final CompositeAggregation agg = aggregations.get(COMPOSITE_AGGREGATION_NAME); final CompositeAggregation agg = aggregations.get(COMPOSITE_AGGREGATION_NAME);
@ -207,11 +421,101 @@ public abstract class TransformIndexer extends AsyncTwoPhaseIndexer<TransformInd
default: default:
// Any other state is a bug, should not happen // Any other state is a bug, should not happen
logger.warn("Encountered unexpected run state [" + runState + "]"); logger.warn("[{}] Encountered unexpected run state [{}]", getJobId(), runState);
throw new IllegalStateException("DataFrame indexer job encountered an illegal state [" + runState + "]"); throw new IllegalStateException("DataFrame indexer job encountered an illegal state [" + runState + "]");
} }
} }
@Override
public synchronized boolean maybeTriggerAsyncJob(long now) {
if (context.getTaskState() == TransformTaskState.FAILED) {
logger.debug("[{}] schedule was triggered for transform but task is failed. Ignoring trigger.", getJobId());
return false;
}
// ignore trigger if indexer is running, prevents log spam in A2P indexer
IndexerState indexerState = getState();
if (IndexerState.INDEXING.equals(indexerState) || IndexerState.STOPPING.equals(indexerState)) {
logger.debug("[{}] indexer for transform has state [{}]. Ignoring trigger.", getJobId(), indexerState);
return false;
}
return super.maybeTriggerAsyncJob(now);
}
@Override
protected void onFailure(Exception exc) {
// the failure handler must not throw an exception due to internal problems
try {
handleFailure(exc);
} catch (Exception e) {
logger.error(new ParameterizedMessage("[{}] transform encountered an unexpected internal exception: ", getJobId()), e);
}
}
@Override
protected void onStop() {
auditor.info(transformConfig.getId(), "Transform has stopped.");
logger.info("[{}] transform has stopped.", transformConfig.getId());
}
@Override
protected void onAbort() {
auditor.info(transformConfig.getId(), "Received abort request, stopping transform.");
logger.info("[{}] transform received abort request. Stopping indexer.", transformConfig.getId());
context.shutdown();
}
synchronized void handleFailure(Exception e) {
logger.warn(new ParameterizedMessage("[{}] transform encountered an exception: ", getJobId()), e);
if (handleCircuitBreakingException(e)) {
return;
}
if (isIrrecoverableFailure(e) || context.getAndIncrementFailureCount() > context.getNumFailureRetries()) {
String failureMessage = isIrrecoverableFailure(e)
? "task encountered irrecoverable failure: " + e.getMessage()
: "task encountered more than " + context.getNumFailureRetries() + " failures; latest failure: " + e.getMessage();
failIndexer(failureMessage);
} else {
// Since our schedule fires again very quickly after failures it is possible to run into the same failure numerous
// times in a row, very quickly. We do not want to spam the audit log with repeated failures, so only record the first one
if (e.getMessage().equals(lastAuditedExceptionMessage) == false) {
auditor
.warning(
getJobId(),
"Transform encountered an exception: " + e.getMessage() + " Will attempt again at next scheduled trigger."
);
lastAuditedExceptionMessage = e.getMessage();
}
}
}
private void sourceHasChanged(ActionListener<Boolean> hasChangedListener) {
checkpointProvider.sourceHasChanged(getLastCheckpoint(), ActionListener.wrap(hasChanged -> {
logger.trace("[{}] change detected [{}].", getJobId(), hasChanged);
hasChangedListener.onResponse(hasChanged);
}, e -> {
logger
.warn(
new ParameterizedMessage("[{}] failed to detect changes for transform. Skipping update till next check.", getJobId()),
e
);
auditor
.warning(
getJobId(),
"Failed to detect changes for transform, skipping update till next check. Exception: " + e.getMessage()
);
hasChangedListener.onResponse(false);
}));
}
private boolean isIrrecoverableFailure(Exception e) {
return e instanceof IndexNotFoundException
|| e instanceof AggregationResultUtils.AggregationExtractionException
|| e instanceof TransformConfigReloadingException;
}
private IterationResult<TransformIndexerPosition> processBuckets(final CompositeAggregation agg) { private IterationResult<TransformIndexerPosition> processBuckets(final CompositeAggregation agg) {
// we reached the end // we reached the end
if (agg.getBuckets().isEmpty()) { if (agg.getBuckets().isEmpty()) {
@ -221,13 +525,16 @@ public abstract class TransformIndexer extends AsyncTwoPhaseIndexer<TransformInd
long docsBeforeProcess = getStats().getNumDocuments(); long docsBeforeProcess = getStats().getNumDocuments();
TransformIndexerPosition oldPosition = getPosition(); TransformIndexerPosition oldPosition = getPosition();
TransformIndexerPosition newPosition = new TransformIndexerPosition(agg.afterKey(), TransformIndexerPosition newPosition = new TransformIndexerPosition(
oldPosition != null ? getPosition().getBucketsPosition() : null); agg.afterKey(),
oldPosition != null ? getPosition().getBucketsPosition() : null
);
IterationResult<TransformIndexerPosition> result = new IterationResult<>( IterationResult<TransformIndexerPosition> result = new IterationResult<>(
processBucketsToIndexRequests(agg).collect(Collectors.toList()), processBucketsToIndexRequests(agg).collect(Collectors.toList()),
newPosition, newPosition,
agg.getBuckets().isEmpty()); agg.getBuckets().isEmpty()
);
// NOTE: progress is also mutated in ClientDataFrameIndexer#onFinished // NOTE: progress is also mutated in ClientDataFrameIndexer#onFinished
if (progress != null) { if (progress != null) {
@ -247,14 +554,12 @@ public abstract class TransformIndexer extends AsyncTwoPhaseIndexer<TransformInd
// reset the runState to fetch changed buckets // reset the runState to fetch changed buckets
runState = RunState.PARTIAL_RUN_IDENTIFY_CHANGES; runState = RunState.PARTIAL_RUN_IDENTIFY_CHANGES;
// advance the cursor for changed bucket detection // advance the cursor for changed bucket detection
return new IterationResult<>(Collections.emptyList(), return new IterationResult<>(Collections.emptyList(), new TransformIndexerPosition(null, changedBucketsAfterKey), false);
new TransformIndexerPosition(null, changedBucketsAfterKey), false);
} }
return processBuckets(agg); return processBuckets(agg);
} }
private IterationResult<TransformIndexerPosition> processChangedBuckets(final CompositeAggregation agg) { private IterationResult<TransformIndexerPosition> processChangedBuckets(final CompositeAggregation agg) {
// initialize the map of changed buckets, the map might be empty if source do not require/implement // initialize the map of changed buckets, the map might be empty if source do not require/implement
// changed bucket detection // changed bucket detection
@ -270,11 +575,7 @@ public abstract class TransformIndexer extends AsyncTwoPhaseIndexer<TransformInd
// else // else
// collect all buckets that require the update // collect all buckets that require the update
agg.getBuckets().stream().forEach(bucket -> { agg.getBuckets().stream().forEach(bucket -> { bucket.getKey().forEach((k, v) -> { changedBuckets.get(k).add(v.toString()); }); });
bucket.getKey().forEach((k, v) -> {
changedBuckets.get(k).add(v.toString());
});
});
// remember the after key but do not store it in the state yet (in the failure we need to retrieve it again) // remember the after key but do not store it in the state yet (in the failure we need to retrieve it again)
changedBucketsAfterKey = agg.afterKey(); changedBucketsAfterKey = agg.afterKey();
@ -335,8 +636,7 @@ public abstract class TransformIndexer extends AsyncTwoPhaseIndexer<TransformInd
TransformConfig config = getConfig(); TransformConfig config = getConfig();
if (this.isContinuous()) { if (this.isContinuous()) {
BoolQueryBuilder filteredQuery = new BoolQueryBuilder() BoolQueryBuilder filteredQuery = new BoolQueryBuilder().filter(pivotQueryBuilder);
.filter(pivotQueryBuilder);
if (lastCheckpoint != null) { if (lastCheckpoint != null) {
filteredQuery.filter(config.getSyncConfig().getRangeQuery(lastCheckpoint, nextCheckpoint)); filteredQuery.filter(config.getSyncConfig().getRangeQuery(lastCheckpoint, nextCheckpoint));
@ -356,8 +656,7 @@ public abstract class TransformIndexer extends AsyncTwoPhaseIndexer<TransformInd
SearchRequest searchRequest = new SearchRequest(getConfig().getSource().getIndex()) SearchRequest searchRequest = new SearchRequest(getConfig().getSource().getIndex())
.allowPartialSearchResults(false) .allowPartialSearchResults(false)
.indicesOptions(IndicesOptions.LENIENT_EXPAND_OPEN); .indicesOptions(IndicesOptions.LENIENT_EXPAND_OPEN);
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder() SearchSourceBuilder sourceBuilder = new SearchSourceBuilder().size(0);
.size(0);
switch (runState) { switch (runState) {
case FULL_RUN: case FULL_RUN:
@ -389,8 +688,7 @@ public abstract class TransformIndexer extends AsyncTwoPhaseIndexer<TransformInd
if (isContinuous()) { if (isContinuous()) {
BoolQueryBuilder filteredQuery = new BoolQueryBuilder() BoolQueryBuilder filteredQuery = new BoolQueryBuilder()
.filter(pivotQueryBuilder) .filter(pivotQueryBuilder)
.filter(config.getSyncConfig() .filter(config.getSyncConfig().getRangeQuery(nextCheckpoint));
.getRangeQuery(nextCheckpoint));
sourceBuilder.query(filteredQuery); sourceBuilder.query(filteredQuery);
} else { } else {
sourceBuilder.query(pivotQueryBuilder); sourceBuilder.query(pivotQueryBuilder);
@ -413,9 +711,9 @@ public abstract class TransformIndexer extends AsyncTwoPhaseIndexer<TransformInd
QueryBuilder pivotQueryBuilder = getConfig().getSource().getQueryConfig().getQuery(); QueryBuilder pivotQueryBuilder = getConfig().getSource().getQueryConfig().getQuery();
TransformConfig config = getConfig(); TransformConfig config = getConfig();
BoolQueryBuilder filteredQuery = new BoolQueryBuilder(). BoolQueryBuilder filteredQuery = new BoolQueryBuilder()
filter(pivotQueryBuilder). .filter(pivotQueryBuilder)
filter(config.getSyncConfig().getRangeQuery(lastCheckpoint, nextCheckpoint)); .filter(config.getSyncConfig().getRangeQuery(lastCheckpoint, nextCheckpoint));
sourceBuilder.query(filteredQuery); sourceBuilder.query(filteredQuery);
@ -435,8 +733,7 @@ public abstract class TransformIndexer extends AsyncTwoPhaseIndexer<TransformInd
BoolQueryBuilder filteredQuery = new BoolQueryBuilder() BoolQueryBuilder filteredQuery = new BoolQueryBuilder()
.filter(pivotQueryBuilder) .filter(pivotQueryBuilder)
.filter(config.getSyncConfig() .filter(config.getSyncConfig().getRangeQuery(nextCheckpoint));
.getRangeQuery(nextCheckpoint));
if (changedBuckets != null && changedBuckets.isEmpty() == false) { if (changedBuckets != null && changedBuckets.isEmpty() == false) {
QueryBuilder pivotFilter = pivot.filterBuckets(changedBuckets); QueryBuilder pivotFilter = pivot.filterBuckets(changedBuckets);
@ -469,8 +766,11 @@ public abstract class TransformIndexer extends AsyncTwoPhaseIndexer<TransformInd
return false; return false;
} }
double reducingFactor = Math.min((double) circuitBreakingException.getByteLimit() / circuitBreakingException.getBytesWanted(), double reducingFactor = Math
1 - (Math.log10(pageSize) * 0.1)); .min(
(double) circuitBreakingException.getByteLimit() / circuitBreakingException.getBytesWanted(),
1 - (Math.log10(pageSize) * 0.1)
);
int newPageSize = (int) Math.round(reducingFactor * pageSize); int newPageSize = (int) Math.round(reducingFactor * pageSize);
@ -480,8 +780,7 @@ public abstract class TransformIndexer extends AsyncTwoPhaseIndexer<TransformInd
return true; return true;
} }
String message = TransformMessages.getMessage(TransformMessages.LOG_TRANSFORM_PIVOT_REDUCE_PAGE_SIZE, pageSize, String message = TransformMessages.getMessage(TransformMessages.LOG_TRANSFORM_PIVOT_REDUCE_PAGE_SIZE, pageSize, newPageSize);
newPageSize);
auditor.info(getJobId(), message); auditor.info(getJobId(), message);
logger.info("Data frame transform [" + getJobId() + "]:" + message); logger.info("Data frame transform [" + getJobId() + "]:" + message);
@ -489,6 +788,35 @@ public abstract class TransformIndexer extends AsyncTwoPhaseIndexer<TransformInd
return true; return true;
} }
protected void failIndexer(String failureMessage) {
logger.error("[{}] transform has failed; experienced: [{}].", getJobId(), failureMessage);
auditor.error(getJobId(), failureMessage);
context.markAsFailed(failureMessage);
}
/**
* Indicates if an audit message should be written when onFinish is called for the given checkpoint
* We audit the first checkpoint, and then every 10 checkpoints until completedCheckpoint == 99
* Then we audit every 100, until completedCheckpoint == 999
*
* Then we always audit every 1_000 checkpoints
*
* @param completedCheckpoint The checkpoint that was just completed
* @return {@code true} if an audit message should be written
*/
protected boolean shouldAuditOnFinish(long completedCheckpoint) {
if (++logCount % logEvery != 0) {
return false;
}
if (completedCheckpoint == 0) {
return true;
}
int log10Checkpoint = (int) Math.floor(Math.log10(completedCheckpoint));
logEvery = log10Checkpoint >= 3 ? 1_000 : (int) Math.pow(10.0, log10Checkpoint);
logCount = 0;
return true;
}
private RunState determineRunStateAtStart() { private RunState determineRunStateAtStart() {
// either 1st run or not a continuous data frame // either 1st run or not a continuous data frame
if (nextCheckpoint.getCheckpoint() == 1 || isContinuous() == false) { if (nextCheckpoint.getCheckpoint() == 1 || isContinuous() == false) {
@ -530,5 +858,9 @@ public abstract class TransformIndexer extends AsyncTwoPhaseIndexer<TransformInd
return null; return null;
} }
protected abstract void sourceHasChanged(ActionListener<Boolean> hasChangedListener); static class TransformConfigReloadingException extends ElasticsearchException {
TransformConfigReloadingException(String msg, Throwable cause, Object... args) {
super(msg, cause, args);
}
}
} }

View File

@ -69,14 +69,16 @@ public class TransformPersistentTasksExecutor extends PersistentTasksExecutor<Tr
private final TransformAuditor auditor; private final TransformAuditor auditor;
private volatile int numFailureRetries; private volatile int numFailureRetries;
public TransformPersistentTasksExecutor(Client client, public TransformPersistentTasksExecutor(
Client client,
TransformConfigManager transformsConfigManager, TransformConfigManager transformsConfigManager,
TransformCheckpointService transformsCheckpointService, TransformCheckpointService transformsCheckpointService,
SchedulerEngine schedulerEngine, SchedulerEngine schedulerEngine,
TransformAuditor auditor, TransformAuditor auditor,
ThreadPool threadPool, ThreadPool threadPool,
ClusterService clusterService, ClusterService clusterService,
Settings settings) { Settings settings
) {
super(TransformField.TASK_NAME, Transform.TASK_THREAD_POOL_NAME); super(TransformField.TASK_NAME, Transform.TASK_THREAD_POOL_NAME);
this.client = client; this.client = client;
this.transformsConfigManager = transformsConfigManager; this.transformsConfigManager = transformsConfigManager;
@ -85,43 +87,50 @@ public class TransformPersistentTasksExecutor extends PersistentTasksExecutor<Tr
this.auditor = auditor; this.auditor = auditor;
this.threadPool = threadPool; this.threadPool = threadPool;
this.clusterService = clusterService; this.clusterService = clusterService;
this.numFailureRetries = TransformTask.NUM_FAILURE_RETRIES_SETTING.get(settings); this.numFailureRetries = Transform.NUM_FAILURE_RETRIES_SETTING.get(settings);
clusterService.getClusterSettings() clusterService.getClusterSettings().addSettingsUpdateConsumer(Transform.NUM_FAILURE_RETRIES_SETTING, this::setNumFailureRetries);
.addSettingsUpdateConsumer(TransformTask.NUM_FAILURE_RETRIES_SETTING, this::setNumFailureRetries);
} }
@Override @Override
public PersistentTasksCustomMetaData.Assignment getAssignment(TransformTaskParams params, ClusterState clusterState) { public PersistentTasksCustomMetaData.Assignment getAssignment(TransformTaskParams params, ClusterState clusterState) {
List<String> unavailableIndices = verifyIndicesPrimaryShardsAreActive(clusterState); List<String> unavailableIndices = verifyIndicesPrimaryShardsAreActive(clusterState);
if (unavailableIndices.size() != 0) { if (unavailableIndices.size() != 0) {
String reason = "Not starting transform [" + params.getId() + "], " + String reason = "Not starting transform ["
"because not all primary shards are active for the following indices [" + + params.getId()
String.join(",", unavailableIndices) + "]"; + "], "
+ "because not all primary shards are active for the following indices ["
+ String.join(",", unavailableIndices)
+ "]";
logger.debug(reason); logger.debug(reason);
return new PersistentTasksCustomMetaData.Assignment(null, reason); return new PersistentTasksCustomMetaData.Assignment(null, reason);
} }
// see gh#48019 disable assignment if any node is using 7.2 or 7.3 // see gh#48019 disable assignment if any node is using 7.2 or 7.3
if (clusterState.getNodes().getMinNodeVersion().before(Version.V_7_4_0)) { if (clusterState.getNodes().getMinNodeVersion().before(Version.V_7_4_0)) {
String reason = "Not starting transform [" + params.getId() + "], " + String reason = "Not starting transform ["
"because cluster contains nodes with version older than 7.4.0"; + params.getId()
+ "], "
+ "because cluster contains nodes with version older than 7.4.0";
logger.debug(reason); logger.debug(reason);
return new PersistentTasksCustomMetaData.Assignment(null, reason); return new PersistentTasksCustomMetaData.Assignment(null, reason);
} }
DiscoveryNode discoveryNode = selectLeastLoadedNode(clusterState, (node) -> DiscoveryNode discoveryNode = selectLeastLoadedNode(
node.isDataNode() && clusterState,
node.getVersion().onOrAfter(params.getVersion()) (node) -> node.isDataNode() && node.getVersion().onOrAfter(params.getVersion())
); );
return discoveryNode == null ? NO_NODE_FOUND : new PersistentTasksCustomMetaData.Assignment(discoveryNode.getId(), ""); return discoveryNode == null ? NO_NODE_FOUND : new PersistentTasksCustomMetaData.Assignment(discoveryNode.getId(), "");
} }
static List<String> verifyIndicesPrimaryShardsAreActive(ClusterState clusterState) { static List<String> verifyIndicesPrimaryShardsAreActive(ClusterState clusterState) {
IndexNameExpressionResolver resolver = new IndexNameExpressionResolver(); IndexNameExpressionResolver resolver = new IndexNameExpressionResolver();
String[] indices = resolver.concreteIndexNames(clusterState, String[] indices = resolver
.concreteIndexNames(
clusterState,
IndicesOptions.lenientExpandOpen(), IndicesOptions.lenientExpandOpen(),
TransformInternalIndexConstants.INDEX_NAME_PATTERN, TransformInternalIndexConstants.INDEX_NAME_PATTERN,
TransformInternalIndexConstants.INDEX_NAME_PATTERN_DEPRECATED); TransformInternalIndexConstants.INDEX_NAME_PATTERN_DEPRECATED
);
List<String> unavailableIndices = new ArrayList<>(indices.length); List<String> unavailableIndices = new ArrayList<>(indices.length);
for (String index : indices) { for (String index : indices) {
IndexRoutingTable routingTable = clusterState.getRoutingTable().index(index); IndexRoutingTable routingTable = clusterState.getRoutingTable().index(index);
@ -145,8 +154,7 @@ public class TransformPersistentTasksExecutor extends PersistentTasksExecutor<Tr
// We want the rest of the state to be populated in the task when it is loaded on the node so that users can force start it again // We want the rest of the state to be populated in the task when it is loaded on the node so that users can force start it again
// later if they want. // later if they want.
final ClientTransformIndexerBuilder indexerBuilder = final ClientTransformIndexerBuilder indexerBuilder = new ClientTransformIndexerBuilder()
new ClientTransformIndexerBuilder()
.setAuditor(auditor) .setAuditor(auditor)
.setClient(client) .setClient(client)
.setTransformsCheckpointService(transformCheckpointService) .setTransformsCheckpointService(transformCheckpointService)
@ -154,18 +162,18 @@ public class TransformPersistentTasksExecutor extends PersistentTasksExecutor<Tr
final SetOnce<TransformState> stateHolder = new SetOnce<>(); final SetOnce<TransformState> stateHolder = new SetOnce<>();
ActionListener<StartTransformAction.Response> startTaskListener = ActionListener.wrap( ActionListener<StartTransformAction.Response> startTaskListener = ActionListener
response -> logger.info("[{}] successfully completed and scheduled task in node operation", transformId), .wrap(response -> logger.info("[{}] successfully completed and scheduled task in node operation", transformId), failure -> {
failure -> { auditor
auditor.error(transformId, "Failed to start transform. " + .error(
"Please stop and attempt to start again. Failure: " + failure.getMessage()); transformId,
logger.error("Failed to start task ["+ transformId +"] in node operation", failure); "Failed to start transform. " + "Please stop and attempt to start again. Failure: " + failure.getMessage()
}
); );
logger.error("Failed to start task [" + transformId + "] in node operation", failure);
});
// <7> load next checkpoint // <7> load next checkpoint
ActionListener<TransformCheckpoint> getTransformNextCheckpointListener = ActionListener.wrap( ActionListener<TransformCheckpoint> getTransformNextCheckpointListener = ActionListener.wrap(nextCheckpoint -> {
nextCheckpoint -> {
if (nextCheckpoint.isEmpty()) { if (nextCheckpoint.isEmpty()) {
// extra safety: reset position and progress if next checkpoint is empty // extra safety: reset position and progress if next checkpoint is empty
@ -173,60 +181,57 @@ public class TransformPersistentTasksExecutor extends PersistentTasksExecutor<Tr
indexerBuilder.setInitialPosition(null); indexerBuilder.setInitialPosition(null);
indexerBuilder.setProgress(null); indexerBuilder.setProgress(null);
} else { } else {
logger.trace("[{}] Loaded next checkpoint [{}] found, starting the task", transformId, logger.trace("[{}] Loaded next checkpoint [{}] found, starting the task", transformId, nextCheckpoint.getCheckpoint());
nextCheckpoint.getCheckpoint());
indexerBuilder.setNextCheckpoint(nextCheckpoint); indexerBuilder.setNextCheckpoint(nextCheckpoint);
} }
final long lastCheckpoint = stateHolder.get().getCheckpoint(); final long lastCheckpoint = stateHolder.get().getCheckpoint();
startTask(buildTask, indexerBuilder, lastCheckpoint, startTaskListener); startTask(buildTask, indexerBuilder, lastCheckpoint, startTaskListener);
}, }, error -> {
error -> {
// TODO: do not use the same error message as for loading the last checkpoint // TODO: do not use the same error message as for loading the last checkpoint
String msg = TransformMessages.getMessage(TransformMessages.FAILED_TO_LOAD_TRANSFORM_CHECKPOINT, transformId); String msg = TransformMessages.getMessage(TransformMessages.FAILED_TO_LOAD_TRANSFORM_CHECKPOINT, transformId);
logger.error(msg, error); logger.error(msg, error);
markAsFailed(buildTask, msg); markAsFailed(buildTask, msg);
} });
);
// <6> load last checkpoint // <6> load last checkpoint
ActionListener<TransformCheckpoint> getTransformLastCheckpointListener = ActionListener.wrap( ActionListener<TransformCheckpoint> getTransformLastCheckpointListener = ActionListener.wrap(lastCheckpoint -> {
lastCheckpoint -> {
indexerBuilder.setLastCheckpoint(lastCheckpoint); indexerBuilder.setLastCheckpoint(lastCheckpoint);
logger.trace("[{}] Loaded last checkpoint [{}], looking for next checkpoint", transformId, logger.trace("[{}] Loaded last checkpoint [{}], looking for next checkpoint", transformId, lastCheckpoint.getCheckpoint());
lastCheckpoint.getCheckpoint()); transformsConfigManager
transformsConfigManager.getTransformCheckpoint(transformId, lastCheckpoint.getCheckpoint() + 1, .getTransformCheckpoint(transformId, lastCheckpoint.getCheckpoint() + 1, getTransformNextCheckpointListener);
getTransformNextCheckpointListener); }, error -> {
},
error -> {
String msg = TransformMessages.getMessage(TransformMessages.FAILED_TO_LOAD_TRANSFORM_CHECKPOINT, transformId); String msg = TransformMessages.getMessage(TransformMessages.FAILED_TO_LOAD_TRANSFORM_CHECKPOINT, transformId);
logger.error(msg, error); logger.error(msg, error);
markAsFailed(buildTask, msg); markAsFailed(buildTask, msg);
} });
);
// <5> Set the previous stats (if they exist), initialize the indexer, start the task (If it is STOPPED) // <5> Set the previous stats (if they exist), initialize the indexer, start the task (If it is STOPPED)
// Since we don't create the task until `_start` is called, if we see that the task state is stopped, attempt to start // Since we don't create the task until `_start` is called, if we see that the task state is stopped, attempt to start
// Schedule execution regardless // Schedule execution regardless
ActionListener<Tuple<TransformStoredDoc, SeqNoPrimaryTermAndIndex>> transformStatsActionListener = ActionListener.wrap( ActionListener<Tuple<TransformStoredDoc, SeqNoPrimaryTermAndIndex>> transformStatsActionListener = ActionListener
stateAndStatsAndSeqNoPrimaryTermAndIndex -> { .wrap(stateAndStatsAndSeqNoPrimaryTermAndIndex -> {
TransformStoredDoc stateAndStats = stateAndStatsAndSeqNoPrimaryTermAndIndex.v1(); TransformStoredDoc stateAndStats = stateAndStatsAndSeqNoPrimaryTermAndIndex.v1();
SeqNoPrimaryTermAndIndex seqNoPrimaryTermAndIndex = stateAndStatsAndSeqNoPrimaryTermAndIndex.v2(); SeqNoPrimaryTermAndIndex seqNoPrimaryTermAndIndex = stateAndStatsAndSeqNoPrimaryTermAndIndex.v2();
// Since we have not set the value for this yet, it SHOULD be null // Since we have not set the value for this yet, it SHOULD be null
buildTask.updateSeqNoPrimaryTermAndIndex(null, seqNoPrimaryTermAndIndex);
logger.trace("[{}] initializing state and stats: [{}]", transformId, stateAndStats.toString()); logger.trace("[{}] initializing state and stats: [{}]", transformId, stateAndStats.toString());
TransformState transformState = stateAndStats.getTransformState(); TransformState transformState = stateAndStats.getTransformState();
indexerBuilder.setInitialStats(stateAndStats.getTransformStats()) indexerBuilder
.setInitialStats(stateAndStats.getTransformStats())
.setInitialPosition(stateAndStats.getTransformState().getPosition()) .setInitialPosition(stateAndStats.getTransformState().getPosition())
.setProgress(stateAndStats.getTransformState().getProgress()) .setProgress(stateAndStats.getTransformState().getProgress())
.setIndexerState(currentIndexerState(transformState)) .setIndexerState(currentIndexerState(transformState))
.setSeqNoPrimaryTermAndIndex(seqNoPrimaryTermAndIndex)
.setShouldStopAtCheckpoint(transformState.shouldStopAtNextCheckpoint()); .setShouldStopAtCheckpoint(transformState.shouldStopAtNextCheckpoint());
logger.debug("[{}] Loading existing state: [{}], position [{}]", logger
.debug(
"[{}] Loading existing state: [{}], position [{}]",
transformId, transformId,
stateAndStats.getTransformState(), stateAndStats.getTransformState(),
stateAndStats.getTransformState().getPosition()); stateAndStats.getTransformState().getPosition()
);
stateHolder.set(transformState); stateHolder.set(transformState);
final long lastCheckpoint = stateHolder.get().getCheckpoint(); final long lastCheckpoint = stateHolder.get().getCheckpoint();
@ -238,8 +243,7 @@ public class TransformPersistentTasksExecutor extends PersistentTasksExecutor<Tr
logger.trace("[{}] Restore last checkpoint: [{}]", transformId, lastCheckpoint); logger.trace("[{}] Restore last checkpoint: [{}]", transformId, lastCheckpoint);
transformsConfigManager.getTransformCheckpoint(transformId, lastCheckpoint, getTransformLastCheckpointListener); transformsConfigManager.getTransformCheckpoint(transformId, lastCheckpoint, getTransformLastCheckpointListener);
} }
}, }, error -> {
error -> {
if (error instanceof ResourceNotFoundException == false) { if (error instanceof ResourceNotFoundException == false) {
String msg = TransformMessages.getMessage(TransformMessages.FAILED_TO_LOAD_TRANSFORM_STATE, transformId); String msg = TransformMessages.getMessage(TransformMessages.FAILED_TO_LOAD_TRANSFORM_STATE, transformId);
logger.error(msg, error); logger.error(msg, error);
@ -248,50 +252,43 @@ public class TransformPersistentTasksExecutor extends PersistentTasksExecutor<Tr
logger.trace("[{}] No stats found (new transform), starting the task", transformId); logger.trace("[{}] No stats found (new transform), starting the task", transformId);
startTask(buildTask, indexerBuilder, null, startTaskListener); startTask(buildTask, indexerBuilder, null, startTaskListener);
} }
} });
);
// <4> set fieldmappings for the indexer, get the previous stats (if they exist) // <4> set fieldmappings for the indexer, get the previous stats (if they exist)
ActionListener<Map<String, String>> getFieldMappingsListener = ActionListener.wrap( ActionListener<Map<String, String>> getFieldMappingsListener = ActionListener.wrap(fieldMappings -> {
fieldMappings -> {
indexerBuilder.setFieldMappings(fieldMappings); indexerBuilder.setFieldMappings(fieldMappings);
transformsConfigManager.getTransformStoredDoc(transformId, transformStatsActionListener); transformsConfigManager.getTransformStoredDoc(transformId, transformStatsActionListener);
}, }, error -> {
error -> { String msg = TransformMessages
String msg = TransformMessages.getMessage(TransformMessages.UNABLE_TO_GATHER_FIELD_MAPPINGS, .getMessage(
indexerBuilder.getTransformConfig().getDestination().getIndex()); TransformMessages.UNABLE_TO_GATHER_FIELD_MAPPINGS,
indexerBuilder.getTransformConfig().getDestination().getIndex()
);
logger.error(msg, error); logger.error(msg, error);
markAsFailed(buildTask, msg); markAsFailed(buildTask, msg);
} });
);
// <3> Validate the transform, assigning it to the indexer, and get the field mappings // <3> Validate the transform, assigning it to the indexer, and get the field mappings
ActionListener<TransformConfig> getTransformConfigListener = ActionListener.wrap( ActionListener<TransformConfig> getTransformConfigListener = ActionListener.wrap(config -> {
config -> {
if (config.isValid()) { if (config.isValid()) {
indexerBuilder.setTransformConfig(config); indexerBuilder.setTransformConfig(config);
SchemaUtil.getDestinationFieldMappings(client, config.getDestination().getIndex(), getFieldMappingsListener); SchemaUtil.getDestinationFieldMappings(client, config.getDestination().getIndex(), getFieldMappingsListener);
} else { } else {
markAsFailed(buildTask, markAsFailed(buildTask, TransformMessages.getMessage(TransformMessages.TRANSFORM_CONFIGURATION_INVALID, transformId));
TransformMessages.getMessage(TransformMessages.TRANSFORM_CONFIGURATION_INVALID, transformId));
} }
}, }, error -> {
error -> {
String msg = TransformMessages.getMessage(TransformMessages.FAILED_TO_LOAD_TRANSFORM_CONFIGURATION, transformId); String msg = TransformMessages.getMessage(TransformMessages.FAILED_TO_LOAD_TRANSFORM_CONFIGURATION, transformId);
logger.error(msg, error); logger.error(msg, error);
markAsFailed(buildTask, msg); markAsFailed(buildTask, msg);
} });
);
// <2> Get the transform config // <2> Get the transform config
ActionListener<Void> templateCheckListener = ActionListener.wrap( ActionListener<Void> templateCheckListener = ActionListener
aVoid -> transformsConfigManager.getTransformConfiguration(transformId, getTransformConfigListener), .wrap(aVoid -> transformsConfigManager.getTransformConfiguration(transformId, getTransformConfigListener), error -> {
error -> {
String msg = "Failed to create internal index mappings"; String msg = "Failed to create internal index mappings";
logger.error(msg, error); logger.error(msg, error);
markAsFailed(buildTask, msg); markAsFailed(buildTask, msg);
} });
);
// <1> Check the internal index template is installed // <1> Check the internal index template is installed
TransformInternalIndex.installLatestVersionedIndexTemplateIfRequired(clusterService, client, templateCheckListener); TransformInternalIndex.installLatestVersionedIndexTemplateIfRequired(clusterService, client, templateCheckListener);
@ -320,10 +317,15 @@ public class TransformPersistentTasksExecutor extends PersistentTasksExecutor<Tr
private void markAsFailed(TransformTask task, String reason) { private void markAsFailed(TransformTask task, String reason) {
CountDownLatch latch = new CountDownLatch(1); CountDownLatch latch = new CountDownLatch(1);
task.markAsFailed(reason, new LatchedActionListener<>(ActionListener.wrap( task
nil -> {}, .fail(
failure -> logger.error("Failed to set task [" + task.getTransformId() +"] to failed", failure) reason,
), latch)); new LatchedActionListener<>(
ActionListener
.wrap(nil -> {}, failure -> logger.error("Failed to set task [" + task.getTransformId() + "] to failed", failure)),
latch
)
);
try { try {
latch.await(MARK_AS_FAILED_TIMEOUT_SEC, TimeUnit.SECONDS); latch.await(MARK_AS_FAILED_TIMEOUT_SEC, TimeUnit.SECONDS);
} catch (InterruptedException e) { } catch (InterruptedException e) {
@ -331,10 +333,12 @@ public class TransformPersistentTasksExecutor extends PersistentTasksExecutor<Tr
} }
} }
private void startTask(TransformTask buildTask, private void startTask(
TransformTask buildTask,
ClientTransformIndexerBuilder indexerBuilder, ClientTransformIndexerBuilder indexerBuilder,
Long previousCheckpoint, Long previousCheckpoint,
ActionListener<StartTransformAction.Response> listener) { ActionListener<StartTransformAction.Response> listener
) {
buildTask.initializeIndexer(indexerBuilder); buildTask.initializeIndexer(indexerBuilder);
// TransformTask#start will fail if the task state is FAILED // TransformTask#start will fail if the task state is FAILED
buildTask.setNumFailureRetries(numFailureRetries).start(previousCheckpoint, listener); buildTask.setNumFailureRetries(numFailureRetries).start(previousCheckpoint, listener);
@ -345,9 +349,25 @@ public class TransformPersistentTasksExecutor extends PersistentTasksExecutor<Tr
} }
@Override @Override
protected AllocatedPersistentTask createTask(long id, String type, String action, TaskId parentTaskId, protected AllocatedPersistentTask createTask(
PersistentTasksCustomMetaData.PersistentTask<TransformTaskParams> persistentTask, Map<String, String> headers) { long id,
return new TransformTask(id, type, action, parentTaskId, persistentTask.getParams(), String type,
(TransformState) persistentTask.getState(), schedulerEngine, auditor, threadPool, headers); String action,
TaskId parentTaskId,
PersistentTasksCustomMetaData.PersistentTask<TransformTaskParams> persistentTask,
Map<String, String> headers
) {
return new TransformTask(
id,
type,
action,
parentTaskId,
persistentTask.getParams(),
(TransformState) persistentTask.getState(),
schedulerEngine,
auditor,
threadPool,
headers
);
} }
} }

View File

@ -26,53 +26,63 @@ import java.util.function.Function;
*/ */
public final class TransformProgressGatherer { public final class TransformProgressGatherer {
private Client client;
TransformProgressGatherer(Client client) {
this.client = client;
}
/** /**
* This gathers the total docs given the config and search * This gathers the total docs given the config and search
* *
* @param client ES Client to make queries
* @param filterQuery The adapted filter that can optionally take into account checkpoint information * @param filterQuery The adapted filter that can optionally take into account checkpoint information
* @param config The transform config containing headers, source, pivot, etc. information * @param config The transform config containing headers, source, pivot, etc. information
* @param progressListener The listener to notify when progress object has been created * @param progressListener The listener to notify when progress object has been created
*/ */
public static void getInitialProgress(Client client, public void getInitialProgress(QueryBuilder filterQuery, TransformConfig config, ActionListener<TransformProgress> progressListener) {
QueryBuilder filterQuery,
TransformConfig config,
ActionListener<TransformProgress> progressListener) {
SearchRequest request = getSearchRequest(config, filterQuery); SearchRequest request = getSearchRequest(config, filterQuery);
ActionListener<SearchResponse> searchResponseActionListener = ActionListener.wrap( ActionListener<SearchResponse> searchResponseActionListener = ActionListener
.wrap(
searchResponse -> progressListener.onResponse(searchResponseToTransformProgressFunction().apply(searchResponse)), searchResponse -> progressListener.onResponse(searchResponseToTransformProgressFunction().apply(searchResponse)),
progressListener::onFailure progressListener::onFailure
); );
ClientHelper.executeWithHeadersAsync(config.getHeaders(), ClientHelper
.executeWithHeadersAsync(
config.getHeaders(),
ClientHelper.TRANSFORM_ORIGIN, ClientHelper.TRANSFORM_ORIGIN,
client, client,
SearchAction.INSTANCE, SearchAction.INSTANCE,
request, request,
searchResponseActionListener); searchResponseActionListener
);
} }
public static SearchRequest getSearchRequest(TransformConfig config, QueryBuilder filteredQuery) { public static SearchRequest getSearchRequest(TransformConfig config, QueryBuilder filteredQuery) {
SearchRequest request = new SearchRequest(config.getSource().getIndex()); SearchRequest request = new SearchRequest(config.getSource().getIndex());
request.allowPartialSearchResults(false); request.allowPartialSearchResults(false);
BoolQueryBuilder existsClauses = QueryBuilders.boolQuery(); BoolQueryBuilder existsClauses = QueryBuilders.boolQuery();
config.getPivotConfig() config
.getPivotConfig()
.getGroupConfig() .getGroupConfig()
.getGroups() .getGroups()
.values() .values()
// TODO change once we allow missing_buckets // TODO change once we allow missing_buckets
.forEach(src -> existsClauses.must(QueryBuilders.existsQuery(src.getField()))); .forEach(src -> existsClauses.must(QueryBuilders.existsQuery(src.getField())));
request.source(new SearchSourceBuilder() request
.source(
new SearchSourceBuilder()
.size(0) .size(0)
.trackTotalHits(true) .trackTotalHits(true)
.query(QueryBuilders.boolQuery() .query(QueryBuilders.boolQuery().filter(filteredQuery).filter(existsClauses))
.filter(filteredQuery) );
.filter(existsClauses)));
return request; return request;
} }
public static Function<SearchResponse, TransformProgress> searchResponseToTransformProgressFunction() { public static Function<SearchResponse, TransformProgress> searchResponseToTransformProgressFunction() {
return searchResponse -> new TransformProgress(searchResponse.getHits().getTotalHits().value, 0L, 0L); return searchResponse -> searchResponse != null
? new TransformProgress(searchResponse.getHits().getTotalHits().value, 0L, 0L)
: null;
} }
} }

View File

@ -13,8 +13,6 @@ import org.apache.lucene.util.SetOnce;
import org.elasticsearch.ElasticsearchException; import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.ElasticsearchStatusException; import org.elasticsearch.ElasticsearchStatusException;
import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.ActionListener;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.persistent.AllocatedPersistentTask; import org.elasticsearch.persistent.AllocatedPersistentTask;
import org.elasticsearch.persistent.PersistentTasksCustomMetaData; import org.elasticsearch.persistent.PersistentTasksCustomMetaData;
@ -27,40 +25,26 @@ import org.elasticsearch.xpack.core.scheduler.SchedulerEngine.Event;
import org.elasticsearch.xpack.core.transform.TransformField; import org.elasticsearch.xpack.core.transform.TransformField;
import org.elasticsearch.xpack.core.transform.TransformMessages; import org.elasticsearch.xpack.core.transform.TransformMessages;
import org.elasticsearch.xpack.core.transform.action.StartTransformAction; import org.elasticsearch.xpack.core.transform.action.StartTransformAction;
import org.elasticsearch.xpack.core.transform.transforms.TransformCheckpointingInfo;
import org.elasticsearch.xpack.core.transform.transforms.TransformIndexerPosition; import org.elasticsearch.xpack.core.transform.transforms.TransformIndexerPosition;
import org.elasticsearch.xpack.core.transform.transforms.TransformIndexerStats; import org.elasticsearch.xpack.core.transform.transforms.TransformIndexerStats;
import org.elasticsearch.xpack.core.transform.transforms.TransformTaskParams;
import org.elasticsearch.xpack.core.transform.transforms.TransformCheckpointingInfo;
import org.elasticsearch.xpack.core.transform.transforms.TransformState; import org.elasticsearch.xpack.core.transform.transforms.TransformState;
import org.elasticsearch.xpack.core.transform.transforms.TransformTaskParams;
import org.elasticsearch.xpack.core.transform.transforms.TransformTaskState; import org.elasticsearch.xpack.core.transform.transforms.TransformTaskState;
import org.elasticsearch.xpack.transform.checkpoint.TransformCheckpointService; import org.elasticsearch.xpack.transform.checkpoint.TransformCheckpointService;
import org.elasticsearch.xpack.transform.notifications.TransformAuditor; import org.elasticsearch.xpack.transform.notifications.TransformAuditor;
import org.elasticsearch.xpack.transform.persistence.SeqNoPrimaryTermAndIndex;
import java.util.Arrays; import java.util.Arrays;
import java.util.Map; import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import static org.elasticsearch.xpack.core.transform.TransformMessages.CANNOT_START_FAILED_TRANSFORM; import static org.elasticsearch.xpack.core.transform.TransformMessages.CANNOT_START_FAILED_TRANSFORM;
import static org.elasticsearch.xpack.core.transform.TransformMessages.CANNOT_STOP_FAILED_TRANSFORM; import static org.elasticsearch.xpack.core.transform.TransformMessages.CANNOT_STOP_FAILED_TRANSFORM;
public class TransformTask extends AllocatedPersistentTask implements SchedulerEngine.Listener, TransformContext.Listener {
public class TransformTask extends AllocatedPersistentTask implements SchedulerEngine.Listener {
// Default interval the scheduler sends an event if the config does not specify a frequency // Default interval the scheduler sends an event if the config does not specify a frequency
private static final long SCHEDULER_NEXT_MILLISECONDS = 60000; private static final long SCHEDULER_NEXT_MILLISECONDS = 60000;
private static final Logger logger = LogManager.getLogger(TransformTask.class); private static final Logger logger = LogManager.getLogger(TransformTask.class);
private static final int DEFAULT_FAILURE_RETRIES = 10;
private volatile int numFailureRetries = DEFAULT_FAILURE_RETRIES;
// How many times the transform task can retry on an non-critical failure
public static final Setting<Integer> NUM_FAILURE_RETRIES_SETTING = Setting.intSetting(
"xpack.transform.num_transform_failure_retries",
DEFAULT_FAILURE_RETRIES,
0,
100,
Setting.Property.NodeScope,
Setting.Property.Dynamic);
private static final IndexerState[] RUNNING_STATES = new IndexerState[] { IndexerState.STARTED, IndexerState.INDEXING }; private static final IndexerState[] RUNNING_STATES = new IndexerState[] { IndexerState.STARTED, IndexerState.INDEXING };
public static final String SCHEDULE_NAME = TransformField.TASK_NAME + "/schedule"; public static final String SCHEDULE_NAME = TransformField.TASK_NAME + "/schedule";
@ -70,19 +54,21 @@ public class TransformTask extends AllocatedPersistentTask implements SchedulerE
private final TransformAuditor auditor; private final TransformAuditor auditor;
private final TransformIndexerPosition initialPosition; private final TransformIndexerPosition initialPosition;
private final IndexerState initialIndexerState; private final IndexerState initialIndexerState;
private final TransformContext context;
private final SetOnce<ClientTransformIndexer> indexer = new SetOnce<>(); private final SetOnce<ClientTransformIndexer> indexer = new SetOnce<>();
private final AtomicReference<TransformTaskState> taskState; public TransformTask(
private final AtomicReference<String> stateReason; long id,
private final AtomicReference<SeqNoPrimaryTermAndIndex> seqNoPrimaryTermAndIndex = new AtomicReference<>(null); String type,
// the checkpoint of this transform, storing the checkpoint until data indexing from source to dest is _complete_ String action,
// Note: Each indexer run creates a new future checkpoint which becomes the current checkpoint only after the indexer run finished TaskId parentTask,
private final AtomicLong currentCheckpoint; TransformTaskParams transform,
TransformState state,
public TransformTask(long id, String type, String action, TaskId parentTask, TransformTaskParams transform, SchedulerEngine schedulerEngine,
TransformState state, SchedulerEngine schedulerEngine, TransformAuditor auditor, TransformAuditor auditor,
ThreadPool threadPool, Map<String, String> headers) { ThreadPool threadPool,
Map<String, String> headers
) {
super(id, type, action, TransformField.PERSISTENT_TASK_DESCRIPTION_PREFIX + transform.getId(), parentTask, headers); super(id, type, action, TransformField.PERSISTENT_TASK_DESCRIPTION_PREFIX + transform.getId(), parentTask, headers);
this.transform = transform; this.transform = transform;
this.schedulerEngine = schedulerEngine; this.schedulerEngine = schedulerEngine;
@ -91,8 +77,9 @@ public class TransformTask extends AllocatedPersistentTask implements SchedulerE
IndexerState initialState = IndexerState.STOPPED; IndexerState initialState = IndexerState.STOPPED;
TransformTaskState initialTaskState = TransformTaskState.STOPPED; TransformTaskState initialTaskState = TransformTaskState.STOPPED;
String initialReason = null; String initialReason = null;
long initialGeneration = 0; long initialCheckpoint = 0;
TransformIndexerPosition initialPosition = null; TransformIndexerPosition initialPosition = null;
if (state != null) { if (state != null) {
initialTaskState = state.getTaskState(); initialTaskState = state.getTaskState();
initialReason = state.getReason(); initialReason = state.getReason();
@ -107,14 +94,13 @@ public class TransformTask extends AllocatedPersistentTask implements SchedulerE
initialState = existingState; initialState = existingState;
} }
initialPosition = state.getPosition(); initialPosition = state.getPosition();
initialGeneration = state.getCheckpoint(); initialCheckpoint = state.getCheckpoint();
} }
this.initialIndexerState = initialState; this.initialIndexerState = initialState;
this.initialPosition = initialPosition; this.initialPosition = initialPosition;
this.currentCheckpoint = new AtomicLong(initialGeneration);
this.taskState = new AtomicReference<>(initialTaskState); this.context = new TransformContext(initialTaskState, initialReason, initialCheckpoint, this);
this.stateReason = new AtomicReference<>(initialReason);
} }
public String getTransformId() { public String getTransformId() {
@ -136,24 +122,26 @@ public class TransformTask extends AllocatedPersistentTask implements SchedulerE
public TransformState getState() { public TransformState getState() {
if (getIndexer() == null) { if (getIndexer() == null) {
return new TransformState( return new TransformState(
taskState.get(), context.getTaskState(),
initialIndexerState, initialIndexerState,
initialPosition, initialPosition,
currentCheckpoint.get(), context.getCheckpoint(),
stateReason.get(), context.getStateReason(),
null, null,
null, null,
false); false
);
} else { } else {
return new TransformState( return new TransformState(
taskState.get(), context.getTaskState(),
indexer.get().getState(), indexer.get().getState(),
indexer.get().getPosition(), indexer.get().getPosition(),
currentCheckpoint.get(), context.getCheckpoint(),
stateReason.get(), context.getStateReason(),
getIndexer().getProgress(), getIndexer().getProgress(),
null, null,
getIndexer().shouldStopAtCheckpoint()); context.shouldStopAtCheckpoint()
);
} }
} }
@ -165,138 +153,149 @@ public class TransformTask extends AllocatedPersistentTask implements SchedulerE
} }
} }
public long getCheckpoint() { public void getCheckpointingInfo(
return currentCheckpoint.get(); TransformCheckpointService transformsCheckpointService,
} ActionListener<TransformCheckpointingInfo> listener
) {
long incrementCheckpoint() {
return currentCheckpoint.getAndIncrement();
}
public void getCheckpointingInfo(TransformCheckpointService transformsCheckpointService,
ActionListener<TransformCheckpointingInfo> listener) {
ClientTransformIndexer indexer = getIndexer(); ClientTransformIndexer indexer = getIndexer();
if (indexer == null) { if (indexer == null) {
transformsCheckpointService.getCheckpointingInfo( transformsCheckpointService.getCheckpointingInfo(transform.getId(), context.getCheckpoint(), initialPosition, null, listener);
transform.getId(),
currentCheckpoint.get(),
initialPosition,
null,
listener);
return; return;
} }
indexer.getCheckpointProvider().getCheckpointingInfo( indexer
.getCheckpointProvider()
.getCheckpointingInfo(
indexer.getLastCheckpoint(), indexer.getLastCheckpoint(),
indexer.getNextCheckpoint(), indexer.getNextCheckpoint(),
indexer.getPosition(), indexer.getPosition(),
indexer.getProgress(), indexer.getProgress(),
ActionListener.wrap( ActionListener.wrap(info -> {
info -> { if (context.getChangesLastDetectedAt() == null) {
if (indexer.getChangesLastDetectedAt() == null) {
listener.onResponse(info); listener.onResponse(info);
} else { } else {
listener.onResponse(info.setChangesLastDetectedAt(indexer.getChangesLastDetectedAt())); listener.onResponse(info.setChangesLastDetectedAt(context.getChangesLastDetectedAt()));
} }
}, }, listener::onFailure)
listener::onFailure );
));
} }
/** /**
* Starts the transform and schedules it to be triggered in the future. * Starts the transform and schedules it to be triggered in the future.
* *
* NOTE: This should ONLY be called via {@link TransformPersistentTasksExecutor}
* *
* @param startingCheckpoint The starting checkpoint, could null. Null indicates that there is no starting checkpoint * @param startingCheckpoint The starting checkpoint, could null. Null indicates that there is no starting checkpoint
* @param listener The listener to alert once started * @param listener The listener to alert once started
*/ */
synchronized void start(Long startingCheckpoint, ActionListener<StartTransformAction.Response> listener) { synchronized void start(Long startingCheckpoint, ActionListener<StartTransformAction.Response> listener) {
logger.debug("[{}] start called with state [{}].", getTransformId(), getState()); logger.debug("[{}] start called with state [{}].", getTransformId(), getState());
if (taskState.get() == TransformTaskState.FAILED) { if (context.getTaskState() == TransformTaskState.FAILED) {
listener.onFailure(new ElasticsearchStatusException( listener
TransformMessages.getMessage(CANNOT_START_FAILED_TRANSFORM, .onFailure(
getTransformId(), new ElasticsearchStatusException(
stateReason.get()), TransformMessages.getMessage(CANNOT_START_FAILED_TRANSFORM, getTransformId(), context.getStateReason()),
RestStatus.CONFLICT)); RestStatus.CONFLICT
)
);
return; return;
} }
if (getIndexer() == null) { if (getIndexer() == null) {
// If our state is failed AND the indexer is null, the user needs to _stop?force=true so that the indexer gets // If our state is failed AND the indexer is null, the user needs to _stop?force=true so that the indexer gets
// fully initialized. // fully initialized.
// If we are NOT failed, then we can assume that `start` was just called early in the process. // If we are NOT failed, then we can assume that `start` was just called early in the process.
String msg = taskState.get() == TransformTaskState.FAILED ? String msg = context.getTaskState() == TransformTaskState.FAILED
"It failed during the initialization process; force stop to allow reinitialization." : ? "It failed during the initialization process; force stop to allow reinitialization."
"Try again later."; : "Try again later.";
listener.onFailure(new ElasticsearchStatusException("Task for transform [{}] not fully initialized. {}", listener
.onFailure(
new ElasticsearchStatusException(
"Task for transform [{}] not fully initialized. {}",
RestStatus.CONFLICT, RestStatus.CONFLICT,
getTransformId(), getTransformId(),
msg)); msg
)
);
return; return;
} }
final IndexerState newState = getIndexer().start(); final IndexerState newState = getIndexer().start();
if (Arrays.stream(RUNNING_STATES).noneMatch(newState::equals)) { if (Arrays.stream(RUNNING_STATES).noneMatch(newState::equals)) {
listener.onFailure(new ElasticsearchException("Cannot start task for transform [{}], because state was [{}]", listener
transform.getId(), newState)); .onFailure(
new ElasticsearchException("Cannot start task for transform [{}], because state was [{}]", transform.getId(), newState)
);
return; return;
} }
stateReason.set(null); context.resetTaskState();
taskState.set(TransformTaskState.STARTED);
if (startingCheckpoint != null) { if (startingCheckpoint != null) {
currentCheckpoint.set(startingCheckpoint); context.setCheckpoint(startingCheckpoint);
} }
final TransformState state = new TransformState( final TransformState state = new TransformState(
TransformTaskState.STARTED, TransformTaskState.STARTED,
IndexerState.STOPPED, IndexerState.STOPPED,
getIndexer().getPosition(), getIndexer().getPosition(),
currentCheckpoint.get(), context.getCheckpoint(),
null, null,
getIndexer().getProgress(), getIndexer().getProgress(),
null, null,
getIndexer().shouldStopAtCheckpoint()); context.shouldStopAtCheckpoint()
);
logger.info("[{}] updating state for transform to [{}].", transform.getId(), state.toString()); logger.info("[{}] updating state for transform to [{}].", transform.getId(), state.toString());
// Even though the indexer information is persisted to an index, we still need TransformTaskState in the clusterstate // Even though the indexer information is persisted to an index, we still need TransformTaskState in the clusterstate
// This keeps track of STARTED, FAILED, STOPPED // This keeps track of STARTED, FAILED, STOPPED
// This is because a FAILED state can occur because we cannot read the config from the internal index, which would imply that // This is because a FAILED state can occur because we cannot read the config from the internal index, which would imply that
// we could not read the previous state information from said index. // we could not read the previous state information from said index.
persistStateToClusterState(state, ActionListener.wrap( persistStateToClusterState(state, ActionListener.wrap(task -> {
task -> { auditor.info(transform.getId(), "Updated transform state to [" + state.getTaskState() + "].");
auditor.info(transform.getId(),
"Updated transform state to [" + state.getTaskState() + "].");
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
// kick off the indexer // kick off the indexer
triggered(new Event(schedulerJobName(), now, now)); triggered(new Event(schedulerJobName(), now, now));
registerWithSchedulerJob(); registerWithSchedulerJob();
listener.onResponse(new StartTransformAction.Response(true)); listener.onResponse(new StartTransformAction.Response(true));
}, }, exc -> {
exc -> { auditor
auditor.warning(transform.getId(), .warning(
"Failed to persist to cluster state while marking task as started. Failure: " + exc.getMessage()); transform.getId(),
"Failed to persist to cluster state while marking task as started. Failure: " + exc.getMessage()
);
logger.error(new ParameterizedMessage("[{}] failed updating state to [{}].", getTransformId(), state), exc); logger.error(new ParameterizedMessage("[{}] failed updating state to [{}].", getTransformId(), state), exc);
getIndexer().stop(); getIndexer().stop();
listener.onFailure(new ElasticsearchException("Error while updating state for transform [" listener
+ transform.getId() + "] to [" + state.getIndexerState() + "].", exc)); .onFailure(
new ElasticsearchException(
"Error while updating state for transform [" + transform.getId() + "] to [" + state.getIndexerState() + "].",
exc
)
);
}));
} }
));
void setShouldStopAtCheckpoint(boolean shouldStopAtCheckpoint) {
this.context.setShouldStopAtCheckpoint(shouldStopAtCheckpoint);
} }
/** /**
* This sets the flag for the task to stop at the next checkpoint. * This sets the flag for the task to stop at the next checkpoint.
* *
* If first persists the flag to cluster state, and then mutates the local variable. * If first persists the flag and then mutates the local variable.
* *
* It only persists to cluster state if the value is different than what is currently held in memory. * It only persists if the value is different than what is currently held in memory.
* @param shouldStopAtCheckpoint whether or not we should stop at the next checkpoint or not * @param shouldStopAtCheckpoint whether or not we should stop at the next checkpoint or not
* @param shouldStopAtCheckpointListener the listener to return to when we have persisted the updated value to the state index. * @param shouldStopAtCheckpointListener the listener to return to when we have persisted the updated value to the state index.
*/ */
public synchronized void setShouldStopAtCheckpoint(boolean shouldStopAtCheckpoint, public synchronized void setShouldStopAtCheckpoint(
ActionListener<Void> shouldStopAtCheckpointListener) { boolean shouldStopAtCheckpoint,
logger.debug("[{}] attempted to set task to stop at checkpoint [{}] with state [{}]", ActionListener<Void> shouldStopAtCheckpointListener
) {
logger
.debug(
"[{}] attempted to set task to stop at checkpoint [{}] with state [{}]",
getTransformId(), getTransformId(),
shouldStopAtCheckpoint, shouldStopAtCheckpoint,
getState()); getState()
if (taskState.get() != TransformTaskState.STARTED || getIndexer() == null) { );
if (context.getTaskState() != TransformTaskState.STARTED || getIndexer() == null) {
shouldStopAtCheckpointListener.onResponse(null); shouldStopAtCheckpointListener.onResponse(null);
return; return;
} }
@ -304,11 +303,14 @@ public class TransformTask extends AllocatedPersistentTask implements SchedulerE
} }
public synchronized void stop(boolean force, boolean shouldStopAtCheckpoint) { public synchronized void stop(boolean force, boolean shouldStopAtCheckpoint) {
logger.debug("[{}] stop called with force [{}], shouldStopAtCheckpoint [{}], state [{}]", logger
.debug(
"[{}] stop called with force [{}], shouldStopAtCheckpoint [{}], state [{}]",
getTransformId(), getTransformId(),
force, force,
shouldStopAtCheckpoint, shouldStopAtCheckpoint,
getState()); getState()
);
if (getIndexer() == null) { if (getIndexer() == null) {
// If there is no indexer the task has not been triggered // If there is no indexer the task has not been triggered
// but it still needs to be stopped and removed // but it still needs to be stopped and removed
@ -320,17 +322,17 @@ public class TransformTask extends AllocatedPersistentTask implements SchedulerE
return; return;
} }
if (taskState.get() == TransformTaskState.FAILED && force == false) { if (context.getTaskState() == TransformTaskState.FAILED && force == false) {
throw new ElasticsearchStatusException( throw new ElasticsearchStatusException(
TransformMessages.getMessage(CANNOT_STOP_FAILED_TRANSFORM, TransformMessages.getMessage(CANNOT_STOP_FAILED_TRANSFORM, getTransformId(), context.getStateReason()),
getTransformId(), RestStatus.CONFLICT
stateReason.get()), );
RestStatus.CONFLICT);
} }
stateReason.set(null); context.resetReasonAndFailureCounter();
// No reason to keep it in the potentially failed state. // No reason to keep it in the potentially failed state.
boolean wasFailed = taskState.compareAndSet(TransformTaskState.FAILED, TransformTaskState.STARTED); boolean wasFailed = context.setTaskState(TransformTaskState.FAILED, TransformTaskState.STARTED);
// shouldStopAtCheckpoint only comes into play when onFinish is called (or doSaveState right after). // shouldStopAtCheckpoint only comes into play when onFinish is called (or doSaveState right after).
// if it is false, stop immediately // if it is false, stop immediately
if (shouldStopAtCheckpoint == false || if (shouldStopAtCheckpoint == false ||
@ -360,18 +362,21 @@ public class TransformTask extends AllocatedPersistentTask implements SchedulerE
return; return;
} }
if (taskState.get() == TransformTaskState.FAILED || taskState.get() == TransformTaskState.STOPPED) { if (context.getTaskState() == TransformTaskState.FAILED || context.getTaskState() == TransformTaskState.STOPPED) {
logger.debug("[{}] schedule was triggered for transform but task is [{}]. Ignoring trigger.", logger
.debug(
"[{}] schedule was triggered for transform but task is [{}]. Ignoring trigger.",
getTransformId(), getTransformId(),
taskState.get()); context.getTaskState()
);
return; return;
} }
// ignore trigger if indexer is running or completely stopped // ignore trigger if indexer is running or completely stopped
IndexerState indexerState = getIndexer().getState(); IndexerState indexerState = getIndexer().getState();
if (IndexerState.INDEXING.equals(indexerState) || if (IndexerState.INDEXING.equals(indexerState)
IndexerState.STOPPING.equals(indexerState) || || IndexerState.STOPPING.equals(indexerState)
IndexerState.STOPPED.equals(indexerState)) { || IndexerState.STOPPED.equals(indexerState)) {
logger.debug("[{}] indexer for transform has state [{}]. Ignoring trigger.", getTransformId(), indexerState); logger.debug("[{}] indexer for transform has state [{}]. Ignoring trigger.", getTransformId(), indexerState);
return; return;
} }
@ -379,7 +384,7 @@ public class TransformTask extends AllocatedPersistentTask implements SchedulerE
logger.debug("[{}] transform indexer schedule has triggered, state: [{}].", event.getJobName(), indexerState); logger.debug("[{}] transform indexer schedule has triggered, state: [{}].", event.getJobName(), indexerState);
// if it runs for the 1st time we just do it, if not we check for changes // if it runs for the 1st time we just do it, if not we check for changes
if (currentCheckpoint.get() == 0) { if (context.getCheckpoint() == 0) {
logger.debug("[{}] trigger initial run.", getTransformId()); logger.debug("[{}] trigger initial run.", getTransformId());
getIndexer().maybeTriggerAsyncJob(System.currentTimeMillis()); getIndexer().maybeTriggerAsyncJob(System.currentTimeMillis());
} else if (getIndexer().isContinuous()) { } else if (getIndexer().isContinuous()) {
@ -391,31 +396,27 @@ public class TransformTask extends AllocatedPersistentTask implements SchedulerE
* Attempt to gracefully cleanup the transform so it can be terminated. * Attempt to gracefully cleanup the transform so it can be terminated.
* This tries to remove the job from the scheduler and completes the persistent task * This tries to remove the job from the scheduler and completes the persistent task
*/ */
synchronized void shutdown() { @Override
public synchronized void shutdown() {
deregisterSchedulerJob(); deregisterSchedulerJob();
markAsCompleted(); markAsCompleted();
} }
void persistStateToClusterState(TransformState state, void persistStateToClusterState(TransformState state, ActionListener<PersistentTasksCustomMetaData.PersistentTask<?>> listener) {
ActionListener<PersistentTasksCustomMetaData.PersistentTask<?>> listener) { updatePersistentTaskState(state, ActionListener.wrap(success -> {
updatePersistentTaskState(state, ActionListener.wrap(
success -> {
logger.debug("[{}] successfully updated state for transform to [{}].", transform.getId(), state.toString()); logger.debug("[{}] successfully updated state for transform to [{}].", transform.getId(), state.toString());
listener.onResponse(success); listener.onResponse(success);
}, }, failure -> {
failure -> { logger.error(new ParameterizedMessage("[{}] failed to update cluster state for transform.", transform.getId()), failure);
logger.error(new ParameterizedMessage("[{}] failed to update cluster state for transform.",
transform.getId()),
failure);
listener.onFailure(failure); listener.onFailure(failure);
} }));
));
} }
synchronized void markAsFailed(String reason, ActionListener<Void> listener) { @Override
public synchronized void fail(String reason, ActionListener<Void> listener) {
// If we are already flagged as failed, this probably means that a second trigger started firing while we were attempting to // If we are already flagged as failed, this probably means that a second trigger started firing while we were attempting to
// flag the previously triggered indexer as failed. Exit early as we are already flagged as failed. // flag the previously triggered indexer as failed. Exit early as we are already flagged as failed.
if (taskState.get() == TransformTaskState.FAILED) { if (context.getTaskState() == TransformTaskState.FAILED) {
logger.warn("[{}] is already failed but encountered new failure; reason [{}].", getTransformId(), reason); logger.warn("[{}] is already failed but encountered new failure; reason [{}].", getTransformId(), reason);
listener.onResponse(null); listener.onResponse(null);
return; return;
@ -441,28 +442,21 @@ public class TransformTask extends AllocatedPersistentTask implements SchedulerE
deregisterSchedulerJob(); deregisterSchedulerJob();
// The idea of stopping at the next checkpoint is no longer valid. Since a failed task could potentially START again, // The idea of stopping at the next checkpoint is no longer valid. Since a failed task could potentially START again,
// we should set this flag to false. // we should set this flag to false.
if (getIndexer() != null) { context.setShouldStopAtCheckpoint(false);
getIndexer().setShouldStopAtCheckpoint(false);
}
// The end user should see that the task is in a failed state, and attempt to stop it again but with force=true // The end user should see that the task is in a failed state, and attempt to stop it again but with force=true
taskState.set(TransformTaskState.FAILED); context.setTaskStateToFailed(reason);
stateReason.set(reason);
TransformState newState = getState(); TransformState newState = getState();
// Even though the indexer information is persisted to an index, we still need TransformTaskState in the clusterstate // Even though the indexer information is persisted to an index, we still need TransformTaskState in the clusterstate
// This keeps track of STARTED, FAILED, STOPPED // This keeps track of STARTED, FAILED, STOPPED
// This is because a FAILED state could occur because we failed to read the config from the internal index, which would imply that // This is because a FAILED state could occur because we failed to read the config from the internal index, which would imply that
// we could not read the previous state information from said index. // we could not read the previous state information from said index.
persistStateToClusterState(newState, ActionListener.wrap( persistStateToClusterState(newState, ActionListener.wrap(r -> listener.onResponse(null), e -> {
r -> listener.onResponse(null),
e -> {
String msg = "Failed to persist to cluster state while marking task as failed with reason [" + reason + "]."; String msg = "Failed to persist to cluster state while marking task as failed with reason [" + reason + "].";
auditor.warning(transform.getId(), auditor.warning(transform.getId(), msg + " Failure: " + e.getMessage());
msg + " Failure: " + e.getMessage()); logger.error(new ParameterizedMessage("[{}] {}", getTransformId(), msg), e);
logger.error(new ParameterizedMessage("[{}] {}", getTransformId(), msg),
e);
listener.onFailure(e); listener.onFailure(e);
} }));
));
} }
/** /**
@ -472,9 +466,7 @@ public class TransformTask extends AllocatedPersistentTask implements SchedulerE
*/ */
@Override @Override
public synchronized void onCancelled() { public synchronized void onCancelled() {
logger.info("[{}] received cancellation request for transform, state: [{}].", logger.info("[{}] received cancellation request for transform, state: [{}].", getTransformId(), context.getTaskState());
getTransformId(),
taskState.get());
if (getIndexer() != null && getIndexer().abort()) { if (getIndexer() != null && getIndexer().abort()) {
// there is no background transform running, we can shutdown safely // there is no background transform running, we can shutdown safely
shutdown(); shutdown();
@ -482,14 +474,10 @@ public class TransformTask extends AllocatedPersistentTask implements SchedulerE
} }
TransformTask setNumFailureRetries(int numFailureRetries) { TransformTask setNumFailureRetries(int numFailureRetries) {
this.numFailureRetries = numFailureRetries; context.setNumFailureRetries(numFailureRetries);
return this; return this;
} }
int getNumFailureRetries() {
return numFailureRetries;
}
private void registerWithSchedulerJob() { private void registerWithSchedulerJob() {
schedulerEngine.register(this); schedulerEngine.register(this);
final SchedulerEngine.Job schedulerJob = new SchedulerEngine.Job(schedulerJobName(), next()); final SchedulerEngine.Job schedulerJob = new SchedulerEngine.Job(schedulerJobName(), next());
@ -513,20 +501,7 @@ public class TransformTask extends AllocatedPersistentTask implements SchedulerE
} }
synchronized void initializeIndexer(ClientTransformIndexerBuilder indexerBuilder) { synchronized void initializeIndexer(ClientTransformIndexerBuilder indexerBuilder) {
indexer.set(indexerBuilder.build(this)); indexer.set(indexerBuilder.build(getThreadPool().executor(ThreadPool.Names.GENERIC), context));
}
void updateSeqNoPrimaryTermAndIndex(SeqNoPrimaryTermAndIndex expectedValue, SeqNoPrimaryTermAndIndex newValue) {
boolean updated = seqNoPrimaryTermAndIndex.compareAndSet(expectedValue, newValue);
// This should never happen. We ONLY ever update this value if at initialization or we just finished updating the document
// famous last words...
assert updated :
"[" + getTransformId() + "] unexpected change to seqNoPrimaryTermAndIndex.";
}
@Nullable
SeqNoPrimaryTermAndIndex getSeqNoPrimaryTermAndIndex() {
return seqNoPrimaryTermAndIndex.get();
} }
ThreadPool getThreadPool() { ThreadPool getThreadPool() {
@ -534,14 +509,7 @@ public class TransformTask extends AllocatedPersistentTask implements SchedulerE
} }
TransformTaskState getTaskState() { TransformTaskState getTaskState() {
return taskState.get(); return context.getTaskState();
} }
void setStateReason(String reason) {
stateReason.set(reason);
}
String getStateReason() {
return stateReason.get();
}
} }

View File

@ -50,33 +50,40 @@ public class DefaultCheckpointProviderTests extends ESTestCase {
client, client,
transformConfigManager, transformConfigManager,
transformAuditor, transformAuditor,
transformConfig); transformConfig
);
assertExpectation( assertExpectation(
new MockLogAppender.SeenEventExpectation("warn when source is empty", new MockLogAppender.SeenEventExpectation(
"warn when source is empty",
checkpointProviderlogger.getName(), checkpointProviderlogger.getName(),
Level.WARN, Level.WARN,
"Source did not resolve to any open indexes for transform [" + transformId + "]"), "[" + transformId + "] Source did not resolve to any open indexes"
new MockTransformAuditor.SeenAuditExpectation("warn when source is empty", ),
new MockTransformAuditor.SeenAuditExpectation(
"warn when source is empty",
org.elasticsearch.xpack.core.common.notifications.Level.WARNING, org.elasticsearch.xpack.core.common.notifications.Level.WARNING,
transformId, transformId,
"Source did not resolve to any open indexes"), "Source did not resolve to any open indexes"
() -> { ),
provider.reportSourceIndexChanges(Collections.singleton("index"), Collections.emptySet()); () -> { provider.reportSourceIndexChanges(Collections.singleton("index"), Collections.emptySet()); }
}); );
assertExpectation( assertExpectation(
new MockLogAppender.UnseenEventExpectation("do not warn if empty again", new MockLogAppender.UnseenEventExpectation(
"do not warn if empty again",
checkpointProviderlogger.getName(), checkpointProviderlogger.getName(),
Level.WARN, Level.WARN,
"Source did not resolve to any concrete indexes"), "Source did not resolve to any concrete indexes"
new MockTransformAuditor.UnseenAuditExpectation("do not warn if empty again", ),
new MockTransformAuditor.UnseenAuditExpectation(
"do not warn if empty again",
org.elasticsearch.xpack.core.common.notifications.Level.WARNING, org.elasticsearch.xpack.core.common.notifications.Level.WARNING,
transformId, transformId,
"Source did not resolve to any concrete indexes"), "Source did not resolve to any concrete indexes"
() -> { ),
provider.reportSourceIndexChanges(Collections.emptySet(), Collections.emptySet()); () -> { provider.reportSourceIndexChanges(Collections.emptySet(), Collections.emptySet()); }
}); );
} }
public void testReportSourceIndexChangesAddDelete() throws Exception { public void testReportSourceIndexChangesAddDelete() throws Exception {
@ -87,48 +94,55 @@ public class DefaultCheckpointProviderTests extends ESTestCase {
client, client,
transformConfigManager, transformConfigManager,
transformAuditor, transformAuditor,
transformConfig); transformConfig
);
assertExpectation( assertExpectation(
new MockLogAppender.SeenEventExpectation("info about adds/removal", new MockLogAppender.SeenEventExpectation(
"info about adds/removal",
checkpointProviderlogger.getName(), checkpointProviderlogger.getName(),
Level.DEBUG, Level.DEBUG,
"Source index resolve found changes, removedIndexes: [index], new indexes: [other_index] for transform [" + "[" + transformId + "] Source index resolve found changes, removedIndexes: [index], new indexes: [other_index]"
transformId + "]"), ),
new MockTransformAuditor.SeenAuditExpectation("info about adds/removal", new MockTransformAuditor.SeenAuditExpectation(
"info about adds/removal",
org.elasticsearch.xpack.core.common.notifications.Level.INFO, org.elasticsearch.xpack.core.common.notifications.Level.INFO,
transformId, transformId,
"Source index resolve found changes, removedIndexes: [index], new indexes: [other_index]"), "Source index resolve found changes, removedIndexes: [index], new indexes: [other_index]"
() -> { ),
provider.reportSourceIndexChanges(Collections.singleton("index"), Collections.singleton("other_index")); () -> { provider.reportSourceIndexChanges(Collections.singleton("index"), Collections.singleton("other_index")); }
}); );
assertExpectation( assertExpectation(
new MockLogAppender.SeenEventExpectation("info about adds/removal", new MockLogAppender.SeenEventExpectation(
"info about adds/removal",
checkpointProviderlogger.getName(), checkpointProviderlogger.getName(),
Level.DEBUG, Level.DEBUG,
"Source index resolve found changes, removedIndexes: [index], new indexes: [] for transform [" + "[" + transformId + "] Source index resolve found changes, removedIndexes: [index], new indexes: []"
transformId + "]"), ),
new MockTransformAuditor.SeenAuditExpectation("info about adds/removal", new MockTransformAuditor.SeenAuditExpectation(
"info about adds/removal",
org.elasticsearch.xpack.core.common.notifications.Level.INFO, org.elasticsearch.xpack.core.common.notifications.Level.INFO,
transformId, transformId,
"Source index resolve found changes, removedIndexes: [index], new indexes: []"), "Source index resolve found changes, removedIndexes: [index], new indexes: []"
() -> { ),
provider.reportSourceIndexChanges(Sets.newHashSet("index", "other_index"), Collections.singleton("other_index")); () -> { provider.reportSourceIndexChanges(Sets.newHashSet("index", "other_index"), Collections.singleton("other_index")); }
}); );
assertExpectation( assertExpectation(
new MockLogAppender.SeenEventExpectation("info about adds/removal", new MockLogAppender.SeenEventExpectation(
"info about adds/removal",
checkpointProviderlogger.getName(), checkpointProviderlogger.getName(),
Level.DEBUG, Level.DEBUG,
"Source index resolve found changes, removedIndexes: [], new indexes: [other_index] for transform [" + "[" + transformId + "] Source index resolve found changes, removedIndexes: [], new indexes: [other_index]"
transformId + "]"), ),
new MockTransformAuditor.SeenAuditExpectation("info about adds/removal", new MockTransformAuditor.SeenAuditExpectation(
"info about adds/removal",
org.elasticsearch.xpack.core.common.notifications.Level.INFO, org.elasticsearch.xpack.core.common.notifications.Level.INFO,
transformId, transformId,
"Source index resolve found changes, removedIndexes: [], new indexes: [other_index]"), "Source index resolve found changes, removedIndexes: [], new indexes: [other_index]"
() -> { ),
provider.reportSourceIndexChanges(Collections.singleton("index"), Sets.newHashSet("index", "other_index")); () -> { provider.reportSourceIndexChanges(Collections.singleton("index"), Sets.newHashSet("index", "other_index")); }
}); );
} }
public void testReportSourceIndexChangesAddDeleteMany() throws Exception { public void testReportSourceIndexChangesAddDeleteMany() throws Exception {
@ -139,7 +153,8 @@ public class DefaultCheckpointProviderTests extends ESTestCase {
client, client,
transformConfigManager, transformConfigManager,
transformAuditor, transformAuditor,
transformConfig); transformConfig
);
HashSet<String> oldSet = new HashSet<>(); HashSet<String> oldSet = new HashSet<>();
for (int i = 0; i < 100; ++i) { for (int i = 0; i < 100; ++i) {
@ -151,23 +166,24 @@ public class DefaultCheckpointProviderTests extends ESTestCase {
} }
assertExpectation( assertExpectation(
new MockLogAppender.SeenEventExpectation("info about adds/removal", new MockLogAppender.SeenEventExpectation(
"info about adds/removal",
checkpointProviderlogger.getName(), checkpointProviderlogger.getName(),
Level.DEBUG, Level.DEBUG,
"Source index resolve found more than 10 changes, [50] removed indexes, [50] new indexes for transform [" + "[" + transformId + "] Source index resolve found more than 10 changes, [50] removed indexes, [50] new indexes"
transformId + "]"), ),
new MockTransformAuditor.SeenAuditExpectation("info about adds/removal", new MockTransformAuditor.SeenAuditExpectation(
"info about adds/removal",
org.elasticsearch.xpack.core.common.notifications.Level.INFO, org.elasticsearch.xpack.core.common.notifications.Level.INFO,
transformId, transformId,
"Source index resolve found more than 10 changes, [50] removed indexes, [50] new indexes"), "Source index resolve found more than 10 changes, [50] removed indexes, [50] new indexes"
() -> { ),
provider.reportSourceIndexChanges(oldSet, newSet); () -> { provider.reportSourceIndexChanges(oldSet, newSet); }
}); );
} }
private void assertExpectation(LoggingExpectation loggingExpectation, private void assertExpectation(LoggingExpectation loggingExpectation, AuditExpectation auditExpectation, Runnable codeBlock)
AuditExpectation auditExpectation, throws IllegalAccessException {
Runnable codeBlock) throws IllegalAccessException {
MockLogAppender mockLogAppender = new MockLogAppender(); MockLogAppender mockLogAppender = new MockLogAppender();
mockLogAppender.start(); mockLogAppender.start();

View File

@ -7,22 +7,22 @@
package org.elasticsearch.xpack.transform.transforms; package org.elasticsearch.xpack.transform.transforms;
import org.elasticsearch.client.Client; import org.elasticsearch.client.Client;
import org.elasticsearch.tasks.TaskId;
import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.xpack.core.indexing.IndexerState; import org.elasticsearch.xpack.core.indexing.IndexerState;
import org.elasticsearch.xpack.core.scheduler.SchedulerEngine;
import org.elasticsearch.xpack.core.transform.transforms.TransformIndexerStats;
import org.elasticsearch.xpack.core.transform.transforms.TransformTaskParams;
import org.elasticsearch.xpack.core.transform.transforms.TransformCheckpoint; import org.elasticsearch.xpack.core.transform.transforms.TransformCheckpoint;
import org.elasticsearch.xpack.core.transform.transforms.TransformConfig; import org.elasticsearch.xpack.core.transform.transforms.TransformConfig;
import org.elasticsearch.xpack.core.transform.transforms.TransformIndexerStats;
import org.elasticsearch.xpack.core.transform.transforms.persistence.TransformInternalIndexConstants;
import org.elasticsearch.xpack.transform.checkpoint.CheckpointProvider; import org.elasticsearch.xpack.transform.checkpoint.CheckpointProvider;
import org.elasticsearch.xpack.transform.notifications.TransformAuditor; import org.elasticsearch.xpack.transform.notifications.TransformAuditor;
import org.elasticsearch.xpack.transform.persistence.SeqNoPrimaryTermAndIndex;
import org.elasticsearch.xpack.transform.persistence.TransformConfigManager; import org.elasticsearch.xpack.transform.persistence.TransformConfigManager;
import java.time.Instant; import java.time.Instant;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -36,19 +36,12 @@ public class ClientTransformIndexerTests extends ESTestCase {
public void testAudiOnFinishFrequency() { public void testAudiOnFinishFrequency() {
ThreadPool threadPool = mock(ThreadPool.class); ThreadPool threadPool = mock(ThreadPool.class);
when(threadPool.executor("generic")).thenReturn(mock(ExecutorService.class)); when(threadPool.executor("generic")).thenReturn(mock(ExecutorService.class));
TransformTask parentTask = new TransformTask(1,
"transform",
"ptask",
new TaskId("transform:1"),
mock(TransformTaskParams.class),
null,
mock(SchedulerEngine.class),
mock(TransformAuditor.class),
threadPool,
Collections.emptyMap());
ClientTransformIndexer indexer = new ClientTransformIndexer( ClientTransformIndexer indexer = new ClientTransformIndexer(
mock(Executor.class),
mock(TransformConfigManager.class), mock(TransformConfigManager.class),
mock(CheckpointProvider.class), mock(CheckpointProvider.class),
new TransformProgressGatherer(mock(Client.class)),
new AtomicReference<>(IndexerState.STOPPED), new AtomicReference<>(IndexerState.STOPPED),
null, null,
mock(Client.class), mock(Client.class),
@ -57,18 +50,12 @@ public class ClientTransformIndexerTests extends ESTestCase {
mock(TransformConfig.class), mock(TransformConfig.class),
Collections.emptyMap(), Collections.emptyMap(),
null, null,
new TransformCheckpoint("transform", new TransformCheckpoint("transform", Instant.now().toEpochMilli(), 0L, Collections.emptyMap(), Instant.now().toEpochMilli()),
Instant.now().toEpochMilli(), new TransformCheckpoint("transform", Instant.now().toEpochMilli(), 2L, Collections.emptyMap(), Instant.now().toEpochMilli()),
0L, new SeqNoPrimaryTermAndIndex(1, 1, TransformInternalIndexConstants.LATEST_INDEX_NAME),
Collections.emptyMap(), mock(TransformContext.class),
Instant.now().toEpochMilli()), false
new TransformCheckpoint("transform", );
Instant.now().toEpochMilli(),
2L,
Collections.emptyMap(),
Instant.now().toEpochMilli()),
parentTask,
false);
List<Boolean> shouldAudit = IntStream.range(0, 100_000).boxed().map(indexer::shouldAuditOnFinish).collect(Collectors.toList()); List<Boolean> shouldAudit = IntStream.range(0, 100_000).boxed().map(indexer::shouldAuditOnFinish).collect(Collectors.toList());

View File

@ -18,26 +18,28 @@ import org.elasticsearch.action.search.ShardSearchFailure;
import org.elasticsearch.client.Client; import org.elasticsearch.client.Client;
import org.elasticsearch.common.breaker.CircuitBreaker.Durability; import org.elasticsearch.common.breaker.CircuitBreaker.Durability;
import org.elasticsearch.common.breaker.CircuitBreakingException; import org.elasticsearch.common.breaker.CircuitBreakingException;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.search.SearchHit; import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits; import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.internal.InternalSearchResponse; import org.elasticsearch.search.internal.InternalSearchResponse;
import org.elasticsearch.search.profile.SearchProfileShardResults; import org.elasticsearch.search.profile.SearchProfileShardResults;
import org.elasticsearch.search.suggest.Suggest; import org.elasticsearch.search.suggest.Suggest;
import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.test.client.NoOpClient;
import org.elasticsearch.xpack.core.indexing.IndexerState; import org.elasticsearch.xpack.core.indexing.IndexerState;
import org.elasticsearch.xpack.core.indexing.IterationResult; import org.elasticsearch.xpack.core.indexing.IterationResult;
import org.elasticsearch.xpack.core.transform.transforms.TransformIndexerPosition;
import org.elasticsearch.xpack.core.transform.transforms.TransformIndexerStats;
import org.elasticsearch.xpack.core.transform.transforms.TransformCheckpoint; import org.elasticsearch.xpack.core.transform.transforms.TransformCheckpoint;
import org.elasticsearch.xpack.core.transform.transforms.TransformConfig; import org.elasticsearch.xpack.core.transform.transforms.TransformConfig;
import org.elasticsearch.xpack.core.transform.transforms.TransformIndexerPosition;
import org.elasticsearch.xpack.core.transform.transforms.TransformIndexerStats;
import org.elasticsearch.xpack.core.transform.transforms.TransformTaskState;
import org.elasticsearch.xpack.core.transform.transforms.pivot.AggregationConfigTests; import org.elasticsearch.xpack.core.transform.transforms.pivot.AggregationConfigTests;
import org.elasticsearch.xpack.core.transform.transforms.pivot.GroupConfigTests; import org.elasticsearch.xpack.core.transform.transforms.pivot.GroupConfigTests;
import org.elasticsearch.xpack.core.transform.transforms.pivot.PivotConfig; import org.elasticsearch.xpack.core.transform.transforms.pivot.PivotConfig;
import org.elasticsearch.xpack.transform.checkpoint.CheckpointProvider;
import org.elasticsearch.xpack.transform.notifications.TransformAuditor; import org.elasticsearch.xpack.transform.notifications.TransformAuditor;
import org.elasticsearch.xpack.transform.persistence.TransformConfigManager;
import org.elasticsearch.xpack.transform.transforms.pivot.Pivot; import org.elasticsearch.xpack.transform.transforms.pivot.Pivot;
import org.junit.After;
import org.junit.Before; import org.junit.Before;
import java.io.PrintWriter; import java.io.PrintWriter;
@ -48,6 +50,7 @@ import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
@ -63,7 +66,6 @@ import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class TransformIndexerTests extends ESTestCase { public class TransformIndexerTests extends ESTestCase {
@ -80,17 +82,36 @@ public class TransformIndexerTests extends ESTestCase {
MockedTransformIndexer( MockedTransformIndexer(
Executor executor, Executor executor,
TransformConfigManager transformsConfigManager,
CheckpointProvider checkpointProvider,
TransformProgressGatherer progressGatherer,
TransformConfig transformConfig, TransformConfig transformConfig,
Map<String, String> fieldMappings, Map<String, String> fieldMappings,
TransformAuditor auditor, TransformAuditor auditor,
AtomicReference<IndexerState> initialState, AtomicReference<IndexerState> initialState,
TransformIndexerPosition initialPosition, TransformIndexerPosition initialPosition,
TransformIndexerStats jobStats, TransformIndexerStats jobStats,
TransformContext context,
Function<SearchRequest, SearchResponse> searchFunction, Function<SearchRequest, SearchResponse> searchFunction,
Function<BulkRequest, BulkResponse> bulkFunction, Function<BulkRequest, BulkResponse> bulkFunction,
Consumer<Exception> failureConsumer) { Consumer<Exception> failureConsumer
super(executor, auditor, transformConfig, fieldMappings, initialState, initialPosition, jobStats, ) {
/* TransformProgress */ null, TransformCheckpoint.EMPTY, TransformCheckpoint.EMPTY); super(
executor,
transformsConfigManager,
checkpointProvider,
progressGatherer,
auditor,
transformConfig,
fieldMappings,
initialState,
initialPosition,
jobStats,
/* TransformProgress */ null,
TransformCheckpoint.EMPTY,
TransformCheckpoint.EMPTY,
context
);
this.searchFunction = searchFunction; this.searchFunction = searchFunction;
this.bulkFunction = bulkFunction; this.bulkFunction = bulkFunction;
this.failureConsumer = failureConsumer; this.failureConsumer = failureConsumer;
@ -180,36 +201,39 @@ public class TransformIndexerTests extends ESTestCase {
fail("failIndexer should not be called, received error: " + message); fail("failIndexer should not be called, received error: " + message);
} }
@Override
protected void sourceHasChanged(ActionListener<Boolean> listener) {
listener.onResponse(false);
}
} }
@Before @Before
public void setUpMocks() { public void setUpMocks() {
client = mock(Client.class); client = new NoOpClient(getTestName());
ThreadPool threadPool = mock(ThreadPool.class); }
when(client.threadPool()).thenReturn(threadPool);
when(threadPool.getThreadContext()).thenReturn(new ThreadContext(Settings.EMPTY)); @After
public void tearDownClient() {
client.close();
} }
public void testPageSizeAdapt() throws Exception { public void testPageSizeAdapt() throws Exception {
Integer pageSize = randomBoolean() ? null : randomIntBetween(500, 10_000); Integer pageSize = randomBoolean() ? null : randomIntBetween(500, 10_000);
TransformConfig config = new TransformConfig(randomAlphaOfLength(10), TransformConfig config = new TransformConfig(
randomAlphaOfLength(10),
randomSourceConfig(), randomSourceConfig(),
randomDestConfig(), randomDestConfig(),
null, null,
null, null,
null, null,
new PivotConfig(GroupConfigTests.randomGroupConfig(), AggregationConfigTests.randomAggregationConfig(), pageSize), new PivotConfig(GroupConfigTests.randomGroupConfig(), AggregationConfigTests.randomAggregationConfig(), pageSize),
randomBoolean() ? null : randomAlphaOfLengthBetween(1, 1000)); randomBoolean() ? null : randomAlphaOfLengthBetween(1, 1000)
);
AtomicReference<IndexerState> state = new AtomicReference<>(IndexerState.STOPPED); AtomicReference<IndexerState> state = new AtomicReference<>(IndexerState.STOPPED);
final long initialPageSize = pageSize == null ? Pivot.DEFAULT_INITIAL_PAGE_SIZE : pageSize; final long initialPageSize = pageSize == null ? Pivot.DEFAULT_INITIAL_PAGE_SIZE : pageSize;
Function<SearchRequest, SearchResponse> searchFunction = searchRequest -> { Function<SearchRequest, SearchResponse> searchFunction = searchRequest -> {
throw new SearchPhaseExecutionException("query", "Partial shards failure", new ShardSearchFailure[] { throw new SearchPhaseExecutionException(
new ShardSearchFailure(new CircuitBreakingException("to much memory", 110, 100, Durability.TRANSIENT)) }); "query",
"Partial shards failure",
new ShardSearchFailure[] {
new ShardSearchFailure(new CircuitBreakingException("to much memory", 110, 100, Durability.TRANSIENT)) }
);
}; };
Function<BulkRequest, BulkResponse> bulkFunction = bulkRequest -> new BulkResponse(new BulkItemResponse[0], 100); Function<BulkRequest, BulkResponse> bulkFunction = bulkRequest -> new BulkResponse(new BulkItemResponse[0], 100);
@ -224,9 +248,24 @@ public class TransformIndexerTests extends ESTestCase {
final ExecutorService executor = Executors.newFixedThreadPool(1); final ExecutorService executor = Executors.newFixedThreadPool(1);
try { try {
TransformAuditor auditor = new TransformAuditor(client, "node_1"); TransformAuditor auditor = new TransformAuditor(client, "node_1");
TransformContext context = new TransformContext(TransformTaskState.STARTED, "", 0, mock(TransformContext.Listener.class));
MockedTransformIndexer indexer = new MockedTransformIndexer(executor, config, Collections.emptyMap(), auditor, state, null, MockedTransformIndexer indexer = new MockedTransformIndexer(
new TransformIndexerStats(), searchFunction, bulkFunction, failureConsumer); executor,
mock(TransformConfigManager.class),
mock(CheckpointProvider.class),
new TransformProgressGatherer(client),
config,
Collections.emptyMap(),
auditor,
state,
null,
new TransformIndexerStats(),
context,
searchFunction,
bulkFunction,
failureConsumer
);
final CountDownLatch latch = indexer.newLatch(1); final CountDownLatch latch = indexer.newLatch(1);
indexer.start(); indexer.start();
assertThat(indexer.getState(), equalTo(IndexerState.STARTED)); assertThat(indexer.getState(), equalTo(IndexerState.STARTED));
@ -234,7 +273,7 @@ public class TransformIndexerTests extends ESTestCase {
assertThat(indexer.getState(), equalTo(IndexerState.INDEXING)); assertThat(indexer.getState(), equalTo(IndexerState.INDEXING));
latch.countDown(); latch.countDown();
assertBusy(() -> assertThat(indexer.getState(), equalTo(IndexerState.STARTED))); assertBusy(() -> assertThat(indexer.getState(), equalTo(IndexerState.STARTED)), 10, TimeUnit.MINUTES);
long pageSizeAfterFirstReduction = indexer.getPageSize(); long pageSizeAfterFirstReduction = indexer.getPageSize();
assertThat(initialPageSize, greaterThan(pageSizeAfterFirstReduction)); assertThat(initialPageSize, greaterThan(pageSizeAfterFirstReduction));
assertThat(pageSizeAfterFirstReduction, greaterThan((long) TransformIndexer.MINIMUM_PAGE_SIZE)); assertThat(pageSizeAfterFirstReduction, greaterThan((long) TransformIndexer.MINIMUM_PAGE_SIZE));
@ -261,22 +300,35 @@ public class TransformIndexerTests extends ESTestCase {
public void testDoProcessAggNullCheck() { public void testDoProcessAggNullCheck() {
Integer pageSize = randomBoolean() ? null : randomIntBetween(500, 10_000); Integer pageSize = randomBoolean() ? null : randomIntBetween(500, 10_000);
TransformConfig config = new TransformConfig(randomAlphaOfLength(10), TransformConfig config = new TransformConfig(
randomAlphaOfLength(10),
randomSourceConfig(), randomSourceConfig(),
randomDestConfig(), randomDestConfig(),
null, null,
null, null,
null, null,
new PivotConfig(GroupConfigTests.randomGroupConfig(), AggregationConfigTests.randomAggregationConfig(), pageSize), new PivotConfig(GroupConfigTests.randomGroupConfig(), AggregationConfigTests.randomAggregationConfig(), pageSize),
randomBoolean() ? null : randomAlphaOfLengthBetween(1, 1000)); randomBoolean() ? null : randomAlphaOfLengthBetween(1, 1000)
SearchResponse searchResponse = new SearchResponse(new InternalSearchResponse( );
new SearchHits( SearchResponse searchResponse = new SearchResponse(
new SearchHit[0], new TotalHits(0L, TotalHits.Relation.EQUAL_TO), 0.0f), new InternalSearchResponse(
new SearchHits(new SearchHit[0], new TotalHits(0L, TotalHits.Relation.EQUAL_TO), 0.0f),
// Simulate completely null aggs // Simulate completely null aggs
null, null,
new Suggest(Collections.emptyList()), new Suggest(Collections.emptyList()),
new SearchProfileShardResults(Collections.emptyMap()), false, false, 1), new SearchProfileShardResults(Collections.emptyMap()),
"", 1, 1, 0, 0, ShardSearchFailure.EMPTY_ARRAY, SearchResponse.Clusters.EMPTY); false,
false,
1
),
"",
1,
1,
0,
0,
ShardSearchFailure.EMPTY_ARRAY,
SearchResponse.Clusters.EMPTY
);
AtomicReference<IndexerState> state = new AtomicReference<>(IndexerState.STOPPED); AtomicReference<IndexerState> state = new AtomicReference<>(IndexerState.STOPPED);
Function<SearchRequest, SearchResponse> searchFunction = searchRequest -> searchResponse; Function<SearchRequest, SearchResponse> searchFunction = searchRequest -> searchResponse;
Function<BulkRequest, BulkResponse> bulkFunction = bulkRequest -> new BulkResponse(new BulkItemResponse[0], 100); Function<BulkRequest, BulkResponse> bulkFunction = bulkRequest -> new BulkResponse(new BulkItemResponse[0], 100);
@ -291,9 +343,24 @@ public class TransformIndexerTests extends ESTestCase {
final ExecutorService executor = Executors.newFixedThreadPool(1); final ExecutorService executor = Executors.newFixedThreadPool(1);
try { try {
TransformAuditor auditor = mock(TransformAuditor.class); TransformAuditor auditor = mock(TransformAuditor.class);
TransformContext context = new TransformContext(TransformTaskState.STARTED, "", 0, mock(TransformContext.Listener.class));
MockedTransformIndexer indexer = new MockedTransformIndexer(executor, config, Collections.emptyMap(), auditor, state, null, MockedTransformIndexer indexer = new MockedTransformIndexer(
new TransformIndexerStats(), searchFunction, bulkFunction, failureConsumer); executor,
mock(TransformConfigManager.class),
mock(CheckpointProvider.class),
new TransformProgressGatherer(client),
config,
Collections.emptyMap(),
auditor,
state,
null,
new TransformIndexerStats(),
context,
searchFunction,
bulkFunction,
failureConsumer
);
IterationResult<TransformIndexerPosition> newPosition = indexer.doProcess(searchResponse); IterationResult<TransformIndexerPosition> newPosition = indexer.doProcess(searchResponse);
assertThat(newPosition.getToIndex(), is(empty())); assertThat(newPosition.getToIndex(), is(empty()));

View File

@ -32,6 +32,7 @@ import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.xpack.core.scheduler.SchedulerEngine; import org.elasticsearch.xpack.core.scheduler.SchedulerEngine;
import org.elasticsearch.xpack.core.transform.transforms.TransformTaskParams; import org.elasticsearch.xpack.core.transform.transforms.TransformTaskParams;
import org.elasticsearch.xpack.core.transform.transforms.persistence.TransformInternalIndexConstants; import org.elasticsearch.xpack.core.transform.transforms.persistence.TransformInternalIndexConstants;
import org.elasticsearch.xpack.transform.Transform;
import org.elasticsearch.xpack.transform.checkpoint.TransformCheckpointService; import org.elasticsearch.xpack.transform.checkpoint.TransformCheckpointService;
import org.elasticsearch.xpack.transform.notifications.TransformAuditor; import org.elasticsearch.xpack.transform.notifications.TransformAuditor;
import org.elasticsearch.xpack.transform.persistence.TransformConfigManager; import org.elasticsearch.xpack.transform.persistence.TransformConfigManager;
@ -53,48 +54,71 @@ public class TransformPersistentTasksExecutorTests extends ESTestCase {
MetaData.Builder metaData = MetaData.builder(); MetaData.Builder metaData = MetaData.builder();
RoutingTable.Builder routingTable = RoutingTable.builder(); RoutingTable.Builder routingTable = RoutingTable.builder();
addIndices(metaData, routingTable); addIndices(metaData, routingTable);
PersistentTasksCustomMetaData.Builder pTasksBuilder = PersistentTasksCustomMetaData.builder() PersistentTasksCustomMetaData.Builder pTasksBuilder = PersistentTasksCustomMetaData
.addTask("transform-task-1", .builder()
.addTask(
"transform-task-1",
TransformTaskParams.NAME, TransformTaskParams.NAME,
new TransformTaskParams("transform-task-1", Version.CURRENT, null), new TransformTaskParams("transform-task-1", Version.CURRENT, null),
new PersistentTasksCustomMetaData.Assignment("current-data-node-with-1-tasks", "")) new PersistentTasksCustomMetaData.Assignment("current-data-node-with-1-tasks", "")
.addTask("transform-task-2", )
.addTask(
"transform-task-2",
TransformTaskParams.NAME, TransformTaskParams.NAME,
new TransformTaskParams("transform-task-2", Version.CURRENT, null), new TransformTaskParams("transform-task-2", Version.CURRENT, null),
new PersistentTasksCustomMetaData.Assignment("current-data-node-with-2-tasks", "")) new PersistentTasksCustomMetaData.Assignment("current-data-node-with-2-tasks", "")
.addTask("transform-task-3", )
.addTask(
"transform-task-3",
TransformTaskParams.NAME, TransformTaskParams.NAME,
new TransformTaskParams("transform-task-3", Version.CURRENT, null), new TransformTaskParams("transform-task-3", Version.CURRENT, null),
new PersistentTasksCustomMetaData.Assignment("current-data-node-with-2-tasks", "")); new PersistentTasksCustomMetaData.Assignment("current-data-node-with-2-tasks", "")
);
PersistentTasksCustomMetaData pTasks = pTasksBuilder.build(); PersistentTasksCustomMetaData pTasks = pTasksBuilder.build();
metaData.putCustom(PersistentTasksCustomMetaData.TYPE, pTasks); metaData.putCustom(PersistentTasksCustomMetaData.TYPE, pTasks);
DiscoveryNodes.Builder nodes = DiscoveryNodes.builder() DiscoveryNodes.Builder nodes = DiscoveryNodes
.add(new DiscoveryNode("past-data-node-1", .builder()
.add(
new DiscoveryNode(
"past-data-node-1",
buildNewFakeTransportAddress(), buildNewFakeTransportAddress(),
Collections.emptyMap(), Collections.emptyMap(),
new HashSet<>(Arrays.asList(DiscoveryNodeRole.DATA_ROLE, DiscoveryNodeRole.MASTER_ROLE)), new HashSet<>(Arrays.asList(DiscoveryNodeRole.DATA_ROLE, DiscoveryNodeRole.MASTER_ROLE)),
Version.V_7_4_0)) Version.V_7_4_0
.add(new DiscoveryNode("current-data-node-with-2-tasks", )
)
.add(
new DiscoveryNode(
"current-data-node-with-2-tasks",
buildNewFakeTransportAddress(), buildNewFakeTransportAddress(),
Collections.emptyMap(), Collections.emptyMap(),
new HashSet<>(Arrays.asList(DiscoveryNodeRole.DATA_ROLE, DiscoveryNodeRole.MASTER_ROLE)), new HashSet<>(Arrays.asList(DiscoveryNodeRole.DATA_ROLE, DiscoveryNodeRole.MASTER_ROLE)),
Version.CURRENT)) Version.CURRENT
.add(new DiscoveryNode("non-data-node-1", )
)
.add(
new DiscoveryNode(
"non-data-node-1",
buildNewFakeTransportAddress(), buildNewFakeTransportAddress(),
Collections.emptyMap(), Collections.emptyMap(),
Collections.singleton(DiscoveryNodeRole.MASTER_ROLE), Collections.singleton(DiscoveryNodeRole.MASTER_ROLE),
Version.CURRENT)) Version.CURRENT
.add(new DiscoveryNode("current-data-node-with-1-tasks", )
)
.add(
new DiscoveryNode(
"current-data-node-with-1-tasks",
buildNewFakeTransportAddress(), buildNewFakeTransportAddress(),
Collections.emptyMap(), Collections.emptyMap(),
new HashSet<>(Arrays.asList(DiscoveryNodeRole.DATA_ROLE, DiscoveryNodeRole.MASTER_ROLE)), new HashSet<>(Arrays.asList(DiscoveryNodeRole.DATA_ROLE, DiscoveryNodeRole.MASTER_ROLE)),
Version.CURRENT)); Version.CURRENT
)
);
ClusterState.Builder csBuilder = ClusterState.builder(new ClusterName("_name")) ClusterState.Builder csBuilder = ClusterState.builder(new ClusterName("_name")).nodes(nodes);
.nodes(nodes);
csBuilder.routingTable(routingTable.build()); csBuilder.routingTable(routingTable.build());
csBuilder.metaData(metaData); csBuilder.metaData(metaData);
@ -102,60 +126,80 @@ public class TransformPersistentTasksExecutorTests extends ESTestCase {
Client client = mock(Client.class); Client client = mock(Client.class);
TransformAuditor mockAuditor = mock(TransformAuditor.class); TransformAuditor mockAuditor = mock(TransformAuditor.class);
TransformConfigManager transformsConfigManager = new TransformConfigManager(client, xContentRegistry()); TransformConfigManager transformsConfigManager = new TransformConfigManager(client, xContentRegistry());
TransformCheckpointService transformCheckpointService = new TransformCheckpointService(client, TransformCheckpointService transformCheckpointService = new TransformCheckpointService(
transformsConfigManager, mockAuditor); client,
ClusterSettings cSettings = new ClusterSettings(Settings.EMPTY, transformsConfigManager,
Collections.singleton(TransformTask.NUM_FAILURE_RETRIES_SETTING)); mockAuditor
);
ClusterSettings cSettings = new ClusterSettings(Settings.EMPTY, Collections.singleton(Transform.NUM_FAILURE_RETRIES_SETTING));
ClusterService clusterService = mock(ClusterService.class); ClusterService clusterService = mock(ClusterService.class);
when(clusterService.getClusterSettings()).thenReturn(cSettings); when(clusterService.getClusterSettings()).thenReturn(cSettings);
when(clusterService.state()).thenReturn(TransformInternalIndexTests.STATE_WITH_LATEST_VERSIONED_INDEX_TEMPLATE); when(clusterService.state()).thenReturn(TransformInternalIndexTests.STATE_WITH_LATEST_VERSIONED_INDEX_TEMPLATE);
TransformPersistentTasksExecutor executor = new TransformPersistentTasksExecutor(client, TransformPersistentTasksExecutor executor = new TransformPersistentTasksExecutor(
client,
transformsConfigManager, transformsConfigManager,
transformCheckpointService, mock(SchedulerEngine.class), transformCheckpointService,
mock(SchedulerEngine.class),
new TransformAuditor(client, ""), new TransformAuditor(client, ""),
mock(ThreadPool.class), mock(ThreadPool.class),
clusterService, clusterService,
Settings.EMPTY); Settings.EMPTY
);
assertThat(executor.getAssignment(new TransformTaskParams("new-task-id", Version.CURRENT, null), cs).getExecutorNode(), assertThat(
equalTo("current-data-node-with-1-tasks")); executor.getAssignment(new TransformTaskParams("new-task-id", Version.CURRENT, null), cs).getExecutorNode(),
assertThat(executor.getAssignment(new TransformTaskParams("new-old-task-id", Version.V_7_2_0, null), cs).getExecutorNode(), equalTo("current-data-node-with-1-tasks")
equalTo("past-data-node-1")); );
} }
public void testDoNotSelectOldNodes() { public void testDoNotSelectOldNodes() {
MetaData.Builder metaData = MetaData.builder(); MetaData.Builder metaData = MetaData.builder();
RoutingTable.Builder routingTable = RoutingTable.builder(); RoutingTable.Builder routingTable = RoutingTable.builder();
addIndices(metaData, routingTable); addIndices(metaData, routingTable);
PersistentTasksCustomMetaData.Builder pTasksBuilder = PersistentTasksCustomMetaData.builder() PersistentTasksCustomMetaData.Builder pTasksBuilder = PersistentTasksCustomMetaData
.addTask("transform-task-1", .builder()
.addTask(
"transform-task-1",
TransformTaskParams.NAME, TransformTaskParams.NAME,
new TransformTaskParams("transform-task-1", Version.CURRENT, null), new TransformTaskParams("transform-task-1", Version.CURRENT, null),
new PersistentTasksCustomMetaData.Assignment("current-data-node-with-1-task", "")); new PersistentTasksCustomMetaData.Assignment("current-data-node-with-1-task", "")
);
PersistentTasksCustomMetaData pTasks = pTasksBuilder.build(); PersistentTasksCustomMetaData pTasks = pTasksBuilder.build();
metaData.putCustom(PersistentTasksCustomMetaData.TYPE, pTasks); metaData.putCustom(PersistentTasksCustomMetaData.TYPE, pTasks);
DiscoveryNodes.Builder nodes = DiscoveryNodes.builder() DiscoveryNodes.Builder nodes = DiscoveryNodes
.add(new DiscoveryNode("old-data-node-1", .builder()
.add(
new DiscoveryNode(
"old-data-node-1",
buildNewFakeTransportAddress(), buildNewFakeTransportAddress(),
Collections.emptyMap(), Collections.emptyMap(),
new HashSet<>(Arrays.asList(DiscoveryNodeRole.DATA_ROLE, DiscoveryNodeRole.MASTER_ROLE)), new HashSet<>(Arrays.asList(DiscoveryNodeRole.DATA_ROLE, DiscoveryNodeRole.MASTER_ROLE)),
Version.V_7_2_0)) Version.V_7_2_0
.add(new DiscoveryNode("current-data-node-with-1-task", )
)
.add(
new DiscoveryNode(
"current-data-node-with-1-task",
buildNewFakeTransportAddress(), buildNewFakeTransportAddress(),
Collections.emptyMap(), Collections.emptyMap(),
new HashSet<>(Arrays.asList(DiscoveryNodeRole.DATA_ROLE, DiscoveryNodeRole.MASTER_ROLE)), new HashSet<>(Arrays.asList(DiscoveryNodeRole.DATA_ROLE, DiscoveryNodeRole.MASTER_ROLE)),
Version.CURRENT)) Version.CURRENT
.add(new DiscoveryNode("non-data-node-1", )
)
.add(
new DiscoveryNode(
"non-data-node-1",
buildNewFakeTransportAddress(), buildNewFakeTransportAddress(),
Collections.emptyMap(), Collections.emptyMap(),
Collections.singleton(DiscoveryNodeRole.MASTER_ROLE), Collections.singleton(DiscoveryNodeRole.MASTER_ROLE),
Version.CURRENT)); Version.CURRENT
)
);
ClusterState.Builder csBuilder = ClusterState.builder(new ClusterName("_name")) ClusterState.Builder csBuilder = ClusterState.builder(new ClusterName("_name")).nodes(nodes);
.nodes(nodes);
csBuilder.routingTable(routingTable.build()); csBuilder.routingTable(routingTable.build());
csBuilder.metaData(metaData); csBuilder.metaData(metaData);
@ -163,41 +207,57 @@ public class TransformPersistentTasksExecutorTests extends ESTestCase {
Client client = mock(Client.class); Client client = mock(Client.class);
TransformAuditor mockAuditor = mock(TransformAuditor.class); TransformAuditor mockAuditor = mock(TransformAuditor.class);
TransformConfigManager transformsConfigManager = new TransformConfigManager(client, xContentRegistry()); TransformConfigManager transformsConfigManager = new TransformConfigManager(client, xContentRegistry());
TransformCheckpointService transformCheckpointService = new TransformCheckpointService(client, TransformCheckpointService transformCheckpointService = new TransformCheckpointService(
transformsConfigManager, mockAuditor); client,
ClusterSettings cSettings = new ClusterSettings(Settings.EMPTY, transformsConfigManager,
Collections.singleton(TransformTask.NUM_FAILURE_RETRIES_SETTING)); mockAuditor
);
ClusterSettings cSettings = new ClusterSettings(Settings.EMPTY, Collections.singleton(Transform.NUM_FAILURE_RETRIES_SETTING));
ClusterService clusterService = mock(ClusterService.class); ClusterService clusterService = mock(ClusterService.class);
when(clusterService.getClusterSettings()).thenReturn(cSettings); when(clusterService.getClusterSettings()).thenReturn(cSettings);
when(clusterService.state()).thenReturn(TransformInternalIndexTests.STATE_WITH_LATEST_VERSIONED_INDEX_TEMPLATE); when(clusterService.state()).thenReturn(TransformInternalIndexTests.STATE_WITH_LATEST_VERSIONED_INDEX_TEMPLATE);
TransformPersistentTasksExecutor executor = new TransformPersistentTasksExecutor(client, TransformPersistentTasksExecutor executor = new TransformPersistentTasksExecutor(
client,
transformsConfigManager, transformsConfigManager,
transformCheckpointService, mock(SchedulerEngine.class), transformCheckpointService,
mock(SchedulerEngine.class),
new TransformAuditor(client, ""), new TransformAuditor(client, ""),
mock(ThreadPool.class), mock(ThreadPool.class),
clusterService, clusterService,
Settings.EMPTY); Settings.EMPTY
);
// old-data-node-1 prevents assignment // old-data-node-1 prevents assignment
assertNull(executor.getAssignment(new TransformTaskParams("new-task-id", Version.CURRENT, null), cs).getExecutorNode()); assertNull(executor.getAssignment(new TransformTaskParams("new-task-id", Version.CURRENT, null), cs).getExecutorNode());
// remove the old 7.2 node // remove the old 7.2 node
nodes = DiscoveryNodes.builder() nodes = DiscoveryNodes
.add(new DiscoveryNode("current-data-node-with-1-task", .builder()
.add(
new DiscoveryNode(
"current-data-node-with-1-task",
buildNewFakeTransportAddress(), buildNewFakeTransportAddress(),
Collections.emptyMap(), Collections.emptyMap(),
new HashSet<>(Arrays.asList(DiscoveryNodeRole.DATA_ROLE, DiscoveryNodeRole.MASTER_ROLE)), new HashSet<>(Arrays.asList(DiscoveryNodeRole.DATA_ROLE, DiscoveryNodeRole.MASTER_ROLE)),
Version.CURRENT)) Version.CURRENT
.add(new DiscoveryNode("non-data-node-1", )
)
.add(
new DiscoveryNode(
"non-data-node-1",
buildNewFakeTransportAddress(), buildNewFakeTransportAddress(),
Collections.emptyMap(), Collections.emptyMap(),
Collections.singleton(DiscoveryNodeRole.MASTER_ROLE), Collections.singleton(DiscoveryNodeRole.MASTER_ROLE),
Version.CURRENT)); Version.CURRENT
)
);
csBuilder.nodes(nodes); csBuilder.nodes(nodes);
cs = csBuilder.build(); cs = csBuilder.build();
assertThat(executor.getAssignment(new TransformTaskParams("new-old-task-id", Version.V_7_2_0, null), cs).getExecutorNode(), assertThat(
equalTo("current-data-node-with-1-task")); executor.getAssignment(new TransformTaskParams("new-old-task-id", Version.V_7_2_0, null), cs).getExecutorNode(),
equalTo("current-data-node-with-1-task")
);
} }
public void testVerifyIndicesPrimaryShardsAreActive() { public void testVerifyIndicesPrimaryShardsAreActive() {
@ -220,11 +280,20 @@ public class TransformPersistentTasksExecutorTests extends ESTestCase {
} else { } else {
Index index = new Index(indexToRemove, "_uuid"); Index index = new Index(indexToRemove, "_uuid");
ShardId shardId = new ShardId(index, 0); ShardId shardId = new ShardId(index, 0);
ShardRouting shardRouting = ShardRouting.newUnassigned(shardId, true, RecoverySource.EmptyStoreRecoverySource.INSTANCE, ShardRouting shardRouting = ShardRouting
new UnassignedInfo(UnassignedInfo.Reason.INDEX_CREATED, "")); .newUnassigned(
shardId,
true,
RecoverySource.EmptyStoreRecoverySource.INSTANCE,
new UnassignedInfo(UnassignedInfo.Reason.INDEX_CREATED, "")
);
shardRouting = shardRouting.initialize("node_id", null, 0L); shardRouting = shardRouting.initialize("node_id", null, 0L);
routingTable.add(IndexRoutingTable.builder(index) routingTable
.addIndexShard(new IndexShardRoutingTable.Builder(shardId).addShard(shardRouting).build())); .add(
IndexRoutingTable
.builder(index)
.addIndexShard(new IndexShardRoutingTable.Builder(shardId).addShard(shardRouting).build())
);
} }
csBuilder.routingTable(routingTable.build()); csBuilder.routingTable(routingTable.build());
@ -240,7 +309,10 @@ public class TransformPersistentTasksExecutorTests extends ESTestCase {
indices.add(TransformInternalIndexConstants.LATEST_INDEX_NAME); indices.add(TransformInternalIndexConstants.LATEST_INDEX_NAME);
for (String indexName : indices) { for (String indexName : indices) {
IndexMetaData.Builder indexMetaData = IndexMetaData.builder(indexName); IndexMetaData.Builder indexMetaData = IndexMetaData.builder(indexName);
indexMetaData.settings(Settings.builder() indexMetaData
.settings(
Settings
.builder()
.put(IndexMetaData.SETTING_VERSION_CREATED, Version.CURRENT) .put(IndexMetaData.SETTING_VERSION_CREATED, Version.CURRENT)
.put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, 1) .put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, 1)
.put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, 0) .put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, 0)
@ -248,12 +320,21 @@ public class TransformPersistentTasksExecutorTests extends ESTestCase {
metaData.put(indexMetaData); metaData.put(indexMetaData);
Index index = new Index(indexName, "_uuid"); Index index = new Index(indexName, "_uuid");
ShardId shardId = new ShardId(index, 0); ShardId shardId = new ShardId(index, 0);
ShardRouting shardRouting = ShardRouting.newUnassigned(shardId, true, RecoverySource.EmptyStoreRecoverySource.INSTANCE, ShardRouting shardRouting = ShardRouting
new UnassignedInfo(UnassignedInfo.Reason.INDEX_CREATED, "")); .newUnassigned(
shardId,
true,
RecoverySource.EmptyStoreRecoverySource.INSTANCE,
new UnassignedInfo(UnassignedInfo.Reason.INDEX_CREATED, "")
);
shardRouting = shardRouting.initialize("node_id", null, 0L); shardRouting = shardRouting.initialize("node_id", null, 0L);
shardRouting = shardRouting.moveToStarted(); shardRouting = shardRouting.moveToStarted();
routingTable.add(IndexRoutingTable.builder(index) routingTable
.addIndexShard(new IndexShardRoutingTable.Builder(shardId).addShard(shardRouting).build())); .add(
IndexRoutingTable
.builder(index)
.addIndexShard(new IndexShardRoutingTable.Builder(shardId).addShard(shardRouting).build())
);
} }
} }