Browse Source

Move parsing of allocation commands into REST

Port them to the ObjectParser.

Don't let plugins register custom allocation commands
Nik Everett 9 years ago
parent
commit
a93f578bf6
15 changed files with 405 additions and 90 deletions
  1. 0 2
      buildSrc/src/main/resources/checkstyle_suppressions.xml
  2. 24 37
      core/src/main/java/org/elasticsearch/action/admin/cluster/reroute/ClusterRerouteRequest.java
  3. 3 12
      core/src/main/java/org/elasticsearch/action/admin/cluster/reroute/ClusterRerouteRequestBuilder.java
  4. 19 0
      core/src/main/java/org/elasticsearch/cluster/routing/allocation/command/AbstractAllocateAllocationCommand.java
  5. 0 2
      core/src/main/java/org/elasticsearch/cluster/routing/allocation/command/AllocateReplicaAllocationCommand.java
  6. 4 1
      core/src/main/java/org/elasticsearch/cluster/routing/allocation/command/AllocationCommand.java
  7. 23 12
      core/src/main/java/org/elasticsearch/cluster/routing/allocation/command/AllocationCommands.java
  8. 14 0
      core/src/main/java/org/elasticsearch/cluster/routing/allocation/command/BasePrimaryAllocationCommand.java
  9. 20 0
      core/src/main/java/org/elasticsearch/cluster/routing/allocation/command/CancelAllocationCommand.java
  10. 20 0
      core/src/main/java/org/elasticsearch/cluster/routing/allocation/command/MoveAllocationCommand.java
  11. 10 10
      core/src/main/java/org/elasticsearch/common/network/NetworkModule.java
  12. 48 13
      core/src/main/java/org/elasticsearch/rest/action/admin/cluster/reroute/RestClusterRerouteAction.java
  13. 214 0
      core/src/test/java/org/elasticsearch/action/admin/cluster/reroute/ClusterRerouteRequestTests.java
  14. 1 1
      core/src/test/java/org/elasticsearch/action/admin/cluster/reroute/ClusterRerouteTests.java
  15. 5 0
      docs/reference/migration/migrate_5_0/allocation.asciidoc

+ 0 - 2
buildSrc/src/main/resources/checkstyle_suppressions.xml

@@ -37,8 +37,6 @@
   <suppress files="core[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]action[/\\]admin[/\\]cluster[/\\]repositories[/\\]put[/\\]TransportPutRepositoryAction.java" checks="LineLength" />
   <suppress files="core[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]action[/\\]admin[/\\]cluster[/\\]repositories[/\\]verify[/\\]TransportVerifyRepositoryAction.java" checks="LineLength" />
   <suppress files="core[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]action[/\\]admin[/\\]cluster[/\\]repositories[/\\]verify[/\\]VerifyRepositoryRequestBuilder.java" checks="LineLength" />
-  <suppress files="core[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]action[/\\]admin[/\\]cluster[/\\]reroute[/\\]ClusterRerouteRequest.java" checks="LineLength" />
-  <suppress files="core[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]action[/\\]admin[/\\]cluster[/\\]reroute[/\\]ClusterRerouteRequestBuilder.java" checks="LineLength" />
   <suppress files="core[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]action[/\\]admin[/\\]cluster[/\\]reroute[/\\]TransportClusterRerouteAction.java" checks="LineLength" />
   <suppress files="core[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]action[/\\]admin[/\\]cluster[/\\]settings[/\\]ClusterUpdateSettingsAction.java" checks="LineLength" />
   <suppress files="core[/\\]src[/\\]main[/\\]java[/\\]org[/\\]elasticsearch[/\\]action[/\\]admin[/\\]cluster[/\\]settings[/\\]ClusterUpdateSettingsRequestBuilder.java" checks="LineLength" />

+ 24 - 37
core/src/main/java/org/elasticsearch/action/admin/cluster/reroute/ClusterRerouteRequest.java

@@ -19,20 +19,15 @@
 
 package org.elasticsearch.action.admin.cluster.reroute;
 
-import org.elasticsearch.ElasticsearchParseException;
 import org.elasticsearch.action.ActionRequestValidationException;
 import org.elasticsearch.action.support.master.AcknowledgedRequest;
 import org.elasticsearch.cluster.routing.allocation.command.AllocationCommand;
-import org.elasticsearch.cluster.routing.allocation.command.AllocationCommandRegistry;
 import org.elasticsearch.cluster.routing.allocation.command.AllocationCommands;
-import org.elasticsearch.common.ParseFieldMatcher;
-import org.elasticsearch.common.bytes.BytesReference;
 import org.elasticsearch.common.io.stream.StreamInput;
 import org.elasticsearch.common.io.stream.StreamOutput;
-import org.elasticsearch.common.xcontent.XContentHelper;
-import org.elasticsearch.common.xcontent.XContentParser;
 
 import java.io.IOException;
