Skip to content

Commit a653a1c

Browse files
authored
[ML] all multiple wildcard values for GET Calendars, Events, and DELETE forecasts (#62563)
This commit adjusts the following APIs so now they not only support an `_all` case, but wildcard patterned Ids as well. - `GET _ml/calendars/<calendar_id>/events` - `GET _ml/calendars/<calendar_id>` - `GET _ml/anomaly_detectors/<job_id>/model_snapshots/<snapshot_id>` - `DELETE _ml/anomaly_detectors/<job_id>/_forecast/<forecast_id>`
1 parent 6ab28e5 commit a653a1c

File tree

19 files changed

+454
-142
lines changed

19 files changed

+454
-142
lines changed

docs/reference/ml/anomaly-detection/apis/delete-forecast.asciidoc

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<titleabbrev>Delete forecast</titleabbrev>
77
++++
88

9-
Deletes forecasts from a {ml} job.
9+
Deletes forecasts from a {ml} job.
1010

1111
[[ml-delete-forecast-request]]
1212
== {api-request-title}
@@ -27,12 +27,12 @@ Deletes forecasts from a {ml} job.
2727
[[ml-delete-forecast-desc]]
2828
== {api-description-title}
2929

30-
By default, forecasts are retained for 14 days. You can specify a different
30+
By default, forecasts are retained for 14 days. You can specify a different
3131
retention period with the `expires_in` parameter in the
3232
<<ml-forecast,forecast jobs API>>. The delete forecast API enables you to delete
3333
one or more forecasts before they expire.
3434

35-
NOTE: When you delete a job, its associated forecasts are deleted.
35+
NOTE: When you delete a job, its associated forecasts are deleted.
3636

3737
For more information, see
3838
{ml-docs}/ml-overview.html#ml-forecasting[Forecasting the future].
@@ -42,26 +42,26 @@ For more information, see
4242

4343
`<forecast_id>`::
4444
(Optional, string) A comma-separated list of forecast identifiers. If you do not
45-
specify this optional parameter or if you specify `_all`, the API deletes all
46-
forecasts from the job.
47-
45+
specify this optional parameter or if you specify `_all` or `*` the API deletes all
46+
forecasts from the job.
47+
4848
`<job_id>`::
4949
(Required, string)
5050
include::{es-repo-dir}/ml/ml-shared.asciidoc[tag=job-id-anomaly-detection]
51-
51+
5252

5353
[[ml-delete-forecast-query-parms]]
5454
== {api-query-parms-title}
5555

5656
`allow_no_forecasts`::
5757
(Optional, boolean) Specifies whether an error occurs when there are no
58-
forecasts. In particular, if this parameter is set to `false` and there are no
58+
forecasts. In particular, if this parameter is set to `false` and there are no
5959
forecasts associated with the job, attempts to delete all forecasts return an
6060
error. The default value is `true`.
6161

6262
`timeout`::
63-
(Optional, <<time-units, time units>>) Specifies the period of time to wait
64-
for the completion of the delete operation. When this period of time elapses,
63+
(Optional, <<time-units, time units>>) Specifies the period of time to wait
64+
for the completion of the delete operation. When this period of time elapses,
6565
the API fails and returns an error. The default value is `30s`.
6666

6767
[[ml-delete-forecast-example]]

docs/reference/ml/anomaly-detection/apis/get-calendar-event.asciidoc

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,10 @@ Retrieves information about the scheduled events in calendars.
2525
[[ml-get-calendar-event-desc]]
2626
== {api-description-title}
2727

28-
You can get scheduled event information for a single calendar or for all
29-
calendars by using `_all`.
28+
You can get scheduled event information for multiple calendars in a single
29+
API request by using a comma-separated list of ids or a wildcard expression.
30+
You can get scheduled event information for all calendars by using `_all`,
31+
by specifying `*` as the `<calendar_id>`, or by omitting the `<calendar_id>`.
3032

3133
For more information, see
3234
{ml-docs}/ml-calendars.html[Calendars and scheduled events].

docs/reference/ml/anomaly-detection/apis/get-calendar.asciidoc

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,12 @@ Retrieves configuration information for calendars.
2525
[[ml-get-calendar-desc]]
2626
== {api-description-title}
2727

28-
You can get information for a single calendar or for all calendars by using
29-
`_all`.
28+
You can get information for multiple calendars in a single API request by using a
29+
comma-separated list of ids or a wildcard expression. You can get
30+
information for all calendars by using `_all`, by specifying `*` as the
31+
`<calendar_id>`, or by omitting the `<calendar_id>`.
3032

31-
For more information, see
33+
For more information, see
3234
{ml-docs}/ml-calendars.html[Calendars and scheduled events].
3335

3436
[[ml-get-calendar-path-parms]]

docs/reference/ml/anomaly-detection/apis/get-snapshot.asciidoc

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,10 @@ include::{es-repo-dir}/ml/ml-shared.asciidoc[tag=job-id-anomaly-detection]
3434
include::{es-repo-dir}/ml/ml-shared.asciidoc[tag=snapshot-id]
3535
+
3636
--
37-
If you do not specify this optional parameter, the API returns information about
38-
all model snapshots.
37+
You can multiple snapshots for a single job in a single API request
38+
by using a comma-separated list of `<snapshot_id>` or a wildcard expression.
39+
You can get all snapshots for all calendars by using `_all`,
40+
by specifying `*` as the `<snapshot_id>`, or by omitting the `<snapshot_id>`.
3941
--
4042

4143
[[ml-get-snapshot-request-body]]
@@ -64,7 +66,7 @@ all model snapshots.
6466
[[ml-get-snapshot-results]]
6567
== {api-response-body-title}
6668

67-
The API returns an array of model snapshot objects, which have the following
69+
The API returns an array of model snapshot objects, which have the following
6870
properties:
6971

7072
`description`::
@@ -73,7 +75,7 @@ properties:
7375
`job_id`::
7476
(string) A numerical character string that uniquely identifies the job that
7577
the snapshot was created for.
76-
78+
7779
`latest_record_time_stamp`::
7880
(date) The timestamp of the latest processed record.
7981

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/action/GetCalendarEventsAction.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ public ActionRequestValidationException validate() {
126126

127127
if (jobId != null && Strings.isAllOrWildcard(calendarId) == false) {
128128
e = ValidateActions.addValidationError("If " + Job.ID.getPreferredName() + " is used " +
129-
Calendar.ID.getPreferredName() + " must be '" + GetCalendarsAction.Request.ALL + "'", e);
129+
Calendar.ID.getPreferredName() + " must be '" + GetCalendarsAction.Request.ALL + "' or '*'", e);
130130
}
131131
return e;
132132
}

