Browse Source

[HLRC] Add GetRollupIndexCaps API (#35102)

Also refactors the caps response tests a bit to share the same
abstract class to reduce duplication of test code
Zachary Tong 7 years ago
parent
commit
2da239fb5e

+ 38 - 0
client/rest-high-level/src/main/java/org/elasticsearch/client/RollupClient.java

@@ -22,6 +22,8 @@ package org.elasticsearch.client;
 import org.elasticsearch.action.ActionListener;
 import org.elasticsearch.client.rollup.DeleteRollupJobRequest;
 import org.elasticsearch.client.rollup.DeleteRollupJobResponse;
+import org.elasticsearch.client.rollup.GetRollupIndexCapsRequest;
+import org.elasticsearch.client.rollup.GetRollupIndexCapsResponse;
 import org.elasticsearch.client.rollup.GetRollupJobRequest;
 import org.elasticsearch.client.rollup.GetRollupJobResponse;
 import org.elasticsearch.client.rollup.GetRollupCapsRequest;
@@ -219,4 +221,40 @@ public class RollupClient {
             listener,
             Collections.emptySet());
     }
+
+    /**
+     * Get the Rollup Index Capabilities of a rollup index or pattern
+     * See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/master/rollup-get-rollup-index-caps.html">
+     * the docs</a> for more.
+     * @param request the request
+     * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
+     * @return the response
+     * @throws IOException in case there is a problem sending the request or parsing back the response
+     */
+    public GetRollupIndexCapsResponse getRollupIndexCapabilities(GetRollupIndexCapsRequest request,
+                                                                 RequestOptions options) throws IOException {
+        return restHighLevelClient.performRequestAndParseEntity(request,
+            RollupRequestConverters::getRollupIndexCaps,
+            options,
+            GetRollupIndexCapsResponse::fromXContent,
+            Collections.emptySet());
+    }
+
+    /**
+     * Asynchronously Get the Rollup Index Capabilities of a rollup index or pattern
+     * See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/master/rollup-get-rollup-index-caps.html">
+     * the docs</a> for more.
+     * @param request the request
+     * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
+     * @param listener the listener to be notified upon request completion
+     */
+    public void getRollupIndexCapabilitiesAsync(GetRollupIndexCapsRequest request, RequestOptions options,
+                                           ActionListener<GetRollupIndexCapsResponse> listener) {
+        restHighLevelClient.performRequestAsyncAndParseEntity(request,
+            RollupRequestConverters::getRollupIndexCaps,
+            options,
+            GetRollupIndexCapsResponse::fromXContent,
+            listener,
+            Collections.emptySet());
+    }
 }

+ 11 - 0
client/rest-high-level/src/main/java/org/elasticsearch/client/RollupRequestConverters.java

@@ -24,6 +24,7 @@ import org.apache.http.client.methods.HttpPost;
 import org.apache.http.client.methods.HttpPut;
 import org.elasticsearch.client.rollup.DeleteRollupJobRequest;
 import org.elasticsearch.client.rollup.GetRollupCapsRequest;
+import org.elasticsearch.client.rollup.GetRollupIndexCapsRequest;
 import org.elasticsearch.client.rollup.GetRollupJobRequest;
 import org.elasticsearch.client.rollup.PutRollupJobRequest;
 import org.elasticsearch.client.rollup.StartRollupJobRequest;
@@ -85,4 +86,14 @@ final class RollupRequestConverters {
         request.setEntity(createEntity(getRollupCapsRequest, REQUEST_BODY_CONTENT_TYPE));
         return request;
     }
+
+    static Request getRollupIndexCaps(final GetRollupIndexCapsRequest getRollupIndexCapsRequest) throws IOException {
+        String endpoint = new RequestConverters.EndpointBuilder()
+            .addCommaSeparatedPathParts(getRollupIndexCapsRequest.indices())
+            .addPathPartAsIs("_xpack", "rollup", "data")
+            .build();
+        Request request = new Request(HttpGet.METHOD_NAME, endpoint);
+        request.setEntity(createEntity(getRollupIndexCapsRequest, REQUEST_BODY_CONTENT_TYPE));
+        return request;
+    }
 }

+ 1 - 20
client/rest-high-level/src/main/java/org/elasticsearch/client/rollup/GetRollupCapsResponse.java

@@ -18,10 +18,6 @@
  */
 package org.elasticsearch.client.rollup;
 
