HLRC: Adding ML Update Filter API (#35522)

* HLRC: Adding ml get filters api

* HLRC: Adding ML Update Filter API
This commit is contained in:
Benjamin Trent 2018-11-14 11:13:11 -06:00 committed by GitHub
parent c8c8ce2374
commit 803eccec11
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 440 additions and 0 deletions

View File

@ -56,6 +56,7 @@ import org.elasticsearch.client.ml.PutJobRequest;
import org.elasticsearch.client.ml.StartDatafeedRequest; import org.elasticsearch.client.ml.StartDatafeedRequest;
import org.elasticsearch.client.ml.StopDatafeedRequest; import org.elasticsearch.client.ml.StopDatafeedRequest;
import org.elasticsearch.client.ml.UpdateDatafeedRequest; import org.elasticsearch.client.ml.UpdateDatafeedRequest;
import org.elasticsearch.client.ml.UpdateFilterRequest;
import org.elasticsearch.client.ml.UpdateJobRequest; import org.elasticsearch.client.ml.UpdateJobRequest;
import org.elasticsearch.client.ml.job.util.PageParams; import org.elasticsearch.client.ml.job.util.PageParams;
import org.elasticsearch.common.Strings; import org.elasticsearch.common.Strings;
@ -510,4 +511,17 @@ final class MLRequestConverters {
} }
return request; return request;
} }
static Request updateFilter(UpdateFilterRequest updateFilterRequest) throws IOException {
String endpoint = new EndpointBuilder()
.addPathPartAsIs("_xpack")
.addPathPartAsIs("ml")
.addPathPartAsIs("filters")
.addPathPart(updateFilterRequest.getFilterId())
.addPathPartAsIs("_update")
.build();
Request request = new Request(HttpPost.METHOD_NAME, endpoint);
request.setEntity(createEntity(updateFilterRequest, REQUEST_BODY_CONTENT_TYPE));
return request;
}
} }

View File

@ -74,6 +74,7 @@ import org.elasticsearch.client.ml.StartDatafeedResponse;
import org.elasticsearch.client.ml.StopDatafeedRequest; import org.elasticsearch.client.ml.StopDatafeedRequest;
import org.elasticsearch.client.ml.StopDatafeedResponse; import org.elasticsearch.client.ml.StopDatafeedResponse;
import org.elasticsearch.client.ml.UpdateDatafeedRequest; import org.elasticsearch.client.ml.UpdateDatafeedRequest;
import org.elasticsearch.client.ml.UpdateFilterRequest;
import org.elasticsearch.client.ml.UpdateJobRequest; import org.elasticsearch.client.ml.UpdateJobRequest;
import org.elasticsearch.client.ml.job.stats.JobStats; import org.elasticsearch.client.ml.job.stats.JobStats;
@ -1288,4 +1289,44 @@ public final class MachineLearningClient {
listener, listener,
Collections.emptySet()); Collections.emptySet());
} }
/**
* Updates a Machine Learning Filter
* <p>
* For additional info
* see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-update-filter.html">
* ML Update Filter documentation</a>
*
* @param request The request
* @param options Additional request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
* @return PutFilterResponse with the updated {@link org.elasticsearch.client.ml.job.config.MlFilter} object
* @throws IOException when there is a serialization issue sending the request or receiving the response
*/
public PutFilterResponse updateFilter(UpdateFilterRequest request, RequestOptions options) throws IOException {
return restHighLevelClient.performRequestAndParseEntity(request,
MLRequestConverters::updateFilter,
options,
PutFilterResponse::fromXContent,
Collections.emptySet());
}
/**
* Updates a Machine Learning Filter asynchronously and notifies listener on completion
* <p>
* For additional info
* see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-update-filter.html">
* ML Update Filter documentation</a>
*
* @param request The request
* @param options Additional request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
* @param listener Listener to be notified upon request completion
*/
public void updateFilterAsync(UpdateFilterRequest request, RequestOptions options, ActionListener<PutFilterResponse> listener) {
restHighLevelClient.performRequestAsyncAndParseEntity(request,
MLRequestConverters::updateFilter,
options,
PutFilterResponse::fromXContent,
listener,
Collections.emptySet());
}
} }

