Add delete_list endpoint (elastic/elasticsearch#409)
Adds a delete_list endpoint. If a list is currently in use by a job, it is not allowed to be deleted Original commit: elastic/x-pack-elasticsearch@7d9a984b3a
This commit is contained in:
parent
af61a51e22
commit
3c711e6dff
|
@ -27,6 +27,7 @@ import org.elasticsearch.threadpool.FixedExecutorBuilder;
|
||||||
import org.elasticsearch.threadpool.ThreadPool;
|
import org.elasticsearch.threadpool.ThreadPool;
|
||||||
import org.elasticsearch.watcher.ResourceWatcherService;
|
import org.elasticsearch.watcher.ResourceWatcherService;
|
||||||
import org.elasticsearch.xpack.prelert.action.DeleteJobAction;
|
import org.elasticsearch.xpack.prelert.action.DeleteJobAction;
|
||||||
|
import org.elasticsearch.xpack.prelert.action.DeleteListAction;
|
||||||
import org.elasticsearch.xpack.prelert.action.DeleteModelSnapshotAction;
|
import org.elasticsearch.xpack.prelert.action.DeleteModelSnapshotAction;
|
||||||
import org.elasticsearch.xpack.prelert.action.GetBucketsAction;
|
import org.elasticsearch.xpack.prelert.action.GetBucketsAction;
|
||||||
import org.elasticsearch.xpack.prelert.action.GetCategoriesDefinitionAction;
|
import org.elasticsearch.xpack.prelert.action.GetCategoriesDefinitionAction;
|
||||||
|
@ -74,6 +75,7 @@ import org.elasticsearch.xpack.prelert.job.usage.UsageReporter;
|
||||||
import org.elasticsearch.xpack.prelert.rest.job.RestJobDataAction;
|
import org.elasticsearch.xpack.prelert.rest.job.RestJobDataAction;
|
||||||
import org.elasticsearch.xpack.prelert.rest.job.RestCloseJobAction;
|
import org.elasticsearch.xpack.prelert.rest.job.RestCloseJobAction;
|
||||||
import org.elasticsearch.xpack.prelert.rest.job.RestFlushJobAction;
|
import org.elasticsearch.xpack.prelert.rest.job.RestFlushJobAction;
|
||||||
|
import org.elasticsearch.xpack.prelert.rest.list.RestDeleteListAction;
|
||||||
import org.elasticsearch.xpack.prelert.rest.results.RestGetInfluencersAction;
|
import org.elasticsearch.xpack.prelert.rest.results.RestGetInfluencersAction;
|
||||||
import org.elasticsearch.xpack.prelert.rest.job.RestDeleteJobAction;
|
import org.elasticsearch.xpack.prelert.rest.job.RestDeleteJobAction;
|
||||||
import org.elasticsearch.xpack.prelert.rest.job.RestGetJobsAction;
|
import org.elasticsearch.xpack.prelert.rest.job.RestGetJobsAction;
|
||||||
|
@ -197,6 +199,7 @@ public class PrelertPlugin extends Plugin implements ActionPlugin {
|
||||||
RestOpenJobAction.class,
|
RestOpenJobAction.class,
|
||||||
RestGetListAction.class,
|
RestGetListAction.class,
|
||||||
RestPutListAction.class,
|
RestPutListAction.class,
|
||||||
|
RestDeleteListAction.class,
|
||||||
RestGetInfluencersAction.class,
|
RestGetInfluencersAction.class,
|
||||||
RestGetRecordsAction.class,
|
RestGetRecordsAction.class,
|
||||||
RestGetBucketsAction.class,
|
RestGetBucketsAction.class,
|
||||||
|
@ -227,6 +230,7 @@ public class PrelertPlugin extends Plugin implements ActionPlugin {
|
||||||
new ActionHandler<>(UpdateJobSchedulerStatusAction.INSTANCE, UpdateJobSchedulerStatusAction.TransportAction.class),
|
new ActionHandler<>(UpdateJobSchedulerStatusAction.INSTANCE, UpdateJobSchedulerStatusAction.TransportAction.class),
|
||||||
new ActionHandler<>(GetListAction.INSTANCE, GetListAction.TransportAction.class),
|
new ActionHandler<>(GetListAction.INSTANCE, GetListAction.TransportAction.class),
|
||||||
new ActionHandler<>(PutListAction.INSTANCE, PutListAction.TransportAction.class),
|
new ActionHandler<>(PutListAction.INSTANCE, PutListAction.TransportAction.class),
|
||||||
|
new ActionHandler<>(DeleteListAction.INSTANCE, DeleteListAction.TransportAction.class),
|
||||||
new ActionHandler<>(GetBucketsAction.INSTANCE, GetBucketsAction.TransportAction.class),
|
new ActionHandler<>(GetBucketsAction.INSTANCE, GetBucketsAction.TransportAction.class),
|
||||||
new ActionHandler<>(GetInfluencersAction.INSTANCE, GetInfluencersAction.TransportAction.class),
|
new ActionHandler<>(GetInfluencersAction.INSTANCE, GetInfluencersAction.TransportAction.class),
|
||||||
new ActionHandler<>(GetRecordsAction.INSTANCE, GetRecordsAction.TransportAction.class),
|
new ActionHandler<>(GetRecordsAction.INSTANCE, GetRecordsAction.TransportAction.class),
|
||||||
|
|
|
@ -0,0 +1,219 @@
|
||||||
|
/*
|
||||||
|
* 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.prelert.action;
|
||||||
|
|
||||||
|
import org.elasticsearch.ResourceNotFoundException;
|
||||||
|
import org.elasticsearch.action.Action;
|
||||||
|
import org.elasticsearch.action.ActionListener;
|
||||||
|
import org.elasticsearch.action.ActionRequestValidationException;
|
||||||
|
import org.elasticsearch.action.delete.DeleteRequest;
|
||||||
|
import org.elasticsearch.action.delete.DeleteResponse;
|
||||||
|
import org.elasticsearch.action.delete.TransportDeleteAction;
|
||||||
|
import org.elasticsearch.action.support.ActionFilters;
|
||||||
|
import org.elasticsearch.action.support.master.AcknowledgedRequest;
|
||||||
|
import org.elasticsearch.action.support.master.AcknowledgedResponse;
|
||||||
|
import org.elasticsearch.action.support.master.MasterNodeOperationRequestBuilder;
|
||||||
|
import org.elasticsearch.action.support.master.TransportMasterNodeAction;
|
||||||
|
import org.elasticsearch.client.ElasticsearchClient;
|
||||||
|
import org.elasticsearch.cluster.ClusterState;
|
||||||
|
import org.elasticsearch.cluster.block.ClusterBlockException;
|
||||||
|
import org.elasticsearch.cluster.block.ClusterBlockLevel;
|
||||||
|
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
|
||||||
|
import org.elasticsearch.cluster.service.ClusterService;
|
||||||
|
import org.elasticsearch.common.ParseField;
|
||||||
|
import org.elasticsearch.common.inject.Inject;
|
||||||
|
import org.elasticsearch.common.io.stream.StreamInput;
|
||||||
|
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||||
|
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.prelert.job.Detector;
|
||||||
|
import org.elasticsearch.xpack.prelert.job.Job;
|
||||||
|
import org.elasticsearch.xpack.prelert.job.messages.Messages;
|
||||||
|
import org.elasticsearch.xpack.prelert.job.metadata.PrelertMetadata;
|
||||||
|
import org.elasticsearch.xpack.prelert.lists.ListDocument;
|
||||||
|
import org.elasticsearch.xpack.prelert.utils.ExceptionsHelper;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
|
||||||
|
public class DeleteListAction extends Action<DeleteListAction.Request, DeleteListAction.Response, DeleteListAction.RequestBuilder> {
|
||||||
|
|
||||||
|
public static final DeleteListAction INSTANCE = new DeleteListAction();
|
||||||
|
public static final String NAME = "cluster:admin/prelert/list/delete";
|
||||||
|
|
||||||
|
private DeleteListAction() {
|
||||||
|
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<Request> {
|
||||||
|
|
||||||
|
public static final ParseField LIST_ID = new ParseField("list_id");
|
||||||
|
|
||||||
|
private String listId;
|
||||||
|
|
||||||
|
Request() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public Request(String listId) {
|
||||||
|
this.listId = ExceptionsHelper.requireNonNull(listId, LIST_ID.getPreferredName());
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getListId() {
|
||||||
|
return listId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ActionRequestValidationException validate() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void readFrom(StreamInput in) throws IOException {
|
||||||
|
super.readFrom(in);
|
||||||
|
listId = in.readString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeTo(StreamOutput out) throws IOException {
|
||||||
|
super.writeTo(out);
|
||||||
|
out.writeString(listId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(listId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (obj == null || getClass() != obj.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Request other = (Request) obj;
|
||||||
|
return Objects.equals(listId, other.listId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class RequestBuilder extends MasterNodeOperationRequestBuilder<Request, Response, RequestBuilder> {
|
||||||
|
|
||||||
|
public RequestBuilder(ElasticsearchClient client, DeleteListAction 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class TransportAction extends TransportMasterNodeAction<Request, Response> {
|
||||||
|
|
||||||
|
private final TransportDeleteAction transportAction;
|
||||||
|
|
||||||
|
// TODO these need to be moved to a settings object later. See #20
|
||||||
|
private static final String PRELERT_INFO_INDEX = "prelert-int";
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public TransportAction(Settings settings, TransportService transportService, ClusterService clusterService,
|
||||||
|
ThreadPool threadPool, ActionFilters actionFilters,
|
||||||
|
IndexNameExpressionResolver indexNameExpressionResolver,
|
||||||
|
TransportDeleteAction transportAction) {
|
||||||
|
super(settings, DeleteListAction.NAME, transportService, clusterService, threadPool, actionFilters,
|
||||||
|
indexNameExpressionResolver, Request::new);
|
||||||
|
this.transportAction = transportAction;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String executor() {
|
||||||
|
return ThreadPool.Names.SAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Response newResponse() {
|
||||||
|
return new Response();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void masterOperation(Request request, ClusterState state, ActionListener<Response> listener) throws Exception {
|
||||||
|
|
||||||
|
final String listId = request.getListId();
|
||||||
|
PrelertMetadata currentPrelertMetadata = state.metaData().custom(PrelertMetadata.TYPE);
|
||||||
|
Map<String, Job> jobs = currentPrelertMetadata.getJobs();
|
||||||
|
List<String> currentlyUsedBy = new ArrayList<>();
|
||||||
|
for (Job job : jobs.values()) {
|
||||||
|
List<Detector> detectors = job.getAnalysisConfig().getDetectors();
|
||||||
|
for (Detector detector : detectors) {
|
||||||
|
if (detector.extractReferencedLists().contains(listId)) {
|
||||||
|
currentlyUsedBy.add(job.getId());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!currentlyUsedBy.isEmpty()) {
|
||||||
|
throw ExceptionsHelper.conflictStatusException("Cannot delete List, currently used by jobs: "
|
||||||
|
+ currentlyUsedBy);
|
||||||
|
}
|
||||||
|
|
||||||
|
DeleteRequest deleteRequest = new DeleteRequest(PRELERT_INFO_INDEX, ListDocument.TYPE.getPreferredName(), listId);
|
||||||
|
transportAction.execute(deleteRequest, new ActionListener<DeleteResponse>() {
|
||||||
|
@Override
|
||||||
|
public void onResponse(DeleteResponse deleteResponse) {
|
||||||
|
if (deleteResponse.status().equals(RestStatus.NOT_FOUND)) {
|
||||||
|
listener.onFailure(new ResourceNotFoundException("Could not delete list with ID [" + listId
|
||||||
|
+ "] because it does not exist"));
|
||||||
|
} else {
|
||||||
|
listener.onResponse(new Response(true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Exception e) {
|
||||||
|
logger.error("Could not delete list with ID [" + listId + "]", e);
|
||||||
|
listener.onFailure(new IllegalStateException("Could not delete list with ID [" + listId + "]", e));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ClusterBlockException checkBlock(Request request, ClusterState state) {
|
||||||
|
return state.blocks().globalBlockedException(ClusterBlockLevel.METADATA_WRITE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
* 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.prelert.rest.list;
|
||||||
|
|
||||||
|
import org.elasticsearch.client.node.NodeClient;
|
||||||
|
import org.elasticsearch.common.inject.Inject;
|
||||||
|
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.prelert.PrelertPlugin;
|
||||||
|
import org.elasticsearch.xpack.prelert.action.DeleteListAction;
|
||||||
|
import org.elasticsearch.xpack.prelert.action.DeleteListAction.Request;
|
||||||
|
import org.elasticsearch.xpack.prelert.action.PutListAction;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class RestDeleteListAction extends BaseRestHandler {
|
||||||
|
|
||||||
|
private final DeleteListAction.TransportAction transportAction;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public RestDeleteListAction(Settings settings, RestController controller, DeleteListAction.TransportAction transportAction) {
|
||||||
|
super(settings);
|
||||||
|
this.transportAction = transportAction;
|
||||||
|
controller.registerHandler(RestRequest.Method.DELETE,
|
||||||
|
PrelertPlugin.BASE_PATH + "lists/{" + Request.LIST_ID.getPreferredName() + "}", this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected RestChannelConsumer prepareRequest(RestRequest restRequest, NodeClient client) throws IOException {
|
||||||
|
Request request = new Request(restRequest.param(Request.LIST_ID.getPreferredName()));
|
||||||
|
return channel -> transportAction.execute(request, new AcknowledgedRestListener<>(channel));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"xpack.prelert.delete_list": {
|
||||||
|
"methods": [ "DELETE" ],
|
||||||
|
"url": {
|
||||||
|
"path": "/_xpack/prelert/lists/{list_id}",
|
||||||
|
"paths": [ "/_xpack/prelert/lists/{list_id}" ],
|
||||||
|
"parts": {
|
||||||
|
"list_id": {
|
||||||
|
"type" : "string",
|
||||||
|
"required" : true,
|
||||||
|
"description" : "The ID of the list to delete"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"body": null
|
||||||
|
}
|
||||||
|
}
|
|
@ -62,3 +62,65 @@ setup:
|
||||||
{
|
{
|
||||||
"items": ["abc", "xyz"]
|
"items": ["abc", "xyz"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
---
|
||||||
|
"Test delete in-use list":
|
||||||
|
- do:
|
||||||
|
xpack.prelert.put_job:
|
||||||
|
body: >
|
||||||
|
{
|
||||||
|
"job_id":"farequote2",
|
||||||
|
"description":"Analysis of response time by airline",
|
||||||
|
"analysis_config" : {
|
||||||
|
"bucket_span":3600,
|
||||||
|
"detectors" :[{"function":"mean","field_name":"airline",
|
||||||
|
"detector_rules": [
|
||||||
|
{
|
||||||
|
"rule_conditions": [
|
||||||
|
{
|
||||||
|
"condition_type": "categorical",
|
||||||
|
"value_list": "foo"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]}]
|
||||||
|
},
|
||||||
|
"data_description" : {
|
||||||
|
"field_delimiter":",",
|
||||||
|
"time_field":"time",
|
||||||
|
"time_format":"yyyy-MM-dd HH:mm:ssX"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
- do:
|
||||||
|
catch: conflict
|
||||||
|
xpack.prelert.delete_list:
|
||||||
|
list_id: "foo"
|
||||||
|
|
||||||
|
---
|
||||||
|
"Test non-existing list":
|
||||||
|
- do:
|
||||||
|
catch: missing
|
||||||
|
xpack.prelert.delete_list:
|
||||||
|
list_id: "does_not_exist"
|
||||||
|
|
||||||
|
---
|
||||||
|
"Test valid delete list":
|
||||||
|
|
||||||
|
- do:
|
||||||
|
xpack.prelert.get_lists:
|
||||||
|
list_id: "foo"
|
||||||
|
|
||||||
|
- match: { count: 1 }
|
||||||
|
- match:
|
||||||
|
lists.0:
|
||||||
|
id: "foo"
|
||||||
|
items: ["abc", "xyz"]
|
||||||
|
|
||||||
|
- do:
|
||||||
|
xpack.prelert.delete_list:
|
||||||
|
list_id: "foo"
|
||||||
|
|
||||||
|
- do:
|
||||||
|
catch: missing
|
||||||
|
xpack.prelert.get_lists:
|
||||||
|
list_id: "foo"
|
||||||
|
|
Loading…
Reference in New Issue