From f73a7803ce7e552320452dec92b9661752d7cd99 Mon Sep 17 00:00:00 2001 From: David Kyle Date: Tue, 9 Jan 2018 11:55:36 +0000 Subject: [PATCH] [ML] Delete calendar events endpoint (elastic/x-pack-elasticsearch#3388) * Delete calendar events endpoint Original commit: elastic/x-pack-elasticsearch@70aebfae2c664de529a8983a2a1ccacb8858305f --- .../xpack/ml/action/DeleteCalendarAction.java | 2 - .../ml/action/DeleteCalendarEventAction.java | 125 ++++++++++++++++++ .../xpack/ml/calendars/ScheduledEvent.java | 26 +++- .../xpack/ml/job/persistence/JobProvider.java | 8 +- .../ScheduledEventsQueryBuilder.java | 26 +++- .../xpack/ml/MachineLearning.java | 11 +- .../action/TransportDeleteCalendarAction.java | 57 +++++--- .../TransportDeleteCalendarEventAction.java | 113 ++++++++++++++++ .../RestDeleteCalendarEventAction.java | 42 ++++++ ...DeleteCalendarEventActionRequestTests.java | 22 +++ .../ml/calendars/ScheduledEventTests.java | 2 +- .../api/xpack.ml.delete_calendar_event.json | 22 +++ .../rest-api-spec/test/ml/calendar_crud.yml | 97 +++++++++++++- 13 files changed, 514 insertions(+), 39 deletions(-) create mode 100644 plugin/core/src/main/java/org/elasticsearch/xpack/ml/action/DeleteCalendarEventAction.java create mode 100644 plugin/src/main/java/org/elasticsearch/xpack/ml/action/TransportDeleteCalendarEventAction.java create mode 100644 plugin/src/main/java/org/elasticsearch/xpack/ml/rest/calendar/RestDeleteCalendarEventAction.java create mode 100644 plugin/src/test/java/org/elasticsearch/xpack/ml/action/DeleteCalendarEventActionRequestTests.java create mode 100644 plugin/src/test/resources/rest-api-spec/api/xpack.ml.delete_calendar_event.json diff --git a/plugin/core/src/main/java/org/elasticsearch/xpack/ml/action/DeleteCalendarAction.java b/plugin/core/src/main/java/org/elasticsearch/xpack/ml/action/DeleteCalendarAction.java index f45d4515d73..673ef1663eb 100644 --- a/plugin/core/src/main/java/org/elasticsearch/xpack/ml/action/DeleteCalendarAction.java +++ b/plugin/core/src/main/java/org/elasticsearch/xpack/ml/action/DeleteCalendarAction.java @@ -45,7 +45,6 @@ public class DeleteCalendarAction extends Action { + + public static final DeleteCalendarEventAction INSTANCE = new DeleteCalendarEventAction(); + public static final String NAME = "cluster:admin/xpack/ml/calendars/events/delete"; + + private DeleteCalendarEventAction() { + super(NAME); + } + + @Override + public RequestBuilder newRequestBuilder(ElasticsearchClient client) { + return new RequestBuilder(client, this); + } + + @Override + public Response newResponse() { + return new Response(); + } + + public static class Request extends AcknowledgedRequest { + private String calendarId; + private String eventId; + + Request() { + } + + public Request(String calendarId, String eventId) { + this.calendarId = ExceptionsHelper.requireNonNull(calendarId, Calendar.ID.getPreferredName()); + this.eventId = ExceptionsHelper.requireNonNull(eventId, ScheduledEvent.EVENT_ID.getPreferredName()); + } + + public String getCalendarId() { + return calendarId; + } + + public String getEventId() { + return eventId; + } + + @Override + public ActionRequestValidationException validate() { + return null; + } + + @Override + public void readFrom(StreamInput in) throws IOException { + super.readFrom(in); + calendarId = in.readString(); + eventId = in.readString(); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + super.writeTo(out); + out.writeString(calendarId); + out.writeString(eventId); + } + + @Override + public int hashCode() { + return Objects.hash(eventId, calendarId); + } + + @Override + public boolean equals(Object obj) { + if (obj == null || getClass() != obj.getClass()) { + return false; + } + + Request other = (Request) obj; + return Objects.equals(eventId, other.eventId) && Objects.equals(calendarId, other.calendarId); + } + } + + public static class RequestBuilder extends ActionRequestBuilder { + + public RequestBuilder(ElasticsearchClient client, DeleteCalendarEventAction action) { + super(client, action, new Request()); + } + } + + public static class Response extends AcknowledgedResponse { + + public Response(boolean acknowledged) { + super(acknowledged); + } + + private Response() {} + + @Override + public void readFrom(StreamInput in) throws IOException { + super.readFrom(in); + readAcknowledged(in); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + super.writeTo(out); + writeAcknowledged(out); + } + } +} diff --git a/plugin/core/src/main/java/org/elasticsearch/xpack/ml/calendars/ScheduledEvent.java b/plugin/core/src/main/java/org/elasticsearch/xpack/ml/calendars/ScheduledEvent.java index 1c99a25720f..21211ebb774 100644 --- a/plugin/core/src/main/java/org/elasticsearch/xpack/ml/calendars/ScheduledEvent.java +++ b/plugin/core/src/main/java/org/elasticsearch/xpack/ml/calendars/ScheduledEvent.java @@ -6,6 +6,7 @@ package org.elasticsearch.xpack.ml.calendars; import org.elasticsearch.common.ParseField; +import org.elasticsearch.common.inject.internal.Nullable; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.io.stream.Writeable; @@ -39,6 +40,7 @@ public class ScheduledEvent implements ToXContentObject, Writeable { public static final ParseField START_TIME = new ParseField("start_time"); public static final ParseField END_TIME = new ParseField("end_time"); public static final ParseField TYPE = new ParseField("type"); + public static final ParseField EVENT_ID = new ParseField("event_id"); public static final ParseField RESULTS_FIELD = new ParseField("events"); @@ -81,12 +83,14 @@ public class ScheduledEvent implements ToXContentObject, Writeable { private final ZonedDateTime startTime; private final ZonedDateTime endTime; private final String calendarId; + private final String eventId; - ScheduledEvent(String description, ZonedDateTime startTime, ZonedDateTime endTime, String calendarId) { + ScheduledEvent(String description, ZonedDateTime startTime, ZonedDateTime endTime, String calendarId, @Nullable String eventId) { this.description = Objects.requireNonNull(description); this.startTime = Objects.requireNonNull(startTime); this.endTime = Objects.requireNonNull(endTime); this.calendarId = Objects.requireNonNull(calendarId); + this.eventId = eventId; } public ScheduledEvent(StreamInput in) throws IOException { @@ -94,6 +98,7 @@ public class ScheduledEvent implements ToXContentObject, Writeable { startTime = ZonedDateTime.ofInstant(Instant.ofEpochMilli(in.readVLong()), ZoneOffset.UTC); endTime = ZonedDateTime.ofInstant(Instant.ofEpochMilli(in.readVLong()), ZoneOffset.UTC); calendarId = in.readString(); + eventId = in.readOptionalString(); } public String getDescription() { @@ -112,6 +117,10 @@ public class ScheduledEvent implements ToXContentObject, Writeable { return calendarId; } + public String getEventId() { + return eventId; + } + /** * Convert the scheduled event to a detection rule. * The rule will have 2 time based conditions for the start and @@ -146,6 +155,7 @@ public class ScheduledEvent implements ToXContentObject, Writeable { out.writeVLong(startTime.toInstant().toEpochMilli()); out.writeVLong(endTime.toInstant().toEpochMilli()); out.writeString(calendarId); + out.writeOptionalString(eventId); } @Override @@ -155,6 +165,9 @@ public class ScheduledEvent implements ToXContentObject, Writeable { builder.dateField(START_TIME.getPreferredName(), START_TIME.getPreferredName() + "_string", startTime.toInstant().toEpochMilli()); builder.dateField(END_TIME.getPreferredName(), END_TIME.getPreferredName() + "_string", endTime.toInstant().toEpochMilli()); builder.field(Calendar.ID.getPreferredName(), calendarId); + if (eventId != null) { + builder.field(EVENT_ID.getPreferredName(), eventId); + } if (params.paramAsBoolean(MlMetaIndex.INCLUDE_TYPE_KEY, false)) { builder.field(TYPE.getPreferredName(), SCHEDULED_EVENT_TYPE); } @@ -197,7 +210,7 @@ public class ScheduledEvent implements ToXContentObject, Writeable { private ZonedDateTime startTime; private ZonedDateTime endTime; private String calendarId; - + private String eventId; public Builder description(String description) { this.description = description; @@ -223,6 +236,11 @@ public class ScheduledEvent implements ToXContentObject, Writeable { return calendarId; } + public Builder eventId(String eventId) { + this.eventId = eventId; + return this; + } + public ScheduledEvent build() { if (description == null) { throw ExceptionsHelper.badRequestException( @@ -249,7 +267,9 @@ public class ScheduledEvent implements ToXContentObject, Writeable { "] must come before end time [" + endTime + "]"); } - return new ScheduledEvent(description, startTime, endTime, calendarId); + ScheduledEvent event = new ScheduledEvent(description, startTime, endTime, calendarId, eventId); + + return event; } } } diff --git a/plugin/core/src/main/java/org/elasticsearch/xpack/ml/job/persistence/JobProvider.java b/plugin/core/src/main/java/org/elasticsearch/xpack/ml/job/persistence/JobProvider.java index 167a51e6fc2..08885bfbd54 100644 --- a/plugin/core/src/main/java/org/elasticsearch/xpack/ml/job/persistence/JobProvider.java +++ b/plugin/core/src/main/java/org/elasticsearch/xpack/ml/job/persistence/JobProvider.java @@ -1062,14 +1062,16 @@ public class JobProvider { List events = new ArrayList<>(); SearchHit[] hits = response.getHits().getHits(); for (SearchHit hit : hits) { - events.add(parseSearchHit(hit, ScheduledEvent.PARSER, handler::onFailure).build()); + ScheduledEvent.Builder event = parseSearchHit(hit, ScheduledEvent.PARSER, handler::onFailure); + event.eventId(hit.getId()); + events.add(event.build()); } handler.onResponse(new QueryPage<>(events, response.getHits().getTotalHits(), ScheduledEvent.RESULTS_FIELD)); }, - handler::onFailure) - , client::search); + handler::onFailure), + client::search); } public void getForecastRequestStats(String jobId, String forecastId, Consumer handler, diff --git a/plugin/core/src/main/java/org/elasticsearch/xpack/ml/job/persistence/ScheduledEventsQueryBuilder.java b/plugin/core/src/main/java/org/elasticsearch/xpack/ml/job/persistence/ScheduledEventsQueryBuilder.java index 4909ef55d77..7eec0ba52f4 100644 --- a/plugin/core/src/main/java/org/elasticsearch/xpack/ml/job/persistence/ScheduledEventsQueryBuilder.java +++ b/plugin/core/src/main/java/org/elasticsearch/xpack/ml/job/persistence/ScheduledEventsQueryBuilder.java @@ -24,8 +24,8 @@ import java.util.List; public class ScheduledEventsQueryBuilder { public static final int DEFAULT_SIZE = 1000; - private int from = 0; - private int size = DEFAULT_SIZE; + private Integer from = 0; + private Integer size = DEFAULT_SIZE; private List calendarIds; private String after; @@ -46,12 +46,22 @@ public class ScheduledEventsQueryBuilder { return this; } - public ScheduledEventsQueryBuilder from(int from) { + /** + * Set the query from parameter. + * @param from If null then no from will be set + * @return this + */ + public ScheduledEventsQueryBuilder from(Integer from) { this.from = from; return this; } - public ScheduledEventsQueryBuilder size(int size) { + /** + * Set the query size parameter. + * @param size If null then no size will be set + * @return this + */ + public ScheduledEventsQueryBuilder size(Integer size) { this.size = size; return this; } @@ -78,8 +88,12 @@ public class ScheduledEventsQueryBuilder { SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); searchSourceBuilder.sort(ScheduledEvent.START_TIME.getPreferredName()); - searchSourceBuilder.from(from); - searchSourceBuilder.size(size); + if (from != null) { + searchSourceBuilder.from(from); + } + if (size != null) { + searchSourceBuilder.size(size); + } if (queries.isEmpty()) { searchSourceBuilder.query(typeQuery); diff --git a/plugin/src/main/java/org/elasticsearch/xpack/ml/MachineLearning.java b/plugin/src/main/java/org/elasticsearch/xpack/ml/MachineLearning.java index 92c1de19c60..fcc8bfaa5f1 100644 --- a/plugin/src/main/java/org/elasticsearch/xpack/ml/MachineLearning.java +++ b/plugin/src/main/java/org/elasticsearch/xpack/ml/MachineLearning.java @@ -52,6 +52,11 @@ import org.elasticsearch.xpack.XPackPlugin; import org.elasticsearch.xpack.XPackSettings; import org.elasticsearch.xpack.ml.action.CloseJobAction; import org.elasticsearch.xpack.ml.action.DeleteCalendarAction; +import org.elasticsearch.xpack.ml.action.DeleteCalendarEventAction; +import org.elasticsearch.xpack.ml.action.GetCalendarEventsAction; +import org.elasticsearch.xpack.ml.action.PostCalendarEventsAction; +import org.elasticsearch.xpack.ml.action.TransportDeleteCalendarEventAction; +import org.elasticsearch.xpack.ml.action.UpdateCalendarJobAction; import org.elasticsearch.xpack.ml.action.DeleteDatafeedAction; import org.elasticsearch.xpack.ml.action.DeleteExpiredDataAction; import org.elasticsearch.xpack.ml.action.DeleteFilterAction; @@ -61,7 +66,6 @@ import org.elasticsearch.xpack.ml.action.FinalizeJobExecutionAction; import org.elasticsearch.xpack.ml.action.FlushJobAction; import org.elasticsearch.xpack.ml.action.ForecastJobAction; import org.elasticsearch.xpack.ml.action.GetBucketsAction; -import org.elasticsearch.xpack.ml.action.GetCalendarEventsAction; import org.elasticsearch.xpack.ml.action.GetCalendarsAction; import org.elasticsearch.xpack.ml.action.GetCategoriesAction; import org.elasticsearch.xpack.ml.action.GetDatafeedsAction; @@ -76,7 +80,6 @@ import org.elasticsearch.xpack.ml.action.GetRecordsAction; import org.elasticsearch.xpack.ml.action.IsolateDatafeedAction; import org.elasticsearch.xpack.ml.action.KillProcessAction; import org.elasticsearch.xpack.ml.action.OpenJobAction; -import org.elasticsearch.xpack.ml.action.PostCalendarEventsAction; import org.elasticsearch.xpack.ml.action.PostDataAction; import org.elasticsearch.xpack.ml.action.PreviewDatafeedAction; import org.elasticsearch.xpack.ml.action.PutCalendarAction; @@ -129,7 +132,6 @@ import org.elasticsearch.xpack.ml.action.TransportUpdateModelSnapshotAction; import org.elasticsearch.xpack.ml.action.TransportUpdateProcessAction; import org.elasticsearch.xpack.ml.action.TransportValidateDetectorAction; import org.elasticsearch.xpack.ml.action.TransportValidateJobConfigAction; -import org.elasticsearch.xpack.ml.action.UpdateCalendarJobAction; import org.elasticsearch.xpack.ml.action.UpdateDatafeedAction; import org.elasticsearch.xpack.ml.action.UpdateJobAction; import org.elasticsearch.xpack.ml.action.UpdateModelSnapshotAction; @@ -163,6 +165,7 @@ import org.elasticsearch.xpack.ml.notifications.AuditMessage; import org.elasticsearch.xpack.ml.notifications.Auditor; import org.elasticsearch.xpack.ml.rest.RestDeleteExpiredDataAction; import org.elasticsearch.xpack.ml.rest.calendar.RestDeleteCalendarAction; +import org.elasticsearch.xpack.ml.rest.calendar.RestDeleteCalendarEventAction; import org.elasticsearch.xpack.ml.rest.calendar.RestDeleteCalendarJobAction; import org.elasticsearch.xpack.ml.rest.calendar.RestGetCalendarEventsAction; import org.elasticsearch.xpack.ml.rest.calendar.RestGetCalendarsAction; @@ -513,6 +516,7 @@ public class MachineLearning implements MachineLearningClientActionPlugin, Actio new RestGetCalendarsAction(settings, restController), new RestPutCalendarAction(settings, restController), new RestDeleteCalendarAction(settings, restController), + new RestDeleteCalendarEventAction(settings, restController), new RestDeleteCalendarJobAction(settings, restController), new RestPutCalendarJobAction(settings, restController), new RestGetCalendarEventsAction(settings, restController), @@ -566,6 +570,7 @@ public class MachineLearning implements MachineLearningClientActionPlugin, Actio new ActionHandler<>(GetCalendarsAction.INSTANCE, TransportGetCalendarsAction.class), new ActionHandler<>(PutCalendarAction.INSTANCE, TransportPutCalendarAction.class), new ActionHandler<>(DeleteCalendarAction.INSTANCE, TransportDeleteCalendarAction.class), + new ActionHandler<>(DeleteCalendarEventAction.INSTANCE, TransportDeleteCalendarEventAction.class), new ActionHandler<>(UpdateCalendarJobAction.INSTANCE, TransportUpdateCalendarJobAction.class), new ActionHandler<>(GetCalendarEventsAction.INSTANCE, TransportGetCalendarEventsAction.class), new ActionHandler<>(PostCalendarEventsAction.INSTANCE, TransportPostCalendarEventsAction.class) diff --git a/plugin/src/main/java/org/elasticsearch/xpack/ml/action/TransportDeleteCalendarAction.java b/plugin/src/main/java/org/elasticsearch/xpack/ml/action/TransportDeleteCalendarAction.java index 2375dc6b716..3a2df99a415 100644 --- a/plugin/src/main/java/org/elasticsearch/xpack/ml/action/TransportDeleteCalendarAction.java +++ b/plugin/src/main/java/org/elasticsearch/xpack/ml/action/TransportDeleteCalendarAction.java @@ -7,18 +7,21 @@ package org.elasticsearch.xpack.ml.action; import org.elasticsearch.ResourceNotFoundException; import org.elasticsearch.action.ActionListener; -import org.elasticsearch.action.bulk.BulkAction; -import org.elasticsearch.action.bulk.BulkRequestBuilder; -import org.elasticsearch.action.bulk.BulkResponse; -import org.elasticsearch.action.delete.DeleteRequest; +import org.elasticsearch.action.get.GetAction; +import org.elasticsearch.action.get.GetRequest; +import org.elasticsearch.action.get.GetResponse; +import org.elasticsearch.action.search.SearchRequest; import org.elasticsearch.action.support.ActionFilters; import org.elasticsearch.action.support.HandledTransportAction; -import org.elasticsearch.action.support.WriteRequest; import org.elasticsearch.client.Client; import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver; import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.rest.RestStatus; +import org.elasticsearch.index.query.QueryBuilder; +import org.elasticsearch.index.query.QueryBuilders; +import org.elasticsearch.index.reindex.DeleteByQueryAction; +import org.elasticsearch.index.reindex.DeleteByQueryRequest; +import org.elasticsearch.search.builder.SearchSourceBuilder; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.transport.TransportService; import org.elasticsearch.xpack.ml.MlMetaIndex; @@ -47,27 +50,41 @@ public class TransportDeleteCalendarAction extends HandledTransportAction() { + GetRequest getRequest = new GetRequest(MlMetaIndex.INDEX_NAME, MlMetaIndex.TYPE, Calendar.documentId(calendarId)); + executeAsyncWithOrigin(client, ML_ORIGIN, GetAction.INSTANCE, getRequest, new ActionListener() { @Override - public void onResponse(BulkResponse bulkResponse) { - if (bulkResponse.getItems()[0].status() == RestStatus.NOT_FOUND) { - listener.onFailure(new ResourceNotFoundException("Could not delete calendar with ID [" + calendarId + public void onResponse(GetResponse getResponse) { + if (getResponse.isExists() == false) { + listener.onFailure(new ResourceNotFoundException("Could not delete calendar [" + calendarId + "] because it does not exist")); - } else { - listener.onResponse(new DeleteCalendarAction.Response(true)); + return; } + + // Delete calendar and events + DeleteByQueryRequest dbqRequest = buildDeleteByQuery(calendarId); + executeAsyncWithOrigin(client, ML_ORIGIN, DeleteByQueryAction.INSTANCE, dbqRequest, ActionListener.wrap( + response -> listener.onResponse(new DeleteCalendarAction.Response(true)), + listener::onFailure)); } @Override public void onFailure(Exception e) { - listener.onFailure(ExceptionsHelper.serverError("Could not delete calendar with ID [" + calendarId + "]", e)); + listener.onFailure(ExceptionsHelper.serverError("Could not delete calendar [" + calendarId + "]", e)); } - }); + } + ); + } + + private DeleteByQueryRequest buildDeleteByQuery(String calendarId) { + SearchRequest searchRequest = new SearchRequest(MlMetaIndex.INDEX_NAME); + // The DBQ request constructor wipes the search request source + // so it has to be set after + DeleteByQueryRequest request = new DeleteByQueryRequest(searchRequest); + request.setSlices(5); + request.setRefresh(true); + + QueryBuilder query = QueryBuilders.termsQuery(Calendar.ID.getPreferredName(), calendarId); + searchRequest.source(new SearchSourceBuilder().query(query)); + return request; } } diff --git a/plugin/src/main/java/org/elasticsearch/xpack/ml/action/TransportDeleteCalendarEventAction.java b/plugin/src/main/java/org/elasticsearch/xpack/ml/action/TransportDeleteCalendarEventAction.java new file mode 100644 index 00000000000..3601003c980 --- /dev/null +++ b/plugin/src/main/java/org/elasticsearch/xpack/ml/action/TransportDeleteCalendarEventAction.java @@ -0,0 +1,113 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +package org.elasticsearch.xpack.ml.action; + +import org.elasticsearch.ElasticsearchStatusException; +import org.elasticsearch.ResourceNotFoundException; +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.delete.DeleteAction; +import org.elasticsearch.action.delete.DeleteRequest; +import org.elasticsearch.action.delete.DeleteResponse; +import org.elasticsearch.action.get.GetAction; +import org.elasticsearch.action.get.GetRequest; +import org.elasticsearch.action.get.GetResponse; +import org.elasticsearch.action.support.ActionFilters; +import org.elasticsearch.action.support.HandledTransportAction; +import org.elasticsearch.action.support.WriteRequest; +import org.elasticsearch.client.Client; +import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver; +import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.rest.RestStatus; +import org.elasticsearch.threadpool.ThreadPool; +import org.elasticsearch.transport.TransportService; +import org.elasticsearch.xpack.ml.MlMetaIndex; +import org.elasticsearch.xpack.ml.calendars.Calendar; +import org.elasticsearch.xpack.ml.utils.ExceptionsHelper; + +import java.util.Map; + +import static org.elasticsearch.xpack.ClientHelper.ML_ORIGIN; +import static org.elasticsearch.xpack.ClientHelper.executeAsyncWithOrigin; + +public class TransportDeleteCalendarEventAction extends HandledTransportAction { + + private final Client client; + + @Inject + public TransportDeleteCalendarEventAction(Settings settings, ThreadPool threadPool, + TransportService transportService, ActionFilters actionFilters, + IndexNameExpressionResolver indexNameExpressionResolver, + Client client) { + super(settings, DeleteCalendarEventAction.NAME, threadPool, transportService, actionFilters, + indexNameExpressionResolver, DeleteCalendarEventAction.Request::new); + this.client = client; + } + + @Override + protected void doExecute(DeleteCalendarEventAction.Request request, ActionListener listener) { + final String eventId = request.getEventId(); + + GetRequest getRequest = new GetRequest(MlMetaIndex.INDEX_NAME, MlMetaIndex.TYPE, eventId); + executeAsyncWithOrigin(client, ML_ORIGIN, GetAction.INSTANCE, getRequest, new ActionListener() { + @Override + public void onResponse(GetResponse getResponse) { + if (getResponse.isExists() == false) { + listener.onFailure(new ResourceNotFoundException("Missing event [" + eventId + "]")); + return; + } + + Map source = getResponse.getSourceAsMap(); + String calendarId = (String) source.get(Calendar.ID.getPreferredName()); + if (calendarId == null) { + listener.onFailure(new ElasticsearchStatusException("Event [" + eventId + "] does not have a valid " + + Calendar.ID.getPreferredName(), RestStatus.BAD_REQUEST)); + return; + } + + if (calendarId.equals(request.getCalendarId()) == false) { + listener.onFailure(new ElasticsearchStatusException( + "Event [" + eventId + "] has " + Calendar.ID.getPreferredName() + + " [" + calendarId + "] which does not match the request " + Calendar.ID.getPreferredName() + + " [" + request.getCalendarId() + "]", RestStatus.BAD_REQUEST)); + return; + } + + deleteEvent(eventId, listener); + } + + @Override + public void onFailure(Exception e) { + listener.onFailure(e); + } + }); + } + + private void deleteEvent(String eventId, ActionListener listener) { + DeleteRequest deleteRequest = new DeleteRequest(MlMetaIndex.INDEX_NAME, MlMetaIndex.TYPE, eventId); + deleteRequest.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE); + + executeAsyncWithOrigin(client, ML_ORIGIN, DeleteAction.INSTANCE, deleteRequest, + new ActionListener() { + @Override + public void onResponse(DeleteResponse response) { + + if (response.status() == RestStatus.NOT_FOUND) { + listener.onFailure(new ResourceNotFoundException("Could not delete event [" + eventId + + "] because it does not exist")); + } else { + listener.onResponse(new DeleteCalendarEventAction.Response(true)); + } + } + + @Override + public void onFailure(Exception e) { + listener.onFailure(ExceptionsHelper.serverError("Could not delete event [" + eventId + "]", e)); + } + }); + } +} diff --git a/plugin/src/main/java/org/elasticsearch/xpack/ml/rest/calendar/RestDeleteCalendarEventAction.java b/plugin/src/main/java/org/elasticsearch/xpack/ml/rest/calendar/RestDeleteCalendarEventAction.java new file mode 100644 index 00000000000..291a5c39bd8 --- /dev/null +++ b/plugin/src/main/java/org/elasticsearch/xpack/ml/rest/calendar/RestDeleteCalendarEventAction.java @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +package org.elasticsearch.xpack.ml.rest.calendar; + +import org.elasticsearch.client.node.NodeClient; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.rest.BaseRestHandler; +import org.elasticsearch.rest.RestController; +import org.elasticsearch.rest.RestRequest; +import org.elasticsearch.rest.action.AcknowledgedRestListener; +import org.elasticsearch.xpack.ml.MachineLearning; +import org.elasticsearch.xpack.ml.action.DeleteCalendarEventAction; +import org.elasticsearch.xpack.ml.calendars.Calendar; +import org.elasticsearch.xpack.ml.calendars.ScheduledEvent; + +import java.io.IOException; + +public class RestDeleteCalendarEventAction extends BaseRestHandler { + + public RestDeleteCalendarEventAction(Settings settings, RestController controller) { + super(settings); + controller.registerHandler(RestRequest.Method.DELETE, + MachineLearning.BASE_PATH + "calendars/{" + Calendar.ID.getPreferredName() + "}/events/{" + + ScheduledEvent.EVENT_ID.getPreferredName() + "}", this); + } + + @Override + public String getName() { + return "xpack_ml_delete_calendar_event_action"; + } + + @Override + protected BaseRestHandler.RestChannelConsumer prepareRequest(RestRequest restRequest, NodeClient client) throws IOException { + String eventId = restRequest.param(ScheduledEvent.EVENT_ID.getPreferredName()); + String calendarId = restRequest.param(Calendar.ID.getPreferredName()); + DeleteCalendarEventAction.Request request = new DeleteCalendarEventAction.Request(calendarId, eventId); + return channel -> client.execute(DeleteCalendarEventAction.INSTANCE, request, new AcknowledgedRestListener<>(channel)); + } +} diff --git a/plugin/src/test/java/org/elasticsearch/xpack/ml/action/DeleteCalendarEventActionRequestTests.java b/plugin/src/test/java/org/elasticsearch/xpack/ml/action/DeleteCalendarEventActionRequestTests.java new file mode 100644 index 00000000000..4facff25f8c --- /dev/null +++ b/plugin/src/test/java/org/elasticsearch/xpack/ml/action/DeleteCalendarEventActionRequestTests.java @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +package org.elasticsearch.xpack.ml.action; + +import org.elasticsearch.test.AbstractStreamableTestCase; +import org.elasticsearch.xpack.ml.action.DeleteCalendarEventAction.Request; + +public class DeleteCalendarEventActionRequestTests extends AbstractStreamableTestCase { + + @Override + protected Request createTestInstance() { + return new Request(randomAlphaOfLengthBetween(1, 20), randomAlphaOfLengthBetween(1, 20)); + } + + @Override + protected Request createBlankInstance() { + return new Request(); + } +} \ No newline at end of file diff --git a/plugin/src/test/java/org/elasticsearch/xpack/ml/calendars/ScheduledEventTests.java b/plugin/src/test/java/org/elasticsearch/xpack/ml/calendars/ScheduledEventTests.java index fae46b00ec0..47a3d6cff44 100644 --- a/plugin/src/test/java/org/elasticsearch/xpack/ml/calendars/ScheduledEventTests.java +++ b/plugin/src/test/java/org/elasticsearch/xpack/ml/calendars/ScheduledEventTests.java @@ -32,7 +32,7 @@ public class ScheduledEventTests extends AbstractSerializingTestCase + { "description": "event 21", "start_time": "2017-12-02T00:00:00Z", "end_time": "2017-12-02T05:00:00Z"} + { "description": "event 22", "start_time": "2017-12-25T00:00:00Z", "end_time": "2017-12-26T00:00:00Z"} + + - do: + catch: bad_request + xpack.ml.post_calendar_events: + calendar_id: "events-2" + body: > + { "description": "event 21", "start_time": "2017-12-02T00:00:00Z", "end_time": "2017-12-03T00:00:00Z", "calendar_id": "events"} + +# Event is not in calendar events-2 + - do: + catch: bad_request + xpack.ml.delete_calendar_event: + calendar_id: "events-2" + event_id: $event_1_id + + - do: + xpack.ml.delete_calendar_event: + calendar_id: "events" + event_id: $event_1_id + + - do: + catch: missing + xpack.ml.delete_calendar_event: + calendar_id: "events" + event_id: "missing event" + + +--- +"Test delete calendar deletes events": + + - do: + xpack.ml.put_calendar: + calendar_id: "cal-foo" + + - do: + xpack.ml.post_calendar_events: + calendar_id: "cal-foo" + body: > + { "description": "event 1", "start_time": "2017-12-01T00:00:00Z", "end_time": "2017-12-02T00:00:00Z" } + { "description": "event 2", "start_time": "2017-12-05T00:00:00Z", "end_time": "2017-12-06T00:00:00Z" } + { "description": "event 2", "start_time": "2017-12-05T00:00:00Z", "end_time": "2017-12-06T00:00:00Z" } + + - do: + xpack.ml.put_calendar: + calendar_id: "cal-bar" + + - do: + xpack.ml.post_calendar_events: + calendar_id: "cal-bar" + body: > + { "description": "event 21", "start_time": "2017-12-02T00:00:00Z", "end_time": "2017-12-02T05:00:00Z"} + { "description": "event 22", "start_time": "2017-12-25T00:00:00Z", "end_time": "2017-12-26T00:00:00Z"} + + - do: + xpack.ml.delete_calendar: + calendar_id: "cal-foo" + +# Check the event from calendar 1 is deleted + - do: + count: + index: .ml-meta + body: + query: + constant_score: + filter: + term: + type: scheduled_event + - match: { count: 2 } + + - do: + count: + index: .ml-meta + body: + query: + bool: + must: + - term: + type: scheduled_event + - term: + calendar_id: cal-foo + - match: { count: 0 } + --- "Test get all calendar events": @@ -386,6 +480,7 @@ { "description": "random2", "start_time": "2018-02-20T00:00:00Z", "end_time": "2018-02-26T00:00:00Z" } +# Calendar Id must be _all if a job id is used - do: catch: /action_request_validation_exception/ xpack.ml.get_calendar_events: