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.watcher.ResourceWatcherService;
|
||||
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.GetBucketsAction;
|
||||
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.RestCloseJobAction;
|
||||
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.job.RestDeleteJobAction;
|
||||
import org.elasticsearch.xpack.prelert.rest.job.RestGetJobsAction;
|
||||
|
@ -197,6 +199,7 @@ public class PrelertPlugin extends Plugin implements ActionPlugin {
|
|||
RestOpenJobAction.class,
|
||||
RestGetListAction.class,
|
||||
RestPutListAction.class,
|
||||
RestDeleteListAction.class,
|
||||
RestGetInfluencersAction.class,
|
||||
RestGetRecordsAction.class,
|
||||
RestGetBucketsAction.class,
|
||||
|
@ -227,6 +230,7 @@ public class PrelertPlugin extends Plugin implements ActionPlugin {
|
|||
new ActionHandler<>(UpdateJobSchedulerStatusAction.INSTANCE, UpdateJobSchedulerStatusAction.TransportAction.class),
|
||||
new ActionHandler<>(GetListAction.INSTANCE, GetListAction.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<>(GetInfluencersAction.INSTANCE, GetInfluencersAction.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"]
|
||||
}
|
||||
|
||||
---
|
||||
"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