浏览代码

[HLRC][ML] Add ML get datafeed API to HLRC (#33715)

Relates #29827
Dimitris Athanasiou 7 年之前
父节点
当前提交
db40315afb
共有 19 个文件被更改,包括 736 次插入97 次删除
  1. 19 0
      client/rest-high-level/src/main/java/org/elasticsearch/client/MLRequestConverters.java
  2. 45 2
      client/rest-high-level/src/main/java/org/elasticsearch/client/MachineLearningClient.java
  3. 2 2
      client/rest-high-level/src/main/java/org/elasticsearch/client/ml/CloseJobRequest.java
  4. 1 1
      client/rest-high-level/src/main/java/org/elasticsearch/client/ml/DeleteForecastRequest.java
  5. 144 0
      client/rest-high-level/src/main/java/org/elasticsearch/client/ml/GetDatafeedRequest.java
  6. 89 0
      client/rest-high-level/src/main/java/org/elasticsearch/client/ml/GetDatafeedResponse.java
  7. 5 5
      client/rest-high-level/src/main/java/org/elasticsearch/client/ml/GetJobRequest.java
  8. 4 4
      client/rest-high-level/src/main/java/org/elasticsearch/client/ml/GetJobStatsRequest.java
  9. 3 3
      client/rest-high-level/src/main/java/org/elasticsearch/client/ml/GetOverallBucketsRequest.java
  10. 1 1
      client/rest-high-level/src/main/java/org/elasticsearch/client/ml/PostDataRequest.java
  11. 18 0
      client/rest-high-level/src/test/java/org/elasticsearch/client/MLRequestConvertersTests.java
  12. 81 0
      client/rest-high-level/src/test/java/org/elasticsearch/client/MachineLearningIT.java
  13. 132 77
      client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/MlClientDocumentationIT.java
  14. 70 0
      client/rest-high-level/src/test/java/org/elasticsearch/client/ml/GetDatafeedRequestTests.java
  15. 58 0
      client/rest-high-level/src/test/java/org/elasticsearch/client/ml/GetDatafeedResponseTests.java
  16. 1 1
      client/rest-high-level/src/test/java/org/elasticsearch/client/ml/GetJobRequestTests.java
  17. 5 1
      client/rest-high-level/src/test/java/org/elasticsearch/client/ml/datafeed/DatafeedConfigTests.java
  18. 56 0
      docs/java-rest/high-level/ml/get-datafeed.asciidoc
  19. 2 0
      docs/java-rest/high-level/supported-apis.asciidoc

+ 19 - 0
client/rest-high-level/src/main/java/org/elasticsearch/client/MLRequestConverters.java

@@ -35,6 +35,7 @@ import org.elasticsearch.client.ml.FlushJobRequest;
 import org.elasticsearch.client.ml.ForecastJobRequest;
 import org.elasticsearch.client.ml.GetBucketsRequest;
 import org.elasticsearch.client.ml.GetCategoriesRequest;
+import org.elasticsearch.client.ml.GetDatafeedRequest;
 import org.elasticsearch.client.ml.GetInfluencersRequest;
 import org.elasticsearch.client.ml.GetJobRequest;
 import org.elasticsearch.client.ml.GetJobStatsRequest;
@@ -197,6 +198,24 @@ final class MLRequestConverters {
         return request;
     }
 
+    static Request getDatafeed(GetDatafeedRequest getDatafeedRequest) {
+        String endpoint = new EndpointBuilder()
+                .addPathPartAsIs("_xpack")
+                .addPathPartAsIs("ml")
+                .addPathPartAsIs("datafeeds")
+                .addPathPart(Strings.collectionToCommaDelimitedString(getDatafeedRequest.getDatafeedIds()))
+                .build();
+        Request request = new Request(HttpGet.METHOD_NAME, endpoint);
+
+        RequestConverters.Params params = new RequestConverters.Params(request);
+        if (getDatafeedRequest.isAllowNoDatafeeds() != null) {
+            params.putParam(GetDatafeedRequest.ALLOW_NO_DATAFEEDS.getPreferredName(),
+                    Boolean.toString(getDatafeedRequest.isAllowNoDatafeeds()));
+        }
+
+        return request;
+    }
+
     static Request deleteDatafeed(DeleteDatafeedRequest deleteDatafeedRequest) {
         String endpoint = new EndpointBuilder()
                 .addPathPartAsIs("_xpack")

+ 45 - 2
client/rest-high-level/src/main/java/org/elasticsearch/client/MachineLearningClient.java

@@ -33,6 +33,8 @@ import org.elasticsearch.client.ml.GetBucketsRequest;
 import org.elasticsearch.client.ml.GetBucketsResponse;
 import org.elasticsearch.client.ml.GetCategoriesRequest;
 import org.elasticsearch.client.ml.GetCategoriesResponse;
+import org.elasticsearch.client.ml.GetDatafeedRequest;
+import org.elasticsearch.client.ml.GetDatafeedResponse;
 import org.elasticsearch.client.ml.GetInfluencersRequest;
 import org.elasticsearch.client.ml.GetInfluencersResponse;
 import org.elasticsearch.client.ml.GetJobRequest;
@@ -466,8 +468,8 @@ public final class MachineLearningClient {
      * For additional info
      * see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-put-datafeed.html">ML PUT datafeed documentation</a>
      *
-     * @param request  The request containing the {@link org.elasticsearch.client.ml.datafeed.DatafeedConfig} settings
-     * @param options  Additional request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
+     * @param request The request containing the {@link org.elasticsearch.client.ml.datafeed.DatafeedConfig} settings
+     * @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 putDatafeedAsync(PutDatafeedRequest request, RequestOptions options, ActionListener<PutDatafeedResponse> listener) {
@@ -479,6 +481,47 @@ public final class MachineLearningClient {
                 Collections.emptySet());
     }
 
+    /**
+     * Gets one or more Machine Learning datafeed configuration info.
+     *
+     * <p>
+     * For additional info
+     * see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-get-datafeed.html">ML GET datafeed documentation</a>
+     *
+     * @param request {@link GetDatafeedRequest} Request containing a list of datafeedId(s) and additional options
+     * @param options Additional request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
+     * @return {@link GetDatafeedResponse} response object containing
+     * the {@link org.elasticsearch.client.ml.datafeed.DatafeedConfig} objects and the number of jobs found
+     * @throws IOException when there is a serialization issue sending the request or receiving the response
+     */
+    public GetDatafeedResponse getDatafeed(GetDatafeedRequest request, RequestOptions options) throws IOException {
+        return restHighLevelClient.performRequestAndParseEntity(request,
+                MLRequestConverters::getDatafeed,
+                options,
+                GetDatafeedResponse::fromXContent,
+                Collections.emptySet());
+    }
+
+    /**
+     * Gets one or more Machine Learning datafeed configuration info, asynchronously.
+     *
+     * <p>
+     * For additional info
+     * see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/ml-get-datafeed.html">ML GET datafeed documentation</a>
+     *
+     * @param request {@link GetDatafeedRequest} Request containing a list of datafeedId(s) and additional options
+     * @param options Additional request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
+     * @param listener Listener to be notified with {@link GetDatafeedResponse} upon request completion
+     */
+    public void getDatafeedAsync(GetDatafeedRequest request, RequestOptions options, ActionListener<GetDatafeedResponse> listener) {
+        restHighLevelClient.performRequestAsyncAndParseEntity(request,
+                MLRequestConverters::getDatafeed,
+                options,
+                GetDatafeedResponse::fromXContent,
+                listener,
+                Collections.emptySet());
+    }
+
     /**
      * Deletes the given Machine Learning Datafeed
      * <p>

+ 2 - 2
client/rest-high-level/src/main/java/org/elasticsearch/client/ml/CloseJobRequest.java

@@ -136,9 +136,9 @@ public class CloseJobRequest extends ActionRequest implements ToXContentObject {
     /**
      * Whether to ignore if a wildcard expression matches no jobs.
      *
-     * This includes `_all` string or when no jobs have been specified
+     * This includes {@code _all} string or when no jobs have been specified
      *
-     * @param allowNoJobs When {@code true} ignore if wildcard or `_all` matches no jobs. Defaults to {@code true}
+     * @param allowNoJobs When {@code true} ignore if wildcard or {@code _all} matches no jobs. Defaults to {@code true}
      */
     public void setAllowNoJobs(boolean allowNoJobs) {
         this.allowNoJobs = allowNoJobs;

+ 1 - 1
client/rest-high-level/src/main/java/org/elasticsearch/client/ml/DeleteForecastRequest.java

@@ -109,7 +109,7 @@ public class DeleteForecastRequest extends ActionRequest implements ToXContentOb
     }
 
     /**
-     * Sets the `allow_no_forecasts` field.
+     * Sets the value of "allow_no_forecasts".
      *
      * @param allowNoForecasts when {@code true} no error is thrown when {@link DeleteForecastRequest#ALL} does not find any forecasts
      */

+ 144 - 0
client/rest-high-level/src/main/java/org/elasticsearch/client/ml/GetDatafeedRequest.java

@@ -0,0 +1,144 @@
+/*
+ * 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.datafeed.DatafeedConfig;
+import org.elasticsearch.common.ParseField;
+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.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Request object to get {@link DatafeedConfig} objects with the matching {@code datafeedId}s.
+ *
+ * {@code _all} explicitly gets all the datafeeds in the cluster
+ * An empty request (no {@code datafeedId}s) implicitly gets all the datafeeds in the cluster
+ */
+public class GetDatafeedRequest extends ActionRequest implements ToXContentObject {
+
+    public static final ParseField DATAFEED_IDS = new ParseField("datafeed_ids");
+    public static final ParseField ALLOW_NO_DATAFEEDS = new ParseField("allow_no_datafeeds");
+
+    private static final String ALL_DATAFEEDS = "_all";
+    private final List<String> datafeedIds;
+    private Boolean allowNoDatafeeds;
+
+    @SuppressWarnings("unchecked")
+    public static final ConstructingObjectParser<GetDatafeedRequest, Void> PARSER = new ConstructingObjectParser<>(
+        "get_datafeed_request",
+        true, a -> new GetDatafeedRequest(a[0] == null ? new ArrayList<>() : (List<String>) a[0]));
+
+    static {
+        PARSER.declareStringArray(ConstructingObjectParser.optionalConstructorArg(), DATAFEED_IDS);
+        PARSER.declareBoolean(GetDatafeedRequest::setAllowNoDatafeeds, ALLOW_NO_DATAFEEDS);
+    }
+
+    /**
+     * Helper method to create a query that will get ALL datafeeds
+     * @return new {@link GetDatafeedRequest} object searching for the datafeedId "_all"
+     */
+    public static GetDatafeedRequest getAllDatafeedsRequest() {
+        return new GetDatafeedRequest(ALL_DATAFEEDS);
+    }
+
+    /**
+     * Get the specified {@link DatafeedConfig} configurations via their unique datafeedIds
+     * @param datafeedIds must not contain any null values
+     */
+    public GetDatafeedRequest(String... datafeedIds) {
+        this(Arrays.asList(datafeedIds));
+    }
+
+    GetDatafeedRequest(List<String> datafeedIds) {
+        if (datafeedIds.stream().anyMatch(Objects::isNull)) {
+            throw new NullPointerException("datafeedIds must not contain null values");
+        }
+        this.datafeedIds = new ArrayList<>(datafeedIds);
+    }
+
+    /**
+     * All the datafeedIds for which to get configuration information
+     */
+    public List<String> getDatafeedIds() {
+        return datafeedIds;
+    }
+
+    /**
+     * Whether to ignore if a wildcard expression matches no datafeeds.
+     *
+     * @param allowNoDatafeeds If this is {@code false}, then an error is returned when a wildcard (or {@code _all})
+     *                        does not match any datafeeds
+     */
+    public void setAllowNoDatafeeds(boolean allowNoDatafeeds) {
+        this.allowNoDatafeeds = allowNoDatafeeds;
+    }
+
+    public Boolean isAllowNoDatafeeds() {
+        return allowNoDatafeeds;
+    }
+
+    @Override
+    public ActionRequestValidationException validate() {
+        return null;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(datafeedIds, allowNoDatafeeds);
+    }
+
+    @Override
+    public boolean equals(Object other) {
+        if (this == other) {
+            return true;
+        }
+
+        if (other == null || other.getClass() != getClass()) {
+            return false;
+        }
+
+        GetDatafeedRequest that = (GetDatafeedRequest) other;
+        return Objects.equals(datafeedIds, that.datafeedIds) &&
+            Objects.equals(allowNoDatafeeds, that.allowNoDatafeeds);
+    }
+
+    @Override
+    public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
+        builder.startObject();
+
+        if (datafeedIds.isEmpty() == false) {
+            builder.field(DATAFEED_IDS.getPreferredName(), datafeedIds);
+        }
+
+        if (allowNoDatafeeds != null) {
+            builder.field(ALLOW_NO_DATAFEEDS.getPreferredName(), allowNoDatafeeds);
+        }
+
+        builder.endObject();
+        return builder;
+    }
+}

+ 89 - 0
client/rest-high-level/src/main/java/org/elasticsearch/client/ml/GetDatafeedResponse.java

@@ -0,0 +1,89 @@
+/*
+ * 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.client.ml.datafeed.DatafeedConfig;
+import org.elasticsearch.common.ParseField;
+import org.elasticsearch.common.Strings;
+import org.elasticsearch.common.xcontent.ConstructingObjectParser;
+import org.elasticsearch.common.xcontent.XContentParser;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg;
+
+/**
+ * Contains a {@link List} of the found {@link DatafeedConfig} objects and the total count found
+ */
+public class GetDatafeedResponse extends AbstractResultResponse<DatafeedConfig> {
+
+    public static final ParseField RESULTS_FIELD = new ParseField("datafeeds");
+
+    @SuppressWarnings("unchecked")
+    public static final ConstructingObjectParser<GetDatafeedResponse, Void> PARSER =
+        new ConstructingObjectParser<>("get_datafeed_response", true,
+            a -> new GetDatafeedResponse((List<DatafeedConfig.Builder>) a[0], (long) a[1]));
+
+    static {
+        PARSER.declareObjectArray(constructorArg(), DatafeedConfig.PARSER, RESULTS_FIELD);
+        PARSER.declareLong(constructorArg(), AbstractResultResponse.COUNT);
+    }
+
+    GetDatafeedResponse(List<DatafeedConfig.Builder> datafeedBuilders, long count) {
+        super(RESULTS_FIELD, datafeedBuilders.stream().map(DatafeedConfig.Builder::build).collect(Collectors.toList()), count);
+    }
+
+    /**
+     * The collection of {@link DatafeedConfig} objects found in the query
+     */
+    public List<DatafeedConfig> datafeeds() {
+        return results;
+    }
+
+    public static GetDatafeedResponse fromXContent(XContentParser parser) throws IOException {
+        return PARSER.parse(parser, null);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(results, count);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+
+        if (obj == null || getClass() != obj.getClass()) {
+            return false;
+        }
+
+        GetDatafeedResponse other = (GetDatafeedResponse) obj;
+        return Objects.equals(results, other.results) && count == other.count;
+    }
+
+    @Override
+    public final String toString() {
+        return Strings.toString(this);
+    }
+}

+ 5 - 5
client/rest-high-level/src/main/java/org/elasticsearch/client/ml/GetJobRequest.java

@@ -33,11 +33,11 @@ import java.util.List;
 import java.util.Objects;
 
 /**
- * Request object to get {@link Job} objects with the matching `jobId`s or
- * `groupName`s.
+ * Request object to get {@link Job} objects with the matching {@code jobId}s or
+ * {@code groupName}s.
  *
- * `_all` explicitly gets all the jobs in the cluster
- * An empty request (no `jobId`s) implicitly gets all the jobs in the cluster
+ * {@code _all} explicitly gets all the jobs in the cluster
+ * An empty request (no {@code jobId}s) implicitly gets all the jobs in the cluster
  */
 public class GetJobRequest extends ActionRequest implements ToXContentObject {
 
@@ -91,7 +91,7 @@ public class GetJobRequest extends ActionRequest implements ToXContentObject {
     /**
      * Whether to ignore if a wildcard expression matches no jobs.
      *
-     * @param allowNoJobs If this is {@code false}, then an error is returned when a wildcard (or `_all`) does not match any jobs
+     * @param allowNoJobs If this is {@code false}, then an error is returned when a wildcard (or {@code _all}) does not match any jobs
      */
     public void setAllowNoJobs(boolean allowNoJobs) {
         this.allowNoJobs = allowNoJobs;

+ 4 - 4
client/rest-high-level/src/main/java/org/elasticsearch/client/ml/GetJobStatsRequest.java

@@ -38,8 +38,8 @@ import java.util.Objects;
 /**
  * Request object to get {@link org.elasticsearch.client.ml.job.stats.JobStats} by their respective jobIds
  *
- * `_all` explicitly gets all the jobs' statistics in the cluster
- * An empty request (no `jobId`s) implicitly gets all the jobs' statistics in the cluster
+ * {@code _all} explicitly gets all the jobs' statistics in the cluster
+ * An empty request (no {@code jobId}s) implicitly gets all the jobs' statistics in the cluster
  */
 public class GetJobStatsRequest extends ActionRequest implements ToXContentObject {
 
@@ -100,9 +100,9 @@ public class GetJobStatsRequest extends ActionRequest implements ToXContentObjec
     /**
      * Whether to ignore if a wildcard expression matches no jobs.
      *
-     * This includes `_all` string or when no jobs have been specified
+     * This includes {@code _all} string or when no jobs have been specified
      *
-     * @param allowNoJobs When {@code true} ignore if wildcard or `_all` matches no jobs. Defaults to {@code true}
+     * @param allowNoJobs When {@code true} ignore if wildcard or {@code _all} matches no jobs. Defaults to {@code true}
      */
     public void setAllowNoJobs(boolean allowNoJobs) {
         this.allowNoJobs = allowNoJobs;

+ 3 - 3
client/rest-high-level/src/main/java/org/elasticsearch/client/ml/GetOverallBucketsRequest.java

@@ -109,7 +109,7 @@ public class GetOverallBucketsRequest extends ActionRequest implements ToXConten
     }
 
     /**
-     * Sets the value of `top_n`.
+     * Sets the value of "top_n".
      * @param topN The number of top job bucket scores to be used in the overall_score calculation. Defaults to 1.
      */
     public void setTopN(Integer topN) {
@@ -121,7 +121,7 @@ public class GetOverallBucketsRequest extends ActionRequest implements ToXConten
     }
 
     /**
-     * Sets the value of `bucket_span`.
+     * Sets the value of "bucket_span".
      * @param bucketSpan The span of the overall buckets. Must be greater or equal to the largest job’s bucket_span.
      *                   Defaults to the largest job’s bucket_span.
      */
@@ -197,7 +197,7 @@ public class GetOverallBucketsRequest extends ActionRequest implements ToXConten
     /**
      * Whether to ignore if a wildcard expression matches no jobs.
      *
-     * If this is `false`, then an error is returned when a wildcard (or `_all`) does not match any jobs
+     * If this is {@code false}, then an error is returned when a wildcard (or {@code _all}) does not match any jobs
      */
     public Boolean isAllowNoJobs() {
         return allowNoJobs;

+ 1 - 1
client/rest-high-level/src/main/java/org/elasticsearch/client/ml/PostDataRequest.java

@@ -38,7 +38,7 @@ import java.util.Map;
 import java.util.Objects;
 
 /**
- * POJO for posting data to a Machine Learning job
+ * Request to post data to a Machine Learning job
  */
 public class PostDataRequest extends ActionRequest implements ToXContentObject {
 

+ 18 - 0
client/rest-high-level/src/test/java/org/elasticsearch/client/MLRequestConvertersTests.java

@@ -31,6 +31,7 @@ import org.elasticsearch.client.ml.FlushJobRequest;
 import org.elasticsearch.client.ml.ForecastJobRequest;
 import org.elasticsearch.client.ml.GetBucketsRequest;
 import org.elasticsearch.client.ml.GetCategoriesRequest;
+import org.elasticsearch.client.ml.GetDatafeedRequest;
 import org.elasticsearch.client.ml.GetInfluencersRequest;
 import org.elasticsearch.client.ml.GetJobRequest;
 import org.elasticsearch.client.ml.GetJobStatsRequest;
@@ -227,6 +228,23 @@ public class MLRequestConvertersTests extends ESTestCase {
         }
     }
 
+    public void testGetDatafeed() {
+        GetDatafeedRequest getDatafeedRequest = new GetDatafeedRequest();
+
+        Request request = MLRequestConverters.getDatafeed(getDatafeedRequest);
+
+        assertEquals(HttpGet.METHOD_NAME, request.getMethod());
+        assertEquals("/_xpack/ml/datafeeds", request.getEndpoint());
+        assertFalse(request.getParameters().containsKey("allow_no_datafeeds"));
+
+        getDatafeedRequest = new GetDatafeedRequest("feed-1", "feed-*");
+        getDatafeedRequest.setAllowNoDatafeeds(true);
+        request = MLRequestConverters.getDatafeed(getDatafeedRequest);
+
+        assertEquals("/_xpack/ml/datafeeds/feed-1,feed-*", request.getEndpoint());
+        assertEquals(Boolean.toString(true), request.getParameters().get("allow_no_datafeeds"));
+    }
+
     public void testDeleteDatafeed() {
         String datafeedId = randomAlphaOfLength(10);
         DeleteDatafeedRequest deleteDatafeedRequest = new DeleteDatafeedRequest(datafeedId);

+ 81 - 0
client/rest-high-level/src/test/java/org/elasticsearch/client/MachineLearningIT.java

@@ -32,6 +32,8 @@ import org.elasticsearch.client.ml.FlushJobRequest;
 import org.elasticsearch.client.ml.FlushJobResponse;
 import org.elasticsearch.client.ml.ForecastJobRequest;
 import org.elasticsearch.client.ml.ForecastJobResponse;
+import org.elasticsearch.client.ml.GetDatafeedRequest;
+import org.elasticsearch.client.ml.GetDatafeedResponse;
 import org.elasticsearch.client.ml.GetJobRequest;
 import org.elasticsearch.client.ml.GetJobResponse;
 import org.elasticsearch.client.ml.GetJobStatsRequest;
@@ -58,6 +60,7 @@ import org.elasticsearch.client.ml.job.config.JobState;
 import org.elasticsearch.client.ml.job.config.JobUpdate;
 import org.elasticsearch.client.ml.job.stats.JobStats;
 import org.elasticsearch.common.unit.TimeValue;
+import org.elasticsearch.rest.RestStatus;
 import org.junit.After;
 
 import java.io.IOException;
@@ -316,6 +319,84 @@ public class MachineLearningIT extends ESRestHighLevelClientTestCase {
         assertThat(createdDatafeed.getIndices(), equalTo(datafeedConfig.getIndices()));
     }
 
+    public void testGetDatafeed() throws Exception {
+        String jobId1 = "test-get-datafeed-job-1";
+        String jobId2 = "test-get-datafeed-job-2";
+        Job job1 = buildJob(jobId1);
+        Job job2 = buildJob(jobId2);
+        MachineLearningClient machineLearningClient = highLevelClient().machineLearning();
+        machineLearningClient.putJob(new PutJobRequest(job1), RequestOptions.DEFAULT);
+        machineLearningClient.putJob(new PutJobRequest(job2), RequestOptions.DEFAULT);
+
+        String datafeedId1 = jobId1 + "-feed";
+        String datafeedId2 = jobId2 + "-feed";
+        DatafeedConfig datafeed1 = DatafeedConfig.builder(datafeedId1, jobId1).setIndices("data_1").build();
+        DatafeedConfig datafeed2 = DatafeedConfig.builder(datafeedId2, jobId2).setIndices("data_2").build();
+        machineLearningClient.putDatafeed(new PutDatafeedRequest(datafeed1), RequestOptions.DEFAULT);
+        machineLearningClient.putDatafeed(new PutDatafeedRequest(datafeed2), RequestOptions.DEFAULT);
+
+        // Test getting specific datafeeds
+        {
+            GetDatafeedRequest request = new GetDatafeedRequest(datafeedId1, datafeedId2);
+            GetDatafeedResponse response = execute(request, machineLearningClient::getDatafeed, machineLearningClient::getDatafeedAsync);
+
+            assertEquals(2, response.count());
+            assertThat(response.datafeeds(), hasSize(2));
+            assertThat(response.datafeeds().stream().map(DatafeedConfig::getId).collect(Collectors.toList()),
+                    containsInAnyOrder(datafeedId1, datafeedId2));
+        }
+
+        // Test getting a single one
+        {
+            GetDatafeedRequest request = new GetDatafeedRequest(datafeedId1);
+            GetDatafeedResponse response = execute(request, machineLearningClient::getDatafeed, machineLearningClient::getDatafeedAsync);
+
+            assertTrue(response.count() == 1L);
+            assertThat(response.datafeeds().get(0).getId(), equalTo(datafeedId1));
+        }
+
+        // Test getting all datafeeds explicitly
+        {
+            GetDatafeedRequest request = GetDatafeedRequest.getAllDatafeedsRequest();
+            GetDatafeedResponse response = execute(request, machineLearningClient::getDatafeed, machineLearningClient::getDatafeedAsync);
+
+            assertTrue(response.count() == 2L);
+            assertTrue(response.datafeeds().size() == 2L);
+            assertThat(response.datafeeds().stream().map(DatafeedConfig::getId).collect(Collectors.toList()),
+                    hasItems(datafeedId1, datafeedId2));
+        }
+
+        // Test getting all datafeeds implicitly
+        {
+            GetDatafeedResponse response = execute(new GetDatafeedRequest(), machineLearningClient::getDatafeed,
+                    machineLearningClient::getDatafeedAsync);
+
+            assertTrue(response.count() >= 2L);
+            assertTrue(response.datafeeds().size() >= 2L);
+            assertThat(response.datafeeds().stream().map(DatafeedConfig::getId).collect(Collectors.toList()),
+                    hasItems(datafeedId1, datafeedId2));
+        }
+
+        // Test get missing pattern with allow_no_datafeeds set to true
+        {
+            GetDatafeedRequest request = new GetDatafeedRequest("missing-*");
+
+            GetDatafeedResponse response = execute(request, machineLearningClient::getDatafeed, machineLearningClient::getDatafeedAsync);
+
+            assertThat(response.count(), equalTo(0L));
+        }
+
+        // Test get missing pattern with allow_no_datafeeds set to false
+        {
+            GetDatafeedRequest request = new GetDatafeedRequest("missing-*");
+            request.setAllowNoDatafeeds(false);
+
+            ElasticsearchStatusException e = expectThrows(ElasticsearchStatusException.class,
+                    () -> execute(request, machineLearningClient::getDatafeed, machineLearningClient::getDatafeedAsync));
+            assertThat(e.status(), equalTo(RestStatus.NOT_FOUND));
+        }
+    }
+
     public void testDeleteDatafeed() throws Exception {
         String jobId = randomValidJobId();
         Job job = buildJob(jobId);

+ 132 - 77
client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/MlClientDocumentationIT.java

@@ -45,6 +45,8 @@ import org.elasticsearch.client.ml.GetBucketsRequest;
 import org.elasticsearch.client.ml.GetBucketsResponse;
 import org.elasticsearch.client.ml.GetCategoriesRequest;
 import org.elasticsearch.client.ml.GetCategoriesResponse;
+import org.elasticsearch.client.ml.GetDatafeedRequest;
+import org.elasticsearch.client.ml.GetDatafeedResponse;
 import org.elasticsearch.client.ml.GetInfluencersRequest;
 import org.elasticsearch.client.ml.GetInfluencersResponse;
 import org.elasticsearch.client.ml.GetJobRequest;
@@ -208,14 +210,14 @@ public class MlClientDocumentationIT extends ESRestHighLevelClientTestCase {
 
         {
             //tag::x-pack-ml-get-job-request
-            GetJobRequest request = new GetJobRequest("get-machine-learning-job1", "get-machine-learning-job*"); //<1>
-            request.setAllowNoJobs(true); //<2>
+            GetJobRequest request = new GetJobRequest("get-machine-learning-job1", "get-machine-learning-job*"); // <1>
+            request.setAllowNoJobs(true); // <2>
             //end::x-pack-ml-get-job-request
 
             //tag::x-pack-ml-get-job-execute
             GetJobResponse response = client.machineLearning().getJob(request, RequestOptions.DEFAULT);
-            long numberOfJobs = response.count(); //<1>
-            List<Job> jobs = response.jobs(); //<2>
+            long numberOfJobs = response.count(); // <1>
+            List<Job> jobs = response.jobs(); // <2>
             //end::x-pack-ml-get-job-execute
 
             assertEquals(2, response.count());
@@ -266,12 +268,12 @@ public class MlClientDocumentationIT extends ESRestHighLevelClientTestCase {
         {
             //tag::x-pack-delete-ml-job-request
             DeleteJobRequest deleteJobRequest = new DeleteJobRequest("my-first-machine-learning-job");
-            deleteJobRequest.setForce(false); //<1>
+            deleteJobRequest.setForce(false); // <1>
             AcknowledgedResponse deleteJobResponse = client.machineLearning().deleteJob(deleteJobRequest, RequestOptions.DEFAULT);
             //end::x-pack-delete-ml-job-request
 
             //tag::x-pack-delete-ml-job-response
-            boolean isAcknowledged = deleteJobResponse.isAcknowledged(); //<1>
+            boolean isAcknowledged = deleteJobResponse.isAcknowledged(); // <1>
             //end::x-pack-delete-ml-job-response
         }
         {
@@ -313,13 +315,13 @@ public class MlClientDocumentationIT extends ESRestHighLevelClientTestCase {
 
         {
             //tag::x-pack-ml-open-job-request
-            OpenJobRequest openJobRequest = new OpenJobRequest("opening-my-first-machine-learning-job"); //<1>
-            openJobRequest.setTimeout(TimeValue.timeValueMinutes(10)); //<2>
+            OpenJobRequest openJobRequest = new OpenJobRequest("opening-my-first-machine-learning-job"); // <1>
+            openJobRequest.setTimeout(TimeValue.timeValueMinutes(10)); // <2>
             //end::x-pack-ml-open-job-request
 
             //tag::x-pack-ml-open-job-execute
             OpenJobResponse openJobResponse = client.machineLearning().openJob(openJobRequest, RequestOptions.DEFAULT);
-            boolean isOpened = openJobResponse.isOpened(); //<1>
+            boolean isOpened = openJobResponse.isOpened(); // <1>
             //end::x-pack-ml-open-job-execute
 
         }
@@ -328,7 +330,7 @@ public class MlClientDocumentationIT extends ESRestHighLevelClientTestCase {
             ActionListener<OpenJobResponse> listener = new ActionListener<OpenJobResponse>() {
                 @Override
                 public void onResponse(OpenJobResponse openJobResponse) {
-                    //<1>
+                    // <1>
                 }
 
                 @Override
@@ -343,7 +345,7 @@ public class MlClientDocumentationIT extends ESRestHighLevelClientTestCase {
             listener = new LatchedActionListener<>(listener, latch);
 
             // tag::x-pack-ml-open-job-execute-async
-            client.machineLearning().openJobAsync(openJobRequest, RequestOptions.DEFAULT, listener); //<1>
+            client.machineLearning().openJobAsync(openJobRequest, RequestOptions.DEFAULT, listener); // <1>
             // end::x-pack-ml-open-job-execute-async
 
             assertTrue(latch.await(30L, TimeUnit.SECONDS));
@@ -359,15 +361,15 @@ public class MlClientDocumentationIT extends ESRestHighLevelClientTestCase {
             client.machineLearning().openJob(new OpenJobRequest(job.getId()), RequestOptions.DEFAULT);
 
             //tag::x-pack-ml-close-job-request
-            CloseJobRequest closeJobRequest = new CloseJobRequest("closing-my-first-machine-learning-job", "otherjobs*"); //<1>
-            closeJobRequest.setForce(false); //<2>
-            closeJobRequest.setAllowNoJobs(true); //<3>
-            closeJobRequest.setTimeout(TimeValue.timeValueMinutes(10)); //<4>
+            CloseJobRequest closeJobRequest = new CloseJobRequest("closing-my-first-machine-learning-job", "otherjobs*"); // <1>
+            closeJobRequest.setForce(false); // <2>
+            closeJobRequest.setAllowNoJobs(true); // <3>
+            closeJobRequest.setTimeout(TimeValue.timeValueMinutes(10)); // <4>
             //end::x-pack-ml-close-job-request
 
             //tag::x-pack-ml-close-job-execute
             CloseJobResponse closeJobResponse = client.machineLearning().closeJob(closeJobRequest, RequestOptions.DEFAULT);
-            boolean isClosed = closeJobResponse.isClosed(); //<1>
+            boolean isClosed = closeJobResponse.isClosed(); // <1>
             //end::x-pack-ml-close-job-execute
 
         }
@@ -380,7 +382,7 @@ public class MlClientDocumentationIT extends ESRestHighLevelClientTestCase {
             ActionListener<CloseJobResponse> listener = new ActionListener<CloseJobResponse>() {
                 @Override
                 public void onResponse(CloseJobResponse closeJobResponse) {
-                    //<1>
+                    // <1>
                 }
 
                 @Override
@@ -396,7 +398,7 @@ public class MlClientDocumentationIT extends ESRestHighLevelClientTestCase {
             listener = new LatchedActionListener<>(listener, latch);
 
             // tag::x-pack-ml-close-job-execute-async
-            client.machineLearning().closeJobAsync(closeJobRequest, RequestOptions.DEFAULT, listener); //<1>
+            client.machineLearning().closeJobAsync(closeJobRequest, RequestOptions.DEFAULT, listener); // <1>
             // end::x-pack-ml-close-job-execute-async
 
             assertTrue(latch.await(30L, TimeUnit.SECONDS));
@@ -427,37 +429,37 @@ public class MlClientDocumentationIT extends ESRestHighLevelClientTestCase {
             customSettings.put("custom-setting-1", "custom-value");
 
             //tag::x-pack-ml-update-job-detector-options
-            JobUpdate.DetectorUpdate detectorUpdate = new JobUpdate.DetectorUpdate(0, //<1>
-                "detector description", //<2>
-                detectionRules); //<3>
+            JobUpdate.DetectorUpdate detectorUpdate = new JobUpdate.DetectorUpdate(0, // <1>
+                "detector description", // <2>
+                detectionRules); // <3>
             //end::x-pack-ml-update-job-detector-options
 
             //tag::x-pack-ml-update-job-options
-            JobUpdate update = new JobUpdate.Builder(jobId) //<1>
-                .setDescription("My description") //<2>
-                .setAnalysisLimits(new AnalysisLimits(1000L, null)) //<3>
-                .setBackgroundPersistInterval(TimeValue.timeValueHours(3)) //<4>
-                .setCategorizationFilters(Arrays.asList("categorization-filter")) //<5>
-                .setDetectorUpdates(Arrays.asList(detectorUpdate)) //<6>
-                .setGroups(Arrays.asList("job-group-1")) //<7>
-                .setResultsRetentionDays(10L) //<8>
-                .setModelPlotConfig(new ModelPlotConfig(true, null)) //<9>
-                .setModelSnapshotRetentionDays(7L) //<10>
-                .setCustomSettings(customSettings) //<11>
-                .setRenormalizationWindowDays(3L) //<12>
+            JobUpdate update = new JobUpdate.Builder(jobId) // <1>
+                .setDescription("My description") // <2>
+                .setAnalysisLimits(new AnalysisLimits(1000L, null)) // <3>
+                .setBackgroundPersistInterval(TimeValue.timeValueHours(3)) // <4>
+                .setCategorizationFilters(Arrays.asList("categorization-filter")) // <5>
+                .setDetectorUpdates(Arrays.asList(detectorUpdate)) // <6>
+                .setGroups(Arrays.asList("job-group-1")) // <7>
+                .setResultsRetentionDays(10L) // <8>
+                .setModelPlotConfig(new ModelPlotConfig(true, null)) // <9>
+                .setModelSnapshotRetentionDays(7L) // <10>
+                .setCustomSettings(customSettings) // <11>
+                .setRenormalizationWindowDays(3L) // <12>
                 .build();
             //end::x-pack-ml-update-job-options
 
 
             //tag::x-pack-ml-update-job-request
-            UpdateJobRequest updateJobRequest = new UpdateJobRequest(update); //<1>
+            UpdateJobRequest updateJobRequest = new UpdateJobRequest(update); // <1>
             //end::x-pack-ml-update-job-request
 
             //tag::x-pack-ml-update-job-execute
             PutJobResponse updateJobResponse = client.machineLearning().updateJob(updateJobRequest, RequestOptions.DEFAULT);
             //end::x-pack-ml-update-job-execute
             //tag::x-pack-ml-update-job-response
-            Job updatedJob = updateJobResponse.getResponse(); //<1>
+            Job updatedJob = updateJobResponse.getResponse(); // <1>
             //end::x-pack-ml-update-job-response
 
             assertEquals(update.getDescription(), updatedJob.getDescription());
@@ -467,7 +469,7 @@ public class MlClientDocumentationIT extends ESRestHighLevelClientTestCase {
             ActionListener<PutJobResponse> listener = new ActionListener<PutJobResponse>() {
                 @Override
                 public void onResponse(PutJobResponse updateJobResponse) {
-                    //<1>
+                    // <1>
                 }
 
                 @Override
@@ -483,7 +485,7 @@ public class MlClientDocumentationIT extends ESRestHighLevelClientTestCase {
             listener = new LatchedActionListener<>(listener, latch);
 
             // tag::x-pack-ml-update-job-execute-async
-            client.machineLearning().updateJobAsync(updateJobRequest, RequestOptions.DEFAULT, listener); //<1>
+            client.machineLearning().updateJobAsync(updateJobRequest, RequestOptions.DEFAULT, listener); // <1>
             // end::x-pack-ml-update-job-execute-async
 
             assertTrue(latch.await(30L, TimeUnit.SECONDS));
@@ -590,6 +592,59 @@ public class MlClientDocumentationIT extends ESRestHighLevelClientTestCase {
         }
     }
 
+    public void testGetDatafeed() throws Exception {
+        RestHighLevelClient client = highLevelClient();
+
+        Job job = MachineLearningIT.buildJob("get-datafeed-job");
+        client.machineLearning().putJob(new PutJobRequest(job), RequestOptions.DEFAULT);
+        String datafeedId = job.getId() + "-feed";
+        DatafeedConfig datafeed = DatafeedConfig.builder(datafeedId, job.getId()).setIndices("foo").build();
+        client.machineLearning().putDatafeed(new PutDatafeedRequest(datafeed), RequestOptions.DEFAULT);
+
+        {
+            //tag::x-pack-ml-get-datafeed-request
+            GetDatafeedRequest request = new GetDatafeedRequest(datafeedId); // <1>
+            request.setAllowNoDatafeeds(true); // <2>
+            //end::x-pack-ml-get-datafeed-request
+
+            //tag::x-pack-ml-get-datafeed-execute
+            GetDatafeedResponse response = client.machineLearning().getDatafeed(request, RequestOptions.DEFAULT);
+            long numberOfDatafeeds = response.count(); // <1>
+            List<DatafeedConfig> datafeeds = response.datafeeds(); // <2>
+            //end::x-pack-ml-get-datafeed-execute
+
+            assertEquals(1, numberOfDatafeeds);
+            assertEquals(1, datafeeds.size());
+        }
+        {
+            GetDatafeedRequest request = new GetDatafeedRequest(datafeedId);
+
+            // tag::x-pack-ml-get-datafeed-listener
+            ActionListener<GetDatafeedResponse> listener = new ActionListener<GetDatafeedResponse>() {
+                @Override
+                public void onResponse(GetDatafeedResponse response) {
+                    // <1>
+                }
+
+                @Override
+                public void onFailure(Exception e) {
+                    // <2>
+                }
+            };
+            // end::x-pack-ml-get-datafeed-listener
+
+            // Replace the empty listener by a blocking listener in test
+            final CountDownLatch latch = new CountDownLatch(1);
+            listener = new LatchedActionListener<>(listener, latch);
+
+            // tag::x-pack-ml-get-datafeed-execute-async
+            client.machineLearning().getDatafeedAsync(request, RequestOptions.DEFAULT, listener); // <1>
+            // end::x-pack-ml-get-datafeed-execute-async
+
+            assertTrue(latch.await(30L, TimeUnit.SECONDS));
+        }
+    }
+
     public void testDeleteDatafeed() throws Exception {
         RestHighLevelClient client = highLevelClient();
 
@@ -604,13 +659,13 @@ public class MlClientDocumentationIT extends ESRestHighLevelClientTestCase {
         {
             //tag::x-pack-delete-ml-datafeed-request
             DeleteDatafeedRequest deleteDatafeedRequest = new DeleteDatafeedRequest(datafeedId);
-            deleteDatafeedRequest.setForce(false); //<1>
+            deleteDatafeedRequest.setForce(false); // <1>
             AcknowledgedResponse deleteDatafeedResponse = client.machineLearning().deleteDatafeed(
                     deleteDatafeedRequest, RequestOptions.DEFAULT);
             //end::x-pack-delete-ml-datafeed-request
 
             //tag::x-pack-delete-ml-datafeed-response
-            boolean isAcknowledged = deleteDatafeedResponse.isAcknowledged(); //<1>
+            boolean isAcknowledged = deleteDatafeedResponse.isAcknowledged(); // <1>
             //end::x-pack-delete-ml-datafeed-response
         }
 
@@ -759,15 +814,15 @@ public class MlClientDocumentationIT extends ESRestHighLevelClientTestCase {
 
         {
             //tag::x-pack-ml-flush-job-request
-            FlushJobRequest flushJobRequest = new FlushJobRequest("flushing-my-first-machine-learning-job"); //<1>
+            FlushJobRequest flushJobRequest = new FlushJobRequest("flushing-my-first-machine-learning-job"); // <1>
             //end::x-pack-ml-flush-job-request
 
             //tag::x-pack-ml-flush-job-request-options
-            flushJobRequest.setCalcInterim(true); //<1>
-            flushJobRequest.setAdvanceTime("2018-08-31T16:35:07+00:00"); //<2>
-            flushJobRequest.setStart("2018-08-31T16:35:17+00:00"); //<3>
-            flushJobRequest.setEnd("2018-08-31T16:35:27+00:00"); //<4>
-            flushJobRequest.setSkipTime("2018-08-31T16:35:00+00:00"); //<5>
+            flushJobRequest.setCalcInterim(true); // <1>
+            flushJobRequest.setAdvanceTime("2018-08-31T16:35:07+00:00"); // <2>
+            flushJobRequest.setStart("2018-08-31T16:35:17+00:00"); // <3>
+            flushJobRequest.setEnd("2018-08-31T16:35:27+00:00"); // <4>
+            flushJobRequest.setSkipTime("2018-08-31T16:35:00+00:00"); // <5>
             //end::x-pack-ml-flush-job-request-options
 
             //tag::x-pack-ml-flush-job-execute
@@ -775,8 +830,8 @@ public class MlClientDocumentationIT extends ESRestHighLevelClientTestCase {
             //end::x-pack-ml-flush-job-execute
 
             //tag::x-pack-ml-flush-job-response
-            boolean isFlushed = flushJobResponse.isFlushed(); //<1>
-            Date lastFinalizedBucketEnd = flushJobResponse.getLastFinalizedBucketEnd(); //<2>
+            boolean isFlushed = flushJobResponse.isFlushed(); // <1>
+            Date lastFinalizedBucketEnd = flushJobResponse.getLastFinalizedBucketEnd(); // <2>
             //end::x-pack-ml-flush-job-response
 
         }
@@ -785,7 +840,7 @@ public class MlClientDocumentationIT extends ESRestHighLevelClientTestCase {
             ActionListener<FlushJobResponse> listener = new ActionListener<FlushJobResponse>() {
                 @Override
                 public void onResponse(FlushJobResponse FlushJobResponse) {
-                    //<1>
+                    // <1>
                 }
 
                 @Override
@@ -801,7 +856,7 @@ public class MlClientDocumentationIT extends ESRestHighLevelClientTestCase {
             listener = new LatchedActionListener<>(listener, latch);
 
             // tag::x-pack-ml-flush-job-execute-async
-            client.machineLearning().flushJobAsync(flushJobRequest, RequestOptions.DEFAULT, listener); //<1>
+            client.machineLearning().flushJobAsync(flushJobRequest, RequestOptions.DEFAULT, listener); // <1>
             // end::x-pack-ml-flush-job-execute-async
 
             assertTrue(latch.await(30L, TimeUnit.SECONDS));
@@ -839,13 +894,13 @@ public class MlClientDocumentationIT extends ESRestHighLevelClientTestCase {
 
         {
             //tag::x-pack-ml-delete-forecast-request
-            DeleteForecastRequest deleteForecastRequest = new DeleteForecastRequest("deleting-forecast-for-job"); //<1>
+            DeleteForecastRequest deleteForecastRequest = new DeleteForecastRequest("deleting-forecast-for-job"); // <1>
             //end::x-pack-ml-delete-forecast-request
 
             //tag::x-pack-ml-delete-forecast-request-options
-            deleteForecastRequest.setForecastIds(forecastId); //<1>
-            deleteForecastRequest.timeout("30s"); //<2>
-            deleteForecastRequest.setAllowNoForecasts(true); //<3>
+            deleteForecastRequest.setForecastIds(forecastId); // <1>
+            deleteForecastRequest.timeout("30s"); // <2>
+            deleteForecastRequest.setAllowNoForecasts(true); // <3>
             //end::x-pack-ml-delete-forecast-request-options
 
             //tag::x-pack-ml-delete-forecast-execute
@@ -854,7 +909,7 @@ public class MlClientDocumentationIT extends ESRestHighLevelClientTestCase {
             //end::x-pack-ml-delete-forecast-execute
 
             //tag::x-pack-ml-delete-forecast-response
-            boolean isAcknowledged = deleteForecastResponse.isAcknowledged(); //<1>
+            boolean isAcknowledged = deleteForecastResponse.isAcknowledged(); // <1>
             //end::x-pack-ml-delete-forecast-response
         }
         {
@@ -862,7 +917,7 @@ public class MlClientDocumentationIT extends ESRestHighLevelClientTestCase {
             ActionListener<AcknowledgedResponse> listener = new ActionListener<AcknowledgedResponse>() {
                 @Override
                 public void onResponse(AcknowledgedResponse DeleteForecastResponse) {
-                    //<1>
+                    // <1>
                 }
 
                 @Override
@@ -879,7 +934,7 @@ public class MlClientDocumentationIT extends ESRestHighLevelClientTestCase {
             listener = new LatchedActionListener<>(listener, latch);
 
             // tag::x-pack-ml-delete-forecast-execute-async
-            client.machineLearning().deleteForecastAsync(deleteForecastRequest, RequestOptions.DEFAULT, listener); //<1>
+            client.machineLearning().deleteForecastAsync(deleteForecastRequest, RequestOptions.DEFAULT, listener); // <1>
             // end::x-pack-ml-delete-forecast-execute-async
 
             assertTrue(latch.await(30L, TimeUnit.SECONDS));
@@ -897,8 +952,8 @@ public class MlClientDocumentationIT extends ESRestHighLevelClientTestCase {
 
         {
             //tag::x-pack-ml-get-job-stats-request
-            GetJobStatsRequest request = new GetJobStatsRequest("get-machine-learning-job-stats1", "get-machine-learning-job-*"); //<1>
-            request.setAllowNoJobs(true); //<2>
+            GetJobStatsRequest request = new GetJobStatsRequest("get-machine-learning-job-stats1", "get-machine-learning-job-*"); // <1>
+            request.setAllowNoJobs(true); // <2>
             //end::x-pack-ml-get-job-stats-request
 
             //tag::x-pack-ml-get-job-stats-execute
@@ -906,8 +961,8 @@ public class MlClientDocumentationIT extends ESRestHighLevelClientTestCase {
             //end::x-pack-ml-get-job-stats-execute
 
             //tag::x-pack-ml-get-job-stats-response
-            long numberOfJobStats = response.count(); //<1>
-            List<JobStats> jobStats = response.jobStats(); //<2>
+            long numberOfJobStats = response.count(); // <1>
+            List<JobStats> jobStats = response.jobStats(); // <2>
             //end::x-pack-ml-get-job-stats-response
 
             assertEquals(2, response.count());
@@ -964,12 +1019,12 @@ public class MlClientDocumentationIT extends ESRestHighLevelClientTestCase {
 
         {
             //tag::x-pack-ml-forecast-job-request
-            ForecastJobRequest forecastJobRequest = new ForecastJobRequest("forecasting-my-first-machine-learning-job"); //<1>
+            ForecastJobRequest forecastJobRequest = new ForecastJobRequest("forecasting-my-first-machine-learning-job"); // <1>
             //end::x-pack-ml-forecast-job-request
 
             //tag::x-pack-ml-forecast-job-request-options
-            forecastJobRequest.setExpiresIn(TimeValue.timeValueHours(48)); //<1>
-            forecastJobRequest.setDuration(TimeValue.timeValueHours(24)); //<2>
+            forecastJobRequest.setExpiresIn(TimeValue.timeValueHours(48)); // <1>
+            forecastJobRequest.setDuration(TimeValue.timeValueHours(24)); // <2>
             //end::x-pack-ml-forecast-job-request-options
 
             //tag::x-pack-ml-forecast-job-execute
@@ -977,8 +1032,8 @@ public class MlClientDocumentationIT extends ESRestHighLevelClientTestCase {
             //end::x-pack-ml-forecast-job-execute
 
             //tag::x-pack-ml-forecast-job-response
-            boolean isAcknowledged = forecastJobResponse.isAcknowledged(); //<1>
-            String forecastId = forecastJobResponse.getForecastId(); //<2>
+            boolean isAcknowledged = forecastJobResponse.isAcknowledged(); // <1>
+            String forecastId = forecastJobResponse.getForecastId(); // <2>
             //end::x-pack-ml-forecast-job-response
             assertTrue(isAcknowledged);
             assertNotNull(forecastId);
@@ -988,7 +1043,7 @@ public class MlClientDocumentationIT extends ESRestHighLevelClientTestCase {
             ActionListener<ForecastJobResponse> listener = new ActionListener<ForecastJobResponse>() {
                 @Override
                 public void onResponse(ForecastJobResponse forecastJobResponse) {
-                    //<1>
+                    // <1>
                 }
 
                 @Override
@@ -1004,7 +1059,7 @@ public class MlClientDocumentationIT extends ESRestHighLevelClientTestCase {
             listener = new LatchedActionListener<>(listener, latch);
 
             // tag::x-pack-ml-forecast-job-execute-async
-            client.machineLearning().forecastJobAsync(forecastJobRequest, RequestOptions.DEFAULT, listener); //<1>
+            client.machineLearning().forecastJobAsync(forecastJobRequest, RequestOptions.DEFAULT, listener); // <1>
             // end::x-pack-ml-forecast-job-execute-async
 
             assertTrue(latch.await(30L, TimeUnit.SECONDS));
@@ -1211,18 +1266,18 @@ public class MlClientDocumentationIT extends ESRestHighLevelClientTestCase {
 
         {
             //tag::x-pack-ml-post-data-request
-            PostDataRequest.JsonBuilder jsonBuilder = new PostDataRequest.JsonBuilder(); //<1>
+            PostDataRequest.JsonBuilder jsonBuilder = new PostDataRequest.JsonBuilder(); // <1>
             Map<String, Object> mapData = new HashMap<>();
             mapData.put("total", 109);
-            jsonBuilder.addDoc(mapData); //<2>
-            jsonBuilder.addDoc("{\"total\":1000}"); //<3>
-            PostDataRequest postDataRequest = new PostDataRequest("test-post-data", jsonBuilder); //<4>
+            jsonBuilder.addDoc(mapData); // <2>
+            jsonBuilder.addDoc("{\"total\":1000}"); // <3>
+            PostDataRequest postDataRequest = new PostDataRequest("test-post-data", jsonBuilder); // <4>
             //end::x-pack-ml-post-data-request
 
 
             //tag::x-pack-ml-post-data-request-options
-            postDataRequest.setResetStart("2018-08-31T16:35:07+00:00"); //<1>
-            postDataRequest.setResetEnd("2018-08-31T16:35:17+00:00"); //<2>
+            postDataRequest.setResetStart("2018-08-31T16:35:07+00:00"); // <1>
+            postDataRequest.setResetEnd("2018-08-31T16:35:17+00:00"); // <2>
             //end::x-pack-ml-post-data-request-options
             postDataRequest.setResetEnd(null);
             postDataRequest.setResetStart(null);
@@ -1232,7 +1287,7 @@ public class MlClientDocumentationIT extends ESRestHighLevelClientTestCase {
             //end::x-pack-ml-post-data-execute
 
             //tag::x-pack-ml-post-data-response
-            DataCounts dataCounts = postDataResponse.getDataCounts(); //<1>
+            DataCounts dataCounts = postDataResponse.getDataCounts(); // <1>
             //end::x-pack-ml-post-data-response
             assertEquals(2, dataCounts.getInputRecordCount());
 
@@ -1242,7 +1297,7 @@ public class MlClientDocumentationIT extends ESRestHighLevelClientTestCase {
             ActionListener<PostDataResponse> listener = new ActionListener<PostDataResponse>() {
                 @Override
                 public void onResponse(PostDataResponse postDataResponse) {
-                    //<1>
+                    // <1>
                 }
 
                 @Override
@@ -1255,14 +1310,14 @@ public class MlClientDocumentationIT extends ESRestHighLevelClientTestCase {
             Map<String, Object> mapData = new HashMap<>();
             mapData.put("total", 109);
             jsonBuilder.addDoc(mapData);
-            PostDataRequest postDataRequest = new PostDataRequest("test-post-data", jsonBuilder); //<1>
+            PostDataRequest postDataRequest = new PostDataRequest("test-post-data", jsonBuilder); // <1>
 
             // Replace the empty listener by a blocking listener in test
             final CountDownLatch latch = new CountDownLatch(1);
             listener = new LatchedActionListener<>(listener, latch);
 
             // tag::x-pack-ml-post-data-execute-async
-            client.machineLearning().postDataAsync(postDataRequest, RequestOptions.DEFAULT, listener); //<1>
+            client.machineLearning().postDataAsync(postDataRequest, RequestOptions.DEFAULT, listener); // <1>
             // end::x-pack-ml-post-data-execute-async
 
             assertTrue(latch.await(30L, TimeUnit.SECONDS));

+ 70 - 0
client/rest-high-level/src/test/java/org/elasticsearch/client/ml/GetDatafeedRequestTests.java

@@ -0,0 +1,70 @@
+/*
+ * 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.client.ml.datafeed.DatafeedConfigTests;
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.test.AbstractXContentTestCase;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+public class GetDatafeedRequestTests extends AbstractXContentTestCase<GetDatafeedRequest> {
+
+    public void testAllDatafeedRequest() {
+        GetDatafeedRequest request = GetDatafeedRequest.getAllDatafeedsRequest();
+
+        assertEquals(request.getDatafeedIds().size(), 1);
+        assertEquals(request.getDatafeedIds().get(0), "_all");
+    }
+
+    public void testNewWithDatafeedId() {
+        Exception exception = expectThrows(NullPointerException.class, () -> new GetDatafeedRequest("feed",null));
+        assertEquals(exception.getMessage(), "datafeedIds must not contain null values");
+    }
+
+    @Override
+    protected GetDatafeedRequest createTestInstance() {
+        int count = randomIntBetween(0, 10);
+        List<String> datafeedIds = new ArrayList<>(count);
+
+        for (int i = 0; i < count; i++) {
+            datafeedIds.add(DatafeedConfigTests.randomValidDatafeedId());
+        }
+
+        GetDatafeedRequest request = new GetDatafeedRequest(datafeedIds);
+
+        if (randomBoolean()) {
+            request.setAllowNoDatafeeds(randomBoolean());
+        }
+
+        return request;
+    }
+
+    @Override
+    protected GetDatafeedRequest doParseInstance(XContentParser parser) throws IOException {
+        return GetDatafeedRequest.PARSER.parse(parser, null);
+    }
+
+    @Override
+    protected boolean supportsUnknownFields() {
+        return false;
+    }
+}

+ 58 - 0
client/rest-high-level/src/test/java/org/elasticsearch/client/ml/GetDatafeedResponseTests.java

@@ -0,0 +1,58 @@
+/*
+ * 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.client.ml.datafeed.DatafeedConfig;
+import org.elasticsearch.client.ml.datafeed.DatafeedConfigTests;
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.test.AbstractXContentTestCase;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Predicate;
+
+public class GetDatafeedResponseTests extends AbstractXContentTestCase<GetDatafeedResponse> {
+
+    @Override
+    protected GetDatafeedResponse createTestInstance() {
+        int count = randomIntBetween(1, 5);
+        List<DatafeedConfig.Builder> results = new ArrayList<>(count);
+        for(int i = 0; i < count; i++) {
+            DatafeedConfigTests.createRandomBuilder();
+            results.add(DatafeedConfigTests.createRandomBuilder());
+        }
+        return new GetDatafeedResponse(results, count);
+    }
+
+    @Override
+    protected GetDatafeedResponse doParseInstance(XContentParser parser) throws IOException {
+        return GetDatafeedResponse.fromXContent(parser);
+    }
+
+    @Override
+    protected Predicate<String> getRandomFieldsExcludeFilter() {
+        return field -> !field.isEmpty();
+    }
+
+    @Override
+    protected boolean supportsUnknownFields() {
+        return true;
+    }
+}

+ 1 - 1
client/rest-high-level/src/test/java/org/elasticsearch/client/ml/GetJobRequestTests.java

@@ -64,6 +64,6 @@ public class GetJobRequestTests extends AbstractXContentTestCase<GetJobRequest>
 
     @Override
     protected boolean supportsUnknownFields() {
-        return true;
+        return false;
     }
 }

+ 5 - 1
client/rest-high-level/src/test/java/org/elasticsearch/client/ml/datafeed/DatafeedConfigTests.java

@@ -44,6 +44,10 @@ public class DatafeedConfigTests extends AbstractXContentTestCase<DatafeedConfig
     }
 
     public static DatafeedConfig createRandom() {
+        return createRandomBuilder().build();
+    }
+
+    public static DatafeedConfig.Builder createRandomBuilder() {
         long bucketSpanMillis = 3600000;
         DatafeedConfig.Builder builder = constructBuilder();
         builder.setIndices(randomStringList(1, 10));
@@ -99,7 +103,7 @@ public class DatafeedConfigTests extends AbstractXContentTestCase<DatafeedConfig
         if (randomBoolean()) {
             builder.setChunkingConfig(ChunkingConfigTests.createRandomizedChunk());
         }
-        return builder.build();
+        return builder;
     }
 
     public static List<String> randomStringList(int min, int max) {

+ 56 - 0
docs/java-rest/high-level/ml/get-datafeed.asciidoc

@@ -0,0 +1,56 @@
+[[java-rest-high-x-pack-ml-get-datafeed]]
+=== Get Datafeed API
+
+The Get Datafeed API provides the ability to get {ml} datafeeds in the cluster.
+It accepts a `GetDatafeedRequest` object and responds
+with a `GetDatafeedResponse` object.
+
+[[java-rest-high-x-pack-ml-get-datafeed-request]]
+==== Get Datafeed Request
+
+A `GetDatafeedRequest` object gets can have any number of `datafeedId` entries.
+However, they all must be non-null. An empty list is the same as requesting for all datafeeds.
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/MlClientDocumentationIT.java[x-pack-ml-get-datafeed-request]
+--------------------------------------------------
+<1> Constructing a new request referencing existing `datafeedIds`, can contain wildcards
+<2> Whether to ignore if a wildcard expression matches no datafeeds.
+ (This includes `_all` string or when no datafeeds have been specified)
+
+[[java-rest-high-x-pack-ml-get-datafeed-execution]]
+==== Execution
+
+The request can be executed through the `MachineLearningClient` contained
+in the `RestHighLevelClient` object, accessed via the `machineLearningClient()` method.
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/MlClientDocumentationIT.java[x-pack-ml-get-datafeed-execute]
+--------------------------------------------------
+<1> The count of retrieved datafeeds
+<2> The retrieved datafeeds
+
+[[java-rest-high-x-pack-ml-get-datafeed-execution-async]]
+==== Asynchronous Execution
+
+The request can also be executed asynchronously:
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/MlClientDocumentationIT.java[x-pack-ml-get-datafeed-execute-async]
+--------------------------------------------------
+<1> The `GetDatafeedRequest` to execute and the `ActionListener` to use when
+the execution completes
+
+The method does not block and returns immediately. The passed `ActionListener` is used
+to notify the caller of completion. A typical `ActionListener` for `GetDatafeedResponse` may
+look like
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/MlClientDocumentationIT.java[x-pack-ml-get-datafeed-listener]
+--------------------------------------------------
+<1> `onResponse` is called back when the action is completed successfully
+<2> `onFailure` is called back when some unexpected error occurs

+ 2 - 0
docs/java-rest/high-level/supported-apis.asciidoc

@@ -221,6 +221,7 @@ The Java High Level REST Client supports the following Machine Learning APIs:
 * <<java-rest-high-x-pack-ml-update-job>>
 * <<java-rest-high-x-pack-ml-get-job-stats>>
 * <<java-rest-high-x-pack-ml-put-datafeed>>
+* <<java-rest-high-x-pack-ml-get-datafeed>>
 * <<java-rest-high-x-pack-ml-delete-datafeed>>
 * <<java-rest-high-x-pack-ml-forecast-job>>
 * <<java-rest-high-x-pack-ml-delete-forecast>>
@@ -240,6 +241,7 @@ include::ml/close-job.asciidoc[]
 include::ml/update-job.asciidoc[]
 include::ml/flush-job.asciidoc[]
 include::ml/put-datafeed.asciidoc[]
+include::ml/get-datafeed.asciidoc[]
 include::ml/delete-datafeed.asciidoc[]
 include::ml/get-job-stats.asciidoc[]
 include::ml/forecast-job.asciidoc[]