[ML] Impove mechanism for ignoring maintenance windows (elastic/x-pack-elasticsearch#1914)

Currently, the autodetect process has an `ignoreDowntime`
parameter which, when set to true, results to time being
skipped over to the end of the bucket of the first data
point received. After that, skipping time requires closing
and opening the job. With regard to datafeeds, this does not
work well with real-time requests which use the advance-time
API in order to ensure results are created for data gaps.

This commit improves this functionality by making it more
flexible and less ambiguous.

- flush API now supports skip_time parameter which
sends a control message to the autodetect process
telling it to skip time to a given value
- the flush API now also returns the last_finalized_bucket_end
time which allows clients to resume data searches correctly
- the datafeed start API issues a skip_time request when the
given start time is after the resume point. It then resumes
the search from the last_finalized_bucket_end time.

relates elastic/x-pack-elasticsearch#1913


Original commit: elastic/x-pack-elasticsearch@caa5fe8016
This commit is contained in:
Dimitris Athanasiou 2017-07-05 11:33:42 +01:00 committed by GitHub
parent 595468a505
commit 15f9b1ed9c
33 changed files with 473 additions and 222 deletions

View File

@ -280,8 +280,7 @@ public class MachineLearning implements ActionPlugin {
throw new ElasticsearchException("Failed to create native process factories for Machine Learning", e); throw new ElasticsearchException("Failed to create native process factories for Machine Learning", e);
} }
} else { } else {
autodetectProcessFactory = (job, modelSnapshot, quantiles, filters, autodetectProcessFactory = (job, modelSnapshot, quantiles, filters, executorService, onProcessCrash) ->
ignoreDowntime, executorService, onProcessCrash) ->
new BlackHoleAutodetectProcess(job.getId()); new BlackHoleAutodetectProcess(job.getId());
// factor of 1.0 makes renormalization a no-op // factor of 1.0 makes renormalization a no-op
normalizerProcessFactory = (jobId, quantilesState, bucketSpan, perPartitionNormalization, normalizerProcessFactory = (jobId, quantilesState, bucketSpan, perPartitionNormalization,

View File

@ -5,6 +5,7 @@
*/ */
package org.elasticsearch.xpack.ml.action; package org.elasticsearch.xpack.ml.action;
import org.elasticsearch.Version;
import org.elasticsearch.action.Action; import org.elasticsearch.action.Action;
import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.ActionRequestBuilder; import org.elasticsearch.action.ActionRequestBuilder;
@ -13,6 +14,7 @@ import org.elasticsearch.action.support.tasks.BaseTasksResponse;
import org.elasticsearch.client.ElasticsearchClient; import org.elasticsearch.client.ElasticsearchClient;
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver; import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.cluster.service.ClusterService;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.ParseField; import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamInput;
@ -28,10 +30,12 @@ import org.elasticsearch.threadpool.ThreadPool;
import org.elasticsearch.transport.TransportService; import org.elasticsearch.transport.TransportService;
import org.elasticsearch.xpack.ml.job.config.Job; import org.elasticsearch.xpack.ml.job.config.Job;
import org.elasticsearch.xpack.ml.job.process.autodetect.AutodetectProcessManager; import org.elasticsearch.xpack.ml.job.process.autodetect.AutodetectProcessManager;
import org.elasticsearch.xpack.ml.job.process.autodetect.output.FlushAcknowledgement;
import org.elasticsearch.xpack.ml.job.process.autodetect.params.FlushJobParams; import org.elasticsearch.xpack.ml.job.process.autodetect.params.FlushJobParams;
import org.elasticsearch.xpack.ml.job.process.autodetect.params.TimeRange; import org.elasticsearch.xpack.ml.job.process.autodetect.params.TimeRange;
import java.io.IOException; import java.io.IOException;
import java.util.Date;
import java.util.Objects; import java.util.Objects;
public class FlushJobAction extends Action<FlushJobAction.Request, FlushJobAction.Response, FlushJobAction.RequestBuilder> { public class FlushJobAction extends Action<FlushJobAction.Request, FlushJobAction.Response, FlushJobAction.RequestBuilder> {
@ -59,6 +63,7 @@ public class FlushJobAction extends Action<FlushJobAction.Request, FlushJobActio
public static final ParseField START = new ParseField("start"); public static final ParseField START = new ParseField("start");
public static final ParseField END = new ParseField("end"); public static final ParseField END = new ParseField("end");
public static final ParseField ADVANCE_TIME = new ParseField("advance_time"); public static final ParseField ADVANCE_TIME = new ParseField("advance_time");
public static final ParseField SKIP_TIME = new ParseField("skip_time");
private static final ObjectParser<Request, Void> PARSER = new ObjectParser<>(NAME, Request::new); private static final ObjectParser<Request, Void> PARSER = new ObjectParser<>(NAME, Request::new);
@ -68,6 +73,7 @@ public class FlushJobAction extends Action<FlushJobAction.Request, FlushJobActio
PARSER.declareString(Request::setStart, START); PARSER.declareString(Request::setStart, START);
PARSER.declareString(Request::setEnd, END); PARSER.declareString(Request::setEnd, END);
PARSER.declareString(Request::setAdvanceTime, ADVANCE_TIME); PARSER.declareString(Request::setAdvanceTime, ADVANCE_TIME);
PARSER.declareString(Request::setSkipTime, SKIP_TIME);
} }
public static Request parseRequest(String jobId, XContentParser parser) { public static Request parseRequest(String jobId, XContentParser parser) {
@ -82,6 +88,7 @@ public class FlushJobAction extends Action<FlushJobAction.Request, FlushJobActio
private String start; private String start;
private String end; private String end;
private String advanceTime; private String advanceTime;
private String skipTime;
Request() { Request() {
} }
@ -114,12 +121,22 @@ public class FlushJobAction extends Action<FlushJobAction.Request, FlushJobActio
this.end = end; this.end = end;
} }
public String getAdvanceTime() { return advanceTime; } public String getAdvanceTime() {
return advanceTime;
}
public void setAdvanceTime(String advanceTime) { public void setAdvanceTime(String advanceTime) {
this.advanceTime = advanceTime; this.advanceTime = advanceTime;
} }
public String getSkipTime() {
return skipTime;
}
public void setSkipTime(String skipTime) {
this.skipTime = skipTime;
}
@Override @Override
public void readFrom(StreamInput in) throws IOException { public void readFrom(StreamInput in) throws IOException {
super.readFrom(in); super.readFrom(in);
@ -127,6 +144,9 @@ public class FlushJobAction extends Action<FlushJobAction.Request, FlushJobActio
start = in.readOptionalString(); start = in.readOptionalString();
end = in.readOptionalString(); end = in.readOptionalString();
advanceTime = in.readOptionalString(); advanceTime = in.readOptionalString();
if (in.getVersion().after(Version.V_5_5_0)) {
skipTime = in.readOptionalString();
}
} }
@Override @Override
@ -136,11 +156,14 @@ public class FlushJobAction extends Action<FlushJobAction.Request, FlushJobActio
out.writeOptionalString(start); out.writeOptionalString(start);
out.writeOptionalString(end); out.writeOptionalString(end);
out.writeOptionalString(advanceTime); out.writeOptionalString(advanceTime);
if (out.getVersion().after(Version.V_5_5_0)) {
out.writeOptionalString(skipTime);
}
} }
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(jobId, calcInterim, start, end, advanceTime); return Objects.hash(jobId, calcInterim, start, end, advanceTime, skipTime);
} }
@Override @Override
@ -156,7 +179,8 @@ public class FlushJobAction extends Action<FlushJobAction.Request, FlushJobActio
calcInterim == other.calcInterim && calcInterim == other.calcInterim &&
Objects.equals(start, other.start) && Objects.equals(start, other.start) &&
Objects.equals(end, other.end) && Objects.equals(end, other.end) &&
Objects.equals(advanceTime, other.advanceTime); Objects.equals(advanceTime, other.advanceTime) &&
Objects.equals(skipTime, other.skipTime);
} }
@Override @Override
@ -173,6 +197,9 @@ public class FlushJobAction extends Action<FlushJobAction.Request, FlushJobActio
if (advanceTime != null) { if (advanceTime != null) {
builder.field(ADVANCE_TIME.getPreferredName(), advanceTime); builder.field(ADVANCE_TIME.getPreferredName(), advanceTime);
} }
if (skipTime != null) {
builder.field(SKIP_TIME.getPreferredName(), skipTime);
}
builder.endObject(); builder.endObject();
return builder; return builder;
} }
@ -188,36 +215,52 @@ public class FlushJobAction extends Action<FlushJobAction.Request, FlushJobActio
public static class Response extends BaseTasksResponse implements Writeable, ToXContentObject { public static class Response extends BaseTasksResponse implements Writeable, ToXContentObject {
private boolean flushed; private boolean flushed;
private Date lastFinalizedBucketEnd;
Response() { Response() {
super(null, null); super(null, null);
} }
Response(boolean flushed) { public Response(boolean flushed, @Nullable Date lastFinalizedBucketEnd) {
super(null, null); super(null, null);
this.flushed = flushed; this.flushed = flushed;
this.lastFinalizedBucketEnd = lastFinalizedBucketEnd;
} }
public boolean isFlushed() { public boolean isFlushed() {
return flushed; return flushed;
} }
public Date getLastFinalizedBucketEnd() {
return lastFinalizedBucketEnd;
}
@Override @Override
public void readFrom(StreamInput in) throws IOException { public void readFrom(StreamInput in) throws IOException {
super.readFrom(in); super.readFrom(in);
flushed = in.readBoolean(); flushed = in.readBoolean();
if (in.getVersion().after(Version.V_5_5_0)) {
lastFinalizedBucketEnd = new Date(in.readVLong());
}
} }
@Override @Override
public void writeTo(StreamOutput out) throws IOException { public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out); super.writeTo(out);
out.writeBoolean(flushed); out.writeBoolean(flushed);
if (out.getVersion().after(Version.V_5_5_0)) {
out.writeVLong(lastFinalizedBucketEnd.getTime());
}
} }
@Override @Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(); builder.startObject();
builder.field("flushed", flushed); builder.field("flushed", flushed);
if (lastFinalizedBucketEnd != null) {
builder.dateField(FlushAcknowledgement.LAST_FINALIZED_BUCKET_END.getPreferredName(),
FlushAcknowledgement.LAST_FINALIZED_BUCKET_END.getPreferredName() + "_string", lastFinalizedBucketEnd.getTime());
}
builder.endObject(); builder.endObject();
return builder; return builder;
} }
@ -227,12 +270,13 @@ public class FlushJobAction extends Action<FlushJobAction.Request, FlushJobActio
if (this == o) return true; if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false; if (o == null || getClass() != o.getClass()) return false;
Response response = (Response) o; Response response = (Response) o;
return flushed == response.flushed; return flushed == response.flushed &&
Objects.equals(lastFinalizedBucketEnd, response.lastFinalizedBucketEnd);
} }
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(flushed); return Objects.hash(flushed, lastFinalizedBucketEnd);
} }
} }
@ -261,6 +305,9 @@ public class FlushJobAction extends Action<FlushJobAction.Request, FlushJobActio
if (request.getAdvanceTime() != null) { if (request.getAdvanceTime() != null) {
paramsBuilder.advanceTime(request.getAdvanceTime()); paramsBuilder.advanceTime(request.getAdvanceTime());
} }
if (request.getSkipTime() != null) {
paramsBuilder.skipTime(request.getSkipTime());
}
TimeRange.Builder timeRangeBuilder = TimeRange.builder(); TimeRange.Builder timeRangeBuilder = TimeRange.builder();
if (request.getStart() != null) { if (request.getStart() != null) {
timeRangeBuilder.startTime(request.getStart()); timeRangeBuilder.startTime(request.getStart());
@ -269,13 +316,12 @@ public class FlushJobAction extends Action<FlushJobAction.Request, FlushJobActio
timeRangeBuilder.endTime(request.getEnd()); timeRangeBuilder.endTime(request.getEnd());
} }
paramsBuilder.forTimeRange(timeRangeBuilder.build()); paramsBuilder.forTimeRange(timeRangeBuilder.build());
processManager.flushJob(task, paramsBuilder.build(), e -> { processManager.flushJob(task, paramsBuilder.build(), ActionListener.wrap(
if (e == null) { flushAcknowledgement -> {
listener.onResponse(new Response(true)); listener.onResponse(new Response(true,
} else { flushAcknowledgement == null ? null : flushAcknowledgement.getLastFinalizedBucketEnd()));
listener.onFailure(e); }, listener::onFailure
} ));
});
} }
} }
} }

View File

