Skip to content

Commit f60401a

Browse files
authored
[Transform] Transform _schedule_now API (elastic#92948)
1 parent a71210c commit f60401a

File tree

19 files changed

+1011
-5
lines changed

19 files changed

+1011
-5
lines changed

docs/changelog/92948.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
pr: 92948
2+
summary: Transform _schedule_now API
3+
area: Transform
4+
type: feature
5+
issues:
6+
- 44722

docs/reference/transform/api-quickref.asciidoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ _transform/
1818
* <<reset-transform,Reset {transforms}>>
1919
* <<start-transform,Start {transforms}>>
2020
* <<stop-transform,Stop {transforms}>>
21+
* <<schedule-now-transform,Schedule Now {transforms}>>
2122
* <<update-transform,Update {transforms}>>
2223

2324
For the full list, see <<transform-apis>>.

docs/reference/transform/apis/index.asciidoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ include::reset-transform.asciidoc[leveloffset=+2]
1515
include::start-transform.asciidoc[leveloffset=+2]
1616
//STOP
1717
include::stop-transform.asciidoc[leveloffset=+2]
18+
//SCHEDULE_NOW
19+
include::schedule-now-transform.asciidoc[leveloffset=+2]
1820
//UPDATE-UPGRADE
1921
include::update-transform.asciidoc[leveloffset=+2]
2022
include::upgrade-transforms.asciidoc[leveloffset=+2]
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
[role="xpack"]
2+
[testenv="basic"]
3+
[[schedule-now-transform]]
4+
= Schedule Now {transform} API
5+
6+
[subs="attributes"]
7+
++++
8+
<titleabbrev>Shedule Now {transform}</titleabbrev>
9+
++++
10+
11+
Schedules now a {transform}.
12+
13+
[[schedule-now-transform-request]]
14+
== {api-request-title}
15+
16+
`POST _transform/<transform_id>/_schedule_now`
17+
18+
[[schedule-now-transform-prereqs]]
19+
== {api-prereq-title}
20+
21+
* Requires the `manage_transform` cluster privilege. This privilege is included
22+
in the `transform_admin` built-in role.
23+
24+
[schedule-now-transform-desc]]
25+
== {api-description-title}
26+
27+
If you _schedule_now a {transform}, it will process the new data instantly,
28+
without waiting for the configured `frequency` interval.
29+
After _schedule_now API is called, the transform will be processed again at
30+
`now + frequency` unless _schedule_now API is called again in the meantime.
31+
32+
[[schedule-now-transform-path-parms]]
33+
== {api-path-parms-title}
34+
35+
`<transform_id>`::
36+
(Required, string)
37+
include::{es-repo-dir}/rest-api/common-parms.asciidoc[tag=transform-id]
38+
39+
[[schedule-now-transform-query-parms]]
40+
== {api-query-parms-title}
41+
42+
`timeout`::
43+
(Optional, time)
44+
Period to wait for a response. If no response is received before the timeout
45+
expires, the request fails and returns an error. Defaults to `30s`.
46+
47+
[[schedule-now-transform-examples]]
48+
== {api-examples-title}
49+
50+
[source,console]
51+
--------------------------------------------------
52+
POST _transform/ecommerce_transform/_schedule_now
53+
--------------------------------------------------
54+
// TEST[skip:setup kibana sample data]
55+
56+
When the {transform} is scheduled now, you receive the following results:
57+
58+
[source,console-result]
59+
----
60+
{
61+
"acknowledged" : true
62+
}
63+
----