+import java.util.Objects;
 
 /**
  * Request to submit cluster reroute allocation commands
@@ -109,8 +104,8 @@ public class ClusterRerouteRequest extends AcknowledgedRequest<ClusterRerouteReq
     /**
      * Set the allocation commands to execute.
      */
-    public ClusterRerouteRequest commands(AllocationCommand... commands) {
-        this.commands = new AllocationCommands(commands);
+    public ClusterRerouteRequest commands(AllocationCommands commands) {
+        this.commands = commands;
         return this;
     }
 
@@ -121,35 +116,6 @@ public class ClusterRerouteRequest extends AcknowledgedRequest<ClusterRerouteReq
         return commands;
     }
 
-    /**
-     * Sets the source for the request.
-     */
-    public ClusterRerouteRequest source(BytesReference source, AllocationCommandRegistry registry, ParseFieldMatcher parseFieldMatcher)
-            throws Exception {
-        try (XContentParser parser = XContentHelper.createParser(source)) {
-            XContentParser.Token token;
-            String currentFieldName = null;
-            while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
-                if (token == XContentParser.Token.FIELD_NAME) {
-                    currentFieldName = parser.currentName();
-                } else if (token == XContentParser.Token.START_ARRAY) {
-                    if ("commands".equals(currentFieldName)) {
-                        this.commands = AllocationCommands.fromXContent(parser, parseFieldMatcher, registry);
-                    } else {
-                        throw new ElasticsearchParseException("failed to parse reroute request, got start array with wrong field name [{}]", currentFieldName);
-                    }
-                } else if (token.isValue()) {
-                    if ("dry_run".equals(currentFieldName) || "dryRun".equals(currentFieldName)) {
-                        dryRun = parser.booleanValue();
-                    } else {
-                        throw new ElasticsearchParseException("failed to parse reroute request, got value with wrong field name [{}]", currentFieldName);
-                    }
-                }
-            }
-        }
-        return this;
-    }
-
     @Override
     public ActionRequestValidationException validate() {
         return null;
@@ -174,4 +140,25 @@ public class ClusterRerouteRequest extends AcknowledgedRequest<ClusterRerouteReq
         out.writeBoolean(retryFailed);
         writeTimeout(out);
     }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null || getClass() != obj.getClass()) {
+            return false;
+        }
+        ClusterRerouteRequest other = (ClusterRerouteRequest) obj;
+        // Override equals and hashCode for testing
+        return Objects.equals(commands, other.commands) &&
+                Objects.equals(dryRun, other.dryRun) &&
+                Objects.equals(explain, other.explain) &&
+                Objects.equals(timeout, other.timeout) &&
+                Objects.equals(retryFailed, other.retryFailed) &&
+                Objects.equals(masterNodeTimeout, other.masterNodeTimeout);
+    }
+
+    @Override
+    public int hashCode() {
+        // Override equals and hashCode for testing
+        return Objects.hash(commands, dryRun, explain, timeout, retryFailed, masterNodeTimeout);
+    }
 }

+ 3 - 12
core/src/main/java/org/elasticsearch/action/admin/cluster/reroute/ClusterRerouteRequestBuilder.java

@@ -22,13 +22,12 @@ package org.elasticsearch.action.admin.cluster.reroute;
 import org.elasticsearch.action.support.master.AcknowledgedRequestBuilder;
 import org.elasticsearch.client.ElasticsearchClient;
 import org.elasticsearch.cluster.routing.allocation.command.AllocationCommand;
-import org.elasticsearch.common.bytes.BytesReference;
 
 /**
  * Builder for a cluster reroute request
  */
-public class ClusterRerouteRequestBuilder extends AcknowledgedRequestBuilder<ClusterRerouteRequest, ClusterRerouteResponse, ClusterRerouteRequestBuilder> {
-
+public class ClusterRerouteRequestBuilder
+        extends AcknowledgedRequestBuilder<ClusterRerouteRequest, ClusterRerouteResponse, ClusterRerouteRequestBuilder> {
     public ClusterRerouteRequestBuilder(ElasticsearchClient client, ClusterRerouteAction action) {
         super(client, action, new ClusterRerouteRequest());
     }
@@ -68,12 +67,4 @@ public class ClusterRerouteRequestBuilder extends AcknowledgedRequestBuilder<Clu
         request.setRetryFailed(retryFailed);
         return this;
     }
-
-    /**
-     * Sets the commands for the request to execute.
-     */
-    public ClusterRerouteRequestBuilder setCommands(AllocationCommand... commands) throws Exception {
-        request.commands(commands);
-        return this;
-    }
-}
+}

+ 19 - 0
core/src/main/java/org/elasticsearch/cluster/routing/allocation/command/AbstractAllocateAllocationCommand.java

@@ -38,6 +38,7 @@ import org.elasticsearch.common.xcontent.XContentBuilder;
 import org.elasticsearch.common.xcontent.XContentParser;
 
 import java.io.IOException;
+import java.util.Objects;
 import java.util.function.Consumer;
 import java.util.function.Function;
 