@ -186,13 +186,15 @@ public class OpenJobAction extends Action<OpenJobAction.Request, OpenJobAction.R
public static class JobParams implements PersistentTaskParams { public static class JobParams implements PersistentTaskParams {
/** TODO Remove in 7.0.0 */
public static final ParseField IGNORE_DOWNTIME = new ParseField("ignore_downtime"); public static final ParseField IGNORE_DOWNTIME = new ParseField("ignore_downtime");
public static final ParseField TIMEOUT = new ParseField("timeout"); public static final ParseField TIMEOUT = new ParseField("timeout");
public static ObjectParser<JobParams, Void> PARSER = new ObjectParser<>(TASK_NAME, JobParams::new); public static ObjectParser<JobParams, Void> PARSER = new ObjectParser<>(TASK_NAME, JobParams::new);
static { static {
PARSER.declareString(JobParams::setJobId, Job.ID); PARSER.declareString(JobParams::setJobId, Job.ID);
PARSER.declareBoolean(JobParams::setIgnoreDowntime, IGNORE_DOWNTIME); PARSER.declareBoolean((p, v) -> {}, IGNORE_DOWNTIME);
PARSER.declareString((params, val) -> PARSER.declareString((params, val) ->
params.setTimeout(TimeValue.parseTimeValue(val, TIMEOUT.getPreferredName())), TIMEOUT); params.setTimeout(TimeValue.parseTimeValue(val, TIMEOUT.getPreferredName())), TIMEOUT);
} }
@ -210,7 +212,6 @@ public class OpenJobAction extends Action<OpenJobAction.Request, OpenJobAction.R
} }
private String jobId; private String jobId;
private boolean ignoreDowntime = true;
// A big state can take a while to restore. For symmetry with the _close endpoint any // A big state can take a while to restore. For symmetry with the _close endpoint any
// changes here should be reflected there too. // changes here should be reflected there too.
private TimeValue timeout = MachineLearning.STATE_PERSIST_RESTORE_TIMEOUT; private TimeValue timeout = MachineLearning.STATE_PERSIST_RESTORE_TIMEOUT;
@ -224,7 +225,10 @@ public class OpenJobAction extends Action<OpenJobAction.Request, OpenJobAction.R
public JobParams(StreamInput in) throws IOException { public JobParams(StreamInput in) throws IOException {
jobId = in.readString(); jobId = in.readString();
ignoreDowntime = in.readBoolean(); if (in.getVersion().onOrBefore(Version.V_5_5_0)) {
// Read `ignoreDowntime`
in.readBoolean();
}
timeout = TimeValue.timeValueMillis(in.readVLong()); timeout = TimeValue.timeValueMillis(in.readVLong());
} }
@ -236,14 +240,6 @@ public class OpenJobAction extends Action<OpenJobAction.Request, OpenJobAction.R
this.jobId = jobId; this.jobId = jobId;
} }
public boolean isIgnoreDowntime() {
return ignoreDowntime;
}
public void setIgnoreDowntime(boolean ignoreDowntime) {
this.ignoreDowntime = ignoreDowntime;
}
public TimeValue getTimeout() { public TimeValue getTimeout() {
return timeout; return timeout;
} }
@ -260,7 +256,10 @@ public class OpenJobAction extends Action<OpenJobAction.Request, OpenJobAction.R
@Override @Override
public void writeTo(StreamOutput out) throws IOException { public void writeTo(StreamOutput out) throws IOException {
out.writeString(jobId); out.writeString(jobId);
out.writeBoolean(ignoreDowntime); if (out.getVersion().onOrBefore(Version.V_5_5_0)) {
// Write `ignoreDowntime` - true by default
out.writeBoolean(true);
}
out.writeVLong(timeout.millis()); out.writeVLong(timeout.millis());
} }
@ -268,7 +267,6 @@ public class OpenJobAction extends Action<OpenJobAction.Request, OpenJobAction.R
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(); builder.startObject();
builder.field(Job.ID.getPreferredName(), jobId); builder.field(Job.ID.getPreferredName(), jobId);
builder.field(IGNORE_DOWNTIME.getPreferredName(), ignoreDowntime);
builder.field(TIMEOUT.getPreferredName(), timeout.getStringRep()); builder.field(TIMEOUT.getPreferredName(), timeout.getStringRep());
builder.endObject(); builder.endObject();
return builder; return builder;
@ -276,7 +274,7 @@ public class OpenJobAction extends Action<OpenJobAction.Request, OpenJobAction.R
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(jobId, ignoreDowntime, timeout); return Objects.hash(jobId, timeout);
} }
@Override @Override
@ -289,7 +287,6 @@ public class OpenJobAction extends Action<OpenJobAction.Request, OpenJobAction.R
} }
OpenJobAction.JobParams other = (OpenJobAction.JobParams) obj; OpenJobAction.JobParams other = (OpenJobAction.JobParams) obj;
return Objects.equals(jobId, other.jobId) && return Objects.equals(jobId, other.jobId) &&
Objects.equals(ignoreDowntime, other.ignoreDowntime) &&
Objects.equals(timeout, other.timeout); Objects.equals(timeout, other.timeout);
} }
@ -588,7 +585,7 @@ public class OpenJobAction extends Action<OpenJobAction.Request, OpenJobAction.R
protected void nodeOperation(AllocatedPersistentTask task, JobParams params) { protected void nodeOperation(AllocatedPersistentTask task, JobParams params) {
JobTask jobTask = (JobTask) task; JobTask jobTask = (JobTask) task;
jobTask.autodetectProcessManager = autodetectProcessManager; jobTask.autodetectProcessManager = autodetectProcessManager;
autodetectProcessManager.openJob(jobTask, params.isIgnoreDowntime(), e2 -> { autodetectProcessManager.openJob(jobTask, e2 -> {
if (e2 == null) { if (e2 == null) {
task.markAsCompleted(); task.markAsCompleted();
} else { } else {

View File

@ -77,7 +77,7 @@ class DatafeedJob {
} }
Long runLookBack(long startTime, Long endTime) throws Exception { Long runLookBack(long startTime, Long endTime) throws Exception {
lookbackStartTimeMs = (lastEndTimeMs != null && lastEndTimeMs + 1 > startTime) ? lastEndTimeMs + 1 : startTime; lookbackStartTimeMs = skipToStartTime(startTime);
Optional<Long> endMs = Optional.ofNullable(endTime); Optional<Long> endMs = Optional.ofNullable(endTime);
long lookbackEnd = endMs.orElse(currentTimeSupplier.get() - queryDelayMs); long lookbackEnd = endMs.orElse(currentTimeSupplier.get() - queryDelayMs);
boolean isLookbackOnly = endMs.isPresent(); boolean isLookbackOnly = endMs.isPresent();
@ -115,6 +115,22 @@ class DatafeedJob {
return null; return null;
} }
private long skipToStartTime(long startTime) {
if (lastEndTimeMs != null) {
if (lastEndTimeMs + 1 > startTime) {
// start time is before last checkpoint, thus continue from checkpoint
return lastEndTimeMs + 1;
}
// start time is after last checkpoint, thus we need to skip time
FlushJobAction.Request request = new FlushJobAction.Request(jobId);
request.setSkipTime(String.valueOf(startTime));
FlushJobAction.Response flushResponse = flushJob(request);
LOGGER.info("Skipped to time [" + flushResponse.getLastFinalizedBucketEnd().getTime() + "]");
return flushResponse.getLastFinalizedBucketEnd().getTime();
}
return startTime;
}
long runRealtime() throws Exception { long runRealtime() throws Exception {
long start = lastEndTimeMs == null ? lookbackStartTimeMs : Math.max(lookbackStartTimeMs, lastEndTimeMs + 1); long start = lastEndTimeMs == null ? lookbackStartTimeMs : Math.max(lookbackStartTimeMs, lastEndTimeMs + 1);
long nowMinusQueryDelay = currentTimeSupplier.get() - queryDelayMs; long nowMinusQueryDelay = currentTimeSupplier.get() - queryDelayMs;
@ -265,10 +281,10 @@ class DatafeedJob {
return (epochMs / frequencyMs) * frequencyMs; return (epochMs / frequencyMs) * frequencyMs;
} }
private void flushJob(FlushJobAction.Request flushRequest) { private FlushJobAction.Response flushJob(FlushJobAction.Request flushRequest) {
try { try {
LOGGER.trace("[" + jobId + "] Sending flush request"); LOGGER.trace("[" + jobId + "] Sending flush request");
client.execute(FlushJobAction.INSTANCE, flushRequest).actionGet(); return client.execute(FlushJobAction.INSTANCE, flushRequest).actionGet();
} catch (Exception e) { } catch (Exception e) {
LOGGER.debug("[" + jobId + "] error while flushing job", e); LOGGER.debug("[" + jobId + "] error while flushing job", e);

View File

@ -78,7 +78,6 @@ public class ProcessCtrl {
*/ */
static final String BUCKET_SPAN_ARG = "--bucketspan="; static final String BUCKET_SPAN_ARG = "--bucketspan=";
public static final String DELETE_STATE_FILES_ARG = "--deleteStateFiles"; public static final String DELETE_STATE_FILES_ARG = "--deleteStateFiles";
static final String IGNORE_DOWNTIME_ARG = "--ignoreDowntime";
static final String LENGTH_ENCODED_INPUT_ARG = "--lengthEncodedInput"; static final String LENGTH_ENCODED_INPUT_ARG = "--lengthEncodedInput";
static final String MODEL_CONFIG_ARG = "--modelconfig="; static final String MODEL_CONFIG_ARG = "--modelconfig=";
public static final String QUANTILES_STATE_PATH_ARG = "--quantilesState="; public static final String QUANTILES_STATE_PATH_ARG = "--quantilesState=";
@ -151,8 +150,7 @@ public class ProcessCtrl {
return rng.nextInt(SECONDS_IN_HOUR); return rng.nextInt(SECONDS_IN_HOUR);
} }
public static List<String> buildAutodetectCommand(Environment env, Settings settings, Job job, Logger logger, boolean ignoreDowntime, public static List<String> buildAutodetectCommand(Environment env, Settings settings, Job job, Logger logger, long controllerPid) {
long controllerPid) {
List<String> command = new ArrayList<>(); List<String> command = new ArrayList<>();
command.add(AUTODETECT_PATH); command.add(AUTODETECT_PATH);
@ -213,10 +211,6 @@ public class ProcessCtrl {
int maxQuantileInterval = BASE_MAX_QUANTILE_INTERVAL + intervalStagger; int maxQuantileInterval = BASE_MAX_QUANTILE_INTERVAL + intervalStagger;
command.add(MAX_QUANTILE_INTERVAL_ARG + maxQuantileInterval); command.add(MAX_QUANTILE_INTERVAL_ARG + maxQuantileInterval);
if (ignoreDowntime) {
command.add(IGNORE_DOWNTIME_ARG);
}
if (modelConfigFilePresent(env)) { if (modelConfigFilePresent(env)) {
String modelConfigFile = XPackPlugin.resolveConfigFile(env, ML_MODEL_CONF).toString(); String modelConfigFile = XPackPlugin.resolveConfigFile(env, ML_MODEL_CONF).toString();
command.add(MODEL_CONFIG_ARG + modelConfigFile); command.add(MODEL_CONFIG_ARG + modelConfigFile);

View File

@ -43,7 +43,6 @@ public class AutodetectBuilder {
private Job job; private Job job;
private List<Path> filesToDelete; private List<Path> filesToDelete;
private Logger logger; private Logger logger;
private boolean ignoreDowntime;
private Set<MlFilter> referencedFilters; private Set<MlFilter> referencedFilters;
private Quantiles quantiles; private Quantiles quantiles;
private Environment env; private Environment env;
@ -68,21 +67,9 @@ public class AutodetectBuilder {
this.job = Objects.requireNonNull(job); this.job = Objects.requireNonNull(job);
this.filesToDelete = Objects.requireNonNull(filesToDelete); this.filesToDelete = Objects.requireNonNull(filesToDelete);
this.logger = Objects.requireNonNull(logger); this.logger = Objects.requireNonNull(logger);
ignoreDowntime = false;
referencedFilters = new HashSet<>(); referencedFilters = new HashSet<>();
} }
/**
* Set ignoreDowntime
*
* @param ignoreDowntime If true set the ignore downtime flag overriding the
* setting in the job configuration
*/
public AutodetectBuilder ignoreDowntime(boolean ignoreDowntime) {
this.ignoreDowntime = ignoreDowntime;
return this;
}
public AutodetectBuilder referencedFilters(Set<MlFilter> filters) { public AutodetectBuilder referencedFilters(Set<MlFilter> filters) {
referencedFilters = filters; referencedFilters = filters;
return this; return this;
@ -103,7 +90,7 @@ public class AutodetectBuilder {
*/ */
public void build() throws IOException, TimeoutException { public void build() throws IOException, TimeoutException {
List<String> command = ProcessCtrl.buildAutodetectCommand(env, settings, job, logger, ignoreDowntime, controller.getPid()); List<String> command = ProcessCtrl.buildAutodetectCommand(env, settings, job, logger, controller.getPid());
buildLimits(command); buildLimits(command);
buildModelPlotConfig(command); buildModelPlotConfig(command);

View File

@ -10,6 +10,7 @@ import org.apache.logging.log4j.message.ParameterizedMessage;
import org.elasticsearch.ElasticsearchException; import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.ExceptionsHelper; import org.elasticsearch.ExceptionsHelper;
import org.elasticsearch.common.CheckedSupplier; import org.elasticsearch.common.CheckedSupplier;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.logging.Loggers; import org.elasticsearch.common.logging.Loggers;
import org.elasticsearch.common.util.concurrent.AbstractRunnable; import org.elasticsearch.common.util.concurrent.AbstractRunnable;
import org.elasticsearch.common.xcontent.NamedXContentRegistry; import org.elasticsearch.common.xcontent.NamedXContentRegistry;
@ -23,6 +24,7 @@ import org.elasticsearch.xpack.ml.job.persistence.StateStreamer;
import org.elasticsearch.xpack.ml.job.process.CountingInputStream; import org.elasticsearch.xpack.ml.job.process.CountingInputStream;
import org.elasticsearch.xpack.ml.job.process.DataCountsReporter; import org.elasticsearch.xpack.ml.job.process.DataCountsReporter;
import org.elasticsearch.xpack.ml.job.process.autodetect.output.AutoDetectResultProcessor; import org.elasticsearch.xpack.ml.job.process.autodetect.output.AutoDetectResultProcessor;
import org.elasticsearch.xpack.ml.job.process.autodetect.output.FlushAcknowledgement;
import org.elasticsearch.xpack.ml.job.process.autodetect.params.DataLoadParams; import org.elasticsearch.xpack.ml.job.process.autodetect.params.DataLoadParams;
import org.elasticsearch.xpack.ml.job.process.autodetect.params.FlushJobParams; import org.elasticsearch.xpack.ml.job.process.autodetect.params.FlushJobParams;
import org.elasticsearch.xpack.ml.job.process.autodetect.state.DataCounts; import org.elasticsearch.xpack.ml.job.process.autodetect.state.DataCounts;
@ -196,23 +198,24 @@ public class AutodetectCommunicator implements Closeable {
}, handler); }, handler);
} }
public void flushJob(FlushJobParams params, BiConsumer<Void, Exception> handler) { public void flushJob(FlushJobParams params, BiConsumer<FlushAcknowledgement, Exception> handler) {
submitOperation(() -> { submitOperation(() -> {
String flushId = autodetectProcess.flushJob(params); String flushId = autodetectProcess.flushJob(params);
waitFlushToCompletion(flushId); return waitFlushToCompletion(flushId);
return null;
}, handler); }, handler);
} }
void waitFlushToCompletion(String flushId) { @Nullable
FlushAcknowledgement waitFlushToCompletion(String flushId) {
LOGGER.debug("[{}] waiting for flush", job.getId()); LOGGER.debug("[{}] waiting for flush", job.getId());
FlushAcknowledgement flushAcknowledgement;
try { try {
boolean isFlushComplete = autoDetectResultProcessor.waitForFlushAcknowledgement(flushId, FLUSH_PROCESS_CHECK_FREQUENCY); flushAcknowledgement = autoDetectResultProcessor.waitForFlushAcknowledgement(flushId, FLUSH_PROCESS_CHECK_FREQUENCY);
while (isFlushComplete == false) { while (flushAcknowledgement == null) {
checkProcessIsAlive(); checkProcessIsAlive();
checkResultsProcessorIsAlive(); checkResultsProcessorIsAlive();
isFlushComplete = autoDetectResultProcessor.waitForFlushAcknowledgement(flushId, FLUSH_PROCESS_CHECK_FREQUENCY); flushAcknowledgement = autoDetectResultProcessor.waitForFlushAcknowledgement(flushId, FLUSH_PROCESS_CHECK_FREQUENCY);
} }
} finally { } finally {
autoDetectResultProcessor.clearAwaitingFlush(flushId); autoDetectResultProcessor.clearAwaitingFlush(flushId);
@ -225,6 +228,8 @@ public class AutodetectCommunicator implements Closeable {
LOGGER.debug("[{}] Flush completed", job.getId()); LOGGER.debug("[{}] Flush completed", job.getId());
} }
return flushAcknowledgement;
} }
/** /**

View File

@ -25,13 +25,11 @@ public interface AutodetectProcessFactory {
* @param modelSnapshot The model snapshot to restore from * @param modelSnapshot The model snapshot to restore from
* @param quantiles The quantiles to push to the native process * @param quantiles The quantiles to push to the native process
* @param filters The filters to push to the native process * @param filters The filters to push to the native process
* @param ignoreDowntime Should gaps in data be treated as anomalous or as a maintenance window after a job re-start
* @param executorService Executor service used to start the async tasks a job needs to operate the analytical process * @param executorService Executor service used to start the async tasks a job needs to operate the analytical process
* @param onProcessCrash Callback to execute if the process stops unexpectedly * @param onProcessCrash Callback to execute if the process stops unexpectedly
* @return The process * @return The process
*/ */
AutodetectProcess createAutodetectProcess(Job job, ModelSnapshot modelSnapshot, Quantiles quantiles, Set<MlFilter> filters, AutodetectProcess createAutodetectProcess(Job job, ModelSnapshot modelSnapshot, Quantiles quantiles, Set<MlFilter> filters,
boolean ignoreDowntime,
ExecutorService executorService, ExecutorService executorService,
Runnable onProcessCrash); Runnable onProcessCrash);
} }

View File

@ -37,6 +37,7 @@ import org.elasticsearch.xpack.ml.job.persistence.JobResultsPersister;
import org.elasticsearch.xpack.ml.job.persistence.StateStreamer; import org.elasticsearch.xpack.ml.job.persistence.StateStreamer;
import org.elasticsearch.xpack.ml.job.process.DataCountsReporter; import org.elasticsearch.xpack.ml.job.process.DataCountsReporter;
import org.elasticsearch.xpack.ml.job.process.autodetect.output.AutoDetectResultProcessor; import org.elasticsearch.xpack.ml.job.process.autodetect.output.AutoDetectResultProcessor;
import org.elasticsearch.xpack.ml.job.process.autodetect.output.FlushAcknowledgement;
import org.elasticsearch.xpack.ml.job.process.autodetect.params.AutodetectParams; import org.elasticsearch.xpack.ml.job.process.autodetect.params.AutodetectParams;
import org.elasticsearch.xpack.ml.job.process.autodetect.params.DataLoadParams; import org.elasticsearch.xpack.ml.job.process.autodetect.params.DataLoadParams;
import org.elasticsearch.xpack.ml.job.process.autodetect.params.FlushJobParams; import org.elasticsearch.xpack.ml.job.process.autodetect.params.FlushJobParams;
@ -201,23 +202,23 @@ public class AutodetectProcessManager extends AbstractComponent {
* @param params Parameters describing the controls that will accompany the flushing * @param params Parameters describing the controls that will accompany the flushing
* (e.g. calculating interim results, time control, etc.) * (e.g. calculating interim results, time control, etc.)
*/ */
public void flushJob(JobTask jobTask, FlushJobParams params, Consumer<Exception> handler) { public void flushJob(JobTask jobTask, FlushJobParams params, ActionListener<FlushAcknowledgement> handler) {
logger.debug("Flushing job {}", jobTask.getJobId()); logger.debug("Flushing job {}", jobTask.getJobId());
AutodetectCommunicator communicator = autoDetectCommunicatorByJob.get(jobTask.getAllocationId()); AutodetectCommunicator communicator = autoDetectCommunicatorByJob.get(jobTask.getAllocationId());
if (communicator == null) { if (communicator == null) {
String message = String.format(Locale.ROOT, "Cannot flush because job [%s] is not open", jobTask.getJobId()); String message = String.format(Locale.ROOT, "Cannot flush because job [%s] is not open", jobTask.getJobId());
logger.debug(message); logger.debug(message);
handler.accept(ExceptionsHelper.conflictStatusException(message)); handler.onFailure(ExceptionsHelper.conflictStatusException(message));
return; return;
} }
communicator.flushJob(params, (aVoid, e) -> { communicator.flushJob(params, (flushAcknowledgement, e) -> {
if (e == null) { if (e != null) {
handler.accept(null);
} else {
String msg = String.format(Locale.ROOT, "[%s] exception while flushing job", jobTask.getJobId()); String msg = String.format(Locale.ROOT, "[%s] exception while flushing job", jobTask.getJobId());
logger.error(msg); logger.error(msg);
handler.accept(ExceptionsHelper.serverError(msg, e)); handler.onFailure(ExceptionsHelper.serverError(msg, e));
} else {
handler.onResponse(flushAcknowledgement);
} }
}); });
} }
@ -240,7 +241,7 @@ public class AutodetectProcessManager extends AbstractComponent {
}); });
} }
public void openJob(JobTask jobTask, boolean ignoreDowntime, Consumer<Exception> handler) { public void openJob(JobTask jobTask, Consumer<Exception> handler) {
String jobId = jobTask.getJobId(); String jobId = jobTask.getJobId();
Job job = jobManager.getJobOrThrowIfUnknown(jobId); Job job = jobManager.getJobOrThrowIfUnknown(jobId);
@ -263,7 +264,7 @@ public class AutodetectProcessManager extends AbstractComponent {
protected void doRun() throws Exception { protected void doRun() throws Exception {
try { try {
AutodetectCommunicator communicator = autoDetectCommunicatorByJob.computeIfAbsent(jobTask.getAllocationId(), AutodetectCommunicator communicator = autoDetectCommunicatorByJob.computeIfAbsent(jobTask.getAllocationId(),
id -> create(jobTask, params, ignoreDowntime, handler)); id -> create(jobTask, params, handler));
communicator.init(params.modelSnapshot()); communicator.init(params.modelSnapshot());
setJobState(jobTask, JobState.OPENED); setJobState(jobTask, JobState.OPENED);
} catch (Exception e1) { } catch (Exception e1) {
@ -286,8 +287,7 @@ public class AutodetectProcessManager extends AbstractComponent {
}); });
} }
AutodetectCommunicator create(JobTask jobTask, AutodetectParams autodetectParams, AutodetectCommunicator create(JobTask jobTask, AutodetectParams autodetectParams, Consumer<Exception> handler) {
boolean ignoreDowntime, Consumer<Exception> handler) {
if (autoDetectCommunicatorByJob.size() == maxAllowedRunningJobs) { if (autoDetectCommunicatorByJob.size() == maxAllowedRunningJobs) {
throw new ElasticsearchStatusException("max running job capacity [" + maxAllowedRunningJobs + "] reached", throw new ElasticsearchStatusException("max running job capacity [" + maxAllowedRunningJobs + "] reached",
RestStatus.TOO_MANY_REQUESTS); RestStatus.TOO_MANY_REQUESTS);
@ -321,8 +321,8 @@ public class AutodetectProcessManager extends AbstractComponent {
renormalizerExecutorService, job.getAnalysisConfig().getUsePerPartitionNormalization()); renormalizerExecutorService, job.getAnalysisConfig().getUsePerPartitionNormalization());
AutodetectProcess process = autodetectProcessFactory.createAutodetectProcess(job, autodetectParams.modelSnapshot(), AutodetectProcess process = autodetectProcessFactory.createAutodetectProcess(job, autodetectParams.modelSnapshot(),
autodetectParams.quantiles(), autodetectParams.filters(), ignoreDowntime, autodetectParams.quantiles(), autodetectParams.filters(), autoDetectExecutorService,
autoDetectExecutorService, () -> setJobState(jobTask, JobState.FAILED)); () -> setJobState(jobTask, JobState.FAILED));
AutoDetectResultProcessor processor = new AutoDetectResultProcessor( AutoDetectResultProcessor processor = new AutoDetectResultProcessor(
client, jobId, renormalizer, jobResultsPersister, autodetectParams.modelSizeStats()); client, jobId, renormalizer, jobResultsPersister, autodetectParams.modelSizeStats());
ExecutorService autodetectWorkerExecutor; ExecutorService autodetectWorkerExecutor;

View File

@ -77,7 +77,7 @@ public class BlackHoleAutodetectProcess implements AutodetectProcess {
*/ */
@Override @Override
public String flushJob(FlushJobParams params) throws IOException { public String flushJob(FlushJobParams params) throws IOException {
FlushAcknowledgement flushAcknowledgement = new FlushAcknowledgement(FLUSH_ID); FlushAcknowledgement flushAcknowledgement = new FlushAcknowledgement(FLUSH_ID, null);
AutodetectResult result = new AutodetectResult(null, null, null, null, null, null, null, null, flushAcknowledgement); AutodetectResult result = new AutodetectResult(null, null, null, null, null, null, null, null, flushAcknowledgement);
results.add(result); results.add(result);
return FLUSH_ID; return FLUSH_ID;

View File

@ -59,13 +59,12 @@ public class NativeAutodetectProcessFactory implements AutodetectProcessFactory
@Override @Override
public AutodetectProcess createAutodetectProcess(Job job, ModelSnapshot modelSnapshot, public AutodetectProcess createAutodetectProcess(Job job, ModelSnapshot modelSnapshot,
Quantiles quantiles, Set<MlFilter> filters, Quantiles quantiles, Set<MlFilter> filters,
boolean ignoreDowntime,
ExecutorService executorService, ExecutorService executorService,
Runnable onProcessCrash) { Runnable onProcessCrash) {
List<Path> filesToDelete = new ArrayList<>(); List<Path> filesToDelete = new ArrayList<>();
ProcessPipes processPipes = new ProcessPipes(env, NAMED_PIPE_HELPER, ProcessCtrl.AUTODETECT, job.getId(), ProcessPipes processPipes = new ProcessPipes(env, NAMED_PIPE_HELPER, ProcessCtrl.AUTODETECT, job.getId(),
true, false, true, true, modelSnapshot != null, !ProcessCtrl.DONT_PERSIST_MODEL_STATE_SETTING.get(settings)); true, false, true, true, modelSnapshot != null, !ProcessCtrl.DONT_PERSIST_MODEL_STATE_SETTING.get(settings));
createNativeProcess(job, quantiles, filters, processPipes, ignoreDowntime, filesToDelete); createNativeProcess(job, quantiles, filters, processPipes, filesToDelete);
int numberOfAnalysisFields = job.getAnalysisConfig().analysisFields().size(); int numberOfAnalysisFields = job.getAnalysisConfig().analysisFields().size();
StateProcessor stateProcessor = new StateProcessor(settings, client); StateProcessor stateProcessor = new StateProcessor(settings, client);
@ -88,11 +87,10 @@ public class NativeAutodetectProcessFactory implements AutodetectProcessFactory
} }
private void createNativeProcess(Job job, Quantiles quantiles, Set<MlFilter> filters, ProcessPipes processPipes, private void createNativeProcess(Job job, Quantiles quantiles, Set<MlFilter> filters, ProcessPipes processPipes,
boolean ignoreDowntime, List<Path> filesToDelete) { List<Path> filesToDelete) {
try { try {
AutodetectBuilder autodetectBuilder = new AutodetectBuilder(job, filesToDelete, LOGGER, env, AutodetectBuilder autodetectBuilder = new AutodetectBuilder(job, filesToDelete, LOGGER, env,
settings, nativeController, processPipes) settings, nativeController, processPipes)
.ignoreDowntime(ignoreDowntime)
.referencedFilters(filters); .referencedFilters(filters);
// if state is null or empty it will be ignored // if state is null or empty it will be ignored

View File

@ -9,6 +9,7 @@ import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.message.ParameterizedMessage; import org.apache.logging.log4j.message.ParameterizedMessage;
import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.ActionListener;
import org.elasticsearch.client.Client; import org.elasticsearch.client.Client;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.logging.Loggers; import org.elasticsearch.common.logging.Loggers;
import org.elasticsearch.index.IndexNotFoundException; import org.elasticsearch.index.IndexNotFoundException;
import org.elasticsearch.xpack.ml.MachineLearning; import org.elasticsearch.xpack.ml.MachineLearning;
@ -231,7 +232,7 @@ public class AutoDetectResultProcessor {
// through to the data store // through to the data store
context.bulkResultsPersister.executeRequest(); context.bulkResultsPersister.executeRequest();
persister.commitResultWrites(context.jobId); persister.commitResultWrites(context.jobId);
flushListener.acknowledgeFlush(flushAcknowledgement.getId()); flushListener.acknowledgeFlush(flushAcknowledgement);
// Interim results may have been produced by the flush, // Interim results may have been produced by the flush,
// which need to be // which need to be
// deleted when the next finalized results come through // deleted when the next finalized results come through
@ -291,13 +292,11 @@ public class AutoDetectResultProcessor {
* *
* @param flushId the id of the flush request to wait for * @param flushId the id of the flush request to wait for
* @param timeout the timeout * @param timeout the timeout
* @return {@code true} if the flush has completed or the parsing finished; {@code false} if the timeout expired * @return The {@link FlushAcknowledgement} if the flush has completed or the parsing finished; {@code null} if the timeout expired
*/ */
public boolean waitForFlushAcknowledgement(String flushId, Duration timeout) { @Nullable
if (failed) { public FlushAcknowledgement waitForFlushAcknowledgement(String flushId, Duration timeout) {
return false; return failed ? null : flushListener.waitForFlush(flushId, timeout);
}
return flushListener.waitForFlush(flushId, timeout);
} }
public void clearAwaitingFlush(String flushId) { public void clearAwaitingFlush(String flushId) {

View File

@ -5,15 +5,20 @@
*/ */
package org.elasticsearch.xpack.ml.job.process.autodetect.output; package org.elasticsearch.xpack.ml.job.process.autodetect.output;
import org.elasticsearch.Version;
import org.elasticsearch.common.ParseField; import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.io.stream.Writeable; import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.common.xcontent.ConstructingObjectParser; import org.elasticsearch.common.xcontent.ConstructingObjectParser;
import org.elasticsearch.common.xcontent.ObjectParser;
import org.elasticsearch.common.xcontent.ToXContentObject; import org.elasticsearch.common.xcontent.ToXContentObject;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.xpack.ml.utils.time.TimeUtils;
import java.io.IOException; import java.io.IOException;
import java.util.Date;
import java.util.Objects; import java.util.Objects;
/** /**
@ -25,44 +30,70 @@ public class FlushAcknowledgement implements ToXContentObject, Writeable {
*/ */
public static final ParseField TYPE = new ParseField("flush"); public static final ParseField TYPE = new ParseField("flush");
public static final ParseField ID = new ParseField("id"); public static final ParseField ID = new ParseField("id");
public static final ParseField LAST_FINALIZED_BUCKET_END = new ParseField("last_finalized_bucket_end");
public static final ConstructingObjectParser<FlushAcknowledgement, Void> PARSER = new ConstructingObjectParser<>( public static final ConstructingObjectParser<FlushAcknowledgement, Void> PARSER = new ConstructingObjectParser<>(
TYPE.getPreferredName(), a -> new FlushAcknowledgement((String) a[0])); TYPE.getPreferredName(), a -> new FlushAcknowledgement((String) a[0], (Date) a[1]));
static { static {
PARSER.declareString(ConstructingObjectParser.constructorArg(), ID); PARSER.declareString(ConstructingObjectParser.constructorArg(), ID);
PARSER.declareField(ConstructingObjectParser.optionalConstructorArg(), p -> {
if (p.currentToken() == XContentParser.Token.VALUE_NUMBER) {
return new Date(p.longValue());
} else if (p.currentToken() == XContentParser.Token.VALUE_STRING) {
return new Date(TimeUtils.dateStringToEpoch(p.text()));
}
throw new IllegalArgumentException(
"unexpected token [" + p.currentToken() + "] for [" + LAST_FINALIZED_BUCKET_END.getPreferredName() + "]");
}, LAST_FINALIZED_BUCKET_END, ObjectParser.ValueType.VALUE);
} }
private String id; private String id;
private Date lastFinalizedBucketEnd;
public FlushAcknowledgement(String id) { public FlushAcknowledgement(String id, Date lastFinalizedBucketEnd) {
this.id = id; this.id = id;
this.lastFinalizedBucketEnd = lastFinalizedBucketEnd;
} }
public FlushAcknowledgement(StreamInput in) throws IOException { public FlushAcknowledgement(StreamInput in) throws IOException {
id = in.readString(); id = in.readString();
if (in.getVersion().after(Version.V_5_5_0)) {
lastFinalizedBucketEnd = new Date(in.readVLong());
}
} }
@Override @Override
public void writeTo(StreamOutput out) throws IOException { public void writeTo(StreamOutput out) throws IOException {
out.writeString(id); out.writeString(id);
if (out.getVersion().after(Version.V_5_5_0)) {
out.writeVLong(lastFinalizedBucketEnd.getTime());
}
} }
public String getId() { public String getId() {
return id; return id;
} }
public Date getLastFinalizedBucketEnd() {
return lastFinalizedBucketEnd;
}
@Override @Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(); builder.startObject();
builder.field(ID.getPreferredName(), id); builder.field(ID.getPreferredName(), id);
if (lastFinalizedBucketEnd != null) {
builder.dateField(LAST_FINALIZED_BUCKET_END.getPreferredName(), LAST_FINALIZED_BUCKET_END.getPreferredName() + "_string",
lastFinalizedBucketEnd.getTime());
}
builder.endObject(); builder.endObject();
return builder; return builder;
} }
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(id); return Objects.hash(id, lastFinalizedBucketEnd);
} }
@Override @Override
@ -74,7 +105,8 @@ public class FlushAcknowledgement implements ToXContentObject, Writeable {
return false; return false;
} }
FlushAcknowledgement other = (FlushAcknowledgement) obj; FlushAcknowledgement other = (FlushAcknowledgement) obj;
return Objects.equals(id, other.id); return Objects.equals(id, other.id) &&
Objects.equals(lastFinalizedBucketEnd, other.lastFinalizedBucketEnd);
} }
} }

View File

@ -5,6 +5,8 @@
*/ */
package org.elasticsearch.xpack.ml.job.process.autodetect.output; package org.elasticsearch.xpack.ml.job.process.autodetect.output;
import org.elasticsearch.common.Nullable;
import java.time.Duration; import java.time.Duration;
import java.util.Iterator; import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@ -15,29 +17,34 @@ import java.util.concurrent.atomic.AtomicBoolean;
class FlushListener { class FlushListener {
final ConcurrentMap<String, CountDownLatch> awaitingFlushed = new ConcurrentHashMap<>(); final ConcurrentMap<String, FlushAcknowledgementHolder> awaitingFlushed = new ConcurrentHashMap<>();
final AtomicBoolean cleared = new AtomicBoolean(false); final AtomicBoolean cleared = new AtomicBoolean(false);
boolean waitForFlush(String flushId, Duration timeout) { @Nullable
FlushAcknowledgement waitForFlush(String flushId, Duration timeout) {
if (cleared.get()) { if (cleared.get()) {
return false; return null;
} }
CountDownLatch latch = awaitingFlushed.computeIfAbsent(flushId, (key) -> new CountDownLatch(1)); FlushAcknowledgementHolder holder = awaitingFlushed.computeIfAbsent(flushId, (key) -> new FlushAcknowledgementHolder(flushId));
try { try {
return latch.await(timeout.toMillis(), TimeUnit.MILLISECONDS); if (holder.latch.await(timeout.toMillis(), TimeUnit.MILLISECONDS)) {
return holder.flushAcknowledgement;
}
} catch (InterruptedException e) { } catch (InterruptedException e) {
Thread.currentThread().interrupt(); Thread.currentThread().interrupt();
return false;
} }
return null;
} }
void acknowledgeFlush(String flushId) { void acknowledgeFlush(FlushAcknowledgement flushAcknowledgement) {
// acknowledgeFlush(...) could be called before waitForFlush(...) // acknowledgeFlush(...) could be called before waitForFlush(...)
// a flush api call writes a flush command to the analytical process and then via a different thread the // a flush api call writes a flush command to the analytical process and then via a different thread the
// result reader then reads whether the flush has been acked. // result reader then reads whether the flush has been acked.
CountDownLatch latch = awaitingFlushed.computeIfAbsent(flushId, (key) -> new CountDownLatch(1)); String flushId = flushAcknowledgement.getId();
latch.countDown(); FlushAcknowledgementHolder holder = awaitingFlushed.computeIfAbsent(flushId, (key) -> new FlushAcknowledgementHolder(flushId));
holder.flushAcknowledgement = flushAcknowledgement;
holder.latch.countDown();
} }
void clear(String flushId) { void clear(String flushId) {
@ -46,11 +53,22 @@ class FlushListener {
void clear() { void clear() {
if (cleared.compareAndSet(false, true)) { if (cleared.compareAndSet(false, true)) {
Iterator<ConcurrentMap.Entry<String, CountDownLatch>> latches = awaitingFlushed.entrySet().iterator(); Iterator<ConcurrentMap.Entry<String, FlushAcknowledgementHolder>> latches = awaitingFlushed.entrySet().iterator();
while (latches.hasNext()) { while (latches.hasNext()) {
latches.next().getValue().countDown(); latches.next().getValue().latch.countDown();
latches.remove(); latches.remove();
} }
} }
} }
private static class FlushAcknowledgementHolder {
private final CountDownLatch latch;
private volatile FlushAcknowledgement flushAcknowledgement;
private FlushAcknowledgementHolder(String flushId) {
this.flushAcknowledgement = new FlushAcknowledgement(flushId, null);
this.latch = new CountDownLatch(1);
}
}
} }

View File

@ -6,6 +6,7 @@
package org.elasticsearch.xpack.ml.job.process.autodetect.params; package org.elasticsearch.xpack.ml.job.process.autodetect.params;
import org.elasticsearch.ElasticsearchParseException; import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.common.Strings;
import org.elasticsearch.xpack.ml.job.messages.Messages; import org.elasticsearch.xpack.ml.job.messages.Messages;
import org.elasticsearch.xpack.ml.utils.ExceptionsHelper; import org.elasticsearch.xpack.ml.utils.ExceptionsHelper;
import org.elasticsearch.xpack.ml.utils.time.TimeUtils; import org.elasticsearch.xpack.ml.utils.time.TimeUtils;
@ -13,14 +14,32 @@ import org.elasticsearch.xpack.ml.utils.time.TimeUtils;
import java.util.Objects; import java.util.Objects;
public class FlushJobParams { public class FlushJobParams {
/**
* Whether interim results should be calculated
*/
private final boolean calcInterim; private final boolean calcInterim;
/**
* The time range for which interim results should be calculated
*/
private final TimeRange timeRange; private final TimeRange timeRange;
/**
* The epoch (seconds) to advance time to
*/
private final Long advanceTimeSeconds; private final Long advanceTimeSeconds;
private FlushJobParams(boolean calcInterim, TimeRange timeRange, Long advanceTimeSeconds) { /**
* The epoch (seconds) to skip time to
*/
private final Long skipTimeSeconds;
private FlushJobParams(boolean calcInterim, TimeRange timeRange, Long advanceTimeSeconds, Long skipTimeSeconds) {
this.calcInterim = calcInterim; this.calcInterim = calcInterim;
this.timeRange = Objects.requireNonNull(timeRange); this.timeRange = Objects.requireNonNull(timeRange);
this.advanceTimeSeconds = advanceTimeSeconds; this.advanceTimeSeconds = advanceTimeSeconds;
this.skipTimeSeconds = skipTimeSeconds;
} }
public boolean shouldCalculateInterim() { public boolean shouldCalculateInterim() {
@ -31,6 +50,10 @@ public class FlushJobParams {
return advanceTimeSeconds != null; return advanceTimeSeconds != null;
} }
public boolean shouldSkipTime() {
return skipTimeSeconds != null;
}
public String getStart() { public String getStart() {
return timeRange.getStart(); return timeRange.getStart();
} }
@ -46,6 +69,13 @@ public class FlushJobParams {
return advanceTimeSeconds; return advanceTimeSeconds;
} }
public long getSkipTime() {
if (!shouldSkipTime()) {
throw new IllegalStateException();
}
return skipTimeSeconds;
}
public static Builder builder() { public static Builder builder() {
return new Builder(); return new Builder();
} }
@ -57,24 +87,20 @@ public class FlushJobParams {
FlushJobParams that = (FlushJobParams) o; FlushJobParams that = (FlushJobParams) o;
return calcInterim == that.calcInterim && return calcInterim == that.calcInterim &&
Objects.equals(timeRange, that.timeRange) && Objects.equals(timeRange, that.timeRange) &&
Objects.equals(advanceTimeSeconds, that.advanceTimeSeconds); Objects.equals(advanceTimeSeconds, that.advanceTimeSeconds) &&
Objects.equals(skipTimeSeconds, that.skipTimeSeconds);
} }
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(calcInterim, timeRange, advanceTimeSeconds); return Objects.hash(calcInterim, timeRange, advanceTimeSeconds, skipTimeSeconds);
} }
public static class Builder { public static class Builder {
private boolean calcInterim = false; private boolean calcInterim = false;
private TimeRange timeRange; private TimeRange timeRange = TimeRange.builder().build();
private String advanceTime; private String advanceTime;
private String skipTime;
private Builder() {
calcInterim = false;
timeRange = TimeRange.builder().build();
advanceTime = "";
}
public Builder calcInterim(boolean value) { public Builder calcInterim(boolean value) {
calcInterim = value; calcInterim = value;
@ -87,14 +113,24 @@ public class FlushJobParams {
} }
public Builder advanceTime(String timestamp) { public Builder advanceTime(String timestamp) {
advanceTime = ExceptionsHelper.requireNonNull(timestamp, "advance"); advanceTime = ExceptionsHelper.requireNonNull(timestamp, "advance_time");
return this;
}
public Builder skipTime(String timestamp) {
skipTime = ExceptionsHelper.requireNonNull(timestamp, "skip_time");
return this; return this;
} }
public FlushJobParams build() { public FlushJobParams build() {
checkValidFlushArgumentsCombination(); checkValidFlushArgumentsCombination();
Long advanceTimeSeconds = checkAdvanceTimeParam(); Long advanceTimeSeconds = parseTimeParam("advance_time", advanceTime);
return new FlushJobParams(calcInterim, timeRange, advanceTimeSeconds); Long skipTimeSeconds = parseTimeParam("skip_time", skipTime);
if (skipTimeSeconds != null && advanceTimeSeconds != null && advanceTimeSeconds <= skipTimeSeconds) {
throw ExceptionsHelper.badRequestException("advance_time [" + advanceTime + "] must be later than skip_time ["
+ skipTime + "]");
}
return new FlushJobParams(calcInterim, timeRange, advanceTimeSeconds, skipTimeSeconds);
} }
private void checkValidFlushArgumentsCombination() { private void checkValidFlushArgumentsCombination() {
@ -107,11 +143,11 @@ public class FlushJobParams {
} }
} }
private Long checkAdvanceTimeParam() { private Long parseTimeParam(String name, String value) {
if (advanceTime != null && !advanceTime.isEmpty()) { if (Strings.isNullOrEmpty(value)) {
return paramToEpochIfValidOrThrow("advance_time", advanceTime) / TimeRange.MILLISECONDS_IN_SECOND; return null;
} }
return null; return paramToEpochIfValidOrThrow(name, value) / TimeRange.MILLISECONDS_IN_SECOND;
} }
private long paramToEpochIfValidOrThrow(String paramName, String date) { private long paramToEpochIfValidOrThrow(String paramName, String date) {

View File

@ -52,6 +52,11 @@ public class ControlMsgToProcessWriter {
*/ */
private static final String ADVANCE_TIME_MESSAGE_CODE = "t"; private static final String ADVANCE_TIME_MESSAGE_CODE = "t";
/**
* This must match the code defined in the api::CAnomalyDetector C++ class.
*/
private static final String SKIP_TIME_MESSAGE_CODE = "s";
/** /**
* This must match the code defined in the api::CAnomalyDetector C++ class. * This must match the code defined in the api::CAnomalyDetector C++ class.
*/ */
@ -108,6 +113,9 @@ public class ControlMsgToProcessWriter {
* (e.g. calculating interim results, time control, etc.) * (e.g. calculating interim results, time control, etc.)
*/ */
public void writeFlushControlMessage(FlushJobParams params) throws IOException { public void writeFlushControlMessage(FlushJobParams params) throws IOException {
if (params.shouldSkipTime()) {
writeMessage(SKIP_TIME_MESSAGE_CODE + params.getSkipTime());
}
if (params.shouldAdvanceTime()) { if (params.shouldAdvanceTime()) {
writeMessage(ADVANCE_TIME_MESSAGE_CODE + params.getAdvanceTime()); writeMessage(ADVANCE_TIME_MESSAGE_CODE + params.getAdvanceTime());
} }

View File

@ -24,6 +24,7 @@ public class RestFlushJobAction extends BaseRestHandler {
private final String DEFAULT_START = ""; private final String DEFAULT_START = "";
private final String DEFAULT_END = ""; private final String DEFAULT_END = "";
private final String DEFAULT_ADVANCE_TIME = ""; private final String DEFAULT_ADVANCE_TIME = "";
private final String DEFAULT_SKIP_TIME = "";
public RestFlushJobAction(Settings settings, RestController controller) { public RestFlushJobAction(Settings settings, RestController controller) {
super(settings); super(settings);
@ -50,6 +51,7 @@ public class RestFlushJobAction extends BaseRestHandler {
request.setStart(restRequest.param(FlushJobAction.Request.START.getPreferredName(), DEFAULT_START)); request.setStart(restRequest.param(FlushJobAction.Request.START.getPreferredName(), DEFAULT_START));
request.setEnd(restRequest.param(FlushJobAction.Request.END.getPreferredName(), DEFAULT_END)); request.setEnd(restRequest.param(FlushJobAction.Request.END.getPreferredName(), DEFAULT_END));
request.setAdvanceTime(restRequest.param(FlushJobAction.Request.ADVANCE_TIME.getPreferredName(), DEFAULT_ADVANCE_TIME)); request.setAdvanceTime(restRequest.param(FlushJobAction.Request.ADVANCE_TIME.getPreferredName(), DEFAULT_ADVANCE_TIME));
request.setSkipTime(restRequest.param(FlushJobAction.Request.SKIP_TIME.getPreferredName(), DEFAULT_SKIP_TIME));
} }
return channel -> client.execute(FlushJobAction.INSTANCE, request, new RestToXContentListener<>(channel)); return channel -> client.execute(FlushJobAction.INSTANCE, request, new RestToXContentListener<>(channel));

View File

@ -42,7 +42,6 @@ public class RestOpenJobAction extends BaseRestHandler {
request = OpenJobAction.Request.parseRequest(restRequest.param(Job.ID.getPreferredName()), restRequest.contentParser()); request = OpenJobAction.Request.parseRequest(restRequest.param(Job.ID.getPreferredName()), restRequest.contentParser());
} else { } else {
OpenJobAction.JobParams jobParams = new OpenJobAction.JobParams(restRequest.param(Job.ID.getPreferredName())); OpenJobAction.JobParams jobParams = new OpenJobAction.JobParams(restRequest.param(Job.ID.getPreferredName()));
jobParams.setIgnoreDowntime(restRequest.paramAsBoolean(OpenJobAction.JobParams.IGNORE_DOWNTIME.getPreferredName(), true));
if (restRequest.hasParam(OpenJobAction.JobParams.TIMEOUT.getPreferredName())) { if (restRequest.hasParam(OpenJobAction.JobParams.TIMEOUT.getPreferredName())) {
TimeValue openTimeout = restRequest.paramAsTime(OpenJobAction.JobParams.TIMEOUT.getPreferredName(), TimeValue openTimeout = restRequest.paramAsTime(OpenJobAction.JobParams.TIMEOUT.getPreferredName(),
TimeValue.timeValueSeconds(20)); TimeValue.timeValueSeconds(20));

View File

@ -18,9 +18,6 @@ public class OpenJobActionRequestTests extends AbstractStreamableXContentTestCas
if (randomBoolean()) { if (randomBoolean()) {
params.setTimeout(TimeValue.timeValueMillis(randomNonNegativeLong())); params.setTimeout(TimeValue.timeValueMillis(randomNonNegativeLong()));
} }
if (randomBoolean()) {
params.setIgnoreDowntime(randomBoolean());
}
return new Request(params); return new Request(params);
} }

View File

@ -7,12 +7,13 @@ package org.elasticsearch.xpack.ml.action;
import org.elasticsearch.test.AbstractStreamableTestCase; import org.elasticsearch.test.AbstractStreamableTestCase;
import org.elasticsearch.xpack.ml.action.FlushJobAction.Response; import org.elasticsearch.xpack.ml.action.FlushJobAction.Response;
import org.joda.time.DateTime;
public class PostDataFlushResponseTests extends AbstractStreamableTestCase<Response> { public class PostDataFlushResponseTests extends AbstractStreamableTestCase<Response> {
@Override @Override
protected Response createTestInstance() { protected Response createTestInstance() {
return new Response(randomBoolean()); return new Response(randomBoolean(), new DateTime(randomDateTimeZone()).toDate());
} }
@Override @Override

View File

@ -27,9 +27,11 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Date; import java.util.Date;
import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.function.Supplier; import java.util.function.Supplier;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue; import static org.hamcrest.Matchers.nullValue;
import static org.mockito.Matchers.any; import static org.mockito.Matchers.any;
@ -139,6 +141,7 @@ public class DatafeedJobTests extends ESTestCase {
assertEquals(10000 + frequencyMs + 100, next); assertEquals(10000 + frequencyMs + 100, next);
verify(dataExtractorFactory).newExtractor(5000 + 1L, currentTime - queryDelayMs); verify(dataExtractorFactory).newExtractor(5000 + 1L, currentTime - queryDelayMs);
assertThat(flushJobRequests.getAllValues().size(), equalTo(1));
FlushJobAction.Request flushRequest = new FlushJobAction.Request("_job_id"); FlushJobAction.Request flushRequest = new FlushJobAction.Request("_job_id");
flushRequest.setCalcInterim(true); flushRequest.setCalcInterim(true);
verify(client).execute(same(FlushJobAction.INSTANCE), eq(flushRequest)); verify(client).execute(same(FlushJobAction.INSTANCE), eq(flushRequest));
@ -148,14 +151,17 @@ public class DatafeedJobTests extends ESTestCase {
// We need to return empty counts so that the lookback doesn't update the last end time // We need to return empty counts so that the lookback doesn't update the last end time
when(postDataFuture.actionGet()).thenReturn(new PostDataAction.Response(new DataCounts("_job_id"))); when(postDataFuture.actionGet()).thenReturn(new PostDataAction.Response(new DataCounts("_job_id")));
currentTime = 10000L; currentTime = 9999L;
long latestFinalBucketEndTimeMs = 5000; long latestFinalBucketEndTimeMs = 5000;
long latestRecordTimeMs = 5000; long latestRecordTimeMs = 5000;
FlushJobAction.Response skipTimeResponse = new FlushJobAction.Response(true, new Date(10000L));
when(flushJobFuture.actionGet()).thenReturn(skipTimeResponse);
long frequencyMs = 1000; long frequencyMs = 1000;
long queryDelayMs = 500; long queryDelayMs = 500;
DatafeedJob datafeedJob = createDatafeedJob(frequencyMs, queryDelayMs, latestFinalBucketEndTimeMs, latestRecordTimeMs); DatafeedJob datafeedJob = createDatafeedJob(frequencyMs, queryDelayMs, latestFinalBucketEndTimeMs, latestRecordTimeMs);
datafeedJob.runLookBack(10000L, null); datafeedJob.runLookBack(currentTime, null);
// advance time // advance time
currentTime = 12000L; currentTime = 12000L;
@ -163,6 +169,13 @@ public class DatafeedJobTests extends ESTestCase {
expectThrows(DatafeedJob.EmptyDataCountException.class, () -> datafeedJob.runRealtime()); expectThrows(DatafeedJob.EmptyDataCountException.class, () -> datafeedJob.runRealtime());
verify(dataExtractorFactory, times(1)).newExtractor(10000L, 11000L); verify(dataExtractorFactory, times(1)).newExtractor(10000L, 11000L);
List<FlushJobAction.Request> capturedFlushJobRequests = flushJobRequests.getAllValues();
assertThat(capturedFlushJobRequests.size(), equalTo(2));
assertThat(capturedFlushJobRequests.get(0).getCalcInterim(), is(false));
assertThat(capturedFlushJobRequests.get(0).getSkipTime(), equalTo("9999"));
assertThat(capturedFlushJobRequests.get(1).getCalcInterim(), is(true));
assertThat(capturedFlushJobRequests.get(1).getSkipTime(), is(nullValue()));
assertThat(capturedFlushJobRequests.get(1).getAdvanceTime(), equalTo("11000"));
Mockito.verifyNoMoreInteractions(dataExtractorFactory); Mockito.verifyNoMoreInteractions(dataExtractorFactory);
} }

View File

@ -354,7 +354,7 @@ public class AutodetectResultProcessorIT extends XPackSingleNodeTestCase {
} }
private FlushAcknowledgement createFlushAcknowledgement() { private FlushAcknowledgement createFlushAcknowledgement() {
return new FlushAcknowledgement(randomAlphaOfLength(5)); return new FlushAcknowledgement(randomAlphaOfLength(5), new Date(randomNonNegativeLong()));
} }
private class ResultsBuilder { private class ResultsBuilder {

View File

@ -51,8 +51,8 @@ public class ProcessCtrlTests extends ESTestCase {
dd.setTimeField("tf"); dd.setTimeField("tf");
job.setDataDescription(dd); job.setDataDescription(dd);
List<String> command = ProcessCtrl.buildAutodetectCommand(env, settings, job.build(), logger, true, pid); List<String> command = ProcessCtrl.buildAutodetectCommand(env, settings, job.build(), logger, pid);
assertEquals(15, command.size()); assertEquals(14, command.size());
assertTrue(command.contains(ProcessCtrl.AUTODETECT_PATH)); assertTrue(command.contains(ProcessCtrl.AUTODETECT_PATH));
assertTrue(command.contains(ProcessCtrl.BUCKET_SPAN_ARG + "120")); assertTrue(command.contains(ProcessCtrl.BUCKET_SPAN_ARG + "120"));
assertTrue(command.contains(ProcessCtrl.LATENCY_ARG + "360")); assertTrue(command.contains(ProcessCtrl.LATENCY_ARG + "360"));
@ -73,7 +73,6 @@ public class ProcessCtrlTests extends ESTestCase {
assertTrue(command.contains(ProcessCtrl.PERSIST_INTERVAL_ARG + expectedPersistInterval)); assertTrue(command.contains(ProcessCtrl.PERSIST_INTERVAL_ARG + expectedPersistInterval));
int expectedMaxQuantileInterval = 21600 + ProcessCtrl.calculateStaggeringInterval(job.getId()); int expectedMaxQuantileInterval = 21600 + ProcessCtrl.calculateStaggeringInterval(job.getId());
assertTrue(command.contains(ProcessCtrl.MAX_QUANTILE_INTERVAL_ARG + expectedMaxQuantileInterval)); assertTrue(command.contains(ProcessCtrl.MAX_QUANTILE_INTERVAL_ARG + expectedMaxQuantileInterval));
assertTrue(command.contains(ProcessCtrl.IGNORE_DOWNTIME_ARG));
} }
public void testBuildAutodetectCommand_defaultTimeField() { public void testBuildAutodetectCommand_defaultTimeField() {
@ -81,7 +80,7 @@ public class ProcessCtrlTests extends ESTestCase {
Environment env = new Environment(settings); Environment env = new Environment(settings);
Job.Builder job = buildJobBuilder("unit-test-job"); Job.Builder job = buildJobBuilder("unit-test-job");
List<String> command = ProcessCtrl.buildAutodetectCommand(env, settings, job.build(), logger, false, pid); List<String> command = ProcessCtrl.buildAutodetectCommand(env, settings, job.build(), logger, pid);
assertTrue(command.contains(ProcessCtrl.TIME_FIELD_ARG + "time")); assertTrue(command.contains(ProcessCtrl.TIME_FIELD_ARG + "time"));
} }
@ -94,38 +93,16 @@ public class ProcessCtrlTests extends ESTestCase {
int expectedPersistInterval = 10800 + ProcessCtrl.calculateStaggeringInterval(job.getId()); int expectedPersistInterval = 10800 + ProcessCtrl.calculateStaggeringInterval(job.getId());
List<String> command = ProcessCtrl.buildAutodetectCommand(env, settings, job.build(), logger, false, pid); List<String> command = ProcessCtrl.buildAutodetectCommand(env, settings, job.build(), logger, pid);
assertFalse(command.contains(ProcessCtrl.PERSIST_INTERVAL_ARG + expectedPersistInterval)); assertFalse(command.contains(ProcessCtrl.PERSIST_INTERVAL_ARG + expectedPersistInterval));
settings = Settings.builder().put(Environment.PATH_HOME_SETTING.getKey(), createTempDir().toString()).build(); settings = Settings.builder().put(Environment.PATH_HOME_SETTING.getKey(), createTempDir().toString()).build();
env = new Environment(settings); env = new Environment(settings);
command = ProcessCtrl.buildAutodetectCommand(env, settings, job.build(), logger, false, pid); command = ProcessCtrl.buildAutodetectCommand(env, settings, job.build(), logger, pid);
assertTrue(command.contains(ProcessCtrl.PERSIST_INTERVAL_ARG + expectedPersistInterval)); assertTrue(command.contains(ProcessCtrl.PERSIST_INTERVAL_ARG + expectedPersistInterval));
} }
public void testBuildAutodetectCommand_GivenNoIgnoreDowntime() {
Settings settings = Settings.builder().put(Environment.PATH_HOME_SETTING.getKey(), createTempDir().toString()).build();
Environment env = new Environment(
settings);
Job.Builder job = buildJobBuilder("foo");
List<String> command = ProcessCtrl.buildAutodetectCommand(env, settings, job.build(), logger, false, pid);
assertFalse(command.contains("--ignoreDowntime"));
}
public void testBuildAutodetectCommand_GivenIgnoreDowntimeParam() {
Settings settings = Settings.builder().put(Environment.PATH_HOME_SETTING.getKey(), createTempDir().toString()).build();
Environment env = new Environment(
settings);
Job.Builder job = buildJobBuilder("foo");
List<String> command = ProcessCtrl.buildAutodetectCommand(env, settings, job.build(), logger, true, pid);
assertTrue(command.contains("--ignoreDowntime"));
}
public void testBuildNormalizerCommand() throws IOException { public void testBuildNormalizerCommand() throws IOException {
Environment env = new Environment( Environment env = new Environment(
Settings.builder().put(Environment.PATH_HOME_SETTING.getKey(), createTempDir().toString()).build()); Settings.builder().put(Environment.PATH_HOME_SETTING.getKey(), createTempDir().toString()).build());

View File

@ -18,6 +18,7 @@ import org.elasticsearch.xpack.ml.job.config.Job;
import org.elasticsearch.xpack.ml.job.persistence.StateStreamer; import org.elasticsearch.xpack.ml.job.persistence.StateStreamer;
import org.elasticsearch.xpack.ml.job.process.DataCountsReporter; import org.elasticsearch.xpack.ml.job.process.DataCountsReporter;
import org.elasticsearch.xpack.ml.job.process.autodetect.output.AutoDetectResultProcessor; import org.elasticsearch.xpack.ml.job.process.autodetect.output.AutoDetectResultProcessor;
import org.elasticsearch.xpack.ml.job.process.autodetect.output.FlushAcknowledgement;
import org.elasticsearch.xpack.ml.job.process.autodetect.params.DataLoadParams; import org.elasticsearch.xpack.ml.job.process.autodetect.params.DataLoadParams;
import org.elasticsearch.xpack.ml.job.process.autodetect.params.FlushJobParams; import org.elasticsearch.xpack.ml.job.process.autodetect.params.FlushJobParams;
import org.elasticsearch.xpack.ml.job.process.autodetect.params.TimeRange; import org.elasticsearch.xpack.ml.job.process.autodetect.params.TimeRange;
@ -35,9 +36,11 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer; import java.util.function.Consumer;
import static org.elasticsearch.mock.orig.Mockito.doAnswer; import static org.elasticsearch.mock.orig.Mockito.doAnswer;
import static org.hamcrest.Matchers.equalTo;
import static org.mockito.Matchers.any; import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyString; import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq; import static org.mockito.Matchers.eq;
@ -70,10 +73,13 @@ public class AutodetectCommunicatorTests extends ESTestCase {
AutodetectProcess process = mockAutodetectProcessWithOutputStream(); AutodetectProcess process = mockAutodetectProcessWithOutputStream();
when(process.isProcessAlive()).thenReturn(true); when(process.isProcessAlive()).thenReturn(true);
AutoDetectResultProcessor processor = mock(AutoDetectResultProcessor.class); AutoDetectResultProcessor processor = mock(AutoDetectResultProcessor.class);
when(processor.waitForFlushAcknowledgement(anyString(), any())).thenReturn(true); FlushAcknowledgement flushAcknowledgement = mock(FlushAcknowledgement.class);
when(processor.waitForFlushAcknowledgement(anyString(), any())).thenReturn(flushAcknowledgement);
try (AutodetectCommunicator communicator = createAutodetectCommunicator(process, processor)) { try (AutodetectCommunicator communicator = createAutodetectCommunicator(process, processor)) {
FlushJobParams params = FlushJobParams.builder().build(); FlushJobParams params = FlushJobParams.builder().build();
communicator.flushJob(params, (aVoid, e) -> {}); AtomicReference<FlushAcknowledgement> flushAcknowledgementHolder = new AtomicReference<>();
communicator.flushJob(params, (f, e) -> flushAcknowledgementHolder.set(f));
assertThat(flushAcknowledgementHolder.get(), equalTo(flushAcknowledgement));
Mockito.verify(process).flushJob(params); Mockito.verify(process).flushJob(params);
} }
} }
@ -83,7 +89,7 @@ public class AutodetectCommunicatorTests extends ESTestCase {
when(process.isProcessAlive()).thenReturn(true); when(process.isProcessAlive()).thenReturn(true);
AutoDetectResultProcessor processor = mock(AutoDetectResultProcessor.class); AutoDetectResultProcessor processor = mock(AutoDetectResultProcessor.class);
when(processor.isFailed()).thenReturn(true); when(processor.isFailed()).thenReturn(true);
when(processor.waitForFlushAcknowledgement(anyString(), any())).thenReturn(false); when(processor.waitForFlushAcknowledgement(anyString(), any())).thenReturn(null);
AutodetectCommunicator communicator = createAutodetectCommunicator(process, processor); AutodetectCommunicator communicator = createAutodetectCommunicator(process, processor);
expectThrows(ElasticsearchException.class, () -> communicator.waitFlushToCompletion("foo")); expectThrows(ElasticsearchException.class, () -> communicator.waitFlushToCompletion("foo"));
} }
@ -103,8 +109,9 @@ public class AutodetectCommunicatorTests extends ESTestCase {
AutodetectProcess process = mockAutodetectProcessWithOutputStream(); AutodetectProcess process = mockAutodetectProcessWithOutputStream();
when(process.isProcessAlive()).thenReturn(true); when(process.isProcessAlive()).thenReturn(true);
AutoDetectResultProcessor autoDetectResultProcessor = Mockito.mock(AutoDetectResultProcessor.class); AutoDetectResultProcessor autoDetectResultProcessor = Mockito.mock(AutoDetectResultProcessor.class);
FlushAcknowledgement flushAcknowledgement = mock(FlushAcknowledgement.class);
when(autoDetectResultProcessor.waitForFlushAcknowledgement(anyString(), eq(Duration.ofSeconds(1)))) when(autoDetectResultProcessor.waitForFlushAcknowledgement(anyString(), eq(Duration.ofSeconds(1))))
.thenReturn(false).thenReturn(true); .thenReturn(null).thenReturn(flushAcknowledgement);
FlushJobParams params = FlushJobParams.builder().build(); FlushJobParams params = FlushJobParams.builder().build();
try (AutodetectCommunicator communicator = createAutodetectCommunicator(process, autoDetectResultProcessor)) { try (AutodetectCommunicator communicator = createAutodetectCommunicator(process, autoDetectResultProcessor)) {

View File

@ -6,6 +6,7 @@
package org.elasticsearch.xpack.ml.job.process.autodetect; package org.elasticsearch.xpack.ml.job.process.autodetect;
import org.elasticsearch.ElasticsearchException; import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.client.Client; import org.elasticsearch.client.Client;
import org.elasticsearch.common.CheckedConsumer; import org.elasticsearch.common.CheckedConsumer;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
@ -134,7 +135,7 @@ public class AutodetectProcessManagerTests extends ESTestCase {
when(jobTask.getJobId()).thenReturn(job.getId()); when(jobTask.getJobId()).thenReturn(job.getId());
AtomicReference<Exception> errorHolder = new AtomicReference<>(); AtomicReference<Exception> errorHolder = new AtomicReference<>();
manager.openJob(jobTask, false, e -> errorHolder.set(e)); manager.openJob(jobTask, e -> errorHolder.set(e));
Exception error = errorHolder.get(); Exception error = errorHolder.get();
assertThat(error, is(notNullValue())); assertThat(error, is(notNullValue()));
@ -150,7 +151,7 @@ public class AutodetectProcessManagerTests extends ESTestCase {
JobTask jobTask = mock(JobTask.class); JobTask jobTask = mock(JobTask.class);
when(jobTask.getJobId()).thenReturn("foo"); when(jobTask.getJobId()).thenReturn("foo");
when(jobTask.getAllocationId()).thenReturn(1L); when(jobTask.getAllocationId()).thenReturn(1L);
manager.openJob(jobTask, false, e -> {}); manager.openJob(jobTask, e -> {});
assertEquals(1, manager.numberOfOpenJobs()); assertEquals(1, manager.numberOfOpenJobs());
assertTrue(manager.jobHasActiveAutodetectProcess(jobTask)); assertTrue(manager.jobHasActiveAutodetectProcess(jobTask));
verify(jobTask).updatePersistentStatus(eq(new JobTaskStatus(JobState.OPENED, 1L)), any()); verify(jobTask).updatePersistentStatus(eq(new JobTaskStatus(JobState.OPENED, 1L)), any());
@ -175,7 +176,7 @@ public class AutodetectProcessManagerTests extends ESTestCase {
when(autodetectProcess.isProcessAlive()).thenReturn(true); when(autodetectProcess.isProcessAlive()).thenReturn(true);
when(autodetectProcess.readAutodetectResults()).thenReturn(Collections.emptyIterator()); when(autodetectProcess.readAutodetectResults()).thenReturn(Collections.emptyIterator());
AutodetectProcessFactory autodetectProcessFactory = AutodetectProcessFactory autodetectProcessFactory =
(j, modelSnapshot, quantiles, filters, i, e, onProcessCrash) -> autodetectProcess; (j, modelSnapshot, quantiles, filters, e, onProcessCrash) -> autodetectProcess;
Settings.Builder settings = Settings.builder(); Settings.Builder settings = Settings.builder();
settings.put(AutodetectProcessManager.MAX_RUNNING_JOBS_PER_NODE.getKey(), 3); settings.put(AutodetectProcessManager.MAX_RUNNING_JOBS_PER_NODE.getKey(), 3);
AutodetectProcessManager manager = spy(new AutodetectProcessManager(settings.build(), client, threadPool, jobManager, jobProvider, AutodetectProcessManager manager = spy(new AutodetectProcessManager(settings.build(), client, threadPool, jobManager, jobProvider,
@ -192,22 +193,22 @@ public class AutodetectProcessManagerTests extends ESTestCase {
JobTask jobTask = mock(JobTask.class); JobTask jobTask = mock(JobTask.class);
when(jobTask.getJobId()).thenReturn("foo"); when(jobTask.getJobId()).thenReturn("foo");
manager.openJob(jobTask, false, e -> {}); manager.openJob(jobTask, e -> {});
jobTask = mock(JobTask.class); jobTask = mock(JobTask.class);
when(jobTask.getJobId()).thenReturn("bar"); when(jobTask.getJobId()).thenReturn("bar");
when(jobTask.getAllocationId()).thenReturn(1L); when(jobTask.getAllocationId()).thenReturn(1L);
manager.openJob(jobTask, false, e -> {}); manager.openJob(jobTask, e -> {});
jobTask = mock(JobTask.class); jobTask = mock(JobTask.class);
when(jobTask.getJobId()).thenReturn("baz"); when(jobTask.getJobId()).thenReturn("baz");
when(jobTask.getAllocationId()).thenReturn(2L); when(jobTask.getAllocationId()).thenReturn(2L);
manager.openJob(jobTask, false, e -> {}); manager.openJob(jobTask, e -> {});
assertEquals(3, manager.numberOfOpenJobs()); assertEquals(3, manager.numberOfOpenJobs());
Exception[] holder = new Exception[1]; Exception[] holder = new Exception[1];
jobTask = mock(JobTask.class); jobTask = mock(JobTask.class);
when(jobTask.getJobId()).thenReturn("foobar"); when(jobTask.getJobId()).thenReturn("foobar");
when(jobTask.getAllocationId()).thenReturn(3L); when(jobTask.getAllocationId()).thenReturn(3L);
manager.openJob(jobTask, false, e -> holder[0] = e); manager.openJob(jobTask, e -> holder[0] = e);
Exception e = holder[0]; Exception e = holder[0];
assertEquals("max running job capacity [3] reached", e.getMessage()); assertEquals("max running job capacity [3] reached", e.getMessage());
@ -216,7 +217,7 @@ public class AutodetectProcessManagerTests extends ESTestCase {
when(jobTask.getJobId()).thenReturn("baz"); when(jobTask.getJobId()).thenReturn("baz");
manager.closeJob(jobTask, false, null); manager.closeJob(jobTask, false, null);
assertEquals(2, manager.numberOfOpenJobs()); assertEquals(2, manager.numberOfOpenJobs());
manager.openJob(jobTask, false, e1 -> {}); manager.openJob(jobTask, e1 -> {});
assertEquals(3, manager.numberOfOpenJobs()); assertEquals(3, manager.numberOfOpenJobs());
} }
@ -228,7 +229,7 @@ public class AutodetectProcessManagerTests extends ESTestCase {
JobTask jobTask = mock(JobTask.class); JobTask jobTask = mock(JobTask.class);
when(jobTask.getJobId()).thenReturn("foo"); when(jobTask.getJobId()).thenReturn("foo");
DataLoadParams params = new DataLoadParams(TimeRange.builder().build(), Optional.empty()); DataLoadParams params = new DataLoadParams(TimeRange.builder().build(), Optional.empty());
manager.openJob(jobTask, false, e -> {}); manager.openJob(jobTask, e -> {});
manager.processData(jobTask, createInputStream(""), randomFrom(XContentType.values()), manager.processData(jobTask, createInputStream(""), randomFrom(XContentType.values()),
params, (dataCounts1, e) -> {}); params, (dataCounts1, e) -> {});
assertEquals(1, manager.numberOfOpenJobs()); assertEquals(1, manager.numberOfOpenJobs());
@ -250,7 +251,7 @@ public class AutodetectProcessManagerTests extends ESTestCase {
JobTask jobTask = mock(JobTask.class); JobTask jobTask = mock(JobTask.class);
when(jobTask.getJobId()).thenReturn("foo"); when(jobTask.getJobId()).thenReturn("foo");
manager.openJob(jobTask, false, e -> {}); manager.openJob(jobTask, e -> {});
Exception[] holder = new Exception[1]; Exception[] holder = new Exception[1];
manager.processData(jobTask, inputStream, xContentType, params, (dataCounts1, e) -> holder[0] = e); manager.processData(jobTask, inputStream, xContentType, params, (dataCounts1, e) -> holder[0] = e);
assertNotNull(holder[0]); assertNotNull(holder[0]);
@ -263,7 +264,7 @@ public class AutodetectProcessManagerTests extends ESTestCase {
JobTask jobTask = mock(JobTask.class); JobTask jobTask = mock(JobTask.class);
when(jobTask.getJobId()).thenReturn("foo"); when(jobTask.getJobId()).thenReturn("foo");
manager.openJob(jobTask, false, e -> {}); manager.openJob(jobTask, e -> {});
manager.processData(jobTask, createInputStream(""), randomFrom(XContentType.values()), manager.processData(jobTask, createInputStream(""), randomFrom(XContentType.values()),
mock(DataLoadParams.class), (dataCounts1, e) -> {}); mock(DataLoadParams.class), (dataCounts1, e) -> {});
@ -282,7 +283,7 @@ public class AutodetectProcessManagerTests extends ESTestCase {
InputStream inputStream = createInputStream(""); InputStream inputStream = createInputStream("");
JobTask jobTask = mock(JobTask.class); JobTask jobTask = mock(JobTask.class);
when(jobTask.getJobId()).thenReturn("foo"); when(jobTask.getJobId()).thenReturn("foo");
manager.openJob(jobTask, false, e -> {}); manager.openJob(jobTask, e -> {});
manager.processData(jobTask, inputStream, xContentType, params, (dataCounts1, e) -> {}); manager.processData(jobTask, inputStream, xContentType, params, (dataCounts1, e) -> {});
verify(communicator).writeToJob(same(inputStream), same(xContentType), same(params), any()); verify(communicator).writeToJob(same(inputStream), same(xContentType), same(params), any());
} }
@ -294,12 +295,12 @@ public class AutodetectProcessManagerTests extends ESTestCase {
JobTask jobTask = mock(JobTask.class); JobTask jobTask = mock(JobTask.class);
when(jobTask.getJobId()).thenReturn("foo"); when(jobTask.getJobId()).thenReturn("foo");
InputStream inputStream = createInputStream(""); InputStream inputStream = createInputStream("");
manager.openJob(jobTask, false, e -> {}); manager.openJob(jobTask, e -> {});
manager.processData(jobTask, inputStream, randomFrom(XContentType.values()), manager.processData(jobTask, inputStream, randomFrom(XContentType.values()),
mock(DataLoadParams.class), (dataCounts1, e) -> {}); mock(DataLoadParams.class), (dataCounts1, e) -> {});
FlushJobParams params = FlushJobParams.builder().build(); FlushJobParams params = FlushJobParams.builder().build();
manager.flushJob(jobTask, params, e -> {}); manager.flushJob(jobTask, params, ActionListener.wrap(flushAcknowledgement -> {}, e -> fail(e.getMessage())));
verify(communicator).flushJob(same(params), any()); verify(communicator).flushJob(same(params), any());
} }
@ -318,7 +319,7 @@ public class AutodetectProcessManagerTests extends ESTestCase {
JobTask jobTask = mock(JobTask.class); JobTask jobTask = mock(JobTask.class);
when(jobTask.getJobId()).thenReturn("foo"); when(jobTask.getJobId()).thenReturn("foo");
Exception[] holder = new Exception[1]; Exception[] holder = new Exception[1];
manager.flushJob(jobTask, params, e -> holder[0] = e); manager.flushJob(jobTask, params, ActionListener.wrap(flushAcknowledgement -> {}, e -> holder[0] = e));
assertEquals("[foo] exception while flushing job", holder[0].getMessage()); assertEquals("[foo] exception while flushing job", holder[0].getMessage());
} }
@ -333,8 +334,7 @@ public class AutodetectProcessManagerTests extends ESTestCase {
// create a jobtask // create a jobtask
JobTask jobTask = mock(JobTask.class); JobTask jobTask = mock(JobTask.class);
when(jobTask.getJobId()).thenReturn("foo"); when(jobTask.getJobId()).thenReturn("foo");
manager.openJob(jobTask, false, e -> { manager.openJob(jobTask, e -> {});
});
manager.processData(jobTask, createInputStream(""), randomFrom(XContentType.values()), mock(DataLoadParams.class), manager.processData(jobTask, createInputStream(""), randomFrom(XContentType.values()), mock(DataLoadParams.class),
(dataCounts1, e) -> { (dataCounts1, e) -> {
}); });
@ -366,7 +366,7 @@ public class AutodetectProcessManagerTests extends ESTestCase {
when(jobTask.getJobId()).thenReturn("foo"); when(jobTask.getJobId()).thenReturn("foo");
assertFalse(manager.jobHasActiveAutodetectProcess(jobTask)); assertFalse(manager.jobHasActiveAutodetectProcess(jobTask));
manager.openJob(jobTask, false, e -> {}); manager.openJob(jobTask, e -> {});
manager.processData(jobTask, createInputStream(""), randomFrom(XContentType.values()), manager.processData(jobTask, createInputStream(""), randomFrom(XContentType.values()),
mock(DataLoadParams.class), (dataCounts1, e) -> {}); mock(DataLoadParams.class), (dataCounts1, e) -> {});
@ -385,7 +385,7 @@ public class AutodetectProcessManagerTests extends ESTestCase {
assertFalse(manager.jobHasActiveAutodetectProcess(jobTask)); assertFalse(manager.jobHasActiveAutodetectProcess(jobTask));
when(communicator.getJobTask()).thenReturn(jobTask); when(communicator.getJobTask()).thenReturn(jobTask);
manager.openJob(jobTask, false, e -> {}); manager.openJob(jobTask, e -> {});
manager.processData(jobTask, createInputStream(""), randomFrom(XContentType.values()), manager.processData(jobTask, createInputStream(""), randomFrom(XContentType.values()),
mock(DataLoadParams.class), (dataCounts1, e) -> {}); mock(DataLoadParams.class), (dataCounts1, e) -> {});
@ -408,7 +408,7 @@ public class AutodetectProcessManagerTests extends ESTestCase {
JobTask jobTask = mock(JobTask.class); JobTask jobTask = mock(JobTask.class);
when(jobTask.getJobId()).thenReturn("foo"); when(jobTask.getJobId()).thenReturn("foo");
manager.openJob(jobTask, false, e -> {}); manager.openJob(jobTask, e -> {});
InputStream inputStream = createInputStream(""); InputStream inputStream = createInputStream("");
DataCounts[] dataCounts = new DataCounts[1]; DataCounts[] dataCounts = new DataCounts[1];
manager.processData(jobTask, inputStream, manager.processData(jobTask, inputStream,
@ -429,7 +429,7 @@ public class AutodetectProcessManagerTests extends ESTestCase {
when(jobManager.getJobOrThrowIfUnknown("my_id")).thenReturn(createJobDetails("my_id")); when(jobManager.getJobOrThrowIfUnknown("my_id")).thenReturn(createJobDetails("my_id"));
AutodetectProcess autodetectProcess = mock(AutodetectProcess.class); AutodetectProcess autodetectProcess = mock(AutodetectProcess.class);
AutodetectProcessFactory autodetectProcessFactory = AutodetectProcessFactory autodetectProcessFactory =
(j, modelSnapshot, quantiles, filters, i, e, onProcessCrash) -> autodetectProcess; (j, modelSnapshot, quantiles, filters, e, onProcessCrash) -> autodetectProcess;
AutodetectProcessManager manager = new AutodetectProcessManager(Settings.EMPTY, client, threadPool, jobManager, jobProvider, AutodetectProcessManager manager = new AutodetectProcessManager(Settings.EMPTY, client, threadPool, jobManager, jobProvider,
jobResultsPersister, jobDataCountsPersister, autodetectProcessFactory, jobResultsPersister, jobDataCountsPersister, autodetectProcessFactory,
normalizerFactory, new NamedXContentRegistry(Collections.emptyList()), auditor); normalizerFactory, new NamedXContentRegistry(Collections.emptyList()), auditor);
@ -437,7 +437,7 @@ public class AutodetectProcessManagerTests extends ESTestCase {
JobTask jobTask = mock(JobTask.class); JobTask jobTask = mock(JobTask.class);
when(jobTask.getJobId()).thenReturn("my_id"); when(jobTask.getJobId()).thenReturn("my_id");
expectThrows(EsRejectedExecutionException.class, expectThrows(EsRejectedExecutionException.class,
() -> manager.create(jobTask, buildAutodetectParams(), false, e -> {})); () -> manager.create(jobTask, buildAutodetectParams(), e -> {}));
verify(autodetectProcess, times(1)).close(); verify(autodetectProcess, times(1)).close();
} }
@ -447,7 +447,7 @@ public class AutodetectProcessManagerTests extends ESTestCase {
JobTask jobTask = mock(JobTask.class); JobTask jobTask = mock(JobTask.class);
when(jobTask.getJobId()).thenReturn("foo"); when(jobTask.getJobId()).thenReturn("foo");
manager.create(jobTask, buildAutodetectParams(), false, e -> {}); manager.create(jobTask, buildAutodetectParams(), e -> {});
String expectedNotification = "Loading model snapshot [N/A], job latest_record_timestamp [N/A]"; String expectedNotification = "Loading model snapshot [N/A], job latest_record_timestamp [N/A]";
verify(auditor).info("foo", expectedNotification); verify(auditor).info("foo", expectedNotification);
@ -463,7 +463,7 @@ public class AutodetectProcessManagerTests extends ESTestCase {
JobTask jobTask = mock(JobTask.class); JobTask jobTask = mock(JobTask.class);
when(jobTask.getJobId()).thenReturn("foo"); when(jobTask.getJobId()).thenReturn("foo");
manager.create(jobTask, buildAutodetectParams(), false, e -> {}); manager.create(jobTask, buildAutodetectParams(), e -> {});
String expectedNotification = "Loading model snapshot [snapshot-1] with " + String expectedNotification = "Loading model snapshot [snapshot-1] with " +
"latest_record_timestamp [1970-01-01T00:00:00.000Z], " + "latest_record_timestamp [1970-01-01T00:00:00.000Z], " +
@ -482,7 +482,7 @@ public class AutodetectProcessManagerTests extends ESTestCase {
JobTask jobTask = mock(JobTask.class); JobTask jobTask = mock(JobTask.class);
when(jobTask.getJobId()).thenReturn("foo"); when(jobTask.getJobId()).thenReturn("foo");
manager.create(jobTask, buildAutodetectParams(), false, e -> {}); manager.create(jobTask, buildAutodetectParams(), e -> {});
String expectedNotification = "Loading model snapshot [N/A], " + String expectedNotification = "Loading model snapshot [N/A], " +
"job latest_record_timestamp [1970-01-01T00:00:00.000Z]"; "job latest_record_timestamp [1970-01-01T00:00:00.000Z]";
@ -501,7 +501,7 @@ public class AutodetectProcessManagerTests extends ESTestCase {
when(jobManager.getJobOrThrowIfUnknown(jobId)).thenReturn(createJobDetails(jobId)); when(jobManager.getJobOrThrowIfUnknown(jobId)).thenReturn(createJobDetails(jobId));
AutodetectProcess autodetectProcess = mock(AutodetectProcess.class); AutodetectProcess autodetectProcess = mock(AutodetectProcess.class);
AutodetectProcessFactory autodetectProcessFactory = AutodetectProcessFactory autodetectProcessFactory =
(j, modelSnapshot, quantiles, filters, i, e, onProcessCrash) -> autodetectProcess; (j, modelSnapshot, quantiles, filters, e, onProcessCrash) -> autodetectProcess;
return new AutodetectProcessManager(Settings.EMPTY, client, threadPool, jobManager, jobProvider, return new AutodetectProcessManager(Settings.EMPTY, client, threadPool, jobManager, jobProvider,
jobResultsPersister, jobDataCountsPersister, autodetectProcessFactory, jobResultsPersister, jobDataCountsPersister, autodetectProcessFactory,
normalizerFactory, new NamedXContentRegistry(Collections.emptyList()), auditor); normalizerFactory, new NamedXContentRegistry(Collections.emptyList()), auditor);
@ -531,7 +531,7 @@ public class AutodetectProcessManagerTests extends ESTestCase {
autodetectProcessFactory, normalizerFactory, autodetectProcessFactory, normalizerFactory,
new NamedXContentRegistry(Collections.emptyList()), auditor); new NamedXContentRegistry(Collections.emptyList()), auditor);
manager = spy(manager); manager = spy(manager);
doReturn(communicator).when(manager).create(any(), eq(buildAutodetectParams()), anyBoolean(), any()); doReturn(communicator).when(manager).create(any(), eq(buildAutodetectParams()), any());
return manager; return manager;
} }
@ -539,7 +539,7 @@ public class AutodetectProcessManagerTests extends ESTestCase {
AutodetectProcessManager manager = createManager(communicator); AutodetectProcessManager manager = createManager(communicator);
JobTask jobTask = mock(JobTask.class); JobTask jobTask = mock(JobTask.class);
when(jobTask.getJobId()).thenReturn("foo"); when(jobTask.getJobId()).thenReturn("foo");
manager.openJob(jobTask, false, e -> {}); manager.openJob(jobTask, e -> {});
manager.processData(jobTask, createInputStream(""), randomFrom(XContentType.values()), manager.processData(jobTask, createInputStream(""), randomFrom(XContentType.values()),
mock(DataLoadParams.class), (dataCounts, e) -> {}); mock(DataLoadParams.class), (dataCounts, e) -> {});
return manager; return manager;

View File

@ -34,6 +34,8 @@ import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;
import static org.mockito.Matchers.any; import static org.mockito.Matchers.any;
import static org.mockito.Matchers.eq; import static org.mockito.Matchers.eq;
import static org.mockito.Matchers.same; import static org.mockito.Matchers.same;
@ -200,7 +202,7 @@ public class AutoDetectResultProcessorTests extends ESTestCase {
when(result.getFlushAcknowledgement()).thenReturn(flushAcknowledgement); when(result.getFlushAcknowledgement()).thenReturn(flushAcknowledgement);
processorUnderTest.processResult(context, result); processorUnderTest.processResult(context, result);
verify(flushListener, times(1)).acknowledgeFlush(JOB_ID); verify(flushListener, times(1)).acknowledgeFlush(flushAcknowledgement);
verify(persister, times(1)).commitResultWrites(JOB_ID); verify(persister, times(1)).commitResultWrites(JOB_ID);
verify(bulkBuilder, times(1)).executeRequest(); verify(bulkBuilder, times(1)).executeRequest();
verifyNoMoreInteractions(persister); verifyNoMoreInteractions(persister);
@ -225,7 +227,7 @@ public class AutoDetectResultProcessorTests extends ESTestCase {
inOrder.verify(persister, times(1)).persistCategoryDefinition(categoryDefinition); inOrder.verify(persister, times(1)).persistCategoryDefinition(categoryDefinition);
inOrder.verify(bulkBuilder, times(1)).executeRequest(); inOrder.verify(bulkBuilder, times(1)).executeRequest();
inOrder.verify(persister, times(1)).commitResultWrites(JOB_ID); inOrder.verify(persister, times(1)).commitResultWrites(JOB_ID);
inOrder.verify(flushListener, times(1)).acknowledgeFlush(JOB_ID); inOrder.verify(flushListener, times(1)).acknowledgeFlush(flushAcknowledgement);
verifyNoMoreInteractions(persister); verifyNoMoreInteractions(persister);
assertTrue(context.deleteInterimRequired); assertTrue(context.deleteInterimRequired);
} }
@ -342,7 +344,9 @@ public class AutoDetectResultProcessorTests extends ESTestCase {
assertTrue(processorUnderTest.isFailed()); assertTrue(processorUnderTest.isFailed());
// Wait for flush should return immediately // Wait for flush should return immediately
assertFalse(processorUnderTest.waitForFlushAcknowledgement("foo", Duration.of(300, ChronoUnit.SECONDS))); FlushAcknowledgement flushAcknowledgement = processorUnderTest.waitForFlushAcknowledgement(
"foo", Duration.of(300, ChronoUnit.SECONDS));
assertThat(flushAcknowledgement, is(nullValue()));
} }
public void testKill() throws TimeoutException { public void testKill() throws TimeoutException {

View File

@ -9,6 +9,8 @@ import org.elasticsearch.common.io.stream.Writeable.Reader;
import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.test.AbstractSerializingTestCase; import org.elasticsearch.test.AbstractSerializingTestCase;
import java.util.Date;
public class FlushAcknowledgementTests extends AbstractSerializingTestCase<FlushAcknowledgement> { public class FlushAcknowledgementTests extends AbstractSerializingTestCase<FlushAcknowledgement> {
@Override @Override
@ -18,7 +20,7 @@ public class FlushAcknowledgementTests extends AbstractSerializingTestCase<Flush
@Override @Override
protected FlushAcknowledgement createTestInstance() { protected FlushAcknowledgement createTestInstance() {
return new FlushAcknowledgement(randomAlphaOfLengthBetween(1, 20)); return new FlushAcknowledgement(randomAlphaOfLengthBetween(1, 20), new Date(randomNonNegativeLong()));
} }
@Override @Override

View File

@ -9,22 +9,26 @@ import org.elasticsearch.test.ESTestCase;
import java.time.Duration; import java.time.Duration;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date;
import java.util.List; import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference;
import static org.hamcrest.Matchers.is;
public class FlushListenerTests extends ESTestCase { public class FlushListenerTests extends ESTestCase {
public void testAcknowledgeFlush() throws Exception { public void testAcknowledgeFlush() throws Exception {
FlushListener listener = new FlushListener(); FlushListener listener = new FlushListener();
AtomicBoolean bool = new AtomicBoolean(); AtomicReference<FlushAcknowledgement> flushAcknowledgementHolder = new AtomicReference<>();
new Thread(() -> { new Thread(() -> {
boolean result = listener.waitForFlush("_id", Duration.ofMillis(10000)); FlushAcknowledgement flushAcknowledgement = listener.waitForFlush("_id", Duration.ofMillis(10000));
bool.set(result); flushAcknowledgementHolder.set(flushAcknowledgement);
}).start(); }).start();
assertBusy(() -> assertTrue(listener.awaitingFlushed.containsKey("_id"))); assertBusy(() -> assertTrue(listener.awaitingFlushed.containsKey("_id")));
assertFalse(bool.get()); assertNull(flushAcknowledgementHolder.get());
listener.acknowledgeFlush("_id"); FlushAcknowledgement flushAcknowledgement = new FlushAcknowledgement("_id", new Date(12345678L));
assertBusy(() -> assertTrue(bool.get())); listener.acknowledgeFlush(flushAcknowledgement);
assertBusy(() -> assertNotNull(flushAcknowledgementHolder.get()));
assertEquals(1, listener.awaitingFlushed.size()); assertEquals(1, listener.awaitingFlushed.size());
listener.clear("_id"); listener.clear("_id");
@ -35,27 +39,26 @@ public class FlushListenerTests extends ESTestCase {
FlushListener listener = new FlushListener(); FlushListener listener = new FlushListener();
int numWaits = 9; int numWaits = 9;
List<AtomicBoolean> bools = new ArrayList<>(numWaits); List<AtomicReference<FlushAcknowledgement>> flushAcknowledgementHolders = new ArrayList<>(numWaits);
for (int i = 0; i < numWaits; i++) { for (int i = 0; i < numWaits; i++) {
int id = i; int id = i;
AtomicBoolean bool = new AtomicBoolean(); AtomicReference<FlushAcknowledgement> flushAcknowledgementHolder = new AtomicReference<>();
bools.add(bool); flushAcknowledgementHolders.add(flushAcknowledgementHolder);
new Thread(() -> { new Thread(() -> {
boolean result = listener.waitForFlush(String.valueOf(id), Duration.ofMillis(10000)); FlushAcknowledgement flushAcknowledgement = listener.waitForFlush(String.valueOf(id), Duration.ofMillis(10000));
bool.set(result); flushAcknowledgementHolder.set(flushAcknowledgement);
}).start(); }).start();
} }
assertBusy(() -> assertEquals(numWaits, listener.awaitingFlushed.size())); assertBusy(() -> assertEquals(numWaits, listener.awaitingFlushed.size()));
for (AtomicBoolean bool : bools) { assertThat(flushAcknowledgementHolders.stream().map(f -> f.get()).filter(f -> f != null).findAny().isPresent(), is(false));
assertFalse(bool.get());
}
assertFalse(listener.cleared.get()); assertFalse(listener.cleared.get());
listener.clear(); listener.clear();
for (AtomicBoolean bool : bools) {
assertBusy(() -> assertTrue(bool.get())); for (AtomicReference<FlushAcknowledgement> f : flushAcknowledgementHolders) {
assertBusy(() -> assertNotNull(f.get()));
} }
assertTrue(listener.awaitingFlushed.isEmpty()); assertTrue(listener.awaitingFlushed.isEmpty());
assertTrue(listener.cleared.get()); assertTrue(listener.cleared.get());
} }
} }

View File

@ -5,27 +5,31 @@
*/ */
package org.elasticsearch.xpack.ml.job.process.autodetect.params; package org.elasticsearch.xpack.ml.job.process.autodetect.params;
import org.elasticsearch.ElasticsearchStatusException;
import org.elasticsearch.test.ESTestCase; import org.elasticsearch.test.ESTestCase;
import static org.hamcrest.Matchers.equalTo;
public class FlushJobParamsTests extends ESTestCase { public class FlushJobParamsTests extends ESTestCase {
public void testBuilder_GivenDefault() { public void testBuilder_GivenDefault() {
FlushJobParams params = FlushJobParams.builder().build(); FlushJobParams params = FlushJobParams.builder().build();
assertFalse(params.shouldCalculateInterim()); assertFalse(params.shouldCalculateInterim());
assertFalse(params.shouldAdvanceTime()); assertFalse(params.shouldAdvanceTime());
assertFalse(params.shouldSkipTime());
assertEquals("", params.getStart()); assertEquals("", params.getStart());
assertEquals("", params.getEnd()); assertEquals("", params.getEnd());
} }
public void testBuilder_GivenCalcInterim() { public void testBuilder_GivenCalcInterim() {
FlushJobParams params = FlushJobParams.builder().calcInterim(true).build(); FlushJobParams params = FlushJobParams.builder().calcInterim(true).build();
assertTrue(params.shouldCalculateInterim()); assertTrue(params.shouldCalculateInterim());
assertFalse(params.shouldAdvanceTime()); assertFalse(params.shouldAdvanceTime());
assertFalse(params.shouldSkipTime());
assertEquals("", params.getStart()); assertEquals("", params.getStart());
assertEquals("", params.getEnd()); assertEquals("", params.getEnd());
} }
public void testBuilder_GivenCalcInterimAndStart() { public void testBuilder_GivenCalcInterimAndStart() {
FlushJobParams params = FlushJobParams.builder() FlushJobParams params = FlushJobParams.builder()
.calcInterim(true) .calcInterim(true)
@ -33,6 +37,7 @@ public class FlushJobParamsTests extends ESTestCase {
.build(); .build();
assertTrue(params.shouldCalculateInterim()); assertTrue(params.shouldCalculateInterim());
assertFalse(params.shouldAdvanceTime()); assertFalse(params.shouldAdvanceTime());
assertFalse(params.shouldSkipTime());
assertEquals("42", params.getStart()); assertEquals("42", params.getStart());
assertEquals("43", params.getEnd()); assertEquals("43", params.getEnd());
} }
@ -47,7 +52,6 @@ public class FlushJobParamsTests extends ESTestCase {
assertEquals("Invalid flush parameters: 'start' has not been specified.", e.getMessage()); assertEquals("Invalid flush parameters: 'start' has not been specified.", e.getMessage());
} }
public void testBuilder_GivenCalcInterimAndStartAndEnd() { public void testBuilder_GivenCalcInterimAndStartAndEnd() {
FlushJobParams params = FlushJobParams.builder() FlushJobParams params = FlushJobParams.builder()
.calcInterim(true) .calcInterim(true)
@ -59,7 +63,6 @@ public class FlushJobParamsTests extends ESTestCase {
assertEquals("7200", params.getEnd()); assertEquals("7200", params.getEnd());
} }
public void testBuilder_GivenAdvanceTime() { public void testBuilder_GivenAdvanceTime() {
FlushJobParams params = FlushJobParams.builder().advanceTime("1821").build(); FlushJobParams params = FlushJobParams.builder().advanceTime("1821").build();
assertFalse(params.shouldCalculateInterim()); assertFalse(params.shouldCalculateInterim());
@ -69,7 +72,6 @@ public class FlushJobParamsTests extends ESTestCase {
assertEquals(1821, params.getAdvanceTime()); assertEquals(1821, params.getAdvanceTime());
} }
public void testBuilder_GivenCalcInterimAndAdvanceTime() { public void testBuilder_GivenCalcInterimAndAdvanceTime() {
FlushJobParams params = FlushJobParams.builder() FlushJobParams params = FlushJobParams.builder()
.calcInterim(true) .calcInterim(true)
@ -82,7 +84,6 @@ public class FlushJobParamsTests extends ESTestCase {
assertEquals(1940, params.getAdvanceTime()); assertEquals(1940, params.getAdvanceTime());
} }
public void testBuilder_GivenCalcInterimWithTimeRangeAndAdvanceTime() { public void testBuilder_GivenCalcInterimWithTimeRangeAndAdvanceTime() {
FlushJobParams params = FlushJobParams.builder() FlushJobParams params = FlushJobParams.builder()
.calcInterim(true) .calcInterim(true)
@ -96,6 +97,27 @@ public class FlushJobParamsTests extends ESTestCase {
assertEquals(1940, params.getAdvanceTime()); assertEquals(1940, params.getAdvanceTime());
} }
public void testBuilder_GivenAdvanceTimeIsEarlierThanSkipTime() {
ElasticsearchStatusException e = expectThrows(ElasticsearchStatusException.class,
() -> FlushJobParams.builder().advanceTime("2017-01-01T00:00:00Z").skipTime("2017-02-01T00:00:00Z").build());
assertEquals("advance_time [2017-01-01T00:00:00Z] must be later than skip_time [2017-02-01T00:00:00Z]", e.getMessage());
}
public void testBuilder_GivenAdvanceTimeIsEqualToSkipTime() {
ElasticsearchStatusException e = expectThrows(ElasticsearchStatusException.class,
() -> FlushJobParams.builder().advanceTime("2017-01-01T00:00:00Z").skipTime("2017-01-01T00:00:00Z").build());
assertEquals("advance_time [2017-01-01T00:00:00Z] must be later than skip_time [2017-01-01T00:00:00Z]", e.getMessage());
}
public void testBuilder_GivenAdvanceTimeIsLaterToSkipTime() {
FlushJobParams params = FlushJobParams.builder().advanceTime("2017-02-01T00:00:00Z").skipTime("2017-01-01T00:00:00Z").build();
assertThat(params.getSkipTime(), equalTo(1483228800L));
assertThat(params.getAdvanceTime(), equalTo(1485907200L));
}
public void testValidate_GivenOnlyStartSpecified() { public void testValidate_GivenOnlyStartSpecified() {
IllegalArgumentException e = expectThrows(IllegalArgumentException.class, IllegalArgumentException e = expectThrows(IllegalArgumentException.class,
() -> FlushJobParams.builder().forTimeRange(TimeRange.builder().startTime("1").build()).build()); () -> FlushJobParams.builder().forTimeRange(TimeRange.builder().startTime("1").build()).build());

View File

@ -41,8 +41,7 @@ public class ControlMsgToProcessWriterTests extends ESTestCase {
public void testWriteFlushControlMessage_GivenAdvanceTime() throws IOException { public void testWriteFlushControlMessage_GivenAdvanceTime() throws IOException {
ControlMsgToProcessWriter writer = new ControlMsgToProcessWriter(lengthEncodedWriter, 2); ControlMsgToProcessWriter writer = new ControlMsgToProcessWriter(lengthEncodedWriter, 2);
FlushJobParams flushJobParams = FlushJobParams.builder() FlushJobParams flushJobParams = FlushJobParams.builder().advanceTime("1234567890").build();
.advanceTime("1234567890").build();
writer.writeFlushControlMessage(flushJobParams); writer.writeFlushControlMessage(flushJobParams);
@ -53,6 +52,30 @@ public class ControlMsgToProcessWriterTests extends ESTestCase {
verifyNoMoreInteractions(lengthEncodedWriter); verifyNoMoreInteractions(lengthEncodedWriter);
} }
public void testWriteFlushControlMessage_GivenSkipTime() throws IOException {
ControlMsgToProcessWriter writer = new ControlMsgToProcessWriter(lengthEncodedWriter, 2);
FlushJobParams flushJobParams = FlushJobParams.builder().skipTime("1234567890").build();
writer.writeFlushControlMessage(flushJobParams);
InOrder inOrder = inOrder(lengthEncodedWriter);
inOrder.verify(lengthEncodedWriter).writeNumFields(4);
inOrder.verify(lengthEncodedWriter, times(3)).writeField("");
inOrder.verify(lengthEncodedWriter).writeField("s1234567890");
verifyNoMoreInteractions(lengthEncodedWriter);
}
public void testWriteFlushControlMessage_GivenSkipAndAdvanceTime() throws IOException {
ControlMsgToProcessWriter writer = new ControlMsgToProcessWriter(lengthEncodedWriter, 2);
FlushJobParams flushJobParams = FlushJobParams.builder().skipTime("1000").advanceTime("2000").build();
writer.writeFlushControlMessage(flushJobParams);
InOrder inOrder = inOrder(lengthEncodedWriter);
inOrder.verify(lengthEncodedWriter).writeField("s1000");
inOrder.verify(lengthEncodedWriter).writeField("t2000");
}
public void testWriteFlushControlMessage_GivenCalcInterimResultsWithNoTimeParams() throws IOException { public void testWriteFlushControlMessage_GivenCalcInterimResultsWithNoTimeParams() throws IOException {
ControlMsgToProcessWriter writer = new ControlMsgToProcessWriter(lengthEncodedWriter, 2); ControlMsgToProcessWriter writer = new ControlMsgToProcessWriter(lengthEncodedWriter, 2);
FlushJobParams flushJobParams = FlushJobParams.builder() FlushJobParams flushJobParams = FlushJobParams.builder()
@ -67,7 +90,7 @@ public class ControlMsgToProcessWriterTests extends ESTestCase {
verifyNoMoreInteractions(lengthEncodedWriter); verifyNoMoreInteractions(lengthEncodedWriter);
} }
public void testWriteFlushControlMessage_GivenNeitherCalcInterimNorAdvanceTime() throws IOException { public void testWriteFlushControlMessage_GivenPlainFlush() throws IOException {
ControlMsgToProcessWriter writer = new ControlMsgToProcessWriter(lengthEncodedWriter, 2); ControlMsgToProcessWriter writer = new ControlMsgToProcessWriter(lengthEncodedWriter, 2);
FlushJobParams flushJobParams = FlushJobParams.builder().build(); FlushJobParams flushJobParams = FlushJobParams.builder().build();

View File

@ -91,7 +91,7 @@ public class AutodetectResultTests extends AbstractSerializingTestCase<Autodetec
categoryDefinition = null; categoryDefinition = null;
} }
if (randomBoolean()) { if (randomBoolean()) {
flushAcknowledgement = new FlushAcknowledgement(randomAlphaOfLengthBetween(1, 20)); flushAcknowledgement = new FlushAcknowledgement(randomAlphaOfLengthBetween(1, 20), new Date(randomNonNegativeLong()));
} else { } else {
flushAcknowledgement = null; flushAcknowledgement = null;
} }

View File

@ -31,7 +31,11 @@
}, },
"advance_time": { "advance_time": {
"type": "string", "type": "string",
"description": "Setting this tells the Engine API that no data prior to advance_time is expected" "description": "Advances time to the given value generating results and updating the model for the advanced interval"
},
"skip_time": {
"type": "string",
"description": "Skips time to the given value without generating results or updating the model for the skipped interval"
} }
} }
}, },

View File

@ -66,6 +66,7 @@ setup:
xpack.ml.flush_job: xpack.ml.flush_job:
job_id: post-data-job job_id: post-data-job
- match: { flushed: true } - match: { flushed: true }
- match: { last_finalized_bucket_end: 1403481600000 }
- do: - do:
xpack.ml.close_job: xpack.ml.close_job:
@ -100,6 +101,7 @@ setup:
xpack.ml.flush_job: xpack.ml.flush_job:
job_id: post-data-job job_id: post-data-job
- match: { flushed: true } - match: { flushed: true }
- match: { last_finalized_bucket_end: 0 }
- do: - do:
xpack.ml.close_job: xpack.ml.close_job:
@ -111,6 +113,68 @@ setup:
job_id: post-data-job job_id: post-data-job
- match: { jobs.0.state: "closed" } - match: { jobs.0.state: "closed" }
---
"Test flush with skip_time":
- do:
xpack.ml.post_data:
job_id: post-data-job
body:
- airline: AAL
responsetime: 132.2046
sourcetype: post-data-job
time: 1403481600
- airline: AAL
responsetime: 990.4628
sourcetype: post-data-job
time: 1403485200
# Skip a bucket
- do:
xpack.ml.flush_job:
job_id: post-data-job
skip_time: 1403488700
- match: { flushed: true }
- match: { last_finalized_bucket_end: 1403488800000 }
# Send some data that should be ignored
- do:
xpack.ml.post_data:
job_id: post-data-job
body:
airline: AAL
responsetime: 132.2046
sourcetype: post-data-job
time: 1403488700
# Send data that will create results for the bucket after the skipped one
- do:
xpack.ml.post_data:
job_id: post-data-job
body:
- airline: AAL
responsetime: 132.2046
sourcetype: post-data-job
time: 1403488900
- airline: AAL
responsetime: 132.2046
sourcetype: post-data-job
time: 1403492400
- do:
xpack.ml.close_job:
job_id: post-data-job
- match: { closed: true }
- do:
xpack.ml.get_buckets:
job_id: "post-data-job"
- match: { count: 2 }
- match: { buckets.0.timestamp: 1403481600000 }
- match: { buckets.0.event_count: 1 }
- match: { buckets.1.timestamp: 1403488800000 }
- match: { buckets.1.event_count: 1 }
--- ---
"Test POST data with invalid parameters": "Test POST data with invalid parameters":