docs/reference/transform/apis/transform-apis.asciidoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,6 @@
1010
* <<reset-transform>>
1111
* <<start-transform>>
1212
* <<stop-transform>>
13+
* <<schedule-now-transform>>
1314
* <<update-transform>>
1415
* <<upgrade-transforms>>
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
{
2+
"transform.schedule_now_transform":{
3+
"documentation":{
4+
"url":"https://www.elastic.co/guide/en/elasticsearch/reference/current/schedule-now-transform.html",
5+
"description":"Schedules now a transform."
6+
},
7+
"stability":"stable",
8+
"visibility":"public",
9+
"headers":{
10+
"accept":[ "application/json"],
11+
"content_type":["application/json"]
12+
},
13+
"url":{
14+
"paths":[
15+
{
16+
"path":"/_transform/{transform_id}/_schedule_now",
17+
"methods":[
18+
"POST"
19+
],
20+
"parts":{
21+
"transform_id":{
22+
"type":"string",
23+
"required":true,
24+
"description":"The id of the transform."
25+
}
26+
}
27+
}
28+
]
29+
},
30+
"params":{
31+
"timeout":{
32+
"type":"time",
33+
"required":false,
34+
"description":"Controls the time to wait for the scheduling to take place"
35+
}
36+
}
37+
}
38+
}
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
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+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
package org.elasticsearch.xpack.core.transform.action;
9+
10+
import org.elasticsearch.ElasticsearchException;
11+
import org.elasticsearch.action.ActionRequestValidationException;
12+
import org.elasticsearch.action.ActionType;
13+
import org.elasticsearch.action.TaskOperationFailure;
14+
import org.elasticsearch.action.support.tasks.BaseTasksRequest;
15+
import org.elasticsearch.action.support.tasks.BaseTasksResponse;
16+
import org.elasticsearch.cluster.metadata.Metadata;
17+
import org.elasticsearch.common.io.stream.StreamInput;
18+
import org.elasticsearch.common.io.stream.StreamOutput;
19+
import org.elasticsearch.common.io.stream.Writeable;
20+
import org.elasticsearch.core.TimeValue;
21+
import org.elasticsearch.xcontent.ToXContentObject;
22+
import org.elasticsearch.xcontent.XContentBuilder;
23+
import org.elasticsearch.xpack.core.transform.TransformField;
24+
import org.elasticsearch.xpack.core.transform.utils.ExceptionsHelper;
25+
26+
import java.io.IOException;
27+
import java.util.Collections;
28+
import java.util.List;
29+
import java.util.Objects;
30+
31+
public class ScheduleNowTransformAction extends ActionType<ScheduleNowTransformAction.Response> {
32+
33+
public static final ScheduleNowTransformAction INSTANCE = new ScheduleNowTransformAction();
34+
public static final String NAME = "cluster:admin/transform/schedule_now";
35+
36+
private ScheduleNowTransformAction() {
37+
super(NAME, ScheduleNowTransformAction.Response::new);
38+
}
39+
40+
public static class Request extends BaseTasksRequest<Request> {
41+
42+
private final String id;
43+
44+
public Request(String id, TimeValue timeout) {
45+
this.id = ExceptionsHelper.requireNonNull(id, TransformField.ID.getPreferredName());
46+
this.setTimeout(ExceptionsHelper.requireNonNull(timeout, TransformField.TIMEOUT.getPreferredName()));
47+
}
48+
49+
public Request(StreamInput in) throws IOException {
50+
super(in);
51+
this.id = in.readString();
52+
}
53+
54+
public static Request fromXContent(final String id, final TimeValue timeout) {
55+
return new Request(id, timeout);
56+
}
57+
58+
@Override
59+
public ActionRequestValidationException validate() {
60+
if (Metadata.ALL.equals(id)) {
61+
ActionRequestValidationException e = new ActionRequestValidationException();
62+
e.addValidationError("_schedule_now API does not support _all wildcard");
63+
return e;
64+
}
65+
return null;
66+
}
67+
68+
public String getId() {
69+
return id;
70+
}
71+
72+
@Override
73+
public void writeTo(StreamOutput out) throws IOException {
74+
super.writeTo(out);
75+
out.writeString(id);
76+
}
77+
78+
@Override
79+
public int hashCode() {
80+
// the base class does not implement hashCode, therefore we need to hash timeout ourselves
81+
return Objects.hash(getTimeout(), id);
82+
}
83+
84+
@Override
85+
public boolean equals(Object obj) {
86+
if (obj == null) {
87+
return false;
88+
}
89+
if (getClass() != obj.getClass()) {
90+
return false;
91+
}
92+
Request other = (Request) obj;
93+
94+
// the base class does not implement equals, therefore we need to check timeout ourselves
95+
return this.id.equals(other.id) && getTimeout().equals(other.getTimeout());
96+
}
97+
}
98+
99+
public static class Response extends BaseTasksResponse implements Writeable, ToXContentObject {
100+
101+
public static final Response TRUE = new Response(true);
102+
103+
private final boolean acknowledged;
104+
105+
public Response(StreamInput in) throws IOException {
106+
super(in);
107+
acknowledged = in.readBoolean();
108+
}
109+
110+
public Response(boolean acknowledged) {
111+
super(Collections.emptyList(), Collections.emptyList());
112+
this.acknowledged = acknowledged;
113+
}
114+
115+
public Response(
116+
List<TaskOperationFailure> taskFailures,
117+
List<? extends ElasticsearchException> nodeFailures,
118+
boolean acknowledged
119+
) {
120+
super(taskFailures, nodeFailures);
121+
this.acknowledged = acknowledged;
122+
}
123+
124+
public boolean isAcknowledged() {
125+
return acknowledged;
126+
}
127+
128+
@Override
129+
public void writeTo(StreamOutput out) throws IOException {
130+
super.writeTo(out);
131+
out.writeBoolean(acknowledged);
132+
}
133+
134+
@Override
135+
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
136+
builder.startObject();
137+
toXContentCommon(builder, params);
138+
builder.field("acknowledged", acknowledged);
139+
builder.endObject();
140+
return builder;
141+
}
142+
143+
@Override
144+
public boolean equals(Object o) {
145+
if (this == o) return true;
146+
if (o == null || getClass() != o.getClass()) return false;
147+
ScheduleNowTransformAction.Response response = (ScheduleNowTransformAction.Response) o;
148+
return acknowledged == response.acknowledged;
149+
}
150+
151+
@Override
152+
public int hashCode() {
153+
return Objects.hash(acknowledged);
154+
}
155+
}
156+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
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+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
package org.elasticsearch.xpack.core.transform.action;
9+
10+
import org.elasticsearch.action.ActionRequestValidationException;
11+
import org.elasticsearch.common.io.stream.Writeable;
12+
import org.elasticsearch.core.TimeValue;
13+
import org.elasticsearch.test.AbstractWireSerializingTestCase;
14+
import org.elasticsearch.xpack.core.transform.action.ScheduleNowTransformAction.Request;
15+
16+
import static org.hamcrest.Matchers.contains;
17+
import static org.hamcrest.Matchers.is;
18+
import static org.hamcrest.Matchers.notNullValue;
19+
import static org.hamcrest.Matchers.nullValue;
20+
21+
public class ScheduleNowTransformActionRequestTests extends AbstractWireSerializingTestCase<Request> {
22+
23+
@Override
24+
protected Request createTestInstance() {
25+
return new Request(randomAlphaOfLengthBetween(1, 20), TimeValue.parseTimeValue(randomTimeValue(), "timeout"));
26+
}
27+
28+
@Override
29+
protected Writeable.Reader<Request> instanceReader() {
30+
return Request::new;
31+
}
32+
33+
@Override
34+
protected Request mutateInstance(Request instance) {
35+
String id = instance.getId();
36+
TimeValue timeout = instance.getTimeout();
37+
38+
switch (between(0, 1)) {
39+
case 0 -> id += randomAlphaOfLengthBetween(1, 5);
40+
case 1 -> timeout = new TimeValue(timeout.duration() + randomLongBetween(1, 5), timeout.timeUnit());
41+
default -> throw new AssertionError("Illegal randomization branch");
42+
}
43+
44+
return new ScheduleNowTransformAction.Request(id, timeout);
45+
}
46+
47+
public void testValidationSuccess() {
48+
Request request = new Request("id", TimeValue.ZERO);
49+
assertThat(request.validate(), is(nullValue()));
50+
}
51+
52+
public void testValidationFailure() {
53+
Request request = new Request("_all", TimeValue.ZERO);
54+
ActionRequestValidationException e = request.validate();
55+
assertThat(e, is(notNullValue()));
56+
assertThat(e.validationErrors(), contains("_schedule_now API does not support _all wildcard"));
57+
}
58+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
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+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
package org.elasticsearch.xpack.core.transform.action;
9+
10+
import org.elasticsearch.common.io.stream.Writeable.Reader;
11+
import org.elasticsearch.xpack.core.transform.action.ScheduleNowTransformAction.Response;
12+
13+
public class ScheduleNowTransformActionResponseTests extends AbstractWireSerializingTransformTestCase<Response> {
14+
15+
@Override
16+
protected Response createTestInstance() {
17+
return new Response(randomBoolean());
18+
}
19+
20+
@Override
21+
protected Reader<Response> instanceReader() {
22+
return Response::new;
23+
}
24+
25+
@Override
26+
protected Response mutateInstance(Response instance) {
27+
boolean acknowledged = instance.isAcknowledged();
28+
return new Response(acknowledged == false);
29+
}
30+
31+
public void testResponseTrue() {
32+
assertTrue(Response.TRUE.isAcknowledged());
33+
}
34+
}

x-pack/plugin/security/qa/operator-privileges-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/operator/Constants.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ public class Constants {
8888
"cluster:admin/transform/reset",
8989
"cluster:admin/transform/start",
9090
"cluster:admin/transform/stop",
91+
"cluster:admin/transform/schedule_now",
9192
"cluster:admin/transform/update",
9293
"cluster:admin/transform/upgrade",
9394
"cluster:admin/transform/validate",

0 commit comments

Comments
 (0)