x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/action/GetCalendarEventsActionRequestTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ public void testValidate() {
5353

5454
ActionRequestValidationException validationException = request.validate();
5555
assertNotNull(validationException);
56-
assertEquals("Validation Failed: 1: If job_id is used calendar_id must be '_all';", validationException.getMessage());
56+
assertEquals("Validation Failed: 1: If job_id is used calendar_id must be '_all' or '*';", validationException.getMessage());
5757

5858
request = new GetCalendarEventsAction.Request("_all");
5959
request.setJobId("foo");

x-pack/plugin/ml/qa/native-multi-node-tests/src/javaRestTest/java/org/elasticsearch/xpack/ml/integration/ForecastIT.java

Lines changed: 78 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ public void testNoData() {
187187
equalTo("Cannot run forecast: Forecast cannot be executed as job requires data to have been processed and modeled"));
188188
}
189189

190-
public void testMemoryStatus() throws Exception {
190+
public void testMemoryStatus() {
191191
Detector.Builder detector = new Detector.Builder("mean", "value");
192192
detector.setByFieldName("clientIP");
193193

@@ -287,6 +287,74 @@ public void testOverflowToDisk() throws Exception {
287287

288288
}
289289

290+
public void testDeleteWildCard() throws Exception {
291+
Detector.Builder detector = new Detector.Builder("mean", "value");
292+
293+
TimeValue bucketSpan = TimeValue.timeValueHours(1);
294+
AnalysisConfig.Builder analysisConfig = new AnalysisConfig.Builder(Collections.singletonList(detector.build()));
295+
analysisConfig.setBucketSpan(bucketSpan);
296+
DataDescription.Builder dataDescription = new DataDescription.Builder();
297+
dataDescription.setTimeFormat("epoch");
298+
299+
Job.Builder job = new Job.Builder("forecast-it-test-delete-wildcard");
300+
job.setAnalysisConfig(analysisConfig);
301+
job.setDataDescription(dataDescription);
302+
303+
registerJob(job);
304+
putJob(job);
305+
openJob(job.getId());
306+
307+
long now = Instant.now().getEpochSecond();
308+
long timestamp = now - 50 * bucketSpan.seconds();
309+
List<String> data = new ArrayList<>();
310+
while (timestamp < now) {
311+
data.add(createJsonRecord(createRecord(timestamp, 10.0)));
312+
data.add(createJsonRecord(createRecord(timestamp, 30.0)));
313+
timestamp += bucketSpan.seconds();
314+
}
315+
316+
postData(job.getId(), data.stream().collect(Collectors.joining()));
317+
flushJob(job.getId(), false);
318+
String forecastIdDefaultDurationDefaultExpiry = forecast(job.getId(), null, null);
319+
String forecastIdDuration1HourNoExpiry = forecast(job.getId(), TimeValue.timeValueHours(1), TimeValue.ZERO);
320+
String forecastId2Duration1HourNoExpiry = forecast(job.getId(), TimeValue.timeValueHours(1), TimeValue.ZERO);
321+
String forecastId2Duration1HourNoExpiry2 = forecast(job.getId(), TimeValue.timeValueHours(1), TimeValue.ZERO);
322+
waitForecastToFinish(job.getId(), forecastIdDefaultDurationDefaultExpiry);
323+
waitForecastToFinish(job.getId(), forecastIdDuration1HourNoExpiry);
324+
waitForecastToFinish(job.getId(), forecastId2Duration1HourNoExpiry);
325+
waitForecastToFinish(job.getId(), forecastId2Duration1HourNoExpiry2);
326+
closeJob(job.getId());
327+
328+
assertNotNull(getForecastStats(job.getId(), forecastIdDefaultDurationDefaultExpiry));
329+
assertNotNull(getForecastStats(job.getId(), forecastIdDuration1HourNoExpiry));
330+
assertNotNull(getForecastStats(job.getId(), forecastId2Duration1HourNoExpiry));
331+
assertNotNull(getForecastStats(job.getId(), forecastId2Duration1HourNoExpiry2));
332+
333+
{
334+
DeleteForecastAction.Request request = new DeleteForecastAction.Request(job.getId(),
335+
forecastIdDefaultDurationDefaultExpiry.substring(0, forecastIdDefaultDurationDefaultExpiry.length() - 2) + "*"
336+
+ ","
337+
+ forecastIdDuration1HourNoExpiry);
338+
AcknowledgedResponse response = client().execute(DeleteForecastAction.INSTANCE, request).actionGet();
339+
assertTrue(response.isAcknowledged());
340+
341+
assertNull(getForecastStats(job.getId(), forecastIdDefaultDurationDefaultExpiry));
342+
assertNull(getForecastStats(job.getId(), forecastIdDuration1HourNoExpiry));
343+
assertNotNull(getForecastStats(job.getId(), forecastId2Duration1HourNoExpiry));
344+
assertNotNull(getForecastStats(job.getId(), forecastId2Duration1HourNoExpiry2));
345+
}
346+
347+
{
348+
DeleteForecastAction.Request request = new DeleteForecastAction.Request(job.getId(), "*");
349+
AcknowledgedResponse response = client().execute(DeleteForecastAction.INSTANCE, request).actionGet();
350+
assertTrue(response.isAcknowledged());
351+
352+
assertNull(getForecastStats(job.getId(), forecastId2Duration1HourNoExpiry));
353+
assertNull(getForecastStats(job.getId(), forecastId2Duration1HourNoExpiry2));
354+
}
355+
356+
}
357+
290358
public void testDelete() throws Exception {
291359
Detector.Builder detector = new Detector.Builder("mean", "value");
292360

@@ -317,6 +385,8 @@ public void testDelete() throws Exception {
317385
flushJob(job.getId(), false);
318386
String forecastIdDefaultDurationDefaultExpiry = forecast(job.getId(), null, null);
319387
String forecastIdDuration1HourNoExpiry = forecast(job.getId(), TimeValue.timeValueHours(1), TimeValue.ZERO);
388+
String forecastId2Duration1HourNoExpiry = forecast(job.getId(), TimeValue.timeValueHours(1), TimeValue.ZERO);
389+
String forecastId2Duration1HourNoExpiry2 = forecast(job.getId(), TimeValue.timeValueHours(1), TimeValue.ZERO);
320390
waitForecastToFinish(job.getId(), forecastIdDefaultDurationDefaultExpiry);
321391
waitForecastToFinish(job.getId(), forecastIdDuration1HourNoExpiry);
322392
closeJob(job.getId());
@@ -333,13 +403,11 @@ public void testDelete() throws Exception {
333403
forecastIdDefaultDurationDefaultExpiry + "," + forecastIdDuration1HourNoExpiry);
334404
AcknowledgedResponse response = client().execute(DeleteForecastAction.INSTANCE, request).actionGet();
335405
assertTrue(response.isAcknowledged());
336-
}
337406

338-
{
339-
ForecastRequestStats forecastStats = getForecastStats(job.getId(), forecastIdDefaultDurationDefaultExpiry);
340-
assertNull(forecastStats);
341-
ForecastRequestStats otherStats = getForecastStats(job.getId(), forecastIdDuration1HourNoExpiry);
342-
assertNull(otherStats);
407+
assertNull(getForecastStats(job.getId(), forecastIdDefaultDurationDefaultExpiry));
408+
assertNull(getForecastStats(job.getId(), forecastIdDuration1HourNoExpiry));
409+
assertNotNull(getForecastStats(job.getId(), forecastId2Duration1HourNoExpiry));
410+
assertNotNull(getForecastStats(job.getId(), forecastId2Duration1HourNoExpiry2));
343411
}
344412

345413
{
@@ -354,6 +422,9 @@ public void testDelete() throws Exception {
354422
DeleteForecastAction.Request request = new DeleteForecastAction.Request(job.getId(), Metadata.ALL);
355423
AcknowledgedResponse response = client().execute(DeleteForecastAction.INSTANCE, request).actionGet();
356424
assertTrue(response.isAcknowledged());
425+
426+
assertNull(getForecastStats(job.getId(), forecastId2Duration1HourNoExpiry));
427+
assertNull(getForecastStats(job.getId(), forecastId2Duration1HourNoExpiry2));
357428
}
358429

359430
{

0 commit comments

Comments
 (0)