@@ -228,4 +229,22 @@ public abstract class AbstractAllocateAllocationCommand implements AllocationCom
 
     protected void extraXContent(XContentBuilder builder) throws IOException {
     }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null || getClass() != obj.getClass()) {
+            return false;
+        }
+        AbstractAllocateAllocationCommand other = (AbstractAllocateAllocationCommand) obj;
+        // Override equals and hashCode for testing
+        return Objects.equals(index, other.index) &&
+                Objects.equals(shardId, other.shardId) &&
+                Objects.equals(node, other.node);
+    }
+
+    @Override
+    public int hashCode() {
+        // Override equals and hashCode for testing
+        return Objects.hash(index, shardId, node);
+    }
 }

+ 0 - 2
core/src/main/java/org/elasticsearch/cluster/routing/allocation/command/AllocateReplicaAllocationCommand.java

@@ -136,6 +136,4 @@ public class AllocateReplicaAllocationCommand extends AbstractAllocateAllocation
         initializeUnassignedShard(allocation, routingNodes, routingNode, shardRouting);
         return new RerouteExplanation(this, decision);
     }
-
-
 }

+ 4 - 1
core/src/main/java/org/elasticsearch/cluster/routing/allocation/command/AllocationCommand.java

@@ -22,13 +22,16 @@ package org.elasticsearch.cluster.routing.allocation.command;
 import org.elasticsearch.cluster.routing.allocation.RerouteExplanation;
 import org.elasticsearch.cluster.routing.allocation.RoutingAllocation;
 import org.elasticsearch.common.io.stream.NamedWriteable;
+import org.elasticsearch.common.network.NetworkModule;
 import org.elasticsearch.common.xcontent.ToXContent;
 import org.elasticsearch.common.xcontent.XContentParser;
 
 import java.io.IOException;
 
 /**
- * This interface defines the basic methods of commands for allocation
+ * A command to move shards in some way.
+ *
+ * Commands are registered in {@link NetworkModule}.
  */
 public interface AllocationCommand extends NamedWriteable, ToXContent {
     interface Parser<T extends AllocationCommand> {

+ 23 - 12
core/src/main/java/org/elasticsearch/cluster/routing/allocation/command/AllocationCommands.java

@@ -20,12 +20,12 @@
 package org.elasticsearch.cluster.routing.allocation.command;
 
 import org.elasticsearch.ElasticsearchParseException;
+import org.elasticsearch.action.support.ToXContentToBytes;
 import org.elasticsearch.cluster.routing.allocation.RoutingAllocation;
 import org.elasticsearch.cluster.routing.allocation.RoutingExplanations;
 import org.elasticsearch.common.ParseFieldMatcher;
 import org.elasticsearch.common.io.stream.StreamInput;
 import org.elasticsearch.common.io.stream.StreamOutput;
-import org.elasticsearch.common.xcontent.ToXContent;
 import org.elasticsearch.common.xcontent.XContentBuilder;
 import org.elasticsearch.common.xcontent.XContentParser;
 
@@ -33,12 +33,13 @@ import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Objects;
 
 /**
  * A simple {@link AllocationCommand} composite managing several
  * {@link AllocationCommand} implementations
  */
-public class AllocationCommands {
+public class AllocationCommands extends ToXContentToBytes {
     private final List<AllocationCommand> commands = new ArrayList<>();
 
     /**
@@ -171,21 +172,31 @@ public class AllocationCommands {
         return commands;
     }
 
-    /**
-     * Writes {@link AllocationCommands} to a {@link XContentBuilder}
-     *
-     * @param commands {@link AllocationCommands} to write
-     * @param builder {@link XContentBuilder} to use
-     * @param params Parameters to use for building
-     * @throws IOException if something bad happens while building the content
-     */
-    public static void toXContent(AllocationCommands commands, XContentBuilder builder, ToXContent.Params params) throws IOException {
+    @Override
+    public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
         builder.startArray("commands");
-        for (AllocationCommand command : commands.commands) {
+        for (AllocationCommand command : commands) {
             builder.startObject();
             builder.field(command.name(), command);
             builder.endObject();
         }
         builder.endArray();
+        return builder;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null || getClass() != obj.getClass()) {
+            return false;
+        }
+        AllocationCommands other = (AllocationCommands) obj;
+        // Override equals and hashCode for testing
+        return Objects.equals(commands, other.commands);
+    }
+
+    @Override
+    public int hashCode() {
+        // Override equals and hashCode for testing
+        return Objects.hashCode(commands);
     }
 }

+ 14 - 0
core/src/main/java/org/elasticsearch/cluster/routing/allocation/command/BasePrimaryAllocationCommand.java

@@ -83,4 +83,18 @@ public abstract class BasePrimaryAllocationCommand extends AbstractAllocateAlloc
     protected void extraXContent(XContentBuilder builder) throws IOException {
         builder.field(ACCEPT_DATA_LOSS_FIELD, acceptDataLoss);
     }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (false == super.equals(obj)) {
+            return false;
+        }
+        BasePrimaryAllocationCommand other = (BasePrimaryAllocationCommand) obj;
+        return acceptDataLoss == other.acceptDataLoss;
+    }
+
+    @Override
+    public int hashCode() {
+        return 31 * super.hashCode() + Boolean.hashCode(acceptDataLoss);
+    }
 }

+ 20 - 0
core/src/main/java/org/elasticsearch/cluster/routing/allocation/command/CancelAllocationCommand.java

@@ -35,6 +35,7 @@ import org.elasticsearch.common.xcontent.XContentBuilder;
 import org.elasticsearch.common.xcontent.XContentParser;
 
 import java.io.IOException;
+import java.util.Objects;
 
 import static org.elasticsearch.cluster.routing.ShardRoutingState.RELOCATING;
 
@@ -240,4 +241,23 @@ public class CancelAllocationCommand implements AllocationCommand {
         }
         return new CancelAllocationCommand(index, shardId, nodeId, allowPrimary);
     }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null || getClass() != obj.getClass()) {
+            return false;
+        }
+        CancelAllocationCommand other = (CancelAllocationCommand) obj;
+        // Override equals and hashCode for testing
+        return Objects.equals(index, other.index) &&
+                Objects.equals(shardId, other.shardId) &&
+                Objects.equals(node, other.node) &&
+                Objects.equals(allowPrimary, other.allowPrimary);
+    }
+
+    @Override
+    public int hashCode() {
+        // Override equals and hashCode for testing
+        return Objects.hash(index, shardId, node, allowPrimary);
+    }
 }