-import org.elasticsearch.common.Strings;
-import org.elasticsearch.common.xcontent.ToXContent;
-import org.elasticsearch.common.xcontent.ToXContentObject;
-import org.elasticsearch.common.xcontent.XContentBuilder;
 import org.elasticsearch.common.xcontent.XContentParser;
 
 import java.io.IOException;
@@ -30,7 +26,7 @@ import java.util.HashMap;
 import java.util.Map;
 import java.util.Objects;
 
-public class GetRollupCapsResponse implements ToXContentObject {
+public class GetRollupCapsResponse {
 
     private final Map<String, RollableIndexCaps> jobs;
 
@@ -42,16 +38,6 @@ public class GetRollupCapsResponse implements ToXContentObject {
         return jobs;
     }
 
-    @Override
-    public XContentBuilder toXContent(XContentBuilder builder, ToXContent.Params params) throws IOException {
-        builder.startObject();
-        for (Map.Entry<String, RollableIndexCaps> entry : jobs.entrySet()) {
-            entry.getValue().toXContent(builder, params);
-        }
-        builder.endObject();
-        return builder;
-    }
-
     public static GetRollupCapsResponse fromXContent(final XContentParser parser) throws IOException {
         Map<String, RollableIndexCaps> jobs = new HashMap<>();
         XContentParser.Token token = parser.nextToken();
@@ -84,9 +70,4 @@ public class GetRollupCapsResponse implements ToXContentObject {
         GetRollupCapsResponse other = (GetRollupCapsResponse) obj;
         return Objects.equals(jobs, other.jobs);
     }
-
-    @Override
-    public final String toString() {
-        return Strings.toString(this);
-    }
 }

+ 95 - 0
client/rest-high-level/src/main/java/org/elasticsearch/client/rollup/GetRollupIndexCapsRequest.java

@@ -0,0 +1,95 @@
+/*
+ * 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.rollup;
+
+import org.elasticsearch.action.support.IndicesOptions;
+import org.elasticsearch.client.Validatable;
+import org.elasticsearch.common.Strings;
+import org.elasticsearch.common.xcontent.ToXContentObject;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Objects;
+
+public class GetRollupIndexCapsRequest implements Validatable, ToXContentObject {
+    private static final String INDICES = "indices";
+    private static final String INDICES_OPTIONS = "indices_options";
+
+    private String[] indices;
+    private IndicesOptions options;
+
+    public GetRollupIndexCapsRequest(final String... indices) {
+        this(indices, IndicesOptions.STRICT_EXPAND_OPEN_FORBID_CLOSED);
+    }
+
+    public GetRollupIndexCapsRequest(final String[] indices, final IndicesOptions options) {
+        if (indices == null || indices.length == 0) {
+            throw new IllegalArgumentException("[indices] must not be null or empty");
+        }
+        for (String index : indices) {
+            if (Strings.isNullOrEmpty(index)) {
+                throw new IllegalArgumentException("[index] must not be null or empty");
+            }
+        }
+        this.indices = indices;
+        this.options = Objects.requireNonNull(options);
+    }
+
+    public IndicesOptions indicesOptions() {
+        return options;
+    }
+
+    public String[] indices() {
+        return indices;
+    }
+
+    @Override
+    public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
+        builder.startObject();
+        {
+            builder.array(INDICES, indices);
+            builder.startObject(INDICES_OPTIONS);
+            {
+                options.toXContent(builder, params);
+            }
+            builder.endObject();
+        }
+        builder.endObject();
+        return builder;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(Arrays.hashCode(indices), options);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        GetRollupIndexCapsRequest other = (GetRollupIndexCapsRequest) obj;
+        return Arrays.equals(indices, other.indices)
+            && Objects.equals(options, other.options);
+    }
+}

+ 73 - 0
client/rest-high-level/src/main/java/org/elasticsearch/client/rollup/GetRollupIndexCapsResponse.java

@@ -0,0 +1,73 @@
+/*
+ * 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.rollup;
+
+import org.elasticsearch.common.xcontent.XContentParser;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+public class GetRollupIndexCapsResponse {
+
+    private final Map<String, RollableIndexCaps> jobs;
+
+    public GetRollupIndexCapsResponse(final Map<String, RollableIndexCaps> jobs) {
+        this.jobs = Collections.unmodifiableMap(Objects.requireNonNull(jobs));
+    }
+
+    public Map<String, RollableIndexCaps> getJobs() {
+        return jobs;
+    }
+
+    public static GetRollupIndexCapsResponse fromXContent(final XContentParser parser) throws IOException {
+        Map<String, RollableIndexCaps> jobs = new HashMap<>();
+        XContentParser.Token token = parser.nextToken();
+        if (token.equals(XContentParser.Token.START_OBJECT)) {
+            while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
+                if (token.equals(XContentParser.Token.FIELD_NAME)) {
+                    String pattern = parser.currentName();
+
+                    RollableIndexCaps cap = RollableIndexCaps.PARSER.apply(pattern).apply(parser, null);
+                    jobs.put(pattern, cap);
+                }
+            }
+        }
+        return new GetRollupIndexCapsResponse(jobs);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(jobs);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        GetRollupIndexCapsResponse other = (GetRollupIndexCapsResponse) obj;
+        return Objects.equals(jobs, other.jobs);
+    }
+}

+ 114 - 0
client/rest-high-level/src/test/java/org/elasticsearch/client/RollupIT.java

@@ -33,6 +33,8 @@ import org.elasticsearch.client.rollup.DeleteRollupJobRequest;
 import org.elasticsearch.client.rollup.DeleteRollupJobResponse;
 import org.elasticsearch.client.rollup.GetRollupCapsRequest;
 import org.elasticsearch.client.rollup.GetRollupCapsResponse;
+import org.elasticsearch.client.rollup.GetRollupIndexCapsRequest;
+import org.elasticsearch.client.rollup.GetRollupIndexCapsResponse;
 import org.elasticsearch.client.rollup.GetRollupJobRequest;
 import org.elasticsearch.client.rollup.GetRollupJobResponse;
 import org.elasticsearch.client.rollup.GetRollupJobResponse.IndexerState;
@@ -348,4 +350,116 @@ public class RollupIT extends ESRestHighLevelClientTestCase {
         List<Map<String, Object>> valueCaps = fieldCaps.get("value").getAggs();
         assertThat(valueCaps.size(), equalTo(SUPPORTED_METRICS.size()));
     }
+
+    public void testGetRollupIndexCaps() throws Exception {
+        final Set<Integer> values = new HashSet<>();
+        double sum = 0.0d;
+        int max = Integer.MIN_VALUE;
+        int min = Integer.MAX_VALUE;
+
+        final BulkRequest bulkRequest = new BulkRequest();
+        bulkRequest.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE);
+        for (int minute = 0; minute < 60; minute++) {
+            for (int second = 0; second < 60; second = second + 10) {
+                final int value = randomIntBetween(0, 100);
+
+                final IndexRequest indexRequest = new IndexRequest("docs", "doc");
+                indexRequest.source(jsonBuilder()
+                    .startObject()
+                    .field("value", value)
+                    .field("date", String.format(Locale.ROOT, "2018-01-01T00:%02d:%02dZ", minute, second))
+                    .endObject());
+                bulkRequest.add(indexRequest);
+
+                values.add(value);
+                sum += value;
+                if (value > max) {
+                    max = value;
+                }
+                if (value < min) {
+                    min = value;
+                }
+            }
+        }
+
+        final int numDocs = bulkRequest.numberOfActions();
+
+        BulkResponse bulkResponse = highLevelClient().bulk(bulkRequest, RequestOptions.DEFAULT);
+        assertEquals(RestStatus.OK, bulkResponse.status());
+        if (bulkResponse.hasFailures())     {
+            for (BulkItemResponse itemResponse : bulkResponse.getItems()) {
+                if (itemResponse.isFailed()) {
+                    logger.fatal(itemResponse.getFailureMessage());
+                }
+            }
+        }
+        assertFalse(bulkResponse.hasFailures());
+
+        RefreshResponse refreshResponse = highLevelClient().indices().refresh(new RefreshRequest("docs"), RequestOptions.DEFAULT);
+        assertEquals(0, refreshResponse.getFailedShards());
+
+        final String id = randomAlphaOfLength(10);
+        final String indexPattern = randomFrom("docs", "d*", "doc*");
+        final String rollupIndex = randomFrom("rollup", "test");
+        final String cron = "*/1 * * * * ?";
+        final int pageSize = randomIntBetween(numDocs, numDocs * 10);
+        // TODO expand this to also test with histogram and terms?
+        final GroupConfig groups = new GroupConfig(new DateHistogramGroupConfig("date", DateHistogramInterval.DAY));
+        final List<MetricConfig> metrics = Collections.singletonList(new MetricConfig("value", SUPPORTED_METRICS));
+        final TimeValue timeout = TimeValue.timeValueSeconds(randomIntBetween(30, 600));
+
+        PutRollupJobRequest putRollupJobRequest =
+            new PutRollupJobRequest(new RollupJobConfig(id, indexPattern, rollupIndex, cron, pageSize, groups, metrics, timeout));
+
+        final RollupClient rollupClient = highLevelClient().rollup();
+        PutRollupJobResponse response = execute(putRollupJobRequest, rollupClient::putRollupJob, rollupClient::putRollupJobAsync);
+        assertTrue(response.isAcknowledged());
+
+        // wait for the PutJob api to create the index w/ metadata
+        highLevelClient().cluster().health(new ClusterHealthRequest(rollupIndex).waitForYellowStatus(), RequestOptions.DEFAULT);
+
+        GetRollupIndexCapsRequest getRollupIndexCapsRequest = new GetRollupIndexCapsRequest(rollupIndex);
+        GetRollupIndexCapsResponse capsResponse = highLevelClient().rollup()
+            .getRollupIndexCapabilities(getRollupIndexCapsRequest, RequestOptions.DEFAULT);
+
+        assertNotNull(capsResponse);
+        Map<String, RollableIndexCaps> rolledPatterns = capsResponse.getJobs();
+        assertThat(rolledPatterns.size(), equalTo(1));
+
+        RollableIndexCaps docsPattern = rolledPatterns.get(rollupIndex);
+        assertThat(docsPattern.getIndexName(), equalTo(rollupIndex));
+
+        List<RollupJobCaps> rollupJobs = docsPattern.getJobCaps();
+        assertThat(rollupJobs.size(), equalTo(1));
+
+        RollupJobCaps jobCaps = rollupJobs.get(0);
+        assertThat(jobCaps.getJobID(), equalTo(id));
+        assertThat(jobCaps.getRollupIndex(), equalTo(rollupIndex));
+        assertThat(jobCaps.getIndexPattern(), equalTo(indexPattern));
+
+        Map<String, RollupJobCaps.RollupFieldCaps> fieldCaps = jobCaps.getFieldCaps();
+
+        List<Map<String, Object>> timestampCaps = fieldCaps.get("date").getAggs();
+        for (Map.Entry<String, Object> entry : timestampCaps.get(0).entrySet()) {
+            switch (entry.getKey()) {
+                case "agg":
+                    assertThat(entry.getValue(), equalTo("date_histogram"));
+                    break;
+                case "delay":
+                    assertThat(entry.getValue(), equalTo("foo"));
+                    break;
+                case "interval":
+                    assertThat(entry.getValue(), equalTo("1d"));
+                    break;
+                case "time_zone":
+                    assertThat(entry.getValue(), equalTo("UTC"));
+                    break;
+                default:
+                    fail("Unknown field cap: [" + entry.getKey() + "]");
+            }
+        }
+
+        List<Map<String, Object>> valueCaps = fieldCaps.get("value").getAggs();
+        assertThat(valueCaps.size(), equalTo(SUPPORTED_METRICS.size()));
+    }
 }

