Skip to content

Commit ba38b2e

Browse files
committed
[ML] fix updating opened jobs scheduled events (#31651) (#32881)
* ML: fix updating opened jobs scheduled events (#31651) * Adding UpdateParamsTests license header * Adding integration test and addressing PR comments * addressing test and job names
1 parent 7a2d700 commit ba38b2e

File tree

5 files changed

+129
-4
lines changed

5 files changed

+129
-4
lines changed

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/job/config/JobUpdate.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,7 +237,7 @@ public Version getJobVersion() {
237237
}
238238

239239
public boolean isAutodetectProcessUpdate() {
240-
return modelPlotConfig != null || detectorUpdates != null;
240+
return modelPlotConfig != null || detectorUpdates != null || groups != null;
241241
}
242242

243243
@Override

x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/job/config/JobUpdateTests.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,8 @@ public void testIsAutodetectProcessUpdate() {
271271
assertTrue(update.isAutodetectProcessUpdate());
272272
update = new JobUpdate.Builder("foo").setDetectorUpdates(Collections.singletonList(mock(JobUpdate.DetectorUpdate.class))).build();
273273
assertTrue(update.isAutodetectProcessUpdate());
274+
update = new JobUpdate.Builder("foo").setGroups(Arrays.asList("bar")).build();
275+
assertTrue(update.isAutodetectProcessUpdate());
274276
}
275277

276278
public void testUpdateAnalysisLimitWithValueGreaterThanMax() {

x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/job/process/autodetect/UpdateParams.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ public static UpdateParams fromJobUpdate(JobUpdate jobUpdate) {
6666
return new Builder(jobUpdate.getJobId())
6767
.modelPlotConfig(jobUpdate.getModelPlotConfig())
6868
.detectorUpdates(jobUpdate.getDetectorUpdates())
69+
.updateScheduledEvents(jobUpdate.getGroups() != null)
6970
.build();
7071
}
7172

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License;
4+
* you may not use this file except in compliance with the Elastic License.
5+
*/
6+
package org.elasticsearch.xpack.ml.job.process.autodetect;
7+
8+
import org.elasticsearch.test.ESTestCase;
9+
import org.elasticsearch.xpack.core.ml.job.config.DetectionRule;
10+
import org.elasticsearch.xpack.core.ml.job.config.JobUpdate;
11+
import org.elasticsearch.xpack.core.ml.job.config.ModelPlotConfig;
12+
import org.elasticsearch.xpack.core.ml.job.config.Operator;
13+
import org.elasticsearch.xpack.core.ml.job.config.RuleCondition;
14+
15+
import java.util.Arrays;
16+
import java.util.Collections;
17+
import java.util.List;
18+
19+
20+
public class UpdateParamsTests extends ESTestCase {
21+
22+
public void testFromJobUpdate() {
23+
String jobId = "foo";
24+
DetectionRule rule = new DetectionRule.Builder(Arrays.asList(
25+
new RuleCondition(RuleCondition.AppliesTo.ACTUAL,
26+
Operator.GT, 1.0))).build();
27+
List<DetectionRule> rules = Arrays.asList(rule);
28+
List<JobUpdate.DetectorUpdate> detectorUpdates = Collections.singletonList(
29+
new JobUpdate.DetectorUpdate(2, null, rules));
30+
JobUpdate.Builder updateBuilder = new JobUpdate.Builder(jobId)
31+
.setModelPlotConfig(new ModelPlotConfig())
32+
.setDetectorUpdates(detectorUpdates);
33+
34+
UpdateParams params = UpdateParams.fromJobUpdate(updateBuilder.build());
35+
36+
assertFalse(params.isUpdateScheduledEvents());
37+
assertEquals(params.getDetectorUpdates(), updateBuilder.build().getDetectorUpdates());
38+
assertEquals(params.getModelPlotConfig(), updateBuilder.build().getModelPlotConfig());
39+
40+
params = UpdateParams.fromJobUpdate(updateBuilder.setGroups(Arrays.asList("bar")).build());
41+
42+
assertTrue(params.isUpdateScheduledEvents());
43+
}
44+
45+
}

x-pack/qa/ml-native-multi-node-tests/src/test/java/org/elasticsearch/xpack/ml/integration/ScheduledEventsIT.java

Lines changed: 80 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,13 @@
1212
import org.elasticsearch.search.sort.SortOrder;
1313
import org.elasticsearch.xpack.core.ml.action.GetBucketsAction;
1414
import org.elasticsearch.xpack.core.ml.action.GetRecordsAction;
15+
import org.elasticsearch.xpack.core.ml.action.UpdateJobAction;
1516
import org.elasticsearch.xpack.core.ml.calendars.ScheduledEvent;
1617
import org.elasticsearch.xpack.core.ml.job.config.AnalysisConfig;
1718
import org.elasticsearch.xpack.core.ml.job.config.DataDescription;
1819
import org.elasticsearch.xpack.core.ml.job.config.Detector;
1920
import org.elasticsearch.xpack.core.ml.job.config.Job;
21+
import org.elasticsearch.xpack.core.ml.job.config.JobUpdate;
2022
import org.elasticsearch.xpack.core.ml.job.results.AnomalyRecord;
2123
import org.elasticsearch.xpack.core.ml.job.results.Bucket;
2224
import org.junit.After;
@@ -193,9 +195,9 @@ public void testScheduledEventWithInterimResults() throws IOException {
193195
/**
194196
* Test an open job picks up changes to scheduled events/calendars
195197
*/
196-
public void testOnlineUpdate() throws Exception {
198+
public void testAddEventsToOpenJob() throws Exception {
197199
TimeValue bucketSpan = TimeValue.timeValueMinutes(30);
198-
Job.Builder job = createJob("scheduled-events-online-update", bucketSpan);
200+
Job.Builder job = createJob("scheduled-events-add-events-to-open-job", bucketSpan);
199201

200202
long startTime = 1514764800000L;
201203
final int bucketCount = 5;
@@ -209,7 +211,7 @@ public void testOnlineUpdate() throws Exception {
209211

210212
// Now create a calendar and events for the job while it is open
211213
String calendarId = "test-calendar-online-update";
212-
putCalendar(calendarId, Collections.singletonList(job.getId()), "testOnlineUpdate calendar");
214+
putCalendar(calendarId, Collections.singletonList(job.getId()), "testAddEventsToOpenJob calendar");
213215

214216
List<ScheduledEvent> events = new ArrayList<>();
215217
long eventStartTime = startTime + (bucketCount + 1) * bucketSpan.millis();
@@ -257,6 +259,81 @@ public void testOnlineUpdate() throws Exception {
257259
assertEquals(0, buckets.get(8).getScheduledEvents().size());
258260
}
259261

262+
/**
263+
* An open job that later gets added to a calendar, should take the scheduled events into account
264+
*/
265+
public void testAddOpenedJobToGroupWithCalendar() throws Exception {
266+
TimeValue bucketSpan = TimeValue.timeValueMinutes(30);
267+
String groupName = "opened-calendar-job-group";
268+
Job.Builder job = createJob("scheduled-events-add-opened-job-to-group-with-calendar", bucketSpan);
269+
270+
long startTime = 1514764800000L;
271+
final int bucketCount = 5;
272+
273+
// Open the job
274+
openJob(job.getId());
275+
276+
// write some buckets of data
277+
postData(job.getId(), generateData(startTime, bucketSpan, bucketCount, bucketIndex -> randomIntBetween(100, 200))
278+
.stream().collect(Collectors.joining()));
279+
280+
String calendarId = "test-calendar-open-job-update";
281+
282+
// Create a new calendar referencing groupName
283+
putCalendar(calendarId, Collections.singletonList(groupName), "testAddOpenedJobToGroupWithCalendar calendar");
284+
285+
// Put events in the calendar
286+
List<ScheduledEvent> events = new ArrayList<>();
287+
long eventStartTime = startTime + (bucketCount + 1) * bucketSpan.millis();
288+
long eventEndTime = eventStartTime + (long)(1.5 * bucketSpan.millis());
289+
events.add(new ScheduledEvent.Builder().description("Some Event")
290+
.startTime(ZonedDateTime.ofInstant(Instant.ofEpochMilli(eventStartTime), ZoneOffset.UTC))
291+
.endTime(ZonedDateTime.ofInstant(Instant.ofEpochMilli(eventEndTime), ZoneOffset.UTC))
292+
.calendarId(calendarId).build());
293+
294+
postScheduledEvents(calendarId, events);
295+
296+
// Update the job to be a member of the group
297+
UpdateJobAction.Request jobUpdateRequest = new UpdateJobAction.Request(job.getId(),
298+
new JobUpdate.Builder(job.getId()).setGroups(Collections.singletonList(groupName)).build());
299+
client().execute(UpdateJobAction.INSTANCE, jobUpdateRequest).actionGet();
300+
301+
// Wait until the notification that the job was updated is indexed
302+
assertBusy(() -> {
303+
SearchResponse searchResponse = client().prepareSearch(".ml-notifications")
304+
.setSize(1)
305+
.addSort("timestamp", SortOrder.DESC)
306+
.setQuery(QueryBuilders.boolQuery()
307+
.filter(QueryBuilders.termQuery("job_id", job.getId()))
308+
.filter(QueryBuilders.termQuery("level", "info"))
309+
).get();
310+
SearchHit[] hits = searchResponse.getHits().getHits();
311+
assertThat(hits.length, equalTo(1));
312+
assertThat(hits[0].getSourceAsMap().get("message"), equalTo("Job updated: [groups]"));
313+
});
314+
315+
// write some more buckets of data that cover the scheduled event period
316+
postData(job.getId(), generateData(startTime + bucketCount * bucketSpan.millis(), bucketSpan, 5,
317+
bucketIndex -> randomIntBetween(100, 200))
318+
.stream().collect(Collectors.joining()));
319+
// and close
320+
closeJob(job.getId());
321+
322+
GetBucketsAction.Request getBucketsRequest = new GetBucketsAction.Request(job.getId());
323+
List<Bucket> buckets = getBuckets(getBucketsRequest);
324+
325+
// the first 6 buckets have no events
326+
for (int i=0; i<=bucketCount; i++) {
327+
assertEquals(0, buckets.get(i).getScheduledEvents().size());
328+
}
329+
// 7th and 8th buckets have the event but the last one does not
330+
assertEquals(1, buckets.get(6).getScheduledEvents().size());
331+
assertEquals("Some Event", buckets.get(6).getScheduledEvents().get(0));
332+
assertEquals(1, buckets.get(7).getScheduledEvents().size());
333+
assertEquals("Some Event", buckets.get(7).getScheduledEvents().get(0));
334+
assertEquals(0, buckets.get(8).getScheduledEvents().size());
335+
}
336+
260337
private Job.Builder createJob(String jobId, TimeValue bucketSpan) {
261338
Detector.Builder detector = new Detector.Builder("count", null);
262339
AnalysisConfig.Builder analysisConfig = new AnalysisConfig.Builder(Collections.singletonList(detector.build()));

0 commit comments

Comments
 (0)