+ 20 - 0
core/src/main/java/org/elasticsearch/cluster/routing/allocation/command/MoveAllocationCommand.java

@@ -34,6 +34,7 @@ import org.elasticsearch.common.xcontent.XContentBuilder;
 import org.elasticsearch.common.xcontent.XContentParser;
 
 import java.io.IOException;
+import java.util.Objects;
 
 /**
  * A command that moves a shard from a specific node to another node.<br>
@@ -195,4 +196,23 @@ public class MoveAllocationCommand implements AllocationCommand {
         }
         return new MoveAllocationCommand(index, shardId, fromNode, toNode);
     }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null || getClass() != obj.getClass()) {
+            return false;
+        }
+        MoveAllocationCommand other = (MoveAllocationCommand) obj;
+        // Override equals and hashCode for testing
+        return Objects.equals(index, other.index) &&
+                Objects.equals(shardId, other.shardId) &&
+                Objects.equals(fromNode, other.fromNode) &&
+                Objects.equals(toNode, other.toNode);
+    }
+
+    @Override
+    public int hashCode() {
+        // Override equals and hashCode for testing
+        return Objects.hash(index, shardId, fromNode, toNode);
+    }
 }

+ 10 - 10
core/src/main/java/org/elasticsearch/common/network/NetworkModule.java

@@ -19,9 +19,6 @@
 
 package org.elasticsearch.common.network;
 
-import java.util.Arrays;
-import java.util.List;
-
 import org.elasticsearch.action.support.replication.ReplicationTask;
 import org.elasticsearch.client.transport.TransportClientNodesService;
 import org.elasticsearch.client.transport.support.TransportProxyClient;
@@ -68,6 +65,12 @@ import org.elasticsearch.rest.action.admin.cluster.snapshots.restore.RestRestore
 import org.elasticsearch.rest.action.admin.cluster.snapshots.status.RestSnapshotsStatusAction;
 import org.elasticsearch.rest.action.admin.cluster.state.RestClusterStateAction;
 import org.elasticsearch.rest.action.admin.cluster.stats.RestClusterStatsAction;
+import org.elasticsearch.rest.action.admin.cluster.storedscripts.RestDeleteSearchTemplateAction;
+import org.elasticsearch.rest.action.admin.cluster.storedscripts.RestDeleteStoredScriptAction;
+import org.elasticsearch.rest.action.admin.cluster.storedscripts.RestGetSearchTemplateAction;
+import org.elasticsearch.rest.action.admin.cluster.storedscripts.RestGetStoredScriptAction;
+import org.elasticsearch.rest.action.admin.cluster.storedscripts.RestPutSearchTemplateAction;
+import org.elasticsearch.rest.action.admin.cluster.storedscripts.RestPutStoredScriptAction;
 import org.elasticsearch.rest.action.admin.cluster.tasks.RestPendingClusterTasksAction;
 import org.elasticsearch.rest.action.admin.indices.alias.RestIndicesAliasesAction;
 import org.elasticsearch.rest.action.admin.indices.alias.delete.RestIndexDeleteAliasesAction;
@@ -134,17 +137,11 @@ import org.elasticsearch.rest.action.ingest.RestGetPipelineAction;
 import org.elasticsearch.rest.action.ingest.RestPutPipelineAction;
 import org.elasticsearch.rest.action.ingest.RestSimulatePipelineAction;
 import org.elasticsearch.rest.action.main.RestMainAction;
-import org.elasticsearch.rest.action.admin.cluster.storedscripts.RestDeleteStoredScriptAction;
-import org.elasticsearch.rest.action.admin.cluster.storedscripts.RestGetStoredScriptAction;
-import org.elasticsearch.rest.action.admin.cluster.storedscripts.RestPutStoredScriptAction;
 import org.elasticsearch.rest.action.search.RestClearScrollAction;
 import org.elasticsearch.rest.action.search.RestMultiSearchAction;
 import org.elasticsearch.rest.action.search.RestSearchAction;
 import org.elasticsearch.rest.action.search.RestSearchScrollAction;
 import org.elasticsearch.rest.action.suggest.RestSuggestAction;
-import org.elasticsearch.rest.action.admin.cluster.storedscripts.RestDeleteSearchTemplateAction;
-import org.elasticsearch.rest.action.admin.cluster.storedscripts.RestGetSearchTemplateAction;
-import org.elasticsearch.rest.action.admin.cluster.storedscripts.RestPutSearchTemplateAction;
 import org.elasticsearch.rest.action.termvectors.RestMultiTermVectorsAction;
 import org.elasticsearch.rest.action.termvectors.RestTermVectorsAction;
 import org.elasticsearch.rest.action.update.RestUpdateAction;
@@ -154,6 +151,9 @@ import org.elasticsearch.transport.TransportService;
 import org.elasticsearch.transport.local.LocalTransport;
 import org.elasticsearch.transport.netty.NettyTransport;
 
+import java.util.Arrays;
+import java.util.List;
+
 /**
  * A module to handle registering and binding all network related classes.
  */