View File

@ -0,0 +1,156 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.client.ml;
import org.elasticsearch.action.ActionRequest;
import org.elasticsearch.action.ActionRequestValidationException;
import org.elasticsearch.client.ml.job.config.MlFilter;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
import org.elasticsearch.common.xcontent.ToXContentObject;
import org.elasticsearch.common.xcontent.XContentBuilder;
import java.io.IOException;
import java.util.Collection;
import java.util.Objects;
import java.util.SortedSet;
import java.util.TreeSet;
/**
* Updates an existing {@link MlFilter} configuration
*/
public class UpdateFilterRequest extends ActionRequest implements ToXContentObject {
public static final ParseField ADD_ITEMS = new ParseField("add_items");
public static final ParseField REMOVE_ITEMS = new ParseField("remove_items");
public static final ConstructingObjectParser<UpdateFilterRequest, Void> PARSER =
new ConstructingObjectParser<>("update_filter_request", (a) -> new UpdateFilterRequest((String)a[0]));
static {
PARSER.declareString(ConstructingObjectParser.constructorArg(), MlFilter.ID);
PARSER.declareStringOrNull(UpdateFilterRequest::setDescription, MlFilter.DESCRIPTION);
PARSER.declareStringArray(UpdateFilterRequest::setAddItems, ADD_ITEMS);
PARSER.declareStringArray(UpdateFilterRequest::setRemoveItems, REMOVE_ITEMS);
}
private String filterId;
private String description;
private SortedSet<String> addItems;
private SortedSet<String> removeItems;
/**
* Construct a new request referencing a non-null, existing filter_id
* @param filterId Id referencing the filter to update
*/
public UpdateFilterRequest(String filterId) {
this.filterId = Objects.requireNonNull(filterId, "[" + MlFilter.ID.getPreferredName() + "] must not be null");
}
public String getFilterId() {
return filterId;
}
public String getDescription() {
return description;
}
/**
* The new description of the filter
* @param description the updated filter description
*/
public void setDescription(String description) {
this.description = description;
}
public SortedSet<String> getAddItems() {
return addItems;
}
/**
* The collection of items to add to the filter
* @param addItems non-null items to add to the filter, defaults to empty array
*/
public void setAddItems(Collection<String> addItems) {
this.addItems = new TreeSet<>(Objects.requireNonNull(addItems,
"[" + ADD_ITEMS.getPreferredName()+"] must not be null"));
}
public SortedSet<String> getRemoveItems() {
return removeItems;
}
/**
* The collection of items to remove from the filter
* @param removeItems non-null items to remove from the filter, defaults to empty array
*/
public void setRemoveItems(Collection<String> removeItems) {
this.removeItems = new TreeSet<>(Objects.requireNonNull(removeItems,
"[" + REMOVE_ITEMS.getPreferredName()+"] must not be null"));
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
builder.field(MlFilter.ID.getPreferredName(), filterId);
if (description != null) {
builder.field(MlFilter.DESCRIPTION.getPreferredName(), description);
}
if (addItems != null) {
builder.field(ADD_ITEMS.getPreferredName(), addItems);
}
if (removeItems != null) {
builder.field(REMOVE_ITEMS.getPreferredName(), removeItems);
}
builder.endObject();
return builder;
}
@Override
public int hashCode() {
return Objects.hash(filterId, description, addItems, removeItems);
}
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
UpdateFilterRequest other = (UpdateFilterRequest) obj;
return Objects.equals(filterId, other.filterId)
&& Objects.equals(description, other.description)
&& Objects.equals(addItems, other.addItems)
&& Objects.equals(removeItems, other.removeItems);
}
@Override
public final String toString() {
return Strings.toString(this);
}
@Override
public ActionRequestValidationException validate() {
return null;
}
}

View File

