Skip to content

Commit 99d2fe2

Browse files
committed
Use optype CREATE for single auto-id index requests (#47353)
Changes auto-id index requests to use optype CREATE, making it compliant with our docs. This will also make these auto-id index requests compatible with the new "create-doc" index privilege (which is based on the optype), the default optype is changed to create, just as it is already documented.
1 parent 0024695 commit 99d2fe2

File tree

10 files changed

+100
-23
lines changed

10 files changed

+100
-23
lines changed

qa/rolling-upgrade/src/test/java/org/elasticsearch/upgrades/IndexingIT.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,10 @@
2323
import org.elasticsearch.client.Request;
2424
import org.elasticsearch.client.Response;
2525
import org.elasticsearch.client.ResponseException;
26+
import org.elasticsearch.cluster.metadata.IndexMetaData;
2627
import org.elasticsearch.common.Booleans;
2728
import org.elasticsearch.common.Strings;
29+
import org.elasticsearch.common.settings.Settings;
2830
import org.elasticsearch.common.xcontent.XContentBuilder;
2931
import org.elasticsearch.rest.action.document.RestBulkAction;
3032

@@ -160,9 +162,10 @@ public void testAutoIdWithOpTypeCreate() throws IOException {
160162

161163
switch (CLUSTER_TYPE) {
162164
case OLD:
163-
Request createTestIndex = new Request("PUT", "/" + indexName);
164-
createTestIndex.setJsonEntity("{\"settings\": {\"index.number_of_replicas\": 0}}");
165-
client().performRequest(createTestIndex);
165+
Settings.Builder settings = Settings.builder()
166+
.put(IndexMetaData.INDEX_NUMBER_OF_SHARDS_SETTING.getKey(), 1)
167+
.put(IndexMetaData.INDEX_NUMBER_OF_REPLICAS_SETTING.getKey(), 0);
168+
createIndex(indexName, settings.build());
166169
break;
167170
case MIXED:
168171
Request waitForGreen = new Request("GET", "/_cluster/health");

rest-api-spec/src/main/resources/rest-api-spec/api/create.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,7 @@
8383
"options":[
8484
"internal",
8585
"external",
86-
"external_gte",
87-
"force"
86+
"external_gte"
8887
],
8988
"description":"Specific version type"
9089
},

rest-api-spec/src/main/resources/rest-api-spec/api/index.json

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,7 @@
9696
"index",
9797
"create"
9898
],
99-
"default":"index",
100-
"description":"Explicit operation type"
99+
"description":"Explicit operation type. Defaults to `index` for requests with an explicit document ID, and to `create`for requests without an explicit document ID"
101100
},
102101
"refresh":{
103102
"type":"enum",
@@ -125,8 +124,7 @@
125124
"options":[
126125
"internal",
127126
"external",
128-
"external_gte",
129-
"force"
127+
"external_gte"
130128
],
131129
"description":"Specific version type"
132130
},

