Skip to content

Use optype CREATE for single auto-id index requests #47353

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,7 @@
"options":[
"internal",
"external",
"external_gte",
"force"
"external_gte"
],
"description":"Specific version type"
},
Expand Down
6 changes: 2 additions & 4 deletions rest-api-spec/src/main/resources/rest-api-spec/api/index.json
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,7 @@
"index",
"create"
],
"default":"index",
"description":"Explicit operation type"
"description":"Explicit operation type. Defaults to `index` for requests with an explicit document ID, and to `create`for requests without an explicit document ID"
},
"refresh":{
"type":"enum",
Expand Down Expand Up @@ -125,8 +124,7 @@
"options":[
"internal",
"external",
"external_gte",
"force"
"external_gte"
],
"description":"Specific version type"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@
import org.elasticsearch.client.node.NodeClient;
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
import org.elasticsearch.cluster.node.DiscoveryNodes;
import org.elasticsearch.cluster.service.ClusterService;
import org.elasticsearch.common.NamedRegistry;
import org.elasticsearch.common.inject.AbstractModule;
import org.elasticsearch.common.inject.TypeLiteral;
Expand Down Expand Up @@ -369,17 +370,19 @@ public class ActionModule extends AbstractModule {
private final RestController restController;
private final RequestValidators<PutMappingRequest> mappingRequestValidators;
private final RequestValidators<IndicesAliasesRequest> indicesAliasesRequestRequestValidators;
private final ClusterService clusterService;

public ActionModule(Settings settings, IndexNameExpressionResolver indexNameExpressionResolver,
IndexScopedSettings indexScopedSettings, ClusterSettings clusterSettings, SettingsFilter settingsFilter,
ThreadPool threadPool, List<ActionPlugin> actionPlugins, NodeClient nodeClient,
CircuitBreakerService circuitBreakerService, UsageService usageService) {
CircuitBreakerService circuitBreakerService, UsageService usageService, ClusterService clusterService) {
this.settings = settings;
this.indexNameExpressionResolver = indexNameExpressionResolver;
this.indexScopedSettings = indexScopedSettings;
this.clusterSettings = clusterSettings;
this.settingsFilter = settingsFilter;
this.actionPlugins = actionPlugins;
this.clusterService = clusterService;
actions = setupActions(actionPlugins);
actionFilters = setupActionFilters(actionPlugins);
autoCreateIndex = new AutoCreateIndex(settings, clusterSettings, indexNameExpressionResolver);
Expand Down Expand Up @@ -629,7 +632,7 @@ public void initRestHandlers(Supplier<DiscoveryNodes> nodesInCluster) {
registerHandler.accept(new RestUpgradeStatusAction(restController));
registerHandler.accept(new RestClearIndicesCacheAction(restController));

registerHandler.accept(new RestIndexAction(restController));
registerHandler.accept(new RestIndexAction(restController, clusterService));
registerHandler.accept(new RestGetAction(restController));
registerHandler.accept(new RestGetSourceAction(restController));
registerHandler.accept(new RestMultiGetAction(settings, restController));
Expand Down
2 changes: 1 addition & 1 deletion server/src/main/java/org/elasticsearch/node/Node.java
Original file line number Diff line number Diff line change
Expand Up @@ -450,7 +450,7 @@ protected Node(

ActionModule actionModule = new ActionModule(settings, clusterModule.getIndexNameExpressionResolver(),
settingsModule.getIndexScopedSettings(), settingsModule.getClusterSettings(), settingsModule.getSettingsFilter(),
threadPool, pluginsService.filterPlugins(ActionPlugin.class), client, circuitBreakerService, usageService);
threadPool, pluginsService.filterPlugins(ActionPlugin.class), client, circuitBreakerService, usageService, clusterService);
modules.add(actionModule);

final RestController restController = actionModule.getRestController();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@
package org.elasticsearch.rest.action.document;

import org.apache.logging.log4j.LogManager;
import org.elasticsearch.Version;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.support.ActiveShardCount;
import org.elasticsearch.client.node.NodeClient;
import org.elasticsearch.cluster.service.ClusterService;
import org.elasticsearch.common.logging.DeprecationLogger;
import org.elasticsearch.index.VersionType;
import org.elasticsearch.index.mapper.MapperService;
Expand All @@ -45,8 +47,13 @@ public class RestIndexAction extends BaseRestHandler {
"index requests is deprecated, use the typeless endpoints instead (/{index}/_doc/{id}, /{index}/_doc, " +
"or /{index}/_create/{id}).";

public RestIndexAction(RestController controller) {
controller.registerHandler(POST, "/{index}/_doc", this); // auto id creation
private final ClusterService clusterService;

public RestIndexAction(RestController controller, ClusterService clusterService) {
this.clusterService = clusterService;

AutoIdHandler autoIdHandler = new AutoIdHandler();
controller.registerHandler(POST, "/{index}/_doc", autoIdHandler); // auto id creation
controller.registerHandler(PUT, "/{index}/_doc/{id}", this);
controller.registerHandler(POST, "/{index}/_doc/{id}", this);

Expand All @@ -55,7 +62,7 @@ public RestIndexAction(RestController controller) {
controller.registerHandler(POST, "/{index}/_create/{id}/", createHandler);

// Deprecated typed endpoints.
controller.registerHandler(POST, "/{index}/{type}", this); // auto id creation
controller.registerHandler(POST, "/{index}/{type}", autoIdHandler); // auto id creation
controller.registerHandler(PUT, "/{index}/{type}/{id}", this);
controller.registerHandler(POST, "/{index}/{type}/{id}", this);
controller.registerHandler(PUT, "/{index}/{type}/{id}/_create", createHandler);
Expand Down Expand Up @@ -90,6 +97,26 @@ void validateOpType(String opType) {
}
}

final class AutoIdHandler extends BaseRestHandler {
protected AutoIdHandler() {
}

@Override
public String getName() {
return "document_create_action";
}

@Override
public RestChannelConsumer prepareRequest(RestRequest request, final NodeClient client) throws IOException {
assert request.params().get("id") == null : "non-null id: " + request.params().get("id");
if (request.params().get("op_type") == null && clusterService.state().nodes().getMinNodeVersion().onOrAfter(Version.CURRENT)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The rest-api-spec (index.json) says default for op_type is index, I think we should change that to be create.

I wonder if we should have an end-goal of removing op_type support here as a breaking change in 8.0?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem is that this API spec mixes auto-id which non-auto-id. In one case, the default op_type is create, in the other case it is index. I have moved the explanation of the default into the description (as we have for many other parameters).

// default to op_type create
request.params().put("op_type", "create");
}
return RestIndexAction.this.prepareRequest(request, client);
}
}

@Override
public RestChannelConsumer prepareRequest(final RestRequest request, final NodeClient client) throws IOException {
IndexRequest indexRequest;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ public void testSetupRestHandlerContainsKnownBuiltin() {
UsageService usageService = new UsageService();
ActionModule actionModule = new ActionModule(settings.getSettings(), new IndexNameExpressionResolver(),
settings.getIndexScopedSettings(), settings.getClusterSettings(), settings.getSettingsFilter(), null, emptyList(), null,
null, usageService);
null, usageService, null);
actionModule.initRestHandlers(null);
// At this point the easiest way to confirm that a handler is loaded is to try to register another one on top of it and to fail
Exception e = expectThrows(IllegalArgumentException.class, () ->
Expand All @@ -132,7 +132,7 @@ public List<RestHandler> getRestHandlers(Settings settings, RestController restC
UsageService usageService = new UsageService();
ActionModule actionModule = new ActionModule(settings.getSettings(), new IndexNameExpressionResolver(),
settings.getIndexScopedSettings(), settings.getClusterSettings(), settings.getSettingsFilter(), threadPool,
singletonList(dupsMainAction), null, null, usageService);
singletonList(dupsMainAction), null, null, usageService, null);
Exception e = expectThrows(IllegalArgumentException.class, () -> actionModule.initRestHandlers(null));
assertThat(e.getMessage(), startsWith("Cannot replace existing handler for [/] for method: GET"));
} finally {
Expand Down Expand Up @@ -164,7 +164,7 @@ public List<RestHandler> getRestHandlers(Settings settings, RestController restC
UsageService usageService = new UsageService();
ActionModule actionModule = new ActionModule(settings.getSettings(), new IndexNameExpressionResolver(),
settings.getIndexScopedSettings(), settings.getClusterSettings(), settings.getSettingsFilter(), threadPool,
singletonList(registersFakeHandler), null, null, usageService);
singletonList(registersFakeHandler), null, null, usageService, null);
actionModule.initRestHandlers(null);
// At this point the easiest way to confirm that a handler is loaded is to try to register another one on top of it and to fail
Exception e = expectThrows(IllegalArgumentException.class, () ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,41 @@
package org.elasticsearch.rest.action.document;

import org.elasticsearch.Version;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.DocWriteRequest;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.cluster.ClusterName;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.node.DiscoveryNode;
import org.elasticsearch.cluster.node.DiscoveryNodes;
import org.elasticsearch.cluster.service.ClusterService;
import org.elasticsearch.common.bytes.BytesArray;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.test.VersionUtils;
import org.elasticsearch.test.rest.FakeRestRequest;
import org.elasticsearch.test.rest.RestActionTestCase;
import org.junit.Before;
import org.mockito.ArgumentCaptor;

import java.util.concurrent.atomic.AtomicReference;

import static org.hamcrest.Matchers.equalTo;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

public class RestIndexActionTests extends RestActionTestCase {

private RestIndexAction action;
private final AtomicReference<ClusterState> clusterStateSupplier = new AtomicReference();

@Before
public void setUpAction() {
action = new RestIndexAction(controller());
ClusterService clusterService = mock(ClusterService.class);
when(clusterService.state()).thenAnswer(invocationOnMock -> clusterStateSupplier.get());
action = new RestIndexAction(controller(), clusterService);
}

public void testTypeInPath() {
Expand Down Expand Up @@ -68,7 +88,6 @@ public void testCreateWithTypeInPath() {
}

public void testCreateOpTypeValidation() {
Settings settings = settings(Version.CURRENT).build();
RestIndexAction.CreateHandler create = action.new CreateHandler();

String opType = randomFrom("CREATE", null);
Expand All @@ -78,4 +97,30 @@ public void testCreateOpTypeValidation() {
IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> create.validateOpType(illegalOpType));
assertThat(e.getMessage(), equalTo("opType must be 'create', found: [" + illegalOpType + "]"));
}

public void testAutoIdDefaultsToOptypeCreate() {
checkAutoIdOpType(Version.CURRENT, DocWriteRequest.OpType.CREATE);
}

public void testAutoIdDefaultsToOptypeIndexForOlderVersions() {
checkAutoIdOpType(VersionUtils.randomVersionBetween(random(), null,
VersionUtils.getPreviousVersion(Version.CURRENT)), DocWriteRequest.OpType.INDEX);
}

private void checkAutoIdOpType(Version minClusterVersion, DocWriteRequest.OpType expectedOpType) {
RestRequest autoIdRequest = new FakeRestRequest.Builder(xContentRegistry())
.withMethod(RestRequest.Method.POST)
.withPath("/some_index/_doc")
.withContent(new BytesArray("{}"), XContentType.JSON)
.build();
clusterStateSupplier.set(ClusterState.builder(ClusterName.DEFAULT)
.nodes(DiscoveryNodes.builder()
.add(new DiscoveryNode("test", buildNewFakeTransportAddress(), minClusterVersion))
.build()).build());
dispatchRequest(autoIdRequest);
ArgumentCaptor<IndexRequest> argumentCaptor = ArgumentCaptor.forClass(IndexRequest.class);
verify(nodeClient).index(argumentCaptor.capture(), any(ActionListener.class));
IndexRequest indexRequest = argumentCaptor.getValue();
assertEquals(expectedOpType, indexRequest.opType());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,13 @@
*/
public abstract class RestActionTestCase extends ESTestCase {
private RestController controller;
protected NodeClient nodeClient;

@Before
public void setUpController() {
nodeClient = mock(NodeClient.class);
controller = new RestController(Collections.emptySet(), null,
mock(NodeClient.class),
nodeClient,
new NoneCircuitBreakerService(),
new UsageService());
}
Expand Down