@ -52,6 +52,7 @@ import org.elasticsearch.client.ml.PutJobRequest;
import org.elasticsearch.client.ml.StartDatafeedRequest; import org.elasticsearch.client.ml.StartDatafeedRequest;
import org.elasticsearch.client.ml.StartDatafeedRequestTests; import org.elasticsearch.client.ml.StartDatafeedRequestTests;
import org.elasticsearch.client.ml.StopDatafeedRequest; import org.elasticsearch.client.ml.StopDatafeedRequest;
import org.elasticsearch.client.ml.UpdateFilterRequest;
import org.elasticsearch.client.ml.UpdateJobRequest; import org.elasticsearch.client.ml.UpdateJobRequest;
import org.elasticsearch.client.ml.calendars.Calendar; import org.elasticsearch.client.ml.calendars.Calendar;
import org.elasticsearch.client.ml.calendars.CalendarTests; import org.elasticsearch.client.ml.calendars.CalendarTests;
@ -74,6 +75,7 @@ import org.elasticsearch.test.ESTestCase;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@ -566,6 +568,23 @@ public class MLRequestConvertersTests extends ESTestCase {
assertThat(request.getParameters().get(PageParams.SIZE.getPreferredName()), equalTo("10")); assertThat(request.getParameters().get(PageParams.SIZE.getPreferredName()), equalTo("10"));
} }
public void testUpdateFilter() throws IOException {
String filterId = randomAlphaOfLength(10);
UpdateFilterRequest updateFilterRequest = new UpdateFilterRequest(filterId);
updateFilterRequest.setDescription(randomAlphaOfLength(10));
updateFilterRequest.setRemoveItems(Arrays.asList("item1", "item2"));
updateFilterRequest.setAddItems(Arrays.asList("item3", "item5"));
Request request = MLRequestConverters.updateFilter(updateFilterRequest);
assertEquals(HttpPost.METHOD_NAME, request.getMethod());
assertThat(request.getEndpoint(), equalTo("/_xpack/ml/filters/"+filterId+"/_update"));
try (XContentParser parser = createParser(JsonXContent.jsonXContent, request.getEntity().getContent())) {
UpdateFilterRequest parsedFilterRequest = UpdateFilterRequest.PARSER.apply(parser, null);
assertThat(parsedFilterRequest, equalTo(updateFilterRequest));
}
}
private static Job createValidJob(String jobId) { private static Job createValidJob(String jobId) {
AnalysisConfig.Builder analysisConfig = AnalysisConfig.builder(Collections.singletonList( AnalysisConfig.Builder analysisConfig = AnalysisConfig.builder(Collections.singletonList(
Detector.builder().setFunction("count").build())); Detector.builder().setFunction("count").build()));

View File

@ -69,6 +69,7 @@ import org.elasticsearch.client.ml.StartDatafeedResponse;
import org.elasticsearch.client.ml.StopDatafeedRequest; import org.elasticsearch.client.ml.StopDatafeedRequest;
import org.elasticsearch.client.ml.StopDatafeedResponse; import org.elasticsearch.client.ml.StopDatafeedResponse;
import org.elasticsearch.client.ml.UpdateDatafeedRequest; import org.elasticsearch.client.ml.UpdateDatafeedRequest;
import org.elasticsearch.client.ml.UpdateFilterRequest;
import org.elasticsearch.client.ml.UpdateJobRequest; import org.elasticsearch.client.ml.UpdateJobRequest;
import org.elasticsearch.client.ml.calendars.Calendar; import org.elasticsearch.client.ml.calendars.Calendar;
import org.elasticsearch.client.ml.calendars.CalendarTests; import org.elasticsearch.client.ml.calendars.CalendarTests;
@ -101,6 +102,7 @@ import java.util.stream.Collectors;
import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.hasItems; import static org.hamcrest.CoreMatchers.hasItems;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
@ -927,6 +929,28 @@ public class MachineLearningIT extends ESRestHighLevelClientTestCase {
} }
} }
public void testUpdateFilter() throws Exception {
String filterId = "update-filter-test";
MlFilter mlFilter = MlFilter.builder(filterId)
.setDescription("old description")
.setItems(Arrays.asList("olditem1", "olditem2"))
.build();
MachineLearningClient machineLearningClient = highLevelClient().machineLearning();
machineLearningClient.putFilter(new PutFilterRequest(mlFilter), RequestOptions.DEFAULT);
UpdateFilterRequest updateFilterRequest = new UpdateFilterRequest(filterId);
updateFilterRequest.setAddItems(Arrays.asList("newItem1", "newItem2"));
updateFilterRequest.setRemoveItems(Collections.singletonList("olditem1"));
updateFilterRequest.setDescription("new description");
MlFilter filter = execute(updateFilterRequest,
machineLearningClient::updateFilter,
machineLearningClient::updateFilterAsync).getResponse();
assertThat(filter.getDescription(), equalTo(updateFilterRequest.getDescription()));
assertThat(filter.getItems(), contains("newItem1", "newItem2", "olditem2"));
}
public static String randomValidJobId() { public static String randomValidJobId() {
CodepointSetGenerator generator = new CodepointSetGenerator("abcdefghijklmnopqrstuvwxyz0123456789".toCharArray()); CodepointSetGenerator generator = new CodepointSetGenerator("abcdefghijklmnopqrstuvwxyz0123456789".toCharArray());
return generator.ofCodePointsLength(random(), 10, 10); return generator.ofCodePointsLength(random(), 10, 10);

View File

@ -87,6 +87,7 @@ import org.elasticsearch.client.ml.StartDatafeedResponse;
import org.elasticsearch.client.ml.StopDatafeedRequest; import org.elasticsearch.client.ml.StopDatafeedRequest;
import org.elasticsearch.client.ml.StopDatafeedResponse; import org.elasticsearch.client.ml.StopDatafeedResponse;
import org.elasticsearch.client.ml.UpdateDatafeedRequest; import org.elasticsearch.client.ml.UpdateDatafeedRequest;
import org.elasticsearch.client.ml.UpdateFilterRequest;
import org.elasticsearch.client.ml.UpdateJobRequest; import org.elasticsearch.client.ml.UpdateJobRequest;
import org.elasticsearch.client.ml.calendars.Calendar; import org.elasticsearch.client.ml.calendars.Calendar;
import org.elasticsearch.client.ml.datafeed.ChunkingConfig; import org.elasticsearch.client.ml.datafeed.ChunkingConfig;
@ -2229,4 +2230,66 @@ public class MlClientDocumentationIT extends ESRestHighLevelClientTestCase {
assertTrue(latch.await(30L, TimeUnit.SECONDS)); assertTrue(latch.await(30L, TimeUnit.SECONDS));
} }
} }
public void testUpdateFilter() throws IOException, InterruptedException {
RestHighLevelClient client = highLevelClient();
String filterId = "update-filter-doc-test";
MlFilter.Builder filterBuilder = MlFilter.builder(filterId).setDescription("test").setItems("*.google.com", "wikipedia.org");
client.machineLearning().putFilter(new PutFilterRequest(filterBuilder.build()), RequestOptions.DEFAULT);
{
// tag::update-filter-request
UpdateFilterRequest request = new UpdateFilterRequest(filterId); // <1>
// end::update-filter-request
// tag::update-filter-description
request.setDescription("my new description"); // <1>
// end::update-filter-description
// tag::update-filter-add-items
request.setAddItems(Arrays.asList("*.bing.com", "*.elastic.co")); // <1>
// end::update-filter-add-items
// tag::update-filter-remove-items
request.setRemoveItems(Arrays.asList("*.google.com")); // <1>
// end::update-filter-remove-items
// tag::update-filter-execute
PutFilterResponse response = client.machineLearning().updateFilter(request, RequestOptions.DEFAULT);
// end::update-filter-execute
// tag::update-filter-response
MlFilter updatedFilter = response.getResponse(); // <1>
// end::update-filter-response
assertEquals(request.getDescription(), updatedFilter.getDescription());
}
{
UpdateFilterRequest request = new UpdateFilterRequest(filterId);
// tag::update-filter-execute-listener
ActionListener<PutFilterResponse> listener = new ActionListener<PutFilterResponse>() {
@Override
public void onResponse(PutFilterResponse putFilterResponse) {
// <1>
}
@Override
public void onFailure(Exception e) {
// <2>
}
};
// end::update-filter-execute-listener
// Replace the empty listener by a blocking listener in test
final CountDownLatch latch = new CountDownLatch(1);
listener = new LatchedActionListener<>(listener, latch);
// tag::update-filter-execute-async
client.machineLearning().updateFilterAsync(request, RequestOptions.DEFAULT, listener); // <1>
// end::update-filter-execute-async
assertTrue(latch.await(30L, TimeUnit.SECONDS));
}
}
} }