@@ -397,7 +397,7 @@ public class NetworkModule extends AbstractModule {
      * @param commandName the names under which the command should be parsed. The {@link ParseField#getPreferredName()} is special because
      *        it is the name under which the command's reader is registered.
      */
-    public <T extends AllocationCommand> void registerAllocationCommand(Writeable.Reader<T> reader, AllocationCommand.Parser<T> parser,
+    private <T extends AllocationCommand> void registerAllocationCommand(Writeable.Reader<T> reader, AllocationCommand.Parser<T> parser,
             ParseField commandName) {
         allocationCommandRegistry.register(parser, commandName);
         namedWriteableRegistry.register(AllocationCommand.class, commandName.getPreferredName(), reader);

+ 48 - 13
core/src/main/java/org/elasticsearch/rest/action/admin/cluster/reroute/RestClusterRerouteAction.java

@@ -25,12 +25,20 @@ import org.elasticsearch.client.Client;
 import org.elasticsearch.client.Requests;
 import org.elasticsearch.cluster.ClusterState;
 import org.elasticsearch.cluster.routing.allocation.command.AllocationCommandRegistry;
+import org.elasticsearch.cluster.routing.allocation.command.AllocationCommands;
+import org.elasticsearch.common.ParseField;
+import org.elasticsearch.common.ParseFieldMatcher;
+import org.elasticsearch.common.ParseFieldMatcherSupplier;
 import org.elasticsearch.common.Strings;
 import org.elasticsearch.common.inject.Inject;
 import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.common.settings.SettingsFilter;
+import org.elasticsearch.common.xcontent.ObjectParser;
+import org.elasticsearch.common.xcontent.ObjectParser.ValueType;
 import org.elasticsearch.common.xcontent.ToXContent;
 import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentHelper;
+import org.elasticsearch.common.xcontent.XContentParser;
 import org.elasticsearch.rest.BaseRestHandler;
 import org.elasticsearch.rest.RestChannel;
 import org.elasticsearch.rest.RestController;
@@ -43,12 +51,17 @@ import java.util.EnumSet;
 /**
  */
 public class RestClusterRerouteAction extends BaseRestHandler {
+    private static final ObjectParser<ClusterRerouteRequest, ParseContext> PARSER = new ObjectParser<>("cluster_reroute");
+    static {
+        PARSER.declareField((p, v, c) -> v.commands(AllocationCommands.fromXContent(p, c.getParseFieldMatcher(), c.registry)),
+                new ParseField("commands"), ValueType.OBJECT_ARRAY);
+        PARSER.declareBoolean(ClusterRerouteRequest::dryRun, new ParseField("dry_run"));
+    }
 
-    private final SettingsFilter settingsFilter;
-
-    private static String DEFAULT_METRICS = Strings
+    private static final String DEFAULT_METRICS = Strings
             .arrayToCommaDelimitedString(EnumSet.complementOf(EnumSet.of(ClusterState.Metric.METADATA)).toArray());
 
+    private final SettingsFilter settingsFilter;
     private final AllocationCommandRegistry registry;
 
     @Inject
@@ -62,16 +75,7 @@ public class RestClusterRerouteAction extends BaseRestHandler {
 
     @Override
     public void handleRequest(final RestRequest request, final RestChannel channel, final Client client) throws Exception {
-        final ClusterRerouteRequest clusterRerouteRequest = Requests.clusterRerouteRequest();
-        clusterRerouteRequest.dryRun(request.paramAsBoolean("dry_run", clusterRerouteRequest.dryRun()));
-        clusterRerouteRequest.setRetryFailed(request.paramAsBoolean("retry_failed", clusterRerouteRequest.isRetryFailed()));
-        clusterRerouteRequest.explain(request.paramAsBoolean("explain", clusterRerouteRequest.explain()));
-        clusterRerouteRequest.timeout(request.paramAsTime("timeout", clusterRerouteRequest.timeout()));
-        clusterRerouteRequest.masterNodeTimeout(request.paramAsTime("master_timeout", clusterRerouteRequest.masterNodeTimeout()));
-        if (request.hasContent()) {
-            clusterRerouteRequest.source(request.content(), registry, parseFieldMatcher);
-        }
-
+        ClusterRerouteRequest clusterRerouteRequest = createRequest(request, registry, parseFieldMatcher);
         client.admin().cluster().reroute(clusterRerouteRequest, new AcknowledgedRestListener<ClusterRerouteResponse>(channel) {
             @Override
             protected void addCustomFields(XContentBuilder builder, ClusterRerouteResponse response) throws IOException {
@@ -90,4 +94,35 @@ public class RestClusterRerouteAction extends BaseRestHandler {
             }
         });
     }
+
+    public static ClusterRerouteRequest createRequest(RestRequest request, AllocationCommandRegistry registry,
+            ParseFieldMatcher parseFieldMatcher) throws IOException {
+        ClusterRerouteRequest clusterRerouteRequest = Requests.clusterRerouteRequest();
+        clusterRerouteRequest.dryRun(request.paramAsBoolean("dry_run", clusterRerouteRequest.dryRun()));
+        clusterRerouteRequest.explain(request.paramAsBoolean("explain", clusterRerouteRequest.explain()));
+        clusterRerouteRequest.timeout(request.paramAsTime("timeout", clusterRerouteRequest.timeout()));
+        clusterRerouteRequest.setRetryFailed(request.paramAsBoolean("retry_failed", clusterRerouteRequest.isRetryFailed()));
+        clusterRerouteRequest.masterNodeTimeout(request.paramAsTime("master_timeout", clusterRerouteRequest.masterNodeTimeout()));
+        if (request.hasContent()) {
+            try (XContentParser parser = XContentHelper.createParser(request.content())) {
+                PARSER.parse(parser, clusterRerouteRequest, new ParseContext(registry, parseFieldMatcher));
+            }
+        }
+        return clusterRerouteRequest;
+    }
+
+    private static class ParseContext implements ParseFieldMatcherSupplier {
+        private final AllocationCommandRegistry registry;
+        private final ParseFieldMatcher parseFieldMatcher;
+
+        private ParseContext(AllocationCommandRegistry registry, ParseFieldMatcher parseFieldMatcher) {
+            this.registry = registry;
+            this.parseFieldMatcher = parseFieldMatcher;
+        }
+
+        @Override
+        public ParseFieldMatcher getParseFieldMatcher() {
+            return parseFieldMatcher;
+        }
+    }
 }

+ 214 - 0
core/src/test/java/org/elasticsearch/action/admin/cluster/reroute/ClusterRerouteRequestTests.java

@@ -0,0 +1,214 @@
+/*
+ * 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.cluster.reroute;
+
+import org.elasticsearch.action.support.master.AcknowledgedRequest;
+import org.elasticsearch.action.support.master.MasterNodeRequest;
+import org.elasticsearch.cluster.routing.allocation.command.AllocateEmptyPrimaryAllocationCommand;
+import org.elasticsearch.cluster.routing.allocation.command.AllocateReplicaAllocationCommand;
+import org.elasticsearch.cluster.routing.allocation.command.AllocateStalePrimaryAllocationCommand;
+import org.elasticsearch.cluster.routing.allocation.command.AllocationCommand;
+import org.elasticsearch.cluster.routing.allocation.command.AllocationCommandRegistry;
+import org.elasticsearch.cluster.routing.allocation.command.CancelAllocationCommand;
+import org.elasticsearch.cluster.routing.allocation.command.MoveAllocationCommand;
+import org.elasticsearch.common.ParseFieldMatcher;
+import org.elasticsearch.common.io.stream.BytesStreamOutput;
+import org.elasticsearch.common.io.stream.NamedWriteableAwareStreamInput;
+import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
+import org.elasticsearch.common.io.stream.StreamInput;
+import org.elasticsearch.common.network.NetworkModule;
+import org.elasticsearch.common.xcontent.ToXContent;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentFactory;
+import org.elasticsearch.common.xcontent.XContentType;
+import org.elasticsearch.rest.RestRequest;
+import org.elasticsearch.rest.action.admin.cluster.reroute.RestClusterRerouteAction;
+import org.elasticsearch.test.ESTestCase;
+import org.elasticsearch.test.rest.FakeRestRequest;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Supplier;
+
+import static java.util.Collections.emptyMap;
+import static java.util.Collections.unmodifiableList;
+import static org.elasticsearch.common.unit.TimeValue.timeValueMillis;
+
+/**
+ * Test for serialization and parsing of {@link ClusterRerouteRequest} and its commands. See the superclass for, well, everything.
+ */
+public class ClusterRerouteRequestTests extends ESTestCase {
+    private static final int ROUNDS = 30;
+    private final List<Supplier<AllocationCommand>> RANDOM_COMMAND_GENERATORS = unmodifiableList(Arrays.asList(
+            () -> new AllocateReplicaAllocationCommand(randomAsciiOfLengthBetween(2, 10), between(0, 1000),
+                    randomAsciiOfLengthBetween(2, 10)),
+            () -> new AllocateEmptyPrimaryAllocationCommand(randomAsciiOfLengthBetween(2, 10), between(0, 1000),
+                    randomAsciiOfLengthBetween(2, 10), randomBoolean()),
+            () -> new AllocateStalePrimaryAllocationCommand(randomAsciiOfLengthBetween(2, 10), between(0, 1000),
+                    randomAsciiOfLengthBetween(2, 10), randomBoolean()),
+            () -> new CancelAllocationCommand(randomAsciiOfLengthBetween(2, 10), between(0, 1000),
+                    randomAsciiOfLengthBetween(2, 10), randomBoolean()),
+            () -> new MoveAllocationCommand(randomAsciiOfLengthBetween(2, 10), between(0, 1000),
+                    randomAsciiOfLengthBetween(2, 10), randomAsciiOfLengthBetween(2, 10))));
+    private final NamedWriteableRegistry namedWriteableRegistry;
+    private final AllocationCommandRegistry allocationCommandRegistry;
+
+    public ClusterRerouteRequestTests() {
+        namedWriteableRegistry = new NamedWriteableRegistry();
+        allocationCommandRegistry = new NetworkModule(null, null, true, namedWriteableRegistry).getAllocationCommandRegistry();
+    }
+
+    private ClusterRerouteRequest randomRequest() {
+        ClusterRerouteRequest request = new ClusterRerouteRequest();
+        int commands = between(0, 10);
+        for (int i = 0; i < commands; i++) {
+            request.add(randomFrom(RANDOM_COMMAND_GENERATORS).get());
+        }
+        request.dryRun(randomBoolean());
+        request.explain(randomBoolean());
+        request.setRetryFailed(randomBoolean());
+        return request;
+    }
+
+    public void testEqualsAndHashCode() {
+        for (int round = 0; round < ROUNDS; round++) {
+            ClusterRerouteRequest request = randomRequest();
+            assertEquals(request, request);
+            assertEquals(request.hashCode(), request.hashCode());
+
+            ClusterRerouteRequest copy = new ClusterRerouteRequest()
+                    .add(request.getCommands().commands().toArray(new AllocationCommand[0]));
+            copy.dryRun(request.dryRun()).explain(request.explain()).timeout(request.timeout()).setRetryFailed(request.isRetryFailed());
+            copy.masterNodeTimeout(request.masterNodeTimeout());
+            assertEquals(request, copy);
+            assertEquals(copy, request); // Commutative
+            assertEquals(request.hashCode(), copy.hashCode());
+
+            // Changing dryRun makes requests not equal
+            copy.dryRun(!copy.dryRun());
+            assertNotEquals(request, copy);
+            assertNotEquals(request.hashCode(), copy.hashCode());
+            copy.dryRun(!copy.dryRun());
+            assertEquals(request, copy);
+            assertEquals(request.hashCode(), copy.hashCode());
+
+            // Changing explain makes requests not equal
+            copy.explain(!copy.explain());
+            assertNotEquals(request, copy);
+            assertNotEquals(request.hashCode(), copy.hashCode());
+            copy.explain(!copy.explain());
+            assertEquals(request, copy);
+            assertEquals(request.hashCode(), copy.hashCode());
+
+            // Changing timeout makes requests not equal
+            copy.timeout(timeValueMillis(request.timeout().millis() + 1));
+            assertNotEquals(request, copy);
+            assertNotEquals(request.hashCode(), copy.hashCode());
+            copy.timeout(request.timeout());
+            assertEquals(request, copy);
+            assertEquals(request.hashCode(), copy.hashCode());
+
+            // Changing masterNodeTime makes requests not equal
+            copy.masterNodeTimeout(timeValueMillis(request.masterNodeTimeout().millis() + 1));
+            assertNotEquals(request, copy);
+            assertNotEquals(request.hashCode(), copy.hashCode());
+            copy.masterNodeTimeout(request.masterNodeTimeout());
+            assertEquals(request, copy);
+            assertEquals(request.hashCode(), copy.hashCode());
+
+            // Changing commands makes requests not equal
+            copy.add(randomFrom(RANDOM_COMMAND_GENERATORS).get());
+            assertNotEquals(request, copy);
+            // Can't check hashCode because we can't be sure that changing commands changes the hashCode. It usually does but might not.
+        }
+    }
+
+    public void testSerialization() throws IOException {
+        for (int round = 0; round < ROUNDS; round++) {
+            ClusterRerouteRequest request = randomRequest();
+            ClusterRerouteRequest copy = roundTripThroughBytes(request);
+            assertEquals(request, copy);
+            assertEquals(request.hashCode(), copy.hashCode());
+            assertNotSame(request, copy);
+        }
+    }
+
+    public void testParsing() throws IOException {
+        for (int round = 0; round < ROUNDS; round++) {
+            ClusterRerouteRequest request = randomRequest();
+            ClusterRerouteRequest copy = roundTripThroughRestRequest(request);
+            assertEquals(request, copy);
+            assertEquals(request.hashCode(), copy.hashCode());
+            assertNotSame(request, copy);
+        }
+    }
+
+    private ClusterRerouteRequest roundTripThroughBytes(ClusterRerouteRequest original) throws IOException {
+        try (BytesStreamOutput output = new BytesStreamOutput()) {
+            original.writeTo(output);
+            try (StreamInput in = new NamedWriteableAwareStreamInput(StreamInput.wrap(output.bytes()), namedWriteableRegistry)) {
+                ClusterRerouteRequest copy = new ClusterRerouteRequest();
+                copy.readFrom(in);
+                return copy;
+            }
+        }
+    }
+
+    private ClusterRerouteRequest roundTripThroughRestRequest(ClusterRerouteRequest original) throws IOException {
+        RestRequest restRequest = toRestRequest(original);
+        return RestClusterRerouteAction.createRequest(restRequest, allocationCommandRegistry, ParseFieldMatcher.STRICT);
+    }
+
+    private static RestRequest toRestRequest(ClusterRerouteRequest original) throws IOException {
+        Map<String, String> params = new HashMap<>();
+        XContentBuilder builder = XContentFactory.contentBuilder(randomFrom(XContentType.values()));
+        boolean hasBody = false;
+        if (randomBoolean()) {
+            builder.prettyPrint();
+        }
+        builder.startObject();
+        if (randomBoolean()) {
+            params.put("dry_run", Boolean.toString(original.dryRun()));
+        } else {
+            hasBody = true;
+            builder.field("dry_run", original.dryRun());
+        }
+        params.put("explain", Boolean.toString(original.explain()));
+        if (false == original.timeout().equals(AcknowledgedRequest.DEFAULT_ACK_TIMEOUT) || randomBoolean()) {
+            params.put("timeout", original.timeout().toString());
+        }
+        if (original.isRetryFailed() || randomBoolean()) {
+            params.put("retry_failed", Boolean.toString(original.isRetryFailed()));
+        }
+        if (false == original.masterNodeTimeout().equals(MasterNodeRequest.DEFAULT_MASTER_NODE_TIMEOUT) || randomBoolean()) {
+            params.put("master_timeout", original.masterNodeTimeout().toString());
+        }
+        if (original.getCommands() != null) {
+            hasBody = true;
+            original.getCommands().toXContent(builder, ToXContent.EMPTY_PARAMS);
+        }
+        builder.endObject();
+
+        return new FakeRestRequest(emptyMap(), params, hasBody ? builder.bytes() : null);
+    }
+}

+ 1 - 1
core/src/test/java/org/elasticsearch/action/admin/cluster/reroute/ClusterRerouteTests.java

@@ -59,7 +59,7 @@ public class ClusterRerouteTests extends ESAllocationTestCase {
         req.setRetryFailed(randomBoolean());
         req.dryRun(randomBoolean());
         req.explain(randomBoolean());
-        req.commands(new AllocateEmptyPrimaryAllocationCommand("foo", 1, "bar", randomBoolean()));
+        req.add(new AllocateEmptyPrimaryAllocationCommand("foo", 1, "bar", randomBoolean()));
         req.timeout(TimeValue.timeValueMillis(randomIntBetween(0, 100)));
         BytesStreamOutput out = new BytesStreamOutput();
         req.writeTo(out);

+ 5 - 0
docs/reference/migration/migrate_5_0/allocation.asciidoc

@@ -39,6 +39,11 @@ command corresponds to the old `allocate` command  with `allow_primary` set to
 false. The new `allocate_empty_primary` command corresponds to the old
 `allocate` command with `allow_primary` set to true.
 
+==== Custom Reroute Commands
+
+Elasticsearch no longer supports plugins registering custom allocation
+commands. It was unused and hopefully unneeded.
+
 ==== `index.shared_filesystem.recover_on_any_node` changes
 
 The behavior of `index.shared_filesystem.recover_on_any_node: true` has been