+ 9 - 114
client/rest-high-level/src/test/java/org/elasticsearch/client/rollup/GetRollupCapsResponseTests.java

@@ -18,52 +18,13 @@
  */
 package org.elasticsearch.client.rollup;
 
-import org.elasticsearch.client.rollup.job.config.DateHistogramGroupConfig;
-import org.elasticsearch.client.rollup.job.config.GroupConfig;
-import org.elasticsearch.client.rollup.job.config.HistogramGroupConfig;
-import org.elasticsearch.client.rollup.job.config.MetricConfig;
-import org.elasticsearch.client.rollup.job.config.RollupJobConfig;
-import org.elasticsearch.client.rollup.job.config.RollupJobConfigTests;
-import org.elasticsearch.client.rollup.job.config.TermsGroupConfig;
+import org.elasticsearch.common.xcontent.XContentBuilder;
 import org.elasticsearch.common.xcontent.XContentParser;
-import org.elasticsearch.search.aggregations.bucket.histogram.DateHistogramAggregationBuilder;
-import org.elasticsearch.search.aggregations.bucket.histogram.HistogramAggregationBuilder;
-import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
-import org.elasticsearch.test.AbstractXContentTestCase;
-import org.junit.Before;
 
 import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
-import java.util.stream.Collectors;
 