server/src/main/java/org/elasticsearch/action/ActionModule.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,7 @@
206206
import org.elasticsearch.client.node.NodeClient;
207207
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
208208
import org.elasticsearch.cluster.node.DiscoveryNodes;
209+
import org.elasticsearch.cluster.service.ClusterService;
209210
import org.elasticsearch.common.NamedRegistry;
210211
import org.elasticsearch.common.inject.AbstractModule;
211212
import org.elasticsearch.common.inject.TypeLiteral;
@@ -367,18 +368,20 @@ public class ActionModule extends AbstractModule {
367368
private final RestController restController;
368369
private final RequestValidators<PutMappingRequest> mappingRequestValidators;
369370
private final RequestValidators<IndicesAliasesRequest> indicesAliasesRequestRequestValidators;
371+
private final ClusterService clusterService;
370372

371373
public ActionModule(boolean transportClient, Settings settings, IndexNameExpressionResolver indexNameExpressionResolver,
372374
IndexScopedSettings indexScopedSettings, ClusterSettings clusterSettings, SettingsFilter settingsFilter,
373375
ThreadPool threadPool, List<ActionPlugin> actionPlugins, NodeClient nodeClient,
374-
CircuitBreakerService circuitBreakerService, UsageService usageService) {
376+
CircuitBreakerService circuitBreakerService, UsageService usageService, ClusterService clusterService) {
375377
this.transportClient = transportClient;
376378
this.settings = settings;
377379
this.indexNameExpressionResolver = indexNameExpressionResolver;
378380
this.indexScopedSettings = indexScopedSettings;
379381
this.clusterSettings = clusterSettings;
380382
this.settingsFilter = settingsFilter;
381383
this.actionPlugins = actionPlugins;
384+
this.clusterService = clusterService;
382385
actions = setupActions(actionPlugins);
383386
actionFilters = setupActionFilters(actionPlugins);
384387
autoCreateIndex = transportClient ? null : new AutoCreateIndex(settings, clusterSettings, indexNameExpressionResolver);
@@ -624,7 +627,7 @@ public void initRestHandlers(Supplier<DiscoveryNodes> nodesInCluster) {
624627
registerHandler.accept(new RestUpgradeStatusAction(restController));
625628
registerHandler.accept(new RestClearIndicesCacheAction(restController));
626629

627-
registerHandler.accept(new RestIndexAction(restController));
630+
registerHandler.accept(new RestIndexAction(restController, clusterService));
628631
registerHandler.accept(new RestGetAction(restController));
629632
registerHandler.accept(new RestGetSourceAction(restController));
630633
registerHandler.accept(new RestMultiGetAction(settings, restController));

server/src/main/java/org/elasticsearch/client/transport/TransportClient.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ private static ClientTemplate buildTemplate(Settings providedSettings, Settings
186186
modules.add(b -> b.bind(ThreadPool.class).toInstance(threadPool));
187187
ActionModule actionModule = new ActionModule(true, settings, null, settingsModule.getIndexScopedSettings(),
188188
settingsModule.getClusterSettings(), settingsModule.getSettingsFilter(), threadPool,
189-
pluginsService.filterPlugins(ActionPlugin.class), null, null, null);
189+
pluginsService.filterPlugins(ActionPlugin.class), null, null, null, null);
190190
modules.add(actionModule);
191191

192192
CircuitBreakerService circuitBreakerService = Node.createCircuitBreakerService(settingsModule.getSettings(),

server/src/main/java/org/elasticsearch/node/Node.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -453,7 +453,7 @@ protected Node(
453453

454454
ActionModule actionModule = new ActionModule(false, settings, clusterModule.getIndexNameExpressionResolver(),
455455
settingsModule.getIndexScopedSettings(), settingsModule.getClusterSettings(), settingsModule.getSettingsFilter(),
456-
threadPool, pluginsService.filterPlugins(ActionPlugin.class), client, circuitBreakerService, usageService);
456+
threadPool, pluginsService.filterPlugins(ActionPlugin.class), client, circuitBreakerService, usageService, clusterService);
457457
modules.add(actionModule);
458458

459459
final RestController restController = actionModule.getRestController();

server/src/main/java/org/elasticsearch/rest/action/document/RestIndexAction.java

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,11 @@
2020
package org.elasticsearch.rest.action.document;
2121

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

48-
public RestIndexAction(RestController controller) {
49-
controller.registerHandler(POST, "/{index}/_doc", this); // auto id creation
50+
private final ClusterService clusterService;
51+
52+
public RestIndexAction(RestController controller, ClusterService clusterService) {
53+
this.clusterService = clusterService;
54+
55+
AutoIdHandler autoIdHandler = new AutoIdHandler();
56+
controller.registerHandler(POST, "/{index}/_doc", autoIdHandler); // auto id creation
5057
controller.registerHandler(PUT, "/{index}/_doc/{id}", this);
5158
controller.registerHandler(POST, "/{index}/_doc/{id}", this);
5259

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

5764
// Deprecated typed endpoints.
58-
controller.registerHandler(POST, "/{index}/{type}", this); // auto id creation
65+
controller.registerHandler(POST, "/{index}/{type}", autoIdHandler); // auto id creation
5966
controller.registerHandler(PUT, "/{index}/{type}/{id}", this);
6067
controller.registerHandler(POST, "/{index}/{type}/{id}", this);
6168
controller.registerHandler(PUT, "/{index}/{type}/{id}/_create", createHandler);
@@ -90,6 +97,26 @@ void validateOpType(String opType) {
9097
}
9198
}
9299

100+
final class AutoIdHandler extends BaseRestHandler {
101+
protected AutoIdHandler() {
102+
}
103+
104+
@Override
105+
public String getName() {
106+
return "document_create_action";
107+
}
108+
109+
@Override
110+
public RestChannelConsumer prepareRequest(RestRequest request, final NodeClient client) throws IOException {
111+
assert request.params().get("id") == null : "non-null id: " + request.params().get("id");
112+
if (request.params().get("op_type") == null && clusterService.state().nodes().getMinNodeVersion().onOrAfter(Version.CURRENT)) {
113+
// default to op_type create
114+
request.params().put("op_type", "create");
115+
}
116+
return RestIndexAction.this.prepareRequest(request, client);
117+
}
118+
}
119+
93120
@Override
94121
public RestChannelConsumer prepareRequest(final RestRequest request, final NodeClient client) throws IOException {
95122
IndexRequest indexRequest;

server/src/test/java/org/elasticsearch/action/ActionModuleTests.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ public void testSetupRestHandlerContainsKnownBuiltin() {
109109
UsageService usageService = new UsageService();
110110
ActionModule actionModule = new ActionModule(false, settings.getSettings(), new IndexNameExpressionResolver(),
111111
settings.getIndexScopedSettings(), settings.getClusterSettings(), settings.getSettingsFilter(), null, emptyList(), null,
112-
null, usageService);
112+
null, usageService, null);
113113
actionModule.initRestHandlers(null);
114114
// 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
115115
Exception e = expectThrows(IllegalArgumentException.class, () ->
@@ -132,7 +132,7 @@ public List<RestHandler> getRestHandlers(Settings settings, RestController restC
132132
UsageService usageService = new UsageService();
133133
ActionModule actionModule = new ActionModule(false, settings.getSettings(), new IndexNameExpressionResolver(),
134134
settings.getIndexScopedSettings(), settings.getClusterSettings(), settings.getSettingsFilter(), threadPool,
135-
singletonList(dupsMainAction), null, null, usageService);
135+
singletonList(dupsMainAction), null, null, usageService, null);
136136
Exception e = expectThrows(IllegalArgumentException.class, () -> actionModule.initRestHandlers(null));
137137
assertThat(e.getMessage(), startsWith("Cannot replace existing handler for [/] for method: GET"));
138138
} finally {
@@ -164,7 +164,7 @@ public List<RestHandler> getRestHandlers(Settings settings, RestController restC
164164
UsageService usageService = new UsageService();
165165
ActionModule actionModule = new ActionModule(false, settings.getSettings(), new IndexNameExpressionResolver(),
166166
settings.getIndexScopedSettings(), settings.getClusterSettings(), settings.getSettingsFilter(), threadPool,
167-
singletonList(registersFakeHandler), null, null, usageService);
167+
singletonList(registersFakeHandler), null, null, usageService, null);
168168
actionModule.initRestHandlers(null);
169169
// 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
170170
Exception e = expectThrows(IllegalArgumentException.class, () ->

server/src/test/java/org/elasticsearch/rest/action/document/RestIndexActionTests.java

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,21 +20,41 @@
2020
package org.elasticsearch.rest.action.document;
2121

2222
import org.elasticsearch.Version;
23-
import org.elasticsearch.common.settings.Settings;
23+
import org.elasticsearch.action.ActionListener;
24+
import org.elasticsearch.action.DocWriteRequest;
25+
import org.elasticsearch.action.index.IndexRequest;
26+
import org.elasticsearch.cluster.ClusterName;
27+
import org.elasticsearch.cluster.ClusterState;
28+
import org.elasticsearch.cluster.node.DiscoveryNode;
29+
import org.elasticsearch.cluster.node.DiscoveryNodes;
30+
import org.elasticsearch.cluster.service.ClusterService;
31+
import org.elasticsearch.common.bytes.BytesArray;
32+
import org.elasticsearch.common.xcontent.XContentType;
2433
import org.elasticsearch.rest.RestRequest;
34+
import org.elasticsearch.test.VersionUtils;
2535
import org.elasticsearch.test.rest.FakeRestRequest;
2636
import org.elasticsearch.test.rest.RestActionTestCase;
2737
import org.junit.Before;
38+
import org.mockito.ArgumentCaptor;
39+
40+
import java.util.concurrent.atomic.AtomicReference;
2841

2942
import static org.hamcrest.Matchers.equalTo;
43+
import static org.mockito.Matchers.any;
44+
import static org.mockito.Mockito.mock;
45+
import static org.mockito.Mockito.verify;
46+
import static org.mockito.Mockito.when;
3047

3148
public class RestIndexActionTests extends RestActionTestCase {
3249

3350
private RestIndexAction action;
51+
private final AtomicReference<ClusterState> clusterStateSupplier = new AtomicReference();
3452

3553
@Before
3654
public void setUpAction() {
37-
action = new RestIndexAction(controller());
55+
ClusterService clusterService = mock(ClusterService.class);
56+
when(clusterService.state()).thenAnswer(invocationOnMock -> clusterStateSupplier.get());
57+
action = new RestIndexAction(controller(), clusterService);
3858
}
3959

4060
public void testTypeInPath() {
@@ -68,7 +88,6 @@ public void testCreateWithTypeInPath() {
6888
}
6989

7090
public void testCreateOpTypeValidation() {
71-
Settings settings = settings(Version.CURRENT).build();
7291
RestIndexAction.CreateHandler create = action.new CreateHandler();
7392

7493
String opType = randomFrom("CREATE", null);
@@ -78,4 +97,30 @@ public void testCreateOpTypeValidation() {
7897
IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> create.validateOpType(illegalOpType));
7998
assertThat(e.getMessage(), equalTo("opType must be 'create', found: [" + illegalOpType + "]"));
8099
}
100+
101+
public void testAutoIdDefaultsToOptypeCreate() {
102+
checkAutoIdOpType(Version.CURRENT, DocWriteRequest.OpType.CREATE);
103+
}
104+
105+
public void testAutoIdDefaultsToOptypeIndexForOlderVersions() {
106+
checkAutoIdOpType(VersionUtils.randomVersionBetween(random(), null,
107+
VersionUtils.getPreviousVersion(Version.CURRENT)), DocWriteRequest.OpType.INDEX);
108+
}
109+
110+
private void checkAutoIdOpType(Version minClusterVersion, DocWriteRequest.OpType expectedOpType) {
111+
RestRequest autoIdRequest = new FakeRestRequest.Builder(xContentRegistry())
112+
.withMethod(RestRequest.Method.POST)
113+
.withPath("/some_index/_doc")
114+
.withContent(new BytesArray("{}"), XContentType.JSON)
115+
.build();
116+
clusterStateSupplier.set(ClusterState.builder(ClusterName.DEFAULT)
117+
.nodes(DiscoveryNodes.builder()
118+
.add(new DiscoveryNode("test", buildNewFakeTransportAddress(), minClusterVersion))
119+
.build()).build());
120+
dispatchRequest(autoIdRequest);
121+
ArgumentCaptor<IndexRequest> argumentCaptor = ArgumentCaptor.forClass(IndexRequest.class);
122+
verify(nodeClient).index(argumentCaptor.capture(), any(ActionListener.class));
123+
IndexRequest indexRequest = argumentCaptor.getValue();
124+
assertEquals(expectedOpType, indexRequest.opType());
125+
}
81126
}

test/framework/src/main/java/org/elasticsearch/test/rest/RestActionTestCase.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,13 @@
3939
*/
4040
public abstract class RestActionTestCase extends ESTestCase {
4141
private RestController controller;
42+
protected NodeClient nodeClient;
4243

4344
@Before
4445
public void setUpController() {
46+
nodeClient = mock(NodeClient.class);
4547
controller = new RestController(Collections.emptySet(), null,
46-
mock(NodeClient.class),
48+
nodeClient,
4749
new NoneCircuitBreakerService(),
4850
new UsageService());
4951
}

0 commit comments

Comments
 (0)