View File

@ -0,0 +1,64 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.client.ml;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.test.AbstractXContentTestCase;
import java.util.ArrayList;
import java.util.List;
public class UpdateFilterRequestTests extends AbstractXContentTestCase<UpdateFilterRequest> {
@Override
protected UpdateFilterRequest createTestInstance() {
UpdateFilterRequest request = new UpdateFilterRequest(randomAlphaOfLength(10));
if (randomBoolean()) {
request.setDescription(randomAlphaOfLength(10));
}
if (randomBoolean()) {
int items = randomInt(10);
List<String> strings = new ArrayList<>(items);
for (int i = 0; i < items; i++) {
strings.add(randomAlphaOfLength(10));
}
request.setAddItems(strings);
}
if (randomBoolean()) {
int items = randomInt(10);
List<String> strings = new ArrayList<>(items);
for (int i = 0; i < items; i++) {
strings.add(randomAlphaOfLength(10));
}
request.setRemoveItems(strings);
}
return request;
}
@Override
protected UpdateFilterRequest doParseInstance(XContentParser parser) {
return UpdateFilterRequest.PARSER.apply(parser, null);
}
@Override
protected boolean supportsUnknownFields() {
return false;
}
}

View File

@ -0,0 +1,57 @@
--
:api: update-filter
:request: UpdateFilterRequest
:response: PutFilterResponse
--
[id="{upid}-{api}"]
=== Update Filter API
The Update Filter API can be used to update an existing {ml} filter
in the cluster. The API accepts a +{request}+ object
as a request and returns a +{response}+.
[id="{upid}-{api}-request"]
==== Update Filter Request
A +{request}+ requires the following argument:
["source","java",subs="attributes,callouts,macros"]
--------------------------------------------------
include-tagged::{doc-tests-file}[{api}-request]
--------------------------------------------------
<1> The id of the existing {ml} filter
==== Optional Arguments
The following arguments are optional:
["source","java",subs="attributes,callouts,macros"]
--------------------------------------------------
include-tagged::{doc-tests-file}[{api}-description]
--------------------------------------------------
<1> The updated description of the {ml} filter
["source","java",subs="attributes,callouts,macros"]
--------------------------------------------------
include-tagged::{doc-tests-file}[{api}-add-items]
--------------------------------------------------
<1> The list of items to add to the {ml} filter
["source","java",subs="attributes,callouts,macros"]
--------------------------------------------------
include-tagged::{doc-tests-file}[{api}-remove-items]
--------------------------------------------------
<1> The list of items to remove from the {ml} filter
include::../execution.asciidoc[]
[id="{upid}-{api}-response"]
==== Response
The returned +{response}+ returns the full representation of
the updated {ml} filter if it has been successfully updated.
["source","java",subs="attributes,callouts,macros"]
--------------------------------------------------
include-tagged::{doc-tests-file}[{api}-response]
--------------------------------------------------
<1> The updated `MlFilter`

View File

@ -267,6 +267,7 @@ The Java High Level REST Client supports the following Machine Learning APIs:
* <<{upid}-put-filter>> * <<{upid}-put-filter>>
* <<{upid}-get-model-snapshots>> * <<{upid}-get-model-snapshots>>
* <<{upid}-get-filters>> * <<{upid}-get-filters>>
* <<{upid}-update-filter>>
include::ml/put-job.asciidoc[] include::ml/put-job.asciidoc[]
include::ml/get-job.asciidoc[] include::ml/get-job.asciidoc[]
@ -298,6 +299,7 @@ include::ml/delete-calendar.asciidoc[]
include::ml/put-filter.asciidoc[] include::ml/put-filter.asciidoc[]
include::ml/get-model-snapshots.asciidoc[] include::ml/get-model-snapshots.asciidoc[]
include::ml/get-filters.asciidoc[] include::ml/get-filters.asciidoc[]
include::ml/update-filter.asciidoc[]
== Migration APIs == Migration APIs