Browse Source

REST high-level client: add support for Rollover Index API (#28698)

Relates to #27205
Luca Cavanna 7 years ago
parent
commit
8bbb3c9ffa
43 changed files with 1026 additions and 370 deletions
  1. 25 1
      client/rest-high-level/src/main/java/org/elasticsearch/client/IndicesClient.java
  2. 16 3
      client/rest-high-level/src/main/java/org/elasticsearch/client/Request.java
  3. 60 0
      client/rest-high-level/src/test/java/org/elasticsearch/client/IndicesClientIT.java
  4. 41 3
      client/rest-high-level/src/test/java/org/elasticsearch/client/RequestTests.java
  5. 94 7
      client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/IndicesClientDocumentationIT.java
  6. 3 2
      docs/java-rest/high-level/indices/create_index.asciidoc
  7. 1 1
      docs/java-rest/high-level/indices/exists_alias.asciidoc
  8. 2 2
      docs/java-rest/high-level/indices/open_index.asciidoc
  9. 131 0
      docs/java-rest/high-level/indices/rollover.asciidoc
  10. 4 4
      docs/java-rest/high-level/indices/split_index.asciidoc
  11. 2 0
      docs/java-rest/high-level/supported-apis.asciidoc
  12. 7 5
      server/src/main/java/org/elasticsearch/action/admin/indices/create/CreateIndexRequest.java
  13. 9 28
      server/src/main/java/org/elasticsearch/action/admin/indices/create/CreateIndexResponse.java
  14. 1 1
      server/src/main/java/org/elasticsearch/action/admin/indices/delete/DeleteIndexResponse.java
  15. 8 29
      server/src/main/java/org/elasticsearch/action/admin/indices/open/OpenIndexResponse.java
  16. 21 17
      server/src/main/java/org/elasticsearch/action/admin/indices/rollover/Condition.java
  17. 7 0
      server/src/main/java/org/elasticsearch/action/admin/indices/rollover/MaxAgeCondition.java
  18. 6 0
      server/src/main/java/org/elasticsearch/action/admin/indices/rollover/MaxDocsCondition.java
  19. 7 0
      server/src/main/java/org/elasticsearch/action/admin/indices/rollover/MaxSizeCondition.java
  20. 72 59
      server/src/main/java/org/elasticsearch/action/admin/indices/rollover/RolloverRequest.java
  21. 1 1
      server/src/main/java/org/elasticsearch/action/admin/indices/rollover/RolloverRequestBuilder.java
  22. 79 63
      server/src/main/java/org/elasticsearch/action/admin/indices/rollover/RolloverResponse.java
  23. 8 7
      server/src/main/java/org/elasticsearch/action/admin/indices/rollover/TransportRolloverAction.java
  24. 2 2
      server/src/main/java/org/elasticsearch/action/support/master/AcknowledgedResponse.java
  25. 76 0
      server/src/main/java/org/elasticsearch/action/support/master/ShardsAcknowledgedResponse.java
  26. 1 1
      server/src/main/java/org/elasticsearch/rest/action/AcknowledgedRestListener.java
  27. 2 9
      server/src/main/java/org/elasticsearch/rest/action/admin/indices/RestCreateIndexAction.java
  28. 2 2
      server/src/main/java/org/elasticsearch/rest/action/admin/indices/RestDeleteIndexAction.java
  29. 2 9
      server/src/main/java/org/elasticsearch/rest/action/admin/indices/RestOpenIndexAction.java
  30. 3 2
      server/src/main/java/org/elasticsearch/rest/action/admin/indices/RestRolloverIndexAction.java
  31. 2 15
      server/src/main/java/org/elasticsearch/rest/action/admin/indices/RestShrinkIndexAction.java
  32. 2 15
      server/src/main/java/org/elasticsearch/rest/action/admin/indices/RestSplitIndexAction.java
  33. 12 4
      server/src/test/java/org/elasticsearch/action/admin/indices/create/CreateIndexRequestTests.java
  34. 13 0
      server/src/test/java/org/elasticsearch/action/admin/indices/create/CreateIndexResponseTests.java
  35. 19 4
      server/src/test/java/org/elasticsearch/action/admin/indices/rollover/ConditionTests.java
  36. 2 9
      server/src/test/java/org/elasticsearch/action/admin/indices/rollover/RolloverIT.java
  37. 106 30
      server/src/test/java/org/elasticsearch/action/admin/indices/rollover/RolloverRequestTests.java
  38. 132 0
      server/src/test/java/org/elasticsearch/action/admin/indices/rollover/RolloverResponseTests.java
  39. 32 31
      server/src/test/java/org/elasticsearch/action/admin/indices/rollover/TransportRolloverActionTests.java
  40. 4 0
      server/src/test/java/org/elasticsearch/action/admin/indices/shrink/ResizeRequestTests.java
  41. 1 1
      test/framework/src/main/java/org/elasticsearch/index/RandomCreateIndexGenerator.java
  42. 2 2
      test/framework/src/main/java/org/elasticsearch/test/AbstractStreamableTestCase.java
  43. 6 1
      test/framework/src/main/java/org/elasticsearch/test/AbstractStreamableXContentTestCase.java

+ 25 - 1
client/rest-high-level/src/main/java/org/elasticsearch/client/IndicesClient.java

@@ -35,6 +35,8 @@ import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest;
 import org.elasticsearch.action.admin.indices.mapping.put.PutMappingResponse;
 import org.elasticsearch.action.admin.indices.open.OpenIndexRequest;
 import org.elasticsearch.action.admin.indices.open.OpenIndexResponse;
+import org.elasticsearch.action.admin.indices.rollover.RolloverRequest;
+import org.elasticsearch.action.admin.indices.rollover.RolloverResponse;
 import org.elasticsearch.action.admin.indices.shrink.ResizeRequest;
 import org.elasticsearch.action.admin.indices.shrink.ResizeResponse;
 
@@ -272,7 +274,7 @@ public final class IndicesClient {
      * Splits an index using the Split Index API
      * <p>
      * See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-split-index.html">
-     * Shrink Index API on elastic.co</a>
+     * Split Index API on elastic.co</a>
      */
     public ResizeResponse split(ResizeRequest resizeRequest, Header... headers) throws IOException {
         return restHighLevelClient.performRequestAndParseEntity(resizeRequest, Request::split, ResizeResponse::fromXContent,
@@ -289,4 +291,26 @@ public final class IndicesClient {
         restHighLevelClient.performRequestAsyncAndParseEntity(resizeRequest, Request::split, ResizeResponse::fromXContent,
                 listener, emptySet(), headers);
     }
+
+    /**
+     * Rolls over an index using the Rollover Index API
+     * <p>
+     * See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-rollover-index.html">
+     * Rollover Index API on elastic.co</a>
+     */
+    public RolloverResponse rollover(RolloverRequest rolloverRequest, Header... headers) throws IOException {
+        return restHighLevelClient.performRequestAndParseEntity(rolloverRequest, Request::rollover, RolloverResponse::fromXContent,
+                emptySet(), headers);
+    }
+
+    /**
+     * Asynchronously rolls over an index using the Rollover Index API
+     * <p>
+     * See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-rollover-index.html">
+     * Rollover Index API on elastic.co</a>
+     */
+    public void rolloverAsync(RolloverRequest rolloverRequest, ActionListener<RolloverResponse> listener, Header... headers) {
+        restHighLevelClient.performRequestAsyncAndParseEntity(rolloverRequest, Request::rollover, RolloverResponse::fromXContent,
+                listener, emptySet(), headers);
+    }
 }

+ 16 - 3
client/rest-high-level/src/main/java/org/elasticsearch/client/Request.java

@@ -38,6 +38,7 @@ import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
 import org.elasticsearch.action.admin.indices.get.GetIndexRequest;
 import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest;
 import org.elasticsearch.action.admin.indices.open.OpenIndexRequest;
+import org.elasticsearch.action.admin.indices.rollover.RolloverRequest;
 import org.elasticsearch.action.admin.indices.shrink.ResizeRequest;
 import org.elasticsearch.action.admin.indices.shrink.ResizeType;
 import org.elasticsearch.action.bulk.BulkRequest;
@@ -498,11 +499,10 @@ public final class Request {
     }
 
     static Request rankEval(RankEvalRequest rankEvalRequest) throws IOException {
-        // TODO maybe indices should be propery of RankEvalRequest and not of the spec
+        // TODO maybe indices should be property of RankEvalRequest and not of the spec
         List<String> indices = rankEvalRequest.getRankEvalSpec().getIndices();
         String endpoint = endpoint(indices.toArray(new String[indices.size()]), Strings.EMPTY_ARRAY, "_rank_eval");
-        HttpEntity entity = null;
-        entity = createEntity(rankEvalRequest.getRankEvalSpec(), REQUEST_BODY_CONTENT_TYPE);
+        HttpEntity entity = createEntity(rankEvalRequest.getRankEvalSpec(), REQUEST_BODY_CONTENT_TYPE);
         return new Request(HttpGet.METHOD_NAME, endpoint, Collections.emptyMap(), entity);
     }
 
@@ -542,6 +542,19 @@ public final class Request {
         return new Request(HttpPut.METHOD_NAME, endpoint, parameters.getParams(), entity);
     }
 
+    static Request rollover(RolloverRequest rolloverRequest) throws IOException {
+        Params params = Params.builder();
+        params.withTimeout(rolloverRequest.timeout());
+        params.withMasterTimeout(rolloverRequest.masterNodeTimeout());
+        params.withWaitForActiveShards(rolloverRequest.getCreateIndexRequest().waitForActiveShards());
+        if (rolloverRequest.isDryRun()) {
+            params.putParam("dry_run", Boolean.TRUE.toString());
+        }
+        String endpoint = buildEndpoint(rolloverRequest.getAlias(), "_rollover", rolloverRequest.getNewIndexName());
+        HttpEntity entity = createEntity(rolloverRequest, REQUEST_BODY_CONTENT_TYPE);
+        return new Request(HttpPost.METHOD_NAME, endpoint, params.getParams(), entity);
+    }
+
     private static HttpEntity createEntity(ToXContent toXContent, XContentType xContentType) throws IOException {
         BytesRef source = XContentHelper.toXContent(toXContent, xContentType, false).toBytesRef();
         return new ByteArrayEntity(source.bytes, source.offset, source.length, createContentType(xContentType));

+ 60 - 0
client/rest-high-level/src/test/java/org/elasticsearch/client/IndicesClientIT.java

@@ -39,11 +39,18 @@ import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest;
 import org.elasticsearch.action.admin.indices.mapping.put.PutMappingResponse;
 import org.elasticsearch.action.admin.indices.open.OpenIndexRequest;
 import org.elasticsearch.action.admin.indices.open.OpenIndexResponse;
+import org.elasticsearch.action.admin.indices.rollover.RolloverRequest;
+import org.elasticsearch.action.admin.indices.rollover.RolloverResponse;
 import org.elasticsearch.action.admin.indices.shrink.ResizeRequest;
 import org.elasticsearch.action.admin.indices.shrink.ResizeResponse;
 import org.elasticsearch.action.admin.indices.shrink.ResizeType;
+import org.elasticsearch.action.index.IndexRequest;
 import org.elasticsearch.action.support.IndicesOptions;
+import org.elasticsearch.action.support.WriteRequest;
 import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.common.unit.ByteSizeUnit;
+import org.elasticsearch.common.unit.ByteSizeValue;
+import org.elasticsearch.common.unit.TimeValue;
 import org.elasticsearch.common.xcontent.XContentBuilder;
 import org.elasticsearch.common.xcontent.json.JsonXContent;
 import org.elasticsearch.common.xcontent.support.XContentMapValues;
@@ -435,4 +442,57 @@ public class IndicesClientIT extends ESRestHighLevelClientTestCase {
         Map<String, Object> aliasData = (Map<String, Object>)XContentMapValues.extractValue("target.aliases.alias", getIndexResponse);
         assertNotNull(aliasData);
     }
+
+    public void testRollover() throws IOException {
+        highLevelClient().indices().create(new CreateIndexRequest("test").alias(new Alias("alias")));
+        RolloverRequest rolloverRequest = new RolloverRequest("alias", "test_new");
+        rolloverRequest.addMaxIndexDocsCondition(1);
+
+        {
+            RolloverResponse rolloverResponse = execute(rolloverRequest, highLevelClient().indices()::rollover,
+                    highLevelClient().indices()::rolloverAsync);
+            assertFalse(rolloverResponse.isRolledOver());
+            assertFalse(rolloverResponse.isDryRun());
+            Map<String, Boolean> conditionStatus = rolloverResponse.getConditionStatus();
+            assertEquals(1, conditionStatus.size());
+            assertFalse(conditionStatus.get("[max_docs: 1]"));
+            assertEquals("test", rolloverResponse.getOldIndex());
+            assertEquals("test_new", rolloverResponse.getNewIndex());
+        }
+
+        highLevelClient().index(new IndexRequest("test", "type", "1").source("field", "value"));
+        highLevelClient().index(new IndexRequest("test", "type", "2").source("field", "value")
+                .setRefreshPolicy(WriteRequest.RefreshPolicy.WAIT_UNTIL));
+        //without the refresh the rollover may not happen as the number of docs seen may be off
+
+        {
+            rolloverRequest.addMaxIndexAgeCondition(new TimeValue(1));
+            rolloverRequest.dryRun(true);
+            RolloverResponse rolloverResponse = execute(rolloverRequest, highLevelClient().indices()::rollover,
+                    highLevelClient().indices()::rolloverAsync);
+            assertFalse(rolloverResponse.isRolledOver());
+            assertTrue(rolloverResponse.isDryRun());
+            Map<String, Boolean> conditionStatus = rolloverResponse.getConditionStatus();
+            assertEquals(2, conditionStatus.size());
+            assertTrue(conditionStatus.get("[max_docs: 1]"));
+            assertTrue(conditionStatus.get("[max_age: 1ms]"));
+            assertEquals("test", rolloverResponse.getOldIndex());
+            assertEquals("test_new", rolloverResponse.getNewIndex());
+        }
+        {
+            rolloverRequest.dryRun(false);
+            rolloverRequest.addMaxIndexSizeCondition(new ByteSizeValue(1, ByteSizeUnit.MB));
+            RolloverResponse rolloverResponse = execute(rolloverRequest, highLevelClient().indices()::rollover,
+                    highLevelClient().indices()::rolloverAsync);
+            assertTrue(rolloverResponse.isRolledOver());
+            assertFalse(rolloverResponse.isDryRun());
+            Map<String, Boolean> conditionStatus = rolloverResponse.getConditionStatus();
+            assertEquals(3, conditionStatus.size());
+            assertTrue(conditionStatus.get("[max_docs: 1]"));
+            assertTrue(conditionStatus.get("[max_age: 1ms]"));
+            assertFalse(conditionStatus.get("[max_size: 1mb]"));
+            assertEquals("test", rolloverResponse.getOldIndex());
+            assertEquals("test_new", rolloverResponse.getNewIndex());
+        }
+    }
 }

+ 41 - 3
client/rest-high-level/src/test/java/org/elasticsearch/client/RequestTests.java

@@ -40,6 +40,7 @@ import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
 import org.elasticsearch.action.admin.indices.get.GetIndexRequest;
 import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest;
 import org.elasticsearch.action.admin.indices.open.OpenIndexRequest;
+import org.elasticsearch.action.admin.indices.rollover.RolloverRequest;
 import org.elasticsearch.action.admin.indices.shrink.ResizeRequest;
 import org.elasticsearch.action.admin.indices.shrink.ResizeType;
 import org.elasticsearch.action.bulk.BulkRequest;
@@ -74,6 +75,7 @@ import org.elasticsearch.common.xcontent.XContentBuilder;
 import org.elasticsearch.common.xcontent.XContentHelper;
 import org.elasticsearch.common.xcontent.XContentParser;
 import org.elasticsearch.common.xcontent.XContentType;
+import org.elasticsearch.index.RandomCreateIndexGenerator;
 import org.elasticsearch.index.VersionType;
 import org.elasticsearch.index.query.TermQueryBuilder;
 import org.elasticsearch.index.rankeval.PrecisionAtK;
@@ -1116,11 +1118,9 @@ public class RequestTests extends ESTestCase {
             if (randomBoolean()) {
                 randomAliases(createIndexRequest);
             }
-            setRandomWaitForActiveShards(createIndexRequest::waitForActiveShards, expectedParams);
             resizeRequest.setTargetIndex(createIndexRequest);
-        } else {
-            setRandomWaitForActiveShards(resizeRequest::setWaitForActiveShards, expectedParams);
         }
+        setRandomWaitForActiveShards(resizeRequest::setWaitForActiveShards, expectedParams);
 
         Request request = function.apply(resizeRequest);
         assertEquals(HttpPut.METHOD_NAME, request.getMethod());
@@ -1144,6 +1144,44 @@ public class RequestTests extends ESTestCase {
         assertEquals(expectedParams, expectedRequest.getParameters());
     }
 
+    public void testRollover() throws IOException {
+        RolloverRequest rolloverRequest = new RolloverRequest(randomAlphaOfLengthBetween(3, 10),
+                randomBoolean() ? null : randomAlphaOfLengthBetween(3, 10));
+        Map<String, String> expectedParams = new HashMap<>();
+        setRandomTimeout(rolloverRequest::timeout, rolloverRequest.timeout(), expectedParams);
+        setRandomMasterTimeout(rolloverRequest, expectedParams);
+        if (randomBoolean()) {
+            rolloverRequest.dryRun(randomBoolean());
+            if (rolloverRequest.isDryRun()) {
+                expectedParams.put("dry_run", "true");
+            }
+        }
+        if (randomBoolean()) {
+            rolloverRequest.addMaxIndexAgeCondition(new TimeValue(randomNonNegativeLong()));
+        }
+        if (randomBoolean()) {
+            String type = randomAlphaOfLengthBetween(3, 10);
+            rolloverRequest.getCreateIndexRequest().mapping(type, RandomCreateIndexGenerator.randomMapping(type));
+        }
+        if (randomBoolean()) {
+            RandomCreateIndexGenerator.randomAliases(rolloverRequest.getCreateIndexRequest());
+        }
+        if (randomBoolean()) {
+            rolloverRequest.getCreateIndexRequest().settings(RandomCreateIndexGenerator.randomIndexSettings());
+        }
+        setRandomWaitForActiveShards(rolloverRequest.getCreateIndexRequest()::waitForActiveShards, expectedParams);
+
+        Request request = Request.rollover(rolloverRequest);
+        if (rolloverRequest.getNewIndexName() == null) {
+            assertEquals("/" + rolloverRequest.getAlias() + "/_rollover", request.getEndpoint());
+        } else {
+            assertEquals("/" + rolloverRequest.getAlias() + "/_rollover/" + rolloverRequest.getNewIndexName(), request.getEndpoint());
+        }
+        assertEquals(HttpPost.METHOD_NAME, request.getMethod());
+        assertToXContentBody(rolloverRequest, request.getEntity());
+        assertEquals(expectedParams, request.getParameters());
+    }
+
     private static void assertToXContentBody(ToXContent expectedBody, HttpEntity actualEntity) throws IOException {
         BytesReference expectedBytes = XContentHelper.toXContent(expectedBody, REQUEST_BODY_CONTENT_TYPE, false);
         assertEquals(XContentType.JSON.mediaTypeWithoutParameters(), actualEntity.getContentType().getValue());

+ 94 - 7
client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/IndicesClientDocumentationIT.java

@@ -38,6 +38,8 @@ import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest;
 import org.elasticsearch.action.admin.indices.mapping.put.PutMappingResponse;
 import org.elasticsearch.action.admin.indices.open.OpenIndexRequest;
 import org.elasticsearch.action.admin.indices.open.OpenIndexResponse;
+import org.elasticsearch.action.admin.indices.rollover.RolloverRequest;
+import org.elasticsearch.action.admin.indices.rollover.RolloverResponse;
 import org.elasticsearch.action.admin.indices.shrink.ResizeRequest;
 import org.elasticsearch.action.admin.indices.shrink.ResizeResponse;
 import org.elasticsearch.action.admin.indices.shrink.ResizeType;
@@ -46,6 +48,8 @@ import org.elasticsearch.action.support.IndicesOptions;
 import org.elasticsearch.client.ESRestHighLevelClientTestCase;
 import org.elasticsearch.client.RestHighLevelClient;
 import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.common.unit.ByteSizeUnit;
+import org.elasticsearch.common.unit.ByteSizeValue;
 import org.elasticsearch.common.unit.TimeValue;
 import org.elasticsearch.common.xcontent.XContentBuilder;
 import org.elasticsearch.common.xcontent.XContentFactory;
@@ -731,7 +735,6 @@ public class IndicesClientDocumentationIT extends ESRestHighLevelClientTestCase
         }
     }
 
-    @SuppressWarnings({"unchecked", "rawtypes"})
     public void testUpdateAliases() throws Exception {
         RestHighLevelClient client = highLevelClient();
 
@@ -811,12 +814,12 @@ public class IndicesClientDocumentationIT extends ESRestHighLevelClientTestCase
         }
     }
 
-    @SuppressWarnings({"unchecked", "rawtypes"})
     public void testShrinkIndex() throws Exception {
         RestHighLevelClient client = highLevelClient();
 
         {
             Map<String, Object> nodes = getAsMap("_nodes");
+            @SuppressWarnings("unchecked")
             String firstNode = ((Map<String, Object>) nodes.get("nodes")).keySet().iterator().next();
             createIndex("source_index", Settings.builder().put("index.number_of_shards", 4).put("index.number_of_replicas", 0).build());
             updateIndexSettings("source_index", Settings.builder().put("index.routing.allocation.require._name", firstNode)
@@ -836,8 +839,8 @@ public class IndicesClientDocumentationIT extends ESRestHighLevelClientTestCase
         request.masterNodeTimeout("1m"); // <2>
         // end::shrink-index-request-masterTimeout
         // tag::shrink-index-request-waitForActiveShards
-        request.getTargetIndexRequest().waitForActiveShards(2); // <1>
-        request.getTargetIndexRequest().waitForActiveShards(ActiveShardCount.DEFAULT); // <2>
+        request.setWaitForActiveShards(2); // <1>
+        request.setWaitForActiveShards(ActiveShardCount.DEFAULT); // <2>
         // end::shrink-index-request-waitForActiveShards
         // tag::shrink-index-request-settings
         request.getTargetIndexRequest().settings(Settings.builder().put("index.number_of_shards", 2)); // <1>
@@ -882,7 +885,6 @@ public class IndicesClientDocumentationIT extends ESRestHighLevelClientTestCase
         assertTrue(latch.await(30L, TimeUnit.SECONDS));
     }
 
-    @SuppressWarnings({"unchecked", "rawtypes"})
     public void testSplitIndex() throws Exception {
         RestHighLevelClient client = highLevelClient();
 
@@ -906,8 +908,8 @@ public class IndicesClientDocumentationIT extends ESRestHighLevelClientTestCase
         request.masterNodeTimeout("1m"); // <2>
         // end::split-index-request-masterTimeout
         // tag::split-index-request-waitForActiveShards
-        request.getTargetIndexRequest().waitForActiveShards(2); // <1>
-        request.getTargetIndexRequest().waitForActiveShards(ActiveShardCount.DEFAULT); // <2>
+        request.setWaitForActiveShards(2); // <1>
+        request.setWaitForActiveShards(ActiveShardCount.DEFAULT); // <2>
         // end::split-index-request-waitForActiveShards
         // tag::split-index-request-settings
         request.getTargetIndexRequest().settings(Settings.builder().put("index.number_of_shards", 4)); // <1>
@@ -951,4 +953,89 @@ public class IndicesClientDocumentationIT extends ESRestHighLevelClientTestCase
 
         assertTrue(latch.await(30L, TimeUnit.SECONDS));
     }
+
+    public void testRolloverIndex() throws Exception {
+        RestHighLevelClient client = highLevelClient();
+
+        {
+            client.indices().create(new CreateIndexRequest("index-1").alias(new Alias("alias")));
+        }
+
+        // tag::rollover-request
+        RolloverRequest request = new RolloverRequest("alias", "index-2"); // <1>
+        request.addMaxIndexAgeCondition(new TimeValue(7, TimeUnit.DAYS)); // <2>
+        request.addMaxIndexDocsCondition(1000); // <3>
+        request.addMaxIndexSizeCondition(new ByteSizeValue(5, ByteSizeUnit.GB)); // <4>
+        // end::rollover-request
+
+        // tag::rollover-request-timeout
+        request.timeout(TimeValue.timeValueMinutes(2)); // <1>
+        request.timeout("2m"); // <2>
+        // end::rollover-request-timeout
+        // tag::rollover-request-masterTimeout
+        request.masterNodeTimeout(TimeValue.timeValueMinutes(1)); // <1>
+        request.masterNodeTimeout("1m"); // <2>
+        // end::rollover-request-masterTimeout
+        // tag::rollover-request-dryRun
+        request.dryRun(true); // <1>
+        // end::rollover-request-dryRun
+        // tag::rollover-request-waitForActiveShards
+        request.getCreateIndexRequest().waitForActiveShards(2); // <1>
+        request.getCreateIndexRequest().waitForActiveShards(ActiveShardCount.DEFAULT); // <2>
+        // end::rollover-request-waitForActiveShards
+        // tag::rollover-request-settings
+        request.getCreateIndexRequest().settings(Settings.builder().put("index.number_of_shards", 4)); // <1>
+        // end::rollover-request-settings
+        // tag::rollover-request-mapping
+        request.getCreateIndexRequest().mapping("type", "field", "type=keyword"); // <1>
+        // end::rollover-request-mapping
+        // tag::rollover-request-alias
+        request.getCreateIndexRequest().alias(new Alias("another_alias")); // <1>
+        // end::rollover-request-alias
+
+        // tag::rollover-execute
+        RolloverResponse rolloverResponse = client.indices().rollover(request);
+        // end::rollover-execute
+
+        // tag::rollover-response
+        boolean acknowledged = rolloverResponse.isAcknowledged(); // <1>
+        boolean shardsAcked = rolloverResponse.isShardsAcknowledged(); // <2>
+        String oldIndex = rolloverResponse.getOldIndex(); // <3>
+        String newIndex = rolloverResponse.getNewIndex(); // <4>
+        boolean isRolledOver = rolloverResponse.isRolledOver(); // <5>
+        boolean isDryRun = rolloverResponse.isDryRun(); // <6>
+        Map<String, Boolean> conditionStatus = rolloverResponse.getConditionStatus();// <7>
+        // end::rollover-response
+        assertFalse(acknowledged);
+        assertFalse(shardsAcked);
+        assertEquals("index-1", oldIndex);
+        assertEquals("index-2", newIndex);
+        assertFalse(isRolledOver);
+        assertTrue(isDryRun);
+        assertEquals(3, conditionStatus.size());
+
+        // tag::rollover-execute-listener
+        ActionListener<RolloverResponse> listener = new ActionListener<RolloverResponse>() {
+            @Override
+            public void onResponse(RolloverResponse rolloverResponse) {
+                // <1>
+            }
+
+            @Override
+            public void onFailure(Exception e) {
+                // <2>
+            }
+        };
+        // end::rollover-execute-listener
+
+        // Replace the empty listener by a blocking listener in test
+        final CountDownLatch latch = new CountDownLatch(1);
+        listener = new LatchedActionListener<>(listener, latch);
+
+        // tag::rollover-execute-async
+        client.indices().rolloverAsync(request,listener); // <1>
+        // end::rollover-execute-async
+
+        assertTrue(latch.await(30L, TimeUnit.SECONDS));
+    }
 }

+ 3 - 2
docs/java-rest/high-level/indices/create_index.asciidoc

@@ -21,6 +21,7 @@ include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[create-index-reque
 --------------------------------------------------
 <1> Settings for this index
 
+[[java-rest-high-create-index-request-mappings]]
 ==== Index mappings
 An index may be created with mappings for its document types
 
@@ -98,9 +99,9 @@ include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[create-index-reque
 include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[create-index-request-waitForActiveShards]
 --------------------------------------------------
 <1> The number of active shard copies to wait for before the create index API returns a
-response, as an `int`.
+response, as an `int`
 <2> The number of active shard copies to wait for before the create index API returns a
-response, as an `ActiveShardCount`.
+response, as an `ActiveShardCount`
 
 [[java-rest-high-create-index-sync]]
 ==== Synchronous Execution

+ 1 - 1
docs/java-rest/high-level/indices/exists_alias.asciidoc

@@ -41,7 +41,7 @@ include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[exists-alias-reque
 --------------------------------------------------
 <1> The `local` flag (defaults to `false`) controls whether the aliases need
 to be looked up in the local cluster state or in the cluster state held by
-the elected master node.
+the elected master node
 
 [[java-rest-high-exists-alias-sync]]
 ==== Synchronous Execution

+ 2 - 2
docs/java-rest/high-level/indices/open_index.asciidoc

@@ -36,9 +36,9 @@ include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[open-index-request
 include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[open-index-request-waitForActiveShards]
 --------------------------------------------------
 <1> The number of active shard copies to wait for before the open index API
-returns a response, as an `int`.
+returns a response, as an `int`
 <2> The number of active shard copies to wait for before  the open index API
-returns a response, as an `ActiveShardCount`.
+returns a response, as an `ActiveShardCount`
 
 ["source","java",subs="attributes,callouts,macros"]
 --------------------------------------------------

+ 131 - 0
docs/java-rest/high-level/indices/rollover.asciidoc

@@ -0,0 +1,131 @@
+[[java-rest-high-rollover-index]]
+=== Rollover Index API
+
+[[java-rest-high-rollover-request]]
+==== Rollover Request
+
+The Rollover Index API requires a `RolloverRequest` instance.
+A `RolloverRequest` requires two string arguments at construction time, and
+one or more conditions that determine when the index has to be rolled over:
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[rollover-request]
+--------------------------------------------------
+<1> The alias (first argument) that points to the index to rollover, and
+optionally the name of the new index in case the rollover operation is performed
+<2> Condition on the age of the index
+<3> Condition on the number of documents in the index
+<4> Condition on the size of the index
+
+==== Optional arguments
+The following arguments can optionally be provided:
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[rollover-request-dryRun]
+--------------------------------------------------
+<1> Whether the rollover should be performed (default) or only simulated
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[rollover-request-timeout]
+--------------------------------------------------
+<1> Timeout to wait for the all the nodes to acknowledge the index is opened
+as a `TimeValue`
+<2> Timeout to wait for the all the nodes to acknowledge the index is opened
+as a `String`
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[rollover-request-masterTimeout]
+--------------------------------------------------
+<1> Timeout to connect to the master node as a `TimeValue`
+<2> Timeout to connect to the master node as a `String`
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[rollover-request-waitForActiveShards]
+--------------------------------------------------
+<1> The number of active shard copies to wait for before the rollover index API
+returns a response, as an `int`
+<2> The number of active shard copies to wait for before the rollover index API
+returns a response, as an `ActiveShardCount`
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[rollover-request-settings]
+--------------------------------------------------
+<1> Add the settings to apply to the new index, which include the number of
+shards to create for it
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[rollover-request-mapping]
+--------------------------------------------------
+<1> Add the mappings to associate the new index with. See <<java-rest-high-create-index-request-mappings>>
+for examples on the different ways to provide mappings
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[rollover-request-alias]
+--------------------------------------------------
+<1> Add the aliases to associate the new index with
+
+[[java-rest-high-rollover-sync]]
+==== Synchronous Execution
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[rollover-execute]
+--------------------------------------------------
+
+[[java-rest-high-rollover-async]]
+==== Asynchronous Execution
+
+The asynchronous execution of a rollover request requires both the `RolloverRequest`
+instance and an `ActionListener` instance to be passed to the asynchronous
+method:
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[rollover-execute-async]
+--------------------------------------------------
+<1> The `RolloverRequest` 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 `RolloverResponse` looks like:
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[rollover-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
+
+[[java-rest-high-rollover-response]]
+==== Rollover Response
+
+The returned `RolloverResponse` allows to retrieve information about the
+executed operation as follows:
+
+["source","java",subs="attributes,callouts,macros"]
+--------------------------------------------------
+include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[rollover-response]
+--------------------------------------------------
+<1> Indicates whether all of the nodes have acknowledged the request
+<2> Indicates whether the requisite number of shard copies were started for
+each shard in the index before timing out
+<3> The name of the old index, eventually rolled over
+<4> The name of the new index
+<5> Whether the index has been rolled over
+<6> Whether the operation was performed or it was a dry run
+<7> The different conditions and whether they were matched or not
+
+

+ 4 - 4
docs/java-rest/high-level/indices/split_index.asciidoc

@@ -38,22 +38,22 @@ include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[split-index-reques
 include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[split-index-request-waitForActiveShards]
 --------------------------------------------------
 <1> The number of active shard copies to wait for before the split index API
-returns a response, as an `int`.
+returns a response, as an `int`
 <2> The number of active shard copies to wait for before the split index API
-returns a response, as an `ActiveShardCount`.
+returns a response, as an `ActiveShardCount`
 
 ["source","java",subs="attributes,callouts,macros"]
 --------------------------------------------------
 include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[split-index-request-settings]
 --------------------------------------------------
 <1> The settings to apply to the target index, which include the number of
-shards to create for it.
+shards to create for it
 
 ["source","java",subs="attributes,callouts,macros"]
 --------------------------------------------------
 include-tagged::{doc-tests}/IndicesClientDocumentationIT.java[split-index-request-aliases]
 --------------------------------------------------
-<1> The aliases to associate the target index with.
+<1> The aliases to associate the target index with
 
 [[java-rest-high-split-index-sync]]
 ==== Synchronous Execution

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

@@ -52,6 +52,7 @@ Index Management::
 * <<java-rest-high-close-index>>
 * <<java-rest-high-shrink-index>>
 * <<java-rest-high-split-index>>
+* <<java-rest-high-rollover-index>>
 
 Mapping Management::
 * <<java-rest-high-put-mapping>>
@@ -67,6 +68,7 @@ include::indices/open_index.asciidoc[]
 include::indices/close_index.asciidoc[]
 include::indices/shrink_index.asciidoc[]
 include::indices/split_index.asciidoc[]
+include::indices/rollover.asciidoc[]
 include::indices/put_mapping.asciidoc[]
 include::indices/update_aliases.asciidoc[]
 include::indices/exists_alias.asciidoc[]

+ 7 - 5
server/src/main/java/org/elasticsearch/action/admin/indices/create/CreateIndexRequest.java

@@ -36,7 +36,6 @@ import org.elasticsearch.common.bytes.BytesReference;
 import org.elasticsearch.common.collect.MapBuilder;
 import org.elasticsearch.common.io.stream.StreamInput;
 import org.elasticsearch.common.io.stream.StreamOutput;
-import org.elasticsearch.common.logging.DeprecationLogger;
 import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.common.xcontent.DeprecationHandler;
 import org.elasticsearch.common.xcontent.LoggingDeprecationHandler;
@@ -57,9 +56,9 @@ import java.util.Objects;
 import java.util.Set;
 
 import static org.elasticsearch.action.ValidateActions.addValidationError;
+import static org.elasticsearch.common.settings.Settings.Builder.EMPTY_SETTINGS;
 import static org.elasticsearch.common.settings.Settings.readSettingsFromStream;
 import static org.elasticsearch.common.settings.Settings.writeSettingsToStream;
-import static org.elasticsearch.common.settings.Settings.Builder.EMPTY_SETTINGS;
 
 /**
  * A request to create an index. Best created with {@link org.elasticsearch.client.Requests#createIndexRequest(String)}.
@@ -72,7 +71,7 @@ import static org.elasticsearch.common.settings.Settings.Builder.EMPTY_SETTINGS;
  */
 public class CreateIndexRequest extends AcknowledgedRequest<CreateIndexRequest> implements IndicesRequest, ToXContentObject {
 
-    private static final ParseField MAPPINGS = new ParseField("mappings");
+    public static final ParseField MAPPINGS = new ParseField("mappings");
     public static final ParseField SETTINGS = new ParseField("settings");
     public static final ParseField ALIASES = new ParseField("aliases");
 
@@ -525,7 +524,12 @@ public class CreateIndexRequest extends AcknowledgedRequest<CreateIndexRequest>
     @Override
     public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
         builder.startObject();
+        innerToXContent(builder, params);
+        builder.endObject();
+        return builder;
+    }
 
+    public XContentBuilder innerToXContent(XContentBuilder builder, Params params) throws IOException {
         builder.startObject(SETTINGS.getPreferredName());
         settings.toXContent(builder, params);
         builder.endObject();
@@ -545,8 +549,6 @@ public class CreateIndexRequest extends AcknowledgedRequest<CreateIndexRequest>
         for (Map.Entry<String, IndexMetaData.Custom> entry : customs.entrySet()) {
             builder.field(entry.getKey(), entry.getValue(), params);
         }
-
-        builder.endObject();
         return builder;
     }
 }

+ 9 - 28
server/src/main/java/org/elasticsearch/action/admin/indices/create/CreateIndexResponse.java

@@ -20,7 +20,7 @@
 package org.elasticsearch.action.admin.indices.create;
 
 import org.elasticsearch.Version;
-import org.elasticsearch.action.support.master.AcknowledgedResponse;
+import org.elasticsearch.action.support.master.ShardsAcknowledgedResponse;
 import org.elasticsearch.common.ParseField;
 import org.elasticsearch.common.io.stream.StreamInput;
 import org.elasticsearch.common.io.stream.StreamOutput;
@@ -37,9 +37,8 @@ import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constru
 /**
  * A response for a create index action.
  */
-public class CreateIndexResponse extends AcknowledgedResponse implements ToXContentObject {
+public class CreateIndexResponse extends ShardsAcknowledgedResponse implements ToXContentObject {
 
-    private static final ParseField SHARDS_ACKNOWLEDGED = new ParseField("shards_acknowledged");
     private static final ParseField INDEX = new ParseField("index");
 
     private static final ConstructingObjectParser<CreateIndexResponse, Void> PARSER = new ConstructingObjectParser<>("create_index",
@@ -50,22 +49,17 @@ public class CreateIndexResponse extends AcknowledgedResponse implements ToXCont
     }
 
     protected static <T extends CreateIndexResponse> void declareFields(ConstructingObjectParser<T, Void> objectParser) {
-        declareAcknowledgedField(objectParser);
-        objectParser.declareField(constructorArg(), (parser, context) -> parser.booleanValue(), SHARDS_ACKNOWLEDGED,
-                ObjectParser.ValueType.BOOLEAN);
-        objectParser.declareField(constructorArg(), (parser, context) -> parser.text(), INDEX, ObjectParser.ValueType.STRING);
+        declareAcknowledgedAndShardsAcknowledgedFields(objectParser);
+        objectParser.declareField(constructorArg(), (parser, context) -> parser.textOrNull(), INDEX, ObjectParser.ValueType.STRING_OR_NULL);
     }
 
-    private boolean shardsAcknowledged;
     private String index;
 
     protected CreateIndexResponse() {
     }
 
     protected CreateIndexResponse(boolean acknowledged, boolean shardsAcknowledged, String index) {
-        super(acknowledged);
-        assert acknowledged || shardsAcknowledged == false; // if its not acknowledged, then shardsAcknowledged should be false too
-        this.shardsAcknowledged = shardsAcknowledged;
+        super(acknowledged, shardsAcknowledged);
         this.index = index;
     }
 
@@ -73,7 +67,7 @@ public class CreateIndexResponse extends AcknowledgedResponse implements ToXCont
     public void readFrom(StreamInput in) throws IOException {
         super.readFrom(in);
         readAcknowledged(in);
-        shardsAcknowledged = in.readBoolean();
+        readShardsAcknowledged(in);
         if (in.getVersion().onOrAfter(Version.V_5_6_0)) {
             index = in.readString();
         }
@@ -83,35 +77,22 @@ public class CreateIndexResponse extends AcknowledgedResponse implements ToXCont
     public void writeTo(StreamOutput out) throws IOException {
         super.writeTo(out);
         writeAcknowledged(out);
-        out.writeBoolean(shardsAcknowledged);
+        writeShardsAcknowledged(out);
         if (out.getVersion().onOrAfter(Version.V_5_6_0)) {
             out.writeString(index);
         }
     }
 
-    /**
-     * Returns true if the requisite number of shards were started before
-     * returning from the index creation operation. If {@link #isAcknowledged()}
-     * is false, then this also returns false.
-     */
-    public boolean isShardsAcknowledged() {
-        return shardsAcknowledged;
-    }
-
     public String index() {
         return index;
     }
 
-    public void addCustomFields(XContentBuilder builder) throws IOException {
-        builder.field(SHARDS_ACKNOWLEDGED.getPreferredName(), isShardsAcknowledged());
-        builder.field(INDEX.getPreferredName(), index());
-    }
-
     @Override
     public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
         builder.startObject();
         addAcknowledgedField(builder);
-        addCustomFields(builder);
+        addShardsAcknowledgedField(builder);
+        builder.field(INDEX.getPreferredName(), index());
         builder.endObject();
         return builder;
     }

+ 1 - 1
server/src/main/java/org/elasticsearch/action/admin/indices/delete/DeleteIndexResponse.java

@@ -68,7 +68,7 @@ public class DeleteIndexResponse extends AcknowledgedResponse implements ToXCont
         return builder;
     }
 
-    public static DeleteIndexResponse fromXContent(XContentParser parser) throws IOException {
+    public static DeleteIndexResponse fromXContent(XContentParser parser) {
         return PARSER.apply(parser, null);
     }
 }

+ 8 - 29
server/src/main/java/org/elasticsearch/action/admin/indices/open/OpenIndexResponse.java

@@ -20,54 +20,33 @@
 package org.elasticsearch.action.admin.indices.open;
 
 import org.elasticsearch.Version;
-import org.elasticsearch.action.support.master.AcknowledgedResponse;
-import org.elasticsearch.common.ParseField;
+import org.elasticsearch.action.support.master.ShardsAcknowledgedResponse;
 import org.elasticsearch.common.io.stream.StreamInput;
 import org.elasticsearch.common.io.stream.StreamOutput;
 import org.elasticsearch.common.xcontent.ConstructingObjectParser;
-import org.elasticsearch.common.xcontent.ObjectParser;
 import org.elasticsearch.common.xcontent.ToXContentObject;
 import org.elasticsearch.common.xcontent.XContentBuilder;
 import org.elasticsearch.common.xcontent.XContentParser;
 
 import java.io.IOException;
 
-import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg;
-
 /**
  * A response for a open index action.
  */
-public class OpenIndexResponse extends AcknowledgedResponse implements ToXContentObject {
-    private static final String SHARDS_ACKNOWLEDGED = "shards_acknowledged";
-    private static final ParseField SHARDS_ACKNOWLEDGED_PARSER = new ParseField(SHARDS_ACKNOWLEDGED);
+public class OpenIndexResponse extends ShardsAcknowledgedResponse implements ToXContentObject {
 
     private static final ConstructingObjectParser<OpenIndexResponse, Void> PARSER = new ConstructingObjectParser<>("open_index", true,
             args -> new OpenIndexResponse((boolean) args[0], (boolean) args[1]));
 
     static {
-        declareAcknowledgedField(PARSER);
-        PARSER.declareField(constructorArg(), (parser, context) -> parser.booleanValue(), SHARDS_ACKNOWLEDGED_PARSER,
-                ObjectParser.ValueType.BOOLEAN);
+        declareAcknowledgedAndShardsAcknowledgedFields(PARSER);
     }
 
-    private boolean shardsAcknowledged;
-
     OpenIndexResponse() {
     }
 
     OpenIndexResponse(boolean acknowledged, boolean shardsAcknowledged) {
-        super(acknowledged);
-        assert acknowledged || shardsAcknowledged == false; // if its not acknowledged, then shards acked should be false too
-        this.shardsAcknowledged = shardsAcknowledged;
-    }
-
-    /**
-     * Returns true if the requisite number of shards were started before
-     * returning from the indices opening operation.  If {@link #isAcknowledged()}
-     * is false, then this also returns false.
-     */
-    public boolean isShardsAcknowledged() {
-        return shardsAcknowledged;
+        super(acknowledged, shardsAcknowledged);
     }
 
     @Override
@@ -75,7 +54,7 @@ public class OpenIndexResponse extends AcknowledgedResponse implements ToXConten
         super.readFrom(in);
         readAcknowledged(in);
         if (in.getVersion().onOrAfter(Version.V_6_1_0)) {
-            shardsAcknowledged = in.readBoolean();
+            readShardsAcknowledged(in);
         }
     }
 
@@ -84,7 +63,7 @@ public class OpenIndexResponse extends AcknowledgedResponse implements ToXConten
         super.writeTo(out);
         writeAcknowledged(out);
         if (out.getVersion().onOrAfter(Version.V_6_1_0)) {
-            out.writeBoolean(shardsAcknowledged);
+            writeShardsAcknowledged(out);
         }
     }
 
@@ -92,12 +71,12 @@ public class OpenIndexResponse extends AcknowledgedResponse implements ToXConten
     public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
         builder.startObject();
         addAcknowledgedField(builder);
-        builder.field(SHARDS_ACKNOWLEDGED, isShardsAcknowledged());
+        addShardsAcknowledgedField(builder);
         builder.endObject();
         return builder;
     }
 
-    public static OpenIndexResponse fromXContent(XContentParser parser) throws IOException {
+    public static OpenIndexResponse fromXContent(XContentParser parser) {
         return PARSER.apply(parser, null);
     }
 }

+ 21 - 17
server/src/main/java/org/elasticsearch/action/admin/indices/rollover/Condition.java

@@ -20,30 +20,16 @@
 package org.elasticsearch.action.admin.indices.rollover;
 
 import org.elasticsearch.Version;
-import org.elasticsearch.common.ParseField;
 import org.elasticsearch.common.io.stream.NamedWriteable;
 import org.elasticsearch.common.unit.ByteSizeValue;
-import org.elasticsearch.common.unit.TimeValue;
-import org.elasticsearch.common.xcontent.ObjectParser;
+import org.elasticsearch.common.xcontent.ToXContentFragment;
 
-import java.util.Set;
+import java.util.Objects;
 
 /**
  * Base class for rollover request conditions
  */
-public abstract class Condition<T> implements NamedWriteable {
-
-    public static ObjectParser<Set<Condition>, Void> PARSER = new ObjectParser<>("conditions", null);
-    static {
-        PARSER.declareString((conditions, s) ->
-            conditions.add(new MaxAgeCondition(TimeValue.parseTimeValue(s, MaxAgeCondition.NAME))),
-            new ParseField(MaxAgeCondition.NAME));
-        PARSER.declareLong((conditions, value) ->
-            conditions.add(new MaxDocsCondition(value)), new ParseField(MaxDocsCondition.NAME));
-        PARSER.declareString((conditions, s) ->
-                conditions.add(new MaxSizeCondition(ByteSizeValue.parseBytesSizeValue(s, MaxSizeCondition.NAME))),
-            new ParseField(MaxSizeCondition.NAME));
-    }
+public abstract class Condition<T> implements NamedWriteable, ToXContentFragment {
 
     protected T value;
     protected final String name;
@@ -62,6 +48,24 @@ public abstract class Condition<T> implements NamedWriteable {
         return true;
     }
 
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        Condition<?> condition = (Condition<?>) o;
+        return Objects.equals(value, condition.value) &&
+                Objects.equals(name, condition.name);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(value, name);
+    }
+
     @Override
     public final String toString() {
         return "[" + name + ": " + value + "]";

+ 7 - 0
server/src/main/java/org/elasticsearch/action/admin/indices/rollover/MaxAgeCondition.java

@@ -22,6 +22,7 @@ package org.elasticsearch.action.admin.indices.rollover;
 import org.elasticsearch.common.io.stream.StreamInput;
 import org.elasticsearch.common.io.stream.StreamOutput;
 import org.elasticsearch.common.unit.TimeValue;
+import org.elasticsearch.common.xcontent.XContentBuilder;
 
 import java.io.IOException;
 
@@ -55,6 +56,12 @@ public class MaxAgeCondition extends Condition<TimeValue> {
 
     @Override
     public void writeTo(StreamOutput out) throws IOException {
+        //TODO here we should just use TimeValue#writeTo and same for de-serialization in the constructor, we lose information this way
         out.writeLong(value.getMillis());
     }
+
+    @Override
+    public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
+        return builder.field(NAME, value.getStringRep());
+    }
 }

+ 6 - 0
server/src/main/java/org/elasticsearch/action/admin/indices/rollover/MaxDocsCondition.java

@@ -21,6 +21,7 @@ package org.elasticsearch.action.admin.indices.rollover;
 
 import org.elasticsearch.common.io.stream.StreamInput;
 import org.elasticsearch.common.io.stream.StreamOutput;
+import org.elasticsearch.common.xcontent.XContentBuilder;
 
 import java.io.IOException;
 
@@ -55,4 +56,9 @@ public class MaxDocsCondition extends Condition<Long> {
     public void writeTo(StreamOutput out) throws IOException {
         out.writeLong(value);
     }
+
+    @Override
+    public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
+        return builder.field(NAME, value);
+    }
 }

+ 7 - 0
server/src/main/java/org/elasticsearch/action/admin/indices/rollover/MaxSizeCondition.java

@@ -24,6 +24,7 @@ import org.elasticsearch.common.io.stream.StreamInput;
 import org.elasticsearch.common.io.stream.StreamOutput;
 import org.elasticsearch.common.unit.ByteSizeUnit;
 import org.elasticsearch.common.unit.ByteSizeValue;
+import org.elasticsearch.common.xcontent.XContentBuilder;
 
 import java.io.IOException;
 
@@ -61,6 +62,12 @@ public class MaxSizeCondition extends Condition<ByteSizeValue> {
 
     @Override
     public void writeTo(StreamOutput out) throws IOException {
+        //TODO here we should just use ByteSizeValue#writeTo and same for de-serialization in the constructor
         out.writeVLong(value.getBytes());
     }
+
+    @Override
+    public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
+        return builder.field(NAME, value.getStringRep());
+    }
 }

+ 72 - 59
server/src/main/java/org/elasticsearch/action/admin/indices/rollover/RolloverRequest.java

@@ -21,7 +21,6 @@ package org.elasticsearch.action.admin.indices.rollover;
 import org.elasticsearch.action.ActionRequestValidationException;
 import org.elasticsearch.action.IndicesRequest;
 import org.elasticsearch.action.admin.indices.create.CreateIndexRequest;
-import org.elasticsearch.action.support.ActiveShardCount;
 import org.elasticsearch.action.support.IndicesOptions;
 import org.elasticsearch.action.support.master.AcknowledgedRequest;
 import org.elasticsearch.common.ParseField;
@@ -30,40 +29,57 @@ import org.elasticsearch.common.io.stream.StreamOutput;
 import org.elasticsearch.common.unit.ByteSizeValue;
 import org.elasticsearch.common.unit.TimeValue;
 import org.elasticsearch.common.xcontent.ObjectParser;
+import org.elasticsearch.common.xcontent.ToXContentObject;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentParser;
 
 import java.io.IOException;
-import java.util.HashSet;
+import java.util.HashMap;
 import java.util.Map;
-import java.util.Objects;
-import java.util.Set;
 
 import static org.elasticsearch.action.ValidateActions.addValidationError;
 
 /**
  * Request class to swap index under an alias upon satisfying conditions
  */
-public class RolloverRequest extends AcknowledgedRequest<RolloverRequest> implements IndicesRequest {
+public class RolloverRequest extends AcknowledgedRequest<RolloverRequest> implements IndicesRequest, ToXContentObject {
+
+    private static final ObjectParser<RolloverRequest, Void> PARSER = new ObjectParser<>("rollover");
+    private static final ObjectParser<Map<String, Condition>, Void> CONDITION_PARSER = new ObjectParser<>("conditions");
+
+    private static final ParseField CONDITIONS = new ParseField("conditions");
+    private static final ParseField MAX_AGE_CONDITION = new ParseField(MaxAgeCondition.NAME);
+    private static final ParseField MAX_DOCS_CONDITION = new ParseField(MaxDocsCondition.NAME);
+    private static final ParseField MAX_SIZE_CONDITION = new ParseField(MaxSizeCondition.NAME);
 
-    public static final ObjectParser<RolloverRequest, Void> PARSER = new ObjectParser<>("conditions", null);
     static {
-        PARSER.declareField((parser, request, context) -> Condition.PARSER.parse(parser, request.conditions, null),
-            new ParseField("conditions"), ObjectParser.ValueType.OBJECT);
+        CONDITION_PARSER.declareString((conditions, s) ->
+                conditions.put(MaxAgeCondition.NAME, new MaxAgeCondition(TimeValue.parseTimeValue(s, MaxAgeCondition.NAME))),
+                MAX_AGE_CONDITION);
+        CONDITION_PARSER.declareLong((conditions, value) ->
+                conditions.put(MaxDocsCondition.NAME, new MaxDocsCondition(value)), MAX_DOCS_CONDITION);
+        CONDITION_PARSER.declareString((conditions, s) ->
+                conditions.put(MaxSizeCondition.NAME, new MaxSizeCondition(ByteSizeValue.parseBytesSizeValue(s, MaxSizeCondition.NAME))),
+                MAX_SIZE_CONDITION);
+
+        PARSER.declareField((parser, request, context) -> CONDITION_PARSER.parse(parser, request.conditions, null),
+            CONDITIONS, ObjectParser.ValueType.OBJECT);
         PARSER.declareField((parser, request, context) -> request.createIndexRequest.settings(parser.map()),
-            new ParseField("settings"), ObjectParser.ValueType.OBJECT);
+            CreateIndexRequest.SETTINGS, ObjectParser.ValueType.OBJECT);
         PARSER.declareField((parser, request, context) -> {
             for (Map.Entry<String, Object> mappingsEntry : parser.map().entrySet()) {
-                request.createIndexRequest.mapping(mappingsEntry.getKey(),
-                    (Map<String, Object>) mappingsEntry.getValue());
+                request.createIndexRequest.mapping(mappingsEntry.getKey(), (Map<String, Object>) mappingsEntry.getValue());
             }
-        }, new ParseField("mappings"), ObjectParser.ValueType.OBJECT);
+        }, CreateIndexRequest.MAPPINGS, ObjectParser.ValueType.OBJECT);
         PARSER.declareField((parser, request, context) -> request.createIndexRequest.aliases(parser.map()),
-            new ParseField("aliases"), ObjectParser.ValueType.OBJECT);
+            CreateIndexRequest.ALIASES, ObjectParser.ValueType.OBJECT);
     }
 
     private String alias;
     private String newIndexName;
     private boolean dryRun;
-    private Set<Condition> conditions = new HashSet<>(2);
+    private Map<String, Condition> conditions = new HashMap<>(2);
+    //the index name "_na_" is never read back, what matters are settings, mappings and aliases
     private CreateIndexRequest createIndexRequest = new CreateIndexRequest("_na_");
 
     RolloverRequest() {}
@@ -75,13 +91,10 @@ public class RolloverRequest extends AcknowledgedRequest<RolloverRequest> implem
 
     @Override
     public ActionRequestValidationException validate() {
-        ActionRequestValidationException validationException = createIndexRequest == null ? null : createIndexRequest.validate();
+        ActionRequestValidationException validationException = createIndexRequest.validate();
         if (alias == null) {
             validationException = addValidationError("index alias is missing", validationException);
         }
-        if (createIndexRequest == null) {
-            validationException = addValidationError("create index request is missing", validationException);
-        }
         return validationException;
     }
 
@@ -93,7 +106,8 @@ public class RolloverRequest extends AcknowledgedRequest<RolloverRequest> implem
         dryRun = in.readBoolean();
         int size = in.readVInt();
         for (int i = 0; i < size; i++) {
-            this.conditions.add(in.readNamedWriteable(Condition.class));
+            Condition condition = in.readNamedWriteable(Condition.class);
+            this.conditions.put(condition.name, condition);
         }
         createIndexRequest = new CreateIndexRequest();
         createIndexRequest.readFrom(in);
@@ -106,7 +120,7 @@ public class RolloverRequest extends AcknowledgedRequest<RolloverRequest> implem
         out.writeOptionalString(newIndexName);
         out.writeBoolean(dryRun);
         out.writeVInt(conditions.size());
-        for (Condition condition : conditions) {
+        for (Condition condition : conditions.values()) {
             if (condition.includedInVersion(out.getVersion())) {
                 out.writeNamedWriteable(condition);
             }
@@ -148,76 +162,75 @@ public class RolloverRequest extends AcknowledgedRequest<RolloverRequest> implem
      * Adds condition to check if the index is at least <code>age</code> old
      */
     public void addMaxIndexAgeCondition(TimeValue age) {
-        this.conditions.add(new MaxAgeCondition(age));
+        MaxAgeCondition maxAgeCondition = new MaxAgeCondition(age);
+        if (this.conditions.containsKey(maxAgeCondition.name)) {
+            throw new IllegalArgumentException(maxAgeCondition.name + " condition is already set");
+        }
+        this.conditions.put(maxAgeCondition.name, maxAgeCondition);
     }
 
     /**
      * Adds condition to check if the index has at least <code>numDocs</code>
      */
     public void addMaxIndexDocsCondition(long numDocs) {
-        this.conditions.add(new MaxDocsCondition(numDocs));
+        MaxDocsCondition maxDocsCondition = new MaxDocsCondition(numDocs);
+        if (this.conditions.containsKey(maxDocsCondition.name)) {
+            throw new IllegalArgumentException(maxDocsCondition.name + " condition is already set");
+        }
+        this.conditions.put(maxDocsCondition.name, maxDocsCondition);
     }
 
     /**
      * Adds a size-based condition to check if the index size is at least <code>size</code>.
      */
     public void addMaxIndexSizeCondition(ByteSizeValue size) {
-        this.conditions.add(new MaxSizeCondition(size));
+        MaxSizeCondition maxSizeCondition = new MaxSizeCondition(size);
+        if (this.conditions.containsKey(maxSizeCondition.name)) {
+            throw new IllegalArgumentException(maxSizeCondition + " condition is already set");
+        }
+        this.conditions.put(maxSizeCondition.name, maxSizeCondition);
     }
 
-    /**
-     * Sets rollover index creation request to override index settings when
-     * the rolled over index has to be created
-     */
-    public void setCreateIndexRequest(CreateIndexRequest createIndexRequest) {
-        this.createIndexRequest = Objects.requireNonNull(createIndexRequest, "create index request must not be null");;
-    }
 
-    boolean isDryRun() {
+    public boolean isDryRun() {
         return dryRun;
     }
 
-    Set<Condition> getConditions() {
+    Map<String, Condition> getConditions() {
         return conditions;
     }
 
-    String getAlias() {
+    public String getAlias() {
         return alias;
     }
 
-    String getNewIndexName() {
+    public String getNewIndexName() {
         return newIndexName;
     }
 
-    CreateIndexRequest getCreateIndexRequest() {
-        return createIndexRequest;
-    }
-
     /**
-     * Sets the number of shard copies that should be active for creation of the
-     * new rollover index to return. Defaults to {@link ActiveShardCount#DEFAULT}, which will
-     * wait for one shard copy (the primary) to become active. Set this value to
-     * {@link ActiveShardCount#ALL} to wait for all shards (primary and all replicas) to be active
-     * before returning. Otherwise, use {@link ActiveShardCount#from(int)} to set this value to any
-     * non-negative integer, up to the number of copies per shard (number of replicas + 1),
-     * to wait for the desired amount of shard copies to become active before returning.
-     * Index creation will only wait up until the timeout value for the number of shard copies
-     * to be active before returning.  Check {@link RolloverResponse#isShardsAcknowledged()} to
-     * determine if the requisite shard copies were all started before returning or timing out.
-     *
-     * @param waitForActiveShards number of active shard copies to wait on
+     * Returns the inner {@link CreateIndexRequest}. Allows to configure mappings, settings and aliases for the new index.
      */
-    public void setWaitForActiveShards(ActiveShardCount waitForActiveShards) {
-        this.createIndexRequest.waitForActiveShards(waitForActiveShards);
+    public CreateIndexRequest getCreateIndexRequest() {
+        return createIndexRequest;
     }
 
-    /**
-     * A shortcut for {@link #setWaitForActiveShards(ActiveShardCount)} where the numerical
-     * shard count is passed in, instead of having to first call {@link ActiveShardCount#from(int)}
-     * to get the ActiveShardCount.
-     */
-    public void setWaitForActiveShards(final int waitForActiveShards) {
-        setWaitForActiveShards(ActiveShardCount.from(waitForActiveShards));
+    @Override
+    public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
+        builder.startObject();
+        createIndexRequest.innerToXContent(builder, params);
+
+        builder.startObject(CONDITIONS.getPreferredName());
+        for (Condition condition : conditions.values()) {
+            condition.toXContent(builder, params);
+        }
+        builder.endObject();
+
+        builder.endObject();
+        return builder;
     }
 
+    public void fromXContent(XContentParser parser) throws IOException {
+        PARSER.parse(parser, this, null);
+    }
 }

+ 1 - 1
server/src/main/java/org/elasticsearch/action/admin/indices/rollover/RolloverRequestBuilder.java

@@ -93,7 +93,7 @@ public class RolloverRequestBuilder extends MasterNodeOperationRequestBuilder<Ro
      * @param waitForActiveShards number of active shard copies to wait on
      */
     public RolloverRequestBuilder waitForActiveShards(ActiveShardCount waitForActiveShards) {
-        this.request.setWaitForActiveShards(waitForActiveShards);
+        this.request.getCreateIndexRequest().waitForActiveShards(waitForActiveShards);
         return this;
     }
 

+ 79 - 63
server/src/main/java/org/elasticsearch/action/admin/indices/rollover/RolloverResponse.java

@@ -19,51 +19,62 @@
 
 package org.elasticsearch.action.admin.indices.rollover;
 
-import org.elasticsearch.action.ActionResponse;
+import org.elasticsearch.action.support.master.ShardsAcknowledgedResponse;
+import org.elasticsearch.common.ParseField;
 import org.elasticsearch.common.io.stream.StreamInput;
 import org.elasticsearch.common.io.stream.StreamOutput;
+import org.elasticsearch.common.xcontent.ConstructingObjectParser;
+import org.elasticsearch.common.xcontent.ObjectParser;
 import org.elasticsearch.common.xcontent.ToXContentObject;
 import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentParser;
 
 import java.io.IOException;
-import java.util.AbstractMap;
-import java.util.HashSet;
+import java.util.HashMap;
 import java.util.Map;
-import java.util.Set;
-import java.util.stream.Collectors;
-
-public final class RolloverResponse extends ActionResponse implements ToXContentObject {
-
-    private static final String NEW_INDEX = "new_index";
-    private static final String OLD_INDEX = "old_index";
-    private static final String DRY_RUN = "dry_run";
-    private static final String ROLLED_OVER = "rolled_over";
-    private static final String CONDITIONS = "conditions";
-    private static final String ACKNOWLEDGED = "acknowledged";
-    private static final String SHARDS_ACKED = "shards_acknowledged";
+import java.util.Objects;
+
+import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg;
+
+public final class RolloverResponse extends ShardsAcknowledgedResponse implements ToXContentObject {
+
+    private static final ParseField NEW_INDEX = new ParseField("new_index");
+    private static final ParseField OLD_INDEX = new ParseField("old_index");
+    private static final ParseField DRY_RUN = new ParseField("dry_run");
+    private static final ParseField ROLLED_OVER = new ParseField("rolled_over");
+    private static final ParseField CONDITIONS = new ParseField("conditions");
+
+    @SuppressWarnings("unchecked")
+    private static final ConstructingObjectParser<RolloverResponse, Void> PARSER = new ConstructingObjectParser<>("rollover",
+            true, args -> new RolloverResponse((String) args[0], (String) args[1], (Map<String,Boolean>) args[2],
+            (Boolean)args[3], (Boolean)args[4], (Boolean) args[5], (Boolean) args[6]));
+
+    static {
+        PARSER.declareField(constructorArg(), (parser, context) -> parser.text(), OLD_INDEX, ObjectParser.ValueType.STRING);
+        PARSER.declareField(constructorArg(), (parser, context) -> parser.text(), NEW_INDEX, ObjectParser.ValueType.STRING);
+        PARSER.declareObject(constructorArg(), (parser, context) -> parser.map(), CONDITIONS);
+        PARSER.declareField(constructorArg(), (parser, context) -> parser.booleanValue(), DRY_RUN, ObjectParser.ValueType.BOOLEAN);
+        PARSER.declareField(constructorArg(), (parser, context) -> parser.booleanValue(), ROLLED_OVER, ObjectParser.ValueType.BOOLEAN);
+        declareAcknowledgedAndShardsAcknowledgedFields(PARSER);
+    }
 
     private String oldIndex;
     private String newIndex;
-    private Set<Map.Entry<String, Boolean>> conditionStatus;
+    private Map<String, Boolean> conditionStatus;
     private boolean dryRun;
     private boolean rolledOver;
-    private boolean acknowledged;
-    private boolean shardsAcknowledged;
 
     RolloverResponse() {
     }
 
-    RolloverResponse(String oldIndex, String newIndex, Set<Condition.Result> conditionResults,
-                     boolean dryRun, boolean rolledOver, boolean acknowledged, boolean shardsAcknowledged) {
+    RolloverResponse(String oldIndex, String newIndex, Map<String, Boolean> conditionResults,
+                             boolean dryRun, boolean rolledOver, boolean acknowledged, boolean shardsAcknowledged) {
+        super(acknowledged, shardsAcknowledged);
         this.oldIndex = oldIndex;
         this.newIndex = newIndex;
         this.dryRun = dryRun;
         this.rolledOver = rolledOver;
-        this.acknowledged = acknowledged;
-        this.shardsAcknowledged = shardsAcknowledged;
-        this.conditionStatus = conditionResults.stream()
-            .map(result -> new AbstractMap.SimpleEntry<>(result.condition.toString(), result.matched))
-            .collect(Collectors.toSet());
+        this.conditionStatus = conditionResults;
     }
 
     /**
@@ -83,7 +94,7 @@ public final class RolloverResponse extends ActionResponse implements ToXContent
     /**
      * Returns the statuses of all the request conditions
      */
-    public Set<Map.Entry<String, Boolean>> getConditionStatus() {
+    public Map<String, Boolean> getConditionStatus() {
         return conditionStatus;
     }
 
@@ -101,42 +112,20 @@ public final class RolloverResponse extends ActionResponse implements ToXContent
         return rolledOver;
     }
 
-    /**
-     * Returns true if the creation of the new rollover index and switching of the
-     * alias to the newly created index was successful, and returns false otherwise.
-     * If {@link #isDryRun()} is true, then this will also return false. If this
-     * returns false, then {@link #isShardsAcknowledged()} will also return false.
-     */
-    public boolean isAcknowledged() {
-        return acknowledged;
-    }
-
-    /**
-     * Returns true if the requisite number of shards were started in the newly
-     * created rollover index before returning. If {@link #isAcknowledged()} is
-     * false, then this will also return false.
-     */
-    public boolean isShardsAcknowledged() {
-        return shardsAcknowledged;
-    }
-
     @Override
     public void readFrom(StreamInput in) throws IOException {
         super.readFrom(in);
         oldIndex = in.readString();
         newIndex = in.readString();
         int conditionSize = in.readVInt();
-        Set<Map.Entry<String, Boolean>> conditions = new HashSet<>(conditionSize);
+        conditionStatus = new HashMap<>(conditionSize);
         for (int i = 0; i < conditionSize; i++) {
-            String condition = in.readString();
-            boolean satisfied = in.readBoolean();
-            conditions.add(new AbstractMap.SimpleEntry<>(condition, satisfied));
+            conditionStatus.put(in.readString(), in.readBoolean());
         }
-        conditionStatus = conditions;
         dryRun = in.readBoolean();
         rolledOver = in.readBoolean();
-        acknowledged = in.readBoolean();
-        shardsAcknowledged = in.readBoolean();
+        readAcknowledged(in);
+        readShardsAcknowledged(in);
     }
 
     @Override
@@ -145,31 +134,58 @@ public final class RolloverResponse extends ActionResponse implements ToXContent
         out.writeString(oldIndex);
         out.writeString(newIndex);
         out.writeVInt(conditionStatus.size());
-        for (Map.Entry<String, Boolean> entry : conditionStatus) {
+        for (Map.Entry<String, Boolean> entry : conditionStatus.entrySet()) {
             out.writeString(entry.getKey());
             out.writeBoolean(entry.getValue());
         }
         out.writeBoolean(dryRun);
         out.writeBoolean(rolledOver);
-        out.writeBoolean(acknowledged);
-        out.writeBoolean(shardsAcknowledged);
+        writeAcknowledged(out);
+        writeShardsAcknowledged(out);
     }
 
     @Override
     public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
         builder.startObject();
-        builder.field(OLD_INDEX, oldIndex);
-        builder.field(NEW_INDEX, newIndex);
-        builder.field(ROLLED_OVER, rolledOver);
-        builder.field(DRY_RUN, dryRun);
-        builder.field(ACKNOWLEDGED, acknowledged);
-        builder.field(SHARDS_ACKED, shardsAcknowledged);
-        builder.startObject(CONDITIONS);
-        for (Map.Entry<String, Boolean> entry : conditionStatus) {
+        builder.field(OLD_INDEX.getPreferredName(), oldIndex);
+        builder.field(NEW_INDEX.getPreferredName(), newIndex);
+        builder.field(ROLLED_OVER.getPreferredName(), rolledOver);
+        builder.field(DRY_RUN.getPreferredName(), dryRun);
+        addAcknowledgedField(builder);
+        addShardsAcknowledgedField(builder);
+        builder.startObject(CONDITIONS.getPreferredName());
+        for (Map.Entry<String, Boolean> entry : conditionStatus.entrySet()) {
             builder.field(entry.getKey(), entry.getValue());
         }
         builder.endObject();
         builder.endObject();
         return builder;
     }
+
+    public static RolloverResponse fromXContent(XContentParser parser) {
+        return PARSER.apply(parser, null);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        RolloverResponse that = (RolloverResponse) o;
+        return isAcknowledged() == that.isAcknowledged() &&
+                isShardsAcknowledged() == that.isShardsAcknowledged() &&
+                dryRun == that.dryRun &&
+                rolledOver == that.rolledOver &&
+                Objects.equals(oldIndex, that.oldIndex) &&
+                Objects.equals(newIndex, that.newIndex) &&
+                Objects.equals(conditionStatus, that.conditionStatus);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(isAcknowledged(), isShardsAcknowledged(), oldIndex, newIndex, conditionStatus, dryRun, rolledOver);
+    }
 }

+ 8 - 7
server/src/main/java/org/elasticsearch/action/admin/indices/rollover/TransportRolloverAction.java

@@ -51,9 +51,10 @@ import org.elasticsearch.threadpool.ThreadPool;
 import org.elasticsearch.transport.TransportService;
 
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.List;
 import java.util.Locale;
-import java.util.Set;
+import java.util.Map;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
 
@@ -122,7 +123,7 @@ public class TransportRolloverAction extends TransportMasterNodeAction<RolloverR
             new ActionListener<IndicesStatsResponse>() {
                 @Override
                 public void onResponse(IndicesStatsResponse statsResponse) {
-                    final Set<Condition.Result> conditionResults = evaluateConditions(rolloverRequest.getConditions(),
+                    final Map<String, Boolean> conditionResults = evaluateConditions(rolloverRequest.getConditions().values(),
                         metaData.index(sourceIndexName), statsResponse);
 
                     if (rolloverRequest.isDryRun()) {
@@ -130,7 +131,7 @@ public class TransportRolloverAction extends TransportMasterNodeAction<RolloverR
                             new RolloverResponse(sourceIndexName, rolloverIndexName, conditionResults, true, false, false, false));
                         return;
                     }
-                    if (conditionResults.size() == 0 || conditionResults.stream().anyMatch(result -> result.matched)) {
+                    if (conditionResults.size() == 0 || conditionResults.values().stream().anyMatch(result -> result)) {
                         CreateIndexClusterStateUpdateRequest updateRequest = prepareCreateIndexRequest(unresolvedName, rolloverIndexName,
                             rolloverRequest);
                         createIndexService.createIndex(updateRequest, ActionListener.wrap(createIndexClusterStateUpdateResponse -> {
@@ -197,17 +198,17 @@ public class TransportRolloverAction extends TransportMasterNodeAction<RolloverR
         }
     }
 
-    static Set<Condition.Result> evaluateConditions(final Set<Condition> conditions,
-                                                    final DocsStats docsStats, final IndexMetaData metaData) {
+    static Map<String, Boolean> evaluateConditions(final Collection<Condition> conditions,
+                                                   final DocsStats docsStats, final IndexMetaData metaData) {
         final long numDocs = docsStats == null ? 0 : docsStats.getCount();
         final long indexSize = docsStats == null ? 0 : docsStats.getTotalSizeInBytes();
         final Condition.Stats stats = new Condition.Stats(numDocs, metaData.getCreationDate(), new ByteSizeValue(indexSize));
         return conditions.stream()
             .map(condition -> condition.evaluate(stats))
-            .collect(Collectors.toSet());
+            .collect(Collectors.toMap(result -> result.condition.toString(), result -> result.matched));
     }
 
-    static Set<Condition.Result> evaluateConditions(final Set<Condition> conditions, final IndexMetaData metaData,
+    static Map<String, Boolean> evaluateConditions(final Collection<Condition> conditions, final IndexMetaData metaData,
                                                     final IndicesStatsResponse statsResponse) {
         return evaluateConditions(conditions, statsResponse.getPrimaries().getDocs(), metaData);
     }

+ 2 - 2
server/src/main/java/org/elasticsearch/action/support/master/AcknowledgedResponse.java

@@ -62,14 +62,14 @@ public abstract class AcknowledgedResponse extends ActionResponse {
     }
 
     /**
-     * Reads the timeout value
+     * Reads the acknowledged value
      */
     protected void readAcknowledged(StreamInput in) throws IOException {
         acknowledged = in.readBoolean();
     }
 
     /**
-     * Writes the timeout value
+     * Writes the acknowledged value
      */
     protected void writeAcknowledged(StreamOutput out) throws IOException {
         out.writeBoolean(acknowledged);

+ 76 - 0
server/src/main/java/org/elasticsearch/action/support/master/ShardsAcknowledgedResponse.java

@@ -0,0 +1,76 @@
+/*
+ * 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.action.support.master;
+
+import org.elasticsearch.common.ParseField;
+import org.elasticsearch.common.io.stream.StreamInput;
+import org.elasticsearch.common.io.stream.StreamOutput;
+import org.elasticsearch.common.xcontent.ConstructingObjectParser;
+import org.elasticsearch.common.xcontent.ObjectParser;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+
+import java.io.IOException;
+
+import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg;
+
+public abstract class ShardsAcknowledgedResponse extends AcknowledgedResponse {
+
+    private static final ParseField SHARDS_ACKNOWLEDGED = new ParseField("shards_acknowledged");
+
+    protected static <T extends ShardsAcknowledgedResponse> void declareAcknowledgedAndShardsAcknowledgedFields(
+            ConstructingObjectParser<T, Void> objectParser) {
+        declareAcknowledgedField(objectParser);
+        objectParser.declareField(constructorArg(), (parser, context) -> parser.booleanValue(), SHARDS_ACKNOWLEDGED,
+                ObjectParser.ValueType.BOOLEAN);
+    }
+
+    private boolean shardsAcknowledged;
+
+
+    protected ShardsAcknowledgedResponse() {
+    }
+
+    protected ShardsAcknowledgedResponse(boolean acknowledged, boolean shardsAcknowledged) {
+        super(acknowledged);
+        assert acknowledged || shardsAcknowledged == false; // if it's not acknowledged, then shards acked should be false too
+        this.shardsAcknowledged = shardsAcknowledged;
+    }
+
+    /**
+     * Returns true if the requisite number of shards were started before
+     * returning from the index creation operation. If {@link #isAcknowledged()}
+     * is false, then this also returns false.
+     */
+    public boolean isShardsAcknowledged() {
+        return shardsAcknowledged;
+    }
+
+    protected void readShardsAcknowledged(StreamInput in) throws IOException {
+        shardsAcknowledged = in.readBoolean();
+    }
+
+    protected void writeShardsAcknowledged(StreamOutput out) throws IOException {
+        out.writeBoolean(shardsAcknowledged);
+    }
+
+    protected void addShardsAcknowledgedField(XContentBuilder builder) throws IOException {
+        builder.field(SHARDS_ACKNOWLEDGED.getPreferredName(), isShardsAcknowledged());
+    }
+}

+ 1 - 1
server/src/main/java/org/elasticsearch/rest/action/AcknowledgedRestListener.java

@@ -28,6 +28,7 @@ import java.io.IOException;
 
 import static org.elasticsearch.rest.RestStatus.OK;
 
+//TODO once all the responses that use this class implement ToXContent we can move to RestToXContentListener and remove this class
 public class AcknowledgedRestListener<T extends AcknowledgedResponse> extends RestBuilderListener<T> {
 
     public AcknowledgedRestListener(RestChannel channel) {
@@ -36,7 +37,6 @@ public class AcknowledgedRestListener<T extends AcknowledgedResponse> extends Re
 
     @Override
     public RestResponse buildResponse(T response, XContentBuilder builder) throws Exception {
-        // TODO - Once AcknowledgedResponse implements ToXContent, this method should be updated to call response.toXContent.
         builder.startObject()
                 .field(Fields.ACKNOWLEDGED, response.isAcknowledged());
         addCustomFields(builder, response);

+ 2 - 9
server/src/main/java/org/elasticsearch/rest/action/admin/indices/RestCreateIndexAction.java

@@ -20,15 +20,13 @@
 package org.elasticsearch.rest.action.admin.indices;
 
 import org.elasticsearch.action.admin.indices.create.CreateIndexRequest;
-import org.elasticsearch.action.admin.indices.create.CreateIndexResponse;
 import org.elasticsearch.action.support.ActiveShardCount;
 import org.elasticsearch.client.node.NodeClient;
 import org.elasticsearch.common.settings.Settings;
-import org.elasticsearch.common.xcontent.XContentBuilder;
 import org.elasticsearch.rest.BaseRestHandler;
 import org.elasticsearch.rest.RestController;
 import org.elasticsearch.rest.RestRequest;
-import org.elasticsearch.rest.action.AcknowledgedRestListener;
+import org.elasticsearch.rest.action.RestToXContentListener;
 
 import java.io.IOException;
 
@@ -52,11 +50,6 @@ public class RestCreateIndexAction extends BaseRestHandler {
         createIndexRequest.timeout(request.paramAsTime("timeout", createIndexRequest.timeout()));
         createIndexRequest.masterNodeTimeout(request.paramAsTime("master_timeout", createIndexRequest.masterNodeTimeout()));
         createIndexRequest.waitForActiveShards(ActiveShardCount.parseString(request.param("wait_for_active_shards")));
-        return channel -> client.admin().indices().create(createIndexRequest, new AcknowledgedRestListener<CreateIndexResponse>(channel) {
-            @Override
-            public void addCustomFields(XContentBuilder builder, CreateIndexResponse response) throws IOException {
-                response.addCustomFields(builder);
-            }
-        });
+        return channel -> client.admin().indices().create(createIndexRequest, new RestToXContentListener<>(channel));
     }
 }

+ 2 - 2
server/src/main/java/org/elasticsearch/rest/action/admin/indices/RestDeleteIndexAction.java

@@ -27,7 +27,7 @@ import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.rest.BaseRestHandler;
 import org.elasticsearch.rest.RestController;
 import org.elasticsearch.rest.RestRequest;
-import org.elasticsearch.rest.action.AcknowledgedRestListener;
+import org.elasticsearch.rest.action.RestToXContentListener;
 
 import java.io.IOException;
 
@@ -49,6 +49,6 @@ public class RestDeleteIndexAction extends BaseRestHandler {
         deleteIndexRequest.timeout(request.paramAsTime("timeout", deleteIndexRequest.timeout()));
         deleteIndexRequest.masterNodeTimeout(request.paramAsTime("master_timeout", deleteIndexRequest.masterNodeTimeout()));
         deleteIndexRequest.indicesOptions(IndicesOptions.fromRequest(request, deleteIndexRequest.indicesOptions()));
-        return channel -> client.admin().indices().delete(deleteIndexRequest, new AcknowledgedRestListener<>(channel));
+        return channel -> client.admin().indices().delete(deleteIndexRequest, new RestToXContentListener<>(channel));
     }
 }

+ 2 - 9
server/src/main/java/org/elasticsearch/rest/action/admin/indices/RestOpenIndexAction.java

@@ -20,17 +20,15 @@
 package org.elasticsearch.rest.action.admin.indices;
 
 import org.elasticsearch.action.admin.indices.open.OpenIndexRequest;
-import org.elasticsearch.action.admin.indices.open.OpenIndexResponse;
 import org.elasticsearch.action.support.ActiveShardCount;
 import org.elasticsearch.action.support.IndicesOptions;
 import org.elasticsearch.client.node.NodeClient;
 import org.elasticsearch.common.Strings;
 import org.elasticsearch.common.settings.Settings;
-import org.elasticsearch.common.xcontent.XContentBuilder;
 import org.elasticsearch.rest.BaseRestHandler;
 import org.elasticsearch.rest.RestController;
 import org.elasticsearch.rest.RestRequest;
-import org.elasticsearch.rest.action.AcknowledgedRestListener;
+import org.elasticsearch.rest.action.RestToXContentListener;
 
 import java.io.IOException;
 
@@ -56,11 +54,6 @@ public class RestOpenIndexAction extends BaseRestHandler {
         if (waitForActiveShards != null) {
             openIndexRequest.waitForActiveShards(ActiveShardCount.parseString(waitForActiveShards));
         }
-        return channel -> client.admin().indices().open(openIndexRequest, new AcknowledgedRestListener<OpenIndexResponse>(channel) {
-            @Override
-            protected void addCustomFields(XContentBuilder builder, OpenIndexResponse response) throws IOException {
-                builder.field("shards_acknowledged", response.isShardsAcknowledged());
-            }
-        });
+        return channel -> client.admin().indices().open(openIndexRequest, new RestToXContentListener<>(channel));
     }
 }

+ 3 - 2
server/src/main/java/org/elasticsearch/rest/action/admin/indices/RestRolloverIndexAction.java

@@ -45,11 +45,12 @@ public class RestRolloverIndexAction extends BaseRestHandler {
     @Override
     public RestChannelConsumer prepareRequest(final RestRequest request, final NodeClient client) throws IOException {
         RolloverRequest rolloverIndexRequest = new RolloverRequest(request.param("index"), request.param("new_index"));
-        request.applyContentParser(parser -> RolloverRequest.PARSER.parse(parser, rolloverIndexRequest, null));
+        request.applyContentParser(rolloverIndexRequest::fromXContent);
         rolloverIndexRequest.dryRun(request.paramAsBoolean("dry_run", false));
         rolloverIndexRequest.timeout(request.paramAsTime("timeout", rolloverIndexRequest.timeout()));
         rolloverIndexRequest.masterNodeTimeout(request.paramAsTime("master_timeout", rolloverIndexRequest.masterNodeTimeout()));
-        rolloverIndexRequest.setWaitForActiveShards(ActiveShardCount.parseString(request.param("wait_for_active_shards")));
+        rolloverIndexRequest.getCreateIndexRequest().waitForActiveShards(
+                ActiveShardCount.parseString(request.param("wait_for_active_shards")));
         return channel -> client.admin().indices().rolloverIndex(rolloverIndexRequest, new RestToXContentListener<>(channel));
     }
 }

+ 2 - 15
server/src/main/java/org/elasticsearch/rest/action/admin/indices/RestShrinkIndexAction.java

@@ -20,16 +20,14 @@
 package org.elasticsearch.rest.action.admin.indices;
 
 import org.elasticsearch.action.admin.indices.shrink.ResizeRequest;
-import org.elasticsearch.action.admin.indices.shrink.ResizeResponse;
 import org.elasticsearch.action.admin.indices.shrink.ResizeType;
 import org.elasticsearch.action.support.ActiveShardCount;
 import org.elasticsearch.client.node.NodeClient;
 import org.elasticsearch.common.settings.Settings;
-import org.elasticsearch.common.xcontent.XContentBuilder;
 import org.elasticsearch.rest.BaseRestHandler;
 import org.elasticsearch.rest.RestController;
 import org.elasticsearch.rest.RestRequest;
-import org.elasticsearch.rest.action.AcknowledgedRestListener;
+import org.elasticsearch.rest.action.RestToXContentListener;
 
 import java.io.IOException;
 
@@ -47,23 +45,12 @@ public class RestShrinkIndexAction extends BaseRestHandler {
 
     @Override
     public RestChannelConsumer prepareRequest(final RestRequest request, final NodeClient client) throws IOException {
-        if (request.param("target") == null) {
-            throw new IllegalArgumentException("no target index");
-        }
-        if (request.param("index") == null) {
-            throw new IllegalArgumentException("no source index");
-        }
         ResizeRequest shrinkIndexRequest = new ResizeRequest(request.param("target"), request.param("index"));
         shrinkIndexRequest.setResizeType(ResizeType.SHRINK);
         request.applyContentParser(shrinkIndexRequest::fromXContent);
         shrinkIndexRequest.timeout(request.paramAsTime("timeout", shrinkIndexRequest.timeout()));
         shrinkIndexRequest.masterNodeTimeout(request.paramAsTime("master_timeout", shrinkIndexRequest.masterNodeTimeout()));
         shrinkIndexRequest.setWaitForActiveShards(ActiveShardCount.parseString(request.param("wait_for_active_shards")));
-        return channel -> client.admin().indices().resizeIndex(shrinkIndexRequest, new AcknowledgedRestListener<ResizeResponse>(channel) {
-            @Override
-            public void addCustomFields(XContentBuilder builder, ResizeResponse response) throws IOException {
-                response.addCustomFields(builder);
-            }
-        });
+        return channel -> client.admin().indices().resizeIndex(shrinkIndexRequest, new RestToXContentListener<>(channel));
     }
 }

+ 2 - 15
server/src/main/java/org/elasticsearch/rest/action/admin/indices/RestSplitIndexAction.java

@@ -20,16 +20,14 @@
 package org.elasticsearch.rest.action.admin.indices;
 
 import org.elasticsearch.action.admin.indices.shrink.ResizeRequest;
-import org.elasticsearch.action.admin.indices.shrink.ResizeResponse;
 import org.elasticsearch.action.admin.indices.shrink.ResizeType;
 import org.elasticsearch.action.support.ActiveShardCount;
 import org.elasticsearch.client.node.NodeClient;
 import org.elasticsearch.common.settings.Settings;
-import org.elasticsearch.common.xcontent.XContentBuilder;
 import org.elasticsearch.rest.BaseRestHandler;
 import org.elasticsearch.rest.RestController;
 import org.elasticsearch.rest.RestRequest;
-import org.elasticsearch.rest.action.AcknowledgedRestListener;
+import org.elasticsearch.rest.action.RestToXContentListener;
 
 import java.io.IOException;
 
@@ -47,23 +45,12 @@ public class RestSplitIndexAction extends BaseRestHandler {
 
     @Override
     public RestChannelConsumer prepareRequest(final RestRequest request, final NodeClient client) throws IOException {
-        if (request.param("target") == null) {
-            throw new IllegalArgumentException("no target index");
-        }
-        if (request.param("index") == null) {
-            throw new IllegalArgumentException("no source index");
-        }
         ResizeRequest shrinkIndexRequest = new ResizeRequest(request.param("target"), request.param("index"));
         shrinkIndexRequest.setResizeType(ResizeType.SPLIT);
         request.applyContentParser(shrinkIndexRequest::fromXContent);
         shrinkIndexRequest.timeout(request.paramAsTime("timeout", shrinkIndexRequest.timeout()));
         shrinkIndexRequest.masterNodeTimeout(request.paramAsTime("master_timeout", shrinkIndexRequest.masterNodeTimeout()));
         shrinkIndexRequest.setWaitForActiveShards(ActiveShardCount.parseString(request.param("wait_for_active_shards")));
-        return channel -> client.admin().indices().resizeIndex(shrinkIndexRequest, new AcknowledgedRestListener<ResizeResponse>(channel) {
-            @Override
-            public void addCustomFields(XContentBuilder builder, ResizeResponse response) throws IOException {
-                response.addCustomFields(builder);
-            }
-        });
+        return channel -> client.admin().indices().resizeIndex(shrinkIndexRequest, new RestToXContentListener<>(channel));
     }
 }

+ 12 - 4
server/src/test/java/org/elasticsearch/action/admin/indices/create/CreateIndexRequestTests.java

@@ -26,11 +26,14 @@ import org.elasticsearch.common.bytes.BytesReference;
 import org.elasticsearch.common.io.stream.BytesStreamOutput;
 import org.elasticsearch.common.io.stream.StreamInput;
 import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.common.xcontent.LoggingDeprecationHandler;
+import org.elasticsearch.common.xcontent.NamedXContentRegistry;
 import org.elasticsearch.common.xcontent.XContentParser;
 import org.elasticsearch.common.xcontent.XContentType;
 import org.elasticsearch.common.xcontent.json.JsonXContent;
 import org.elasticsearch.index.RandomCreateIndexGenerator;
 import org.elasticsearch.test.ESTestCase;
+import org.elasticsearch.test.hamcrest.ElasticsearchAssertions;
 
 import java.io.IOException;
 import java.util.Map;
@@ -119,17 +122,22 @@ public class CreateIndexRequestTests extends ESTestCase {
         assertMappingsEqual(createIndexRequest.mappings(), parsedCreateIndexRequest.mappings());
         assertAliasesEqual(createIndexRequest.aliases(), parsedCreateIndexRequest.aliases());
         assertEquals(createIndexRequest.settings(), parsedCreateIndexRequest.settings());
+
+        BytesReference finalBytes = toShuffledXContent(parsedCreateIndexRequest, xContentType, EMPTY_PARAMS, humanReadable);
+        ElasticsearchAssertions.assertToXContentEquivalent(originalBytes, finalBytes, xContentType);
     }
 
-    private void assertMappingsEqual(Map<String, String> expected, Map<String, String> actual) throws IOException {
+    public static void assertMappingsEqual(Map<String, String> expected, Map<String, String> actual) throws IOException {
         assertEquals(expected.keySet(), actual.keySet());
 
         for (Map.Entry<String, String> expectedEntry : expected.entrySet()) {
             String expectedValue = expectedEntry.getValue();
             String actualValue = actual.get(expectedEntry.getKey());
-            XContentParser expectedJson = createParser(XContentType.JSON.xContent(), expectedValue);
-            XContentParser actualJson = createParser(XContentType.JSON.xContent(), actualValue);
-            assertEquals(expectedJson.mapOrdered(), actualJson.mapOrdered());
+            XContentParser expectedJson = JsonXContent.jsonXContent.createParser(NamedXContentRegistry.EMPTY,
+                    LoggingDeprecationHandler.INSTANCE, expectedValue);
+            XContentParser actualJson = JsonXContent.jsonXContent.createParser(NamedXContentRegistry.EMPTY,
+                    LoggingDeprecationHandler.INSTANCE, actualValue);
+            assertEquals(expectedJson.map(), actualJson.map());
         }
     }
 

+ 13 - 0
server/src/test/java/org/elasticsearch/action/admin/indices/create/CreateIndexResponseTests.java

@@ -27,6 +27,7 @@ import org.elasticsearch.common.io.stream.StreamInput;
 import org.elasticsearch.common.xcontent.ToXContent;
 import org.elasticsearch.common.xcontent.XContentParser;
 import org.elasticsearch.common.xcontent.XContentType;
+import org.elasticsearch.common.xcontent.json.JsonXContent;
 import org.elasticsearch.test.ESTestCase;
 
 import java.io.IOException;
@@ -76,6 +77,18 @@ public class CreateIndexResponseTests extends ESTestCase {
         assertEquals("{\"acknowledged\":true,\"shards_acknowledged\":false,\"index\":\"index_name\"}", output);
     }
 
+    public void testToAndFromXContentIndexNull() throws IOException {
+        CreateIndexResponse response = new CreateIndexResponse(true, false, null);
+        String output = Strings.toString(response);
+        assertEquals("{\"acknowledged\":true,\"shards_acknowledged\":false,\"index\":null}", output);
+        try (XContentParser parser = createParser(JsonXContent.jsonXContent, output)) {
+            CreateIndexResponse parsedResponse = CreateIndexResponse.fromXContent(parser);
+            assertNull(parsedResponse.index());
+            assertTrue(parsedResponse.isAcknowledged());
+            assertFalse(parsedResponse.isShardsAcknowledged());
+        }
+    }
+
     public void testToAndFromXContent() throws IOException {
         doFromXContentTestWithRandomFields(false);
     }

+ 19 - 4
server/src/test/java/org/elasticsearch/action/admin/indices/rollover/ConditionTests.java

@@ -23,12 +23,13 @@ import org.elasticsearch.common.unit.ByteSizeUnit;
 import org.elasticsearch.common.unit.ByteSizeValue;
 import org.elasticsearch.common.unit.TimeValue;
 import org.elasticsearch.test.ESTestCase;
+import org.elasticsearch.test.EqualsHashCodeTestUtils;
 
 import static org.hamcrest.Matchers.equalTo;
 
 public class ConditionTests extends ESTestCase {
 
-    public void testMaxAge() throws Exception {
+    public void testMaxAge() {
         final MaxAgeCondition maxAgeCondition = new MaxAgeCondition(TimeValue.timeValueHours(1));
 
         long indexCreatedMatch = System.currentTimeMillis() - TimeValue.timeValueMinutes(61).getMillis();
@@ -42,7 +43,7 @@ public class ConditionTests extends ESTestCase {
         assertThat(evaluate.matched, equalTo(false));
     }
 
-    public void testMaxDocs() throws Exception {
+    public void testMaxDocs() {
         final MaxDocsCondition maxDocsCondition = new MaxDocsCondition(100L);
 
         long maxDocsMatch = randomIntBetween(100, 1000);
@@ -56,7 +57,7 @@ public class ConditionTests extends ESTestCase {
         assertThat(evaluate.matched, equalTo(false));
     }
 
-    public void testMaxSize() throws Exception {
+    public void testMaxSize() {
         MaxSizeCondition maxSizeCondition = new MaxSizeCondition(new ByteSizeValue(randomIntBetween(10, 20), ByteSizeUnit.MB));
 
         Condition.Result result = maxSizeCondition.evaluate(new Condition.Stats(randomNonNegativeLong(), randomNonNegativeLong(),
@@ -72,7 +73,21 @@ public class ConditionTests extends ESTestCase {
         assertThat(result.matched, equalTo(true));
     }
 
-    private ByteSizeValue randomByteSize() {
+    public void testEqualsAndHashCode() {
+        MaxDocsCondition maxDocsCondition = new MaxDocsCondition(randomLong());
+        EqualsHashCodeTestUtils.checkEqualsAndHashCode(maxDocsCondition, condition -> new MaxDocsCondition(condition.value),
+                condition -> new MaxDocsCondition(randomLong()));
+
+        MaxSizeCondition maxSizeCondition = new MaxSizeCondition(randomByteSize());
+        EqualsHashCodeTestUtils.checkEqualsAndHashCode(maxSizeCondition, condition -> new MaxSizeCondition(condition.value),
+                condition -> new MaxSizeCondition(randomByteSize()));
+
+        MaxAgeCondition maxAgeCondition = new MaxAgeCondition(new TimeValue(randomNonNegativeLong()));
+        EqualsHashCodeTestUtils.checkEqualsAndHashCode(maxAgeCondition, condition -> new MaxAgeCondition(condition.value),
+                condition -> new MaxAgeCondition(new TimeValue(randomNonNegativeLong())));
+    }
+
+    private static ByteSizeValue randomByteSize() {
         return new ByteSizeValue(randomNonNegativeLong(), ByteSizeUnit.BYTES);
     }
 }

+ 2 - 9
server/src/test/java/org/elasticsearch/action/admin/indices/rollover/RolloverIT.java

@@ -37,15 +37,12 @@ import org.joda.time.format.DateTimeFormat;
 
 import java.util.Collection;
 import java.util.Collections;
-import java.util.Map;
 import java.util.Set;
-import java.util.stream.Collectors;
 
 import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
 import static org.hamcrest.Matchers.containsInAnyOrder;
 import static org.hamcrest.Matchers.equalTo;
 import static org.hamcrest.Matchers.everyItem;
-import static org.hamcrest.Matchers.hasProperty;
 import static org.hamcrest.Matchers.is;
 
 @ESIntegTestCase.ClusterScope(scope = ESIntegTestCase.Scope.TEST)
@@ -143,12 +140,8 @@ public class RolloverIT extends ESIntegTestCase {
         assertThat(response.isDryRun(), equalTo(false));
         assertThat(response.isRolledOver(), equalTo(false));
         assertThat(response.getConditionStatus().size(), equalTo(2));
-
-
-        assertThat(response.getConditionStatus(), everyItem(hasProperty("value", is(false))));
-        Set<String> conditions = response.getConditionStatus().stream()
-            .map(Map.Entry::getKey)
-            .collect(Collectors.toSet());
+        assertThat(response.getConditionStatus().values(), everyItem(is(false)));
+        Set<String> conditions = response.getConditionStatus().keySet();
         assertThat(conditions, containsInAnyOrder(
             new MaxSizeCondition(new ByteSizeValue(10, ByteSizeUnit.MB)).toString(),
             new MaxAgeCondition(TimeValue.timeValueHours(4)).toString()));

+ 106 - 30
server/src/test/java/org/elasticsearch/action/admin/indices/rollover/RolloverRequestTests.java

@@ -19,6 +19,10 @@
 
 package org.elasticsearch.action.admin.indices.rollover;
 
+import org.elasticsearch.action.ActionRequestValidationException;
+import org.elasticsearch.action.admin.indices.create.CreateIndexRequest;
+import org.elasticsearch.action.admin.indices.create.CreateIndexRequestTests;
+import org.elasticsearch.common.ParsingException;
 import org.elasticsearch.common.bytes.BytesReference;
 import org.elasticsearch.common.io.stream.BytesStreamOutput;
 import org.elasticsearch.common.io.stream.NamedWriteableAwareStreamInput;
@@ -29,15 +33,22 @@ import org.elasticsearch.common.unit.ByteSizeValue;
 import org.elasticsearch.common.unit.TimeValue;
 import org.elasticsearch.common.xcontent.XContentBuilder;
 import org.elasticsearch.common.xcontent.XContentFactory;
+import org.elasticsearch.common.xcontent.XContentType;
+import org.elasticsearch.index.RandomCreateIndexGenerator;
 import org.elasticsearch.indices.IndicesModule;
 import org.elasticsearch.test.ESTestCase;
+import org.elasticsearch.test.XContentTestUtils;
+import org.elasticsearch.test.hamcrest.ElasticsearchAssertions;
 import org.junit.Before;
 
+import java.io.IOException;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
-import java.util.Set;
-import java.util.stream.Collectors;
+import java.util.Map;
+import java.util.function.Consumer;
 
+import static org.elasticsearch.common.xcontent.ToXContent.EMPTY_PARAMS;
 import static org.hamcrest.Matchers.equalTo;
 
 public class RolloverRequestTests extends ESTestCase {
@@ -61,23 +72,15 @@ public class RolloverRequestTests extends ESTestCase {
                     .field("max_size", "45gb")
                 .endObject()
             .endObject();
-        RolloverRequest.PARSER.parse(createParser(builder), request, null);
-        Set<Condition> conditions = request.getConditions();
+        request.fromXContent(createParser(builder));
+        Map<String, Condition> conditions = request.getConditions();
         assertThat(conditions.size(), equalTo(3));
-        for (Condition condition : conditions) {
-            if (condition instanceof MaxAgeCondition) {
-                MaxAgeCondition maxAgeCondition = (MaxAgeCondition) condition;
-                assertThat(maxAgeCondition.value.getMillis(), equalTo(TimeValue.timeValueHours(24 * 10).getMillis()));
-            } else if (condition instanceof MaxDocsCondition) {
-                MaxDocsCondition maxDocsCondition = (MaxDocsCondition) condition;
-                assertThat(maxDocsCondition.value, equalTo(100L));
-            } else if (condition instanceof MaxSizeCondition) {
-                MaxSizeCondition maxSizeCondition = (MaxSizeCondition) condition;
-                assertThat(maxSizeCondition.value.getBytes(), equalTo(ByteSizeUnit.GB.toBytes(45)));
-            } else {
-                fail("unexpected condition " + condition);
-            }
-        }
+        MaxAgeCondition maxAgeCondition = (MaxAgeCondition)conditions.get(MaxAgeCondition.NAME);
+        assertThat(maxAgeCondition.value.getMillis(), equalTo(TimeValue.timeValueHours(24 * 10).getMillis()));
+        MaxDocsCondition maxDocsCondition = (MaxDocsCondition)conditions.get(MaxDocsCondition.NAME);
+        assertThat(maxDocsCondition.value, equalTo(100L));
+        MaxSizeCondition maxSizeCondition = (MaxSizeCondition)conditions.get(MaxSizeCondition.NAME);
+        assertThat(maxSizeCondition.value.getBytes(), equalTo(ByteSizeUnit.GB.toBytes(45)));
     }
 
     public void testParsingWithIndexSettings() throws Exception {
@@ -105,8 +108,8 @@ public class RolloverRequestTests extends ESTestCase {
                     .startObject("alias1").endObject()
                 .endObject()
             .endObject();
-        RolloverRequest.PARSER.parse(createParser(builder), request, null);
-        Set<Condition> conditions = request.getConditions();
+        request.fromXContent(createParser(builder));
+        Map<String, Condition> conditions = request.getConditions();
         assertThat(conditions.size(), equalTo(2));
         assertThat(request.getCreateIndexRequest().mappings().size(), equalTo(1));
         assertThat(request.getCreateIndexRequest().aliases().size(), equalTo(1));
@@ -126,19 +129,92 @@ public class RolloverRequestTests extends ESTestCase {
                 cloneRequest.readFrom(in);
                 assertThat(cloneRequest.getNewIndexName(), equalTo(originalRequest.getNewIndexName()));
                 assertThat(cloneRequest.getAlias(), equalTo(originalRequest.getAlias()));
+                for (Map.Entry<String, Condition> entry : cloneRequest.getConditions().entrySet()) {
+                    Condition condition = originalRequest.getConditions().get(entry.getKey());
+                    //here we compare the string representation as there is some information loss when serializing
+                    //and de-serializing MaxAgeCondition
+                    assertEquals(condition.toString(), entry.getValue().toString());
+                }
+            }
+        }
+    }
 
-                List<String> originalConditions = originalRequest.getConditions().stream()
-                    .map(Condition::toString)
-                    .sorted()
-                    .collect(Collectors.toList());
+    public void testToAndFromXContent() throws IOException {
+        RolloverRequest rolloverRequest = createTestItem();
 
-                List<String> cloneConditions = cloneRequest.getConditions().stream()
-                    .map(Condition::toString)
-                    .sorted()
-                    .collect(Collectors.toList());
+        final XContentType xContentType = randomFrom(XContentType.values());
+        boolean humanReadable = randomBoolean();
+        BytesReference originalBytes = toShuffledXContent(rolloverRequest, xContentType, EMPTY_PARAMS, humanReadable);
 
-                assertThat(originalConditions, equalTo(cloneConditions));
-            }
+        RolloverRequest parsedRolloverRequest = new RolloverRequest();
+        parsedRolloverRequest.fromXContent(createParser(xContentType.xContent(), originalBytes));
+
+        CreateIndexRequest createIndexRequest = rolloverRequest.getCreateIndexRequest();
+        CreateIndexRequest parsedCreateIndexRequest = parsedRolloverRequest.getCreateIndexRequest();
+        CreateIndexRequestTests.assertMappingsEqual(createIndexRequest.mappings(), parsedCreateIndexRequest.mappings());
+        CreateIndexRequestTests.assertAliasesEqual(createIndexRequest.aliases(), parsedCreateIndexRequest.aliases());
+        assertEquals(createIndexRequest.settings(), parsedCreateIndexRequest.settings());
+        assertEquals(rolloverRequest.getConditions(), parsedRolloverRequest.getConditions());
+
+        BytesReference finalBytes = toShuffledXContent(parsedRolloverRequest, xContentType, EMPTY_PARAMS, humanReadable);
+        ElasticsearchAssertions.assertToXContentEquivalent(originalBytes, finalBytes, xContentType);
+    }
+
+    public void testUnknownFields() throws IOException {
+        final RolloverRequest request = new RolloverRequest();
+        XContentType xContentType = randomFrom(XContentType.values());
+        final XContentBuilder builder = XContentFactory.contentBuilder(xContentType);
+        builder.startObject();
+        {
+            builder.startObject("conditions");
+            builder.field("max_age", "10d");
+            builder.endObject();
+        }
+        builder.endObject();
+        BytesReference mutated = XContentTestUtils.insertRandomFields(xContentType, builder.bytes(), null, random());
+        expectThrows(ParsingException.class, () -> request.fromXContent(createParser(xContentType.xContent(), mutated)));
+    }
+
+    public void testSameConditionCanOnlyBeAddedOnce() {
+        RolloverRequest rolloverRequest = new RolloverRequest();
+        Consumer<RolloverRequest> rolloverRequestConsumer = randomFrom(conditionsGenerator);
+        rolloverRequestConsumer.accept(rolloverRequest);
+        expectThrows(IllegalArgumentException.class, () -> rolloverRequestConsumer.accept(rolloverRequest));
+    }
+
+    public void testValidation() {
+        RolloverRequest rolloverRequest = new RolloverRequest();
+        assertNotNull(rolloverRequest.getCreateIndexRequest());
+        ActionRequestValidationException validationException = rolloverRequest.validate();
+        assertNotNull(validationException);
+        assertEquals(1, validationException.validationErrors().size());
+        assertEquals("index alias is missing", validationException.validationErrors().get(0));
+    }
+
+    private static List<Consumer<RolloverRequest>> conditionsGenerator = new ArrayList<>();
+    static {
+        conditionsGenerator.add((request) -> request.addMaxIndexDocsCondition(randomNonNegativeLong()));
+        conditionsGenerator.add((request) -> request.addMaxIndexSizeCondition(new ByteSizeValue(randomNonNegativeLong())));
+        conditionsGenerator.add((request) -> request.addMaxIndexAgeCondition(new TimeValue(randomNonNegativeLong())));
+    }
+
+    private static RolloverRequest createTestItem() throws IOException {
+        RolloverRequest rolloverRequest = new RolloverRequest();
+        if (randomBoolean()) {
+            String type = randomAlphaOfLengthBetween(3, 10);
+            rolloverRequest.getCreateIndexRequest().mapping(type, RandomCreateIndexGenerator.randomMapping(type));
+        }
+        if (randomBoolean()) {
+            RandomCreateIndexGenerator.randomAliases(rolloverRequest.getCreateIndexRequest());
+        }
+        if (randomBoolean()) {
+            rolloverRequest.getCreateIndexRequest().settings(RandomCreateIndexGenerator.randomIndexSettings());
+        }
+        int numConditions = randomIntBetween(0, 3);
+        List<Consumer<RolloverRequest>> conditions = randomSubsetOf(numConditions, conditionsGenerator);
+        for (Consumer<RolloverRequest> consumer : conditions) {
+            consumer.accept(rolloverRequest);
         }
+        return rolloverRequest;
     }
 }

+ 132 - 0
server/src/test/java/org/elasticsearch/action/admin/indices/rollover/RolloverResponseTests.java

@@ -0,0 +1,132 @@
+/*
+ * 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.action.admin.indices.rollover;
+
+import org.elasticsearch.common.unit.TimeValue;
+import org.elasticsearch.common.xcontent.XContentParser;
+import org.elasticsearch.test.AbstractStreamableXContentTestCase;
+import org.elasticsearch.test.EqualsHashCodeTestUtils;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+
+public class RolloverResponseTests extends AbstractStreamableXContentTestCase<RolloverResponse> {
+
+    @Override
+    protected RolloverResponse createTestInstance() {
+        boolean acknowledged = randomBoolean();
+        boolean shardsAcknowledged = acknowledged && randomBoolean();
+        return new RolloverResponse(randomAlphaOfLengthBetween(3, 10),
+                randomAlphaOfLengthBetween(3, 10), randomResults(true), randomBoolean(), randomBoolean(), acknowledged, shardsAcknowledged);
+    }
+
+    private static Map<String, Boolean> randomResults(boolean allowNoItems) {
+        Map<String, Boolean> results = new HashMap<>();
+        int numResults = randomIntBetween(allowNoItems ? 0 : 1, 3);
+        List<Supplier<Condition<?>>> conditions = randomSubsetOf(numResults, conditionSuppliers);
+        for (Supplier<Condition<?>> condition : conditions) {
+            Condition<?> cond = condition.get();
+            results.put(cond.name, randomBoolean());
+        }
+        return results;
+    }
+
+    private static final List<Supplier<Condition<?>>> conditionSuppliers = new ArrayList<>();
+    static {
+        conditionSuppliers.add(() -> new MaxAgeCondition(new TimeValue(randomNonNegativeLong())));
+        conditionSuppliers.add(() -> new MaxDocsCondition(randomNonNegativeLong()));
+        conditionSuppliers.add(() -> new MaxDocsCondition(randomNonNegativeLong()));
+    }
+
+    @Override
+    protected RolloverResponse createBlankInstance() {
+        return new RolloverResponse();
+    }
+
+    @Override
+    protected RolloverResponse doParseInstance(XContentParser parser) {
+        return RolloverResponse.fromXContent(parser);
+    }
+
+    @Override
+    protected Predicate<String> getRandomFieldsExcludeFilter() {
+        return field -> field.startsWith("conditions");
+    }
+
+    @Override
+    protected EqualsHashCodeTestUtils.MutateFunction<RolloverResponse> getMutateFunction() {
+        return response -> {
+            int i = randomIntBetween(0, 6);
+            switch(i) {
+                case 0:
+                    return new RolloverResponse(response.getOldIndex() + randomAlphaOfLengthBetween(2, 5),
+                            response.getNewIndex(), response.getConditionStatus(), response.isDryRun(), response.isRolledOver(),
+                            response.isAcknowledged(), response.isShardsAcknowledged());
+                case 1:
+                    return new RolloverResponse(response.getOldIndex(), response.getNewIndex() + randomAlphaOfLengthBetween(2, 5),
+                            response.getConditionStatus(), response.isDryRun(), response.isRolledOver(),
+                            response.isAcknowledged(), response.isShardsAcknowledged());
+                case 2:
+                    Map<String, Boolean> results;
+                    if (response.getConditionStatus().isEmpty()) {
+                        results = randomResults(false);
+                    } else {
+                        results = new HashMap<>(response.getConditionStatus().size());
+                        List<String> keys = randomSubsetOf(randomIntBetween(1, response.getConditionStatus().size()),
+                                response.getConditionStatus().keySet());
+                        for (Map.Entry<String, Boolean> entry : response.getConditionStatus().entrySet()) {
+                            boolean value = keys.contains(entry.getKey()) ? entry.getValue() == false : entry.getValue();
+                            results.put(entry.getKey(), value);
+                        }
+                    }
+                    return new RolloverResponse(response.getOldIndex(), response.getNewIndex(), results, response.isDryRun(),
+                            response.isRolledOver(), response.isAcknowledged(), response.isShardsAcknowledged());
+                case 3:
+                    return new RolloverResponse(response.getOldIndex(), response.getNewIndex(),
+                            response.getConditionStatus(), response.isDryRun() == false, response.isRolledOver(),
+                            response.isAcknowledged(), response.isShardsAcknowledged());
+                case 4:
+                    return new RolloverResponse(response.getOldIndex(), response.getNewIndex(),
+                            response.getConditionStatus(), response.isDryRun(), response.isRolledOver() == false,
+                            response.isAcknowledged(), response.isShardsAcknowledged());
+                case 5: {
+                    boolean acknowledged = response.isAcknowledged() == false;
+                    boolean shardsAcknowledged = acknowledged && response.isShardsAcknowledged();
+                    return new RolloverResponse(response.getOldIndex(), response.getNewIndex(),
+                            response.getConditionStatus(), response.isDryRun(), response.isRolledOver(),
+                            acknowledged, shardsAcknowledged);
+                }
+                case 6: {
+                    boolean shardsAcknowledged = response.isShardsAcknowledged() == false;
+                    boolean acknowledged = shardsAcknowledged || response.isAcknowledged();
+                    return new RolloverResponse(response.getOldIndex(), response.getNewIndex(),
+                            response.getConditionStatus(), response.isDryRun(), response.isRolledOver(),
+                            acknowledged, shardsAcknowledged);
+                }
+                default:
+                    throw new UnsupportedOperationException();
+            }
+        };
+    }
+}

+ 32 - 31
server/src/test/java/org/elasticsearch/action/admin/indices/rollover/TransportRolloverActionTests.java

@@ -44,6 +44,7 @@ import org.mockito.ArgumentCaptor;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Locale;
+import java.util.Map;
 import java.util.Set;
 
 import static org.elasticsearch.action.admin.indices.rollover.TransportRolloverAction.evaluateConditions;
@@ -58,7 +59,7 @@ import static org.mockito.Mockito.when;
 
 public class TransportRolloverActionTests extends ESTestCase {
 
-    public void testDocStatsSelectionFromPrimariesOnly() throws Exception {
+    public void testDocStatsSelectionFromPrimariesOnly() {
         long docsInPrimaryShards = 100;
         long docsInShards = 200;
 
@@ -70,7 +71,7 @@ public class TransportRolloverActionTests extends ESTestCase {
         assertEquals(docsInPrimaryShards, argument.getValue().numDocs);
     }
 
-    public void testEvaluateConditions() throws Exception {
+    public void testEvaluateConditions() {
         MaxDocsCondition maxDocsCondition = new MaxDocsCondition(100L);
         MaxAgeCondition maxAgeCondition = new MaxAgeCondition(TimeValue.timeValueHours(2));
         MaxSizeCondition maxSizeCondition = new MaxSizeCondition(new ByteSizeValue(randomIntBetween(10, 100), ByteSizeUnit.MB));
@@ -89,29 +90,29 @@ public class TransportRolloverActionTests extends ESTestCase {
             .settings(settings)
             .build();
         final Set<Condition> conditions = Sets.newHashSet(maxDocsCondition, maxAgeCondition, maxSizeCondition);
-        Set<Condition.Result> results = evaluateConditions(conditions,
+        Map<String, Boolean> results = evaluateConditions(conditions,
             new DocsStats(matchMaxDocs, 0L, ByteSizeUnit.MB.toBytes(120)), metaData);
         assertThat(results.size(), equalTo(3));
-        for (Condition.Result result : results) {
-            assertThat(result.matched, equalTo(true));
+        for (Boolean matched : results.values()) {
+            assertThat(matched, equalTo(true));
         }
 
         results = evaluateConditions(conditions, new DocsStats(notMatchMaxDocs, 0, notMatchMaxSize.getBytes()), metaData);
         assertThat(results.size(), equalTo(3));
-        for (Condition.Result result : results) {
-            if (result.condition instanceof MaxAgeCondition) {
-                assertThat(result.matched, equalTo(true));
-            } else if (result.condition instanceof MaxDocsCondition) {
-                assertThat(result.matched, equalTo(false));
-            } else if (result.condition instanceof MaxSizeCondition) {
-                assertThat(result.matched, equalTo(false));
+        for (Map.Entry<String, Boolean> entry : results.entrySet()) {
+            if (entry.getKey().equals(maxAgeCondition.toString())) {
+                assertThat(entry.getValue(), equalTo(true));
+            } else if (entry.getKey().equals(maxDocsCondition.toString())) {
+                assertThat(entry.getValue(), equalTo(false));
+            } else if (entry.getKey().equals(maxSizeCondition.toString())) {
+                assertThat(entry.getValue(), equalTo(false));
             } else {
-                fail("unknown condition result found " + result.condition);
+                fail("unknown condition result found " + entry.getKey());
             }
         }
     }
 
-    public void testEvaluateWithoutDocStats() throws Exception {
+    public void testEvaluateWithoutDocStats() {
         MaxDocsCondition maxDocsCondition = new MaxDocsCondition(randomNonNegativeLong());
         MaxAgeCondition maxAgeCondition = new MaxAgeCondition(TimeValue.timeValueHours(randomIntBetween(1, 3)));
         MaxSizeCondition maxSizeCondition = new MaxSizeCondition(new ByteSizeValue(randomNonNegativeLong()));
@@ -128,23 +129,23 @@ public class TransportRolloverActionTests extends ESTestCase {
             .creationDate(System.currentTimeMillis() - TimeValue.timeValueHours(randomIntBetween(5, 10)).getMillis())
             .settings(settings)
             .build();
-        Set<Condition.Result> results = evaluateConditions(conditions, null, metaData);
+        Map<String, Boolean> results = evaluateConditions(conditions, null, metaData);
         assertThat(results.size(), equalTo(3));
 
-        for (Condition.Result result : results) {
-            if (result.condition instanceof MaxAgeCondition) {
-                assertThat(result.matched, equalTo(true));
-            } else if (result.condition instanceof MaxDocsCondition) {
-                assertThat(result.matched, equalTo(false));
-            } else if (result.condition instanceof MaxSizeCondition) {
-                assertThat(result.matched, equalTo(false));
+        for (Map.Entry<String, Boolean> entry : results.entrySet()) {
+            if (entry.getKey().equals(maxAgeCondition.toString())) {
+                assertThat(entry.getValue(), equalTo(true));
+            } else if (entry.getKey().equals(maxDocsCondition.toString())) {
+                assertThat(entry.getValue(), equalTo(false));
+            } else if (entry.getKey().equals(maxSizeCondition.toString())) {
+                assertThat(entry.getValue(), equalTo(false));
             } else {
-                fail("unknown condition result found " + result.condition);
+                fail("unknown condition result found " + entry.getKey());
             }
         }
     }
 
-    public void testCreateUpdateAliasRequest() throws Exception {
+    public void testCreateUpdateAliasRequest() {
         String sourceAlias = randomAlphaOfLength(10);
         String sourceIndex = randomAlphaOfLength(10);
         String targetIndex = randomAlphaOfLength(10);
@@ -171,7 +172,7 @@ public class TransportRolloverActionTests extends ESTestCase {
         assertTrue(foundRemove);
     }
 
-    public void testValidation() throws Exception {
+    public void testValidation() {
         String index1 = randomAlphaOfLength(10);
         String alias = randomAlphaOfLength(10);
         String index2 = randomAlphaOfLength(10);
@@ -206,7 +207,7 @@ public class TransportRolloverActionTests extends ESTestCase {
         TransportRolloverAction.validate(metaData, new RolloverRequest(alias, randomAlphaOfLength(10)));
     }
 
-    public void testGenerateRolloverIndexName() throws Exception {
+    public void testGenerateRolloverIndexName() {
         String invalidIndexName = randomAlphaOfLength(10) + "A";
         IndexNameExpressionResolver indexNameExpressionResolver = new IndexNameExpressionResolver(Settings.EMPTY);
         expectThrows(IllegalArgumentException.class, () ->
@@ -224,12 +225,12 @@ public class TransportRolloverActionTests extends ESTestCase {
             indexNameExpressionResolver));
     }
 
-    public void testCreateIndexRequest() throws Exception {
+    public void testCreateIndexRequest() {
         String alias = randomAlphaOfLength(10);
         String rolloverIndex = randomAlphaOfLength(10);
         final RolloverRequest rolloverRequest = new RolloverRequest(alias, randomAlphaOfLength(10));
         final ActiveShardCount activeShardCount = randomBoolean() ? ActiveShardCount.ALL : ActiveShardCount.ONE;
-        rolloverRequest.setWaitForActiveShards(activeShardCount);
+        rolloverRequest.getCreateIndexRequest().waitForActiveShards(activeShardCount);
         final Settings settings = Settings.builder()
             .put(IndexMetaData.SETTING_VERSION_CREATED, Version.CURRENT)
             .put(IndexMetaData.SETTING_INDEX_UUID, UUIDs.randomBase64UUID())
@@ -244,7 +245,7 @@ public class TransportRolloverActionTests extends ESTestCase {
         assertThat(createIndexRequest.cause(), equalTo("rollover_index"));
     }
 
-    public void testRejectDuplicateAlias() throws Exception {
+    public void testRejectDuplicateAlias() {
         final IndexTemplateMetaData template = IndexTemplateMetaData.builder("test-template")
             .patterns(Arrays.asList("foo-*", "bar-*"))
             .putAlias(AliasMetaData.builder("foo-write")).putAlias(AliasMetaData.builder("bar-write"))
@@ -271,7 +272,7 @@ public class TransportRolloverActionTests extends ESTestCase {
         return response;
     }
 
-    private IndexMetaData createMetaData() {
+    private static IndexMetaData createMetaData() {
         final Settings settings = Settings.builder()
             .put(IndexMetaData.SETTING_VERSION_CREATED, Version.CURRENT)
             .put(IndexMetaData.SETTING_INDEX_UUID, UUIDs.randomBase64UUID())
@@ -284,7 +285,7 @@ public class TransportRolloverActionTests extends ESTestCase {
             .build();
     }
 
-    private Condition createTestCondition() {
+    private static Condition createTestCondition() {
         final Condition condition = mock(Condition.class);
         when(condition.evaluate(any())).thenReturn(new Condition.Result(condition, true));
         return condition;

+ 4 - 0
server/src/test/java/org/elasticsearch/action/admin/indices/shrink/ResizeRequestTests.java

@@ -28,6 +28,7 @@ import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.common.xcontent.XContentType;
 import org.elasticsearch.index.RandomCreateIndexGenerator;
 import org.elasticsearch.test.ESTestCase;
+import org.elasticsearch.test.hamcrest.ElasticsearchAssertions;
 
 import java.io.IOException;
 
@@ -76,6 +77,9 @@ public class ResizeRequestTests extends ESTestCase {
         CreateIndexRequestTests.assertAliasesEqual(resizeRequest.getTargetIndexRequest().aliases(),
                 parsedResizeRequest.getTargetIndexRequest().aliases());
         assertEquals(resizeRequest.getTargetIndexRequest().settings(), parsedResizeRequest.getTargetIndexRequest().settings());
+
+        BytesReference finalBytes = toShuffledXContent(parsedResizeRequest, xContentType, EMPTY_PARAMS, humanReadable);
+        ElasticsearchAssertions.assertToXContentEquivalent(originalBytes, finalBytes, xContentType);
     }
 
     private static ResizeRequest createTestItem() {

+ 1 - 1
test/framework/src/main/java/org/elasticsearch/index/RandomCreateIndexGenerator.java

@@ -76,7 +76,7 @@ public final class RandomCreateIndexGenerator {
         return builder.build();
     }
 
-    private static XContentBuilder randomMapping(String type) throws IOException {
+    public static XContentBuilder randomMapping(String type) throws IOException {
         XContentBuilder builder = XContentFactory.jsonBuilder();
         builder.startObject().startObject(type);
 

+ 2 - 2
test/framework/src/main/java/org/elasticsearch/test/AbstractStreamableTestCase.java

@@ -58,7 +58,7 @@ public abstract class AbstractStreamableTestCase<T extends Streamable> extends E
     }
 
     /**
-     * Returns a {@link MutateFunction} that can be used to make create a copy
+     * Returns a {@link MutateFunction} that can be used to create a copy
      * of the given instance that is different to this instance. This defaults
      * to null.
      */
@@ -71,7 +71,7 @@ public abstract class AbstractStreamableTestCase<T extends Streamable> extends E
      * Tests that the equals and hashcode methods are consistent and copied
      * versions of the instance have are equal.
      */
-    public void testEqualsAndHashcode() throws IOException {
+    public void testEqualsAndHashcode() {
         for (int runs = 0; runs < NUMBER_OF_TEST_RUNS; runs++) {
             EqualsHashCodeTestUtils.checkEqualsAndHashCode(createTestInstance(), getCopyFunction(), getMutateFunction());
         }

+ 6 - 1
test/framework/src/main/java/org/elasticsearch/test/AbstractStreamableXContentTestCase.java

@@ -27,6 +27,7 @@ import org.elasticsearch.common.xcontent.XContentParser;
 import org.elasticsearch.common.xcontent.XContentType;
 
 import java.io.IOException;
+import java.util.function.Predicate;
 
 import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertToXContentEquivalent;
 
@@ -44,7 +45,7 @@ public abstract class AbstractStreamableXContentTestCase<T extends ToXContent &
             BytesReference withRandomFields;
             if (supportsUnknownFields()) {
                 // we add a few random fields to check that parser is lenient on new fields
-                withRandomFields = XContentTestUtils.insertRandomFields(xContentType, shuffled, null, random());
+                withRandomFields = XContentTestUtils.insertRandomFields(xContentType, shuffled, getRandomFieldsExcludeFilter(), random());
             } else {
                 withRandomFields = shuffled;
             }
@@ -74,6 +75,10 @@ public abstract class AbstractStreamableXContentTestCase<T extends ToXContent &
         return true;
     }
 
+    protected Predicate<String> getRandomFieldsExcludeFilter() {
+        return field -> false;
+    }
+
     private T parseInstance(XContentParser parser) throws IOException {
         T parsedInstance = doParseInstance(parser);
         assertNull(parser.nextToken());