-import static java.util.Collections.singletonMap;
-
-public class GetRollupCapsResponseTests extends AbstractXContentTestCase<GetRollupCapsResponse> {
-
-    private Map<String, RollableIndexCaps> indices;
-
-    @Before
-    private void setupIndices() throws IOException {
-        int numIndices = randomIntBetween(1,5);
-        indices = new HashMap<>(numIndices);
-        for (int i = 0; i < numIndices; i++) {
-            String indexName = "index_" + randomAlphaOfLength(10);
-            int numJobs = randomIntBetween(1,5);
-            List<RollupJobCaps> jobs = new ArrayList<>(numJobs);
-            for (int j = 0; j < numJobs; j++) {
-                RollupJobConfig config = RollupJobConfigTests.randomRollupJobConfig(randomAlphaOfLength(10));
-                jobs.add(new RollupJobCaps(config.getId(), config.getIndexPattern(),
-                    config.getRollupIndex(), createRollupFieldCaps(config)));
-            }
-            RollableIndexCaps cap = new RollableIndexCaps(indexName, jobs);
-            indices.put(indexName, cap);
-        }
-    }
+public class GetRollupCapsResponseTests extends RollupCapsResponseTestCase<GetRollupCapsResponse> {
 
     @Override
     protected GetRollupCapsResponse createTestInstance() {
@@ -71,82 +32,16 @@ public class GetRollupCapsResponseTests extends AbstractXContentTestCase<GetRoll
     }
 
     @Override
-    protected boolean supportsUnknownFields() {
-        return false;
+    protected void toXContent(GetRollupCapsResponse response, XContentBuilder builder) throws IOException {
+        builder.startObject();
+        for (Map.Entry<String, RollableIndexCaps> entry : response.getJobs().entrySet()) {
+            entry.getValue().toXContent(builder, null);
+        }
+        builder.endObject();
     }
 
     @Override
-    protected GetRollupCapsResponse doParseInstance(final XContentParser parser) throws IOException {
+    protected GetRollupCapsResponse fromXContent(XContentParser parser) throws IOException {
         return GetRollupCapsResponse.fromXContent(parser);
     }
-
-    /**
-     * Lifted from core's RollupJobCaps, so that we can test without having to include this actual logic in the request
-     */
-    private static Map<String, RollupJobCaps.RollupFieldCaps> createRollupFieldCaps(final RollupJobConfig rollupJobConfig) {
-        final Map<String, List<Map<String, Object>>> tempFieldCaps = new HashMap<>();
-
-        final GroupConfig groupConfig = rollupJobConfig.getGroupConfig();
-        if (groupConfig != null) {
-            // Create RollupFieldCaps for the date histogram
-            final DateHistogramGroupConfig dateHistogram = groupConfig.getDateHistogram();
-            final Map<String, Object> dateHistogramAggCap = new HashMap<>();
-            dateHistogramAggCap.put("agg", DateHistogramAggregationBuilder.NAME);
-            dateHistogramAggCap.put("interval", dateHistogram.getInterval().toString());
-            if (dateHistogram.getDelay() != null) {
-                dateHistogramAggCap.put("delay", dateHistogram.getDelay().toString());
-            }
-            dateHistogramAggCap.put("time_zone", dateHistogram.getTimeZone());
-
-            List<Map<String, Object>> dateAggCaps = tempFieldCaps.getOrDefault(dateHistogram.getField(), new ArrayList<>());
-            dateAggCaps.add(dateHistogramAggCap);
-            tempFieldCaps.put(dateHistogram.getField(), dateAggCaps);
-
-            // Create RollupFieldCaps for the histogram
-            final HistogramGroupConfig histogram = groupConfig.getHistogram();
-            if (histogram != null) {
-                final Map<String, Object> histogramAggCap = new HashMap<>();
-                histogramAggCap.put("agg", HistogramAggregationBuilder.NAME);
-                histogramAggCap.put("interval", histogram.getInterval());
-                Arrays.stream(rollupJobConfig.getGroupConfig().getHistogram().getFields()).forEach(field -> {
-                    List<Map<String, Object>> caps = tempFieldCaps.getOrDefault(field, new ArrayList<>());
-                    caps.add(histogramAggCap);
-                    tempFieldCaps.put(field, caps);
-                });
-            }
-
-            // Create RollupFieldCaps for the term
-            final TermsGroupConfig terms = groupConfig.getTerms();
-            if (terms != null) {
-                final Map<String, Object> termsAggCap = singletonMap("agg", TermsAggregationBuilder.NAME);
-                Arrays.stream(rollupJobConfig.getGroupConfig().getTerms().getFields()).forEach(field -> {
-                    List<Map<String, Object>> caps = tempFieldCaps.getOrDefault(field, new ArrayList<>());
-                    caps.add(termsAggCap);
-                    tempFieldCaps.put(field, caps);
-                });
-            }
-        }
-
-        // Create RollupFieldCaps for the metrics
-        final List<MetricConfig> metricsConfig = rollupJobConfig.getMetricsConfig();
-        if (metricsConfig.size() > 0) {
-            rollupJobConfig.getMetricsConfig().forEach(metricConfig -> {
-                final List<Map<String, Object>> metrics = metricConfig.getMetrics().stream()
-                    .map(metric -> singletonMap("agg", (Object) metric))
-                    .collect(Collectors.toList());
-                metrics.forEach(m -> {
-                    List<Map<String, Object>> caps = tempFieldCaps
-                        .getOrDefault(metricConfig.getField(), new ArrayList<>());
-                    caps.add(m);
-                    tempFieldCaps.put(metricConfig.getField(), caps);
-                });
-            });
-        }
-
-        return Collections.unmodifiableMap(tempFieldCaps.entrySet()
-            .stream()
-            .collect(Collectors.toMap(Map.Entry::getKey,
-                e -> new RollupJobCaps.RollupFieldCaps(e.getValue()))));
-    }
-
 }

+ 38 - 0
client/rest-high-level/src/test/java/org/elasticsearch/client/rollup/GetRollupIndexCapsRequestTests.java

@@ -0,0 +1,38 @@
+/*
+ * 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.rollup;
+
+import org.elasticsearch.test.ESTestCase;
+
+import static org.hamcrest.Matchers.equalTo;
+
+public class GetRollupIndexCapsRequestTests extends ESTestCase {
+
+    public void testNullOrEmptyIndices() {
+        IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> new GetRollupIndexCapsRequest((String[]) null));
+        assertThat(e.getMessage(), equalTo("[indices] must not be null or empty"));
+
+        String[] indices = new String[]{};
+        e = expectThrows(IllegalArgumentException.class, () -> new GetRollupIndexCapsRequest(indices));
+        assertThat(e.getMessage(), equalTo("[indices] must not be null or empty"));
+
+        e = expectThrows(IllegalArgumentException.class, () -> new GetRollupIndexCapsRequest(new String[]{"foo", null}));
+        assertThat(e.getMessage(), equalTo("[index] must not be null or empty"));
+    }
+}

+ 47 - 0
client/rest-high-level/src/test/java/org/elasticsearch/client/rollup/GetRollupIndexCapsResponseTests.java

@@ -0,0 +1,47 @@
+/*
+ * 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.rollup;
+
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentParser;
+
+import java.io.IOException;
+import java.util.Map;
+
+public class GetRollupIndexCapsResponseTests extends RollupCapsResponseTestCase<GetRollupIndexCapsResponse> {
+
+    @Override
+    protected GetRollupIndexCapsResponse createTestInstance() {
+        return new GetRollupIndexCapsResponse(indices);
+    }
+
+    @Override
+    protected void toXContent(GetRollupIndexCapsResponse response, XContentBuilder builder) throws IOException {
+        builder.startObject();
+        for (Map.Entry<String, RollableIndexCaps> entry : response.getJobs().entrySet()) {
+            entry.getValue().toXContent(builder, null);
+        }
+        builder.endObject();
+    }
+
+    @Override
+    protected GetRollupIndexCapsResponse fromXContent(XContentParser parser) throws IOException {
+        return GetRollupIndexCapsResponse.fromXContent(parser);
+    }
+}

+ 156 - 0
client/rest-high-level/src/test/java/org/elasticsearch/client/rollup/RollupCapsResponseTestCase.java

@@ -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.rollup;
+
+import org.elasticsearch.client.rollup.job.config.DateHistogramGroupConfig;
+import org.elasticsearch.client.rollup.job.config.GroupConfig;
+import org.elasticsearch.client.rollup.job.config.HistogramGroupConfig;
+import org.elasticsearch.client.rollup.job.config.MetricConfig;
+import org.elasticsearch.client.rollup.job.config.RollupJobConfig;
+import org.elasticsearch.client.rollup.job.config.RollupJobConfigTests;
+import org.elasticsearch.client.rollup.job.config.TermsGroupConfig;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.search.aggregations.bucket.histogram.DateHistogramAggregationBuilder;
+import org.elasticsearch.search.aggregations.bucket.histogram.HistogramAggregationBuilder;
+import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
+import org.elasticsearch.test.ESTestCase;
+import org.junit.Before;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import static java.util.Collections.singletonMap;
+import static org.elasticsearch.test.AbstractXContentTestCase.xContentTester;
+
+abstract class RollupCapsResponseTestCase<T> extends ESTestCase {
+
+    protected Map<String, RollableIndexCaps> indices;
+
+    protected abstract T createTestInstance();
+
+    protected abstract void toXContent(T response, XContentBuilder builder) throws IOException;
+
+    protected abstract T fromXContent(XContentParser parser) throws IOException;
+
+    public void testFromXContent() throws IOException {
+        xContentTester(
+            this::createParser,
+            this::createTestInstance,
+            this::toXContent,
+            this::fromXContent)
+            .supportsUnknownFields(false)
+            .randomFieldsExcludeFilter(field ->
+                field.endsWith("job_id"))
+            .test();
+    }
+
+    @Before
+    private void setupIndices() throws IOException {
+        int numIndices = randomIntBetween(1,5);
+        indices = new HashMap<>(numIndices);
+        for (int i = 0; i < numIndices; i++) {
+            String indexName = "index_" + randomAlphaOfLength(10);
+            int numJobs = randomIntBetween(1,5);
+            List<RollupJobCaps> jobs = new ArrayList<>(numJobs);
+            for (int j = 0; j < numJobs; j++) {
+                RollupJobConfig config = RollupJobConfigTests.randomRollupJobConfig(randomAlphaOfLength(10));
+                jobs.add(new RollupJobCaps(config.getId(), config.getIndexPattern(),
+                    config.getRollupIndex(), createRollupFieldCaps(config)));
+            }
+            RollableIndexCaps cap = new RollableIndexCaps(indexName, jobs);
+            indices.put(indexName, cap);
+        }
+    }
+
+    /**
+     * Lifted from core's RollupJobCaps, so that we can test without having to include this actual logic in the request
+     */
+    private static Map<String, RollupJobCaps.RollupFieldCaps> createRollupFieldCaps(final RollupJobConfig rollupJobConfig) {
+        final Map<String, List<Map<String, Object>>> tempFieldCaps = new HashMap<>();
+
+        final GroupConfig groupConfig = rollupJobConfig.getGroupConfig();
+        if (groupConfig != null) {
+            // Create RollupFieldCaps for the date histogram
+            final DateHistogramGroupConfig dateHistogram = groupConfig.getDateHistogram();
+            final Map<String, Object> dateHistogramAggCap = new HashMap<>();
+            dateHistogramAggCap.put("agg", DateHistogramAggregationBuilder.NAME);
+            dateHistogramAggCap.put("interval", dateHistogram.getInterval().toString());
+            if (dateHistogram.getDelay() != null) {
+                dateHistogramAggCap.put("delay", dateHistogram.getDelay().toString());
+            }
+            dateHistogramAggCap.put("time_zone", dateHistogram.getTimeZone());
+
+            List<Map<String, Object>> dateAggCaps = tempFieldCaps.getOrDefault(dateHistogram.getField(), new ArrayList<>());
+            dateAggCaps.add(dateHistogramAggCap);
+            tempFieldCaps.put(dateHistogram.getField(), dateAggCaps);
+
+            // Create RollupFieldCaps for the histogram
+            final HistogramGroupConfig histogram = groupConfig.getHistogram();
+            if (histogram != null) {
+                final Map<String, Object> histogramAggCap = new HashMap<>();
+                histogramAggCap.put("agg", HistogramAggregationBuilder.NAME);
+                histogramAggCap.put("interval", histogram.getInterval());
+                Arrays.stream(rollupJobConfig.getGroupConfig().getHistogram().getFields()).forEach(field -> {
+                    List<Map<String, Object>> caps = tempFieldCaps.getOrDefault(field, new ArrayList<>());
+                    caps.add(histogramAggCap);
+                    tempFieldCaps.put(field, caps);
+                });
+            }
+
+            // Create RollupFieldCaps for the term
+            final TermsGroupConfig terms = groupConfig.getTerms();
+            if (terms != null) {
+                final Map<String, Object> termsAggCap = singletonMap("agg", TermsAggregationBuilder.NAME);
+                Arrays.stream(rollupJobConfig.getGroupConfig().getTerms().getFields()).forEach(field -> {
+                    List<Map<String, Object>> caps = tempFieldCaps.getOrDefault(field, new ArrayList<>());
+                    caps.add(termsAggCap);
+                    tempFieldCaps.put(field, caps);
+                });
+            }
+        }
+
+        // Create RollupFieldCaps for the metrics
+        final List<MetricConfig> metricsConfig = rollupJobConfig.getMetricsConfig();
+        if (metricsConfig.size() > 0) {
+            rollupJobConfig.getMetricsConfig().forEach(metricConfig -> {
+                final List<Map<String, Object>> metrics = metricConfig.getMetrics().stream()
+                    .map(metric -> singletonMap("agg", (Object) metric))
+                    .collect(Collectors.toList());
+                metrics.forEach(m -> {
+                    List<Map<String, Object>> caps = tempFieldCaps
+                        .getOrDefault(metricConfig.getField(), new ArrayList<>());
+                    caps.add(m);
+                    tempFieldCaps.put(metricConfig.getField(), caps);
+                });
+            });
+        }
+
+        return Collections.unmodifiableMap(tempFieldCaps.entrySet()
+            .stream()
+            .collect(Collectors.toMap(Map.Entry::getKey,
+                e -> new RollupJobCaps.RollupFieldCaps(e.getValue()))));
+    }
+}

+ 84 - 0
docs/java-rest/high-level/rollup/get_rollup_index_caps.asciidoc

@@ -0,0 +1,84 @@
+--
+:api: rollup-get-rollup-index-caps
+:request: GetRollupIndexCapsRequest
+:response: GetRollupIndexCapsResponse
+--
+
+[id="{upid}-x-pack-{api}"]
+=== Get Rollup Index Capabilities API
+
+The Get Rollup Index Capabilities API allows the user to determine if a concrete index or index pattern contains
+stored rollup jobs and data.  If it contains data stored from rollup jobs, the capabilities of those jobs
+are returned. The API accepts a `GetRollupIndexCapsRequest` object as a request and returns a `GetRollupIndexCapsResponse`.
+
+[id="{upid}-x-pack-{api}-request"]
+==== Get Rollup Index Capabilities Request
+
+A +{request}+ requires a single parameter: the target index or index pattern (e.g. `rollup-foo`):
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests-file}[x-pack-{api}-request]
+--------------------------------------------------
+
+[id="{upid}-x-pack-{api}-execution"]
+==== Execution
+
+The Get Rollup Index Capabilities API can be executed through a `RollupClient`
+instance. Such instance can be retrieved from a `RestHighLevelClient`
+using the `rollup()` method:
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests-file}[x-pack-{api}-execute]
+--------------------------------------------------
+
+[id="{upid}-x-pack-{api}-response"]
+==== Response
+
+The returned +{response}+ holds lists and maps of values which correspond to the capabilities
+of the rollup index/index pattern (what jobs are stored in the index, their capabilities, what
+aggregations are available, etc).  Because multiple jobs can be stored in one index, the
+response may include several jobs with different configurations.
+
+The capabilities are essentially the same as the original job configuration, just presented in a different
+manner. For example, if we had created a job with the following config:
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests-file}[x-pack-{api}-setup]
+--------------------------------------------------
+
+The +{response}+ object would contain the same information, laid out in a slightly different manner:
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests-file}[x-pack-{api}-response]
+--------------------------------------------------
+
+[id="{upid}-x-pack-{api}-async"]
+==== Asynchronous Execution
+
+This request can be executed asynchronously:
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests-file}[x-pack-{api}-execute-async]
+--------------------------------------------------
+<1> The +{request}+ to execute and the `ActionListener` to use when
+the execution completes
+
+The asynchronous method does not block and returns immediately. Once it is
+completed the `ActionListener` is called back using the `onResponse` method
+if the execution successfully completed or using the `onFailure` method if
+it failed.
+
+A typical listener for +{response}+ looks like:
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests-file}[x-pack-{api}-execute-listener]
+--------------------------------------------------
+<1> Called when the execution is successfully completed. The response is
+provided as an argument
+<2> Called in case of failure. The raised exception is provided as an argument

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

@@ -311,12 +311,14 @@ The Java High Level REST Client supports the following Rollup APIs:
 * <<{upid}-rollup-delete-job>>
 * <<java-rest-high-x-pack-rollup-get-job>>
 * <<{upid}-x-pack-rollup-get-rollup-caps>>
+* <<{upid}-x-pack-rollup-get-rollup-index-caps>>
 
 include::rollup/put_job.asciidoc[]
 include::rollup/start_job.asciidoc[]
 include::rollup/delete_job.asciidoc[]
 include::rollup/get_job.asciidoc[]
 include::rollup/get_rollup_caps.asciidoc[]
+include::rollup/get_rollup_index_caps.asciidoc[]
 
 == Security APIs