Move parsing of allocation commands into REST

Port them to the ObjectParser.

Don't let plugins register custom allocation commands
This commit is contained in:
Nik Everett 2016-04-15 17:32:25 -04:00
parent f210605af8
commit a93f578bf6
15 changed files with 405 additions and 90 deletions

View File

@ -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" />

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

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

View File

@ -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> {

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);

View File

@ -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;
}
}
}

View File

@ -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);
}
}

View File

@ -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);

View File

@ -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