Skip to content

Commit 6d03081

Browse files
authored
Add auto create action (#56122)
Backport of #55858 to 7.x branch. Currently the TransportBulkAction detects whether an index is missing and then decides whether it should be auto created. The coordination of the index creation also happens in the TransportBulkAction on the coordinating node. This change adds a new transport action that the TransportBulkAction delegates to if missing indices need to be created. The reasons for this change: * Auto creation of data streams can't occur on the coordinating node. Based on the index template (v2) either a regular index or a data stream should be created. However if the coordinating node is slow in processing cluster state updates then it may be unaware of the existence of certain index templates, which then can load to the TransportBulkAction creating an index instead of a data stream. Therefor the coordination of creating an index or data stream should occur on the master node. See #55377 * From a security perspective it is useful to know whether index creation originates from the create index api or from auto creating a new index via the bulk or index api. For example a user would be allowed to auto create an index, but not to use the create index api. The auto create action will allow security to distinguish these two different patterns of index creation. This change adds the following new transport actions: AutoCreateAction, the TransportBulkAction redirects to this action and this action will actually create the index (instead of the TransportCreateIndexAction). Later via #55377, can improve the AutoCreateAction to also determine whether an index or data stream should be created. The create_index index privilege is also modified, so that if this permission is granted then a user is also allowed to auto create indices. This change does not yet add an auto_create index privilege. A future change can introduce this new index privilege or modify an existing index / write index privilege. Relates to #53100
1 parent 6b5cf1b commit 6d03081

File tree

12 files changed

+224
-17
lines changed

12 files changed

+224
-17
lines changed

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@
2828
import org.elasticsearch.action.admin.cluster.configuration.ClearVotingConfigExclusionsAction;
2929
import org.elasticsearch.action.admin.cluster.configuration.TransportAddVotingConfigExclusionsAction;
3030
import org.elasticsearch.action.admin.cluster.configuration.TransportClearVotingConfigExclusionsAction;
31+
import org.elasticsearch.action.admin.indices.create.AutoCreateAction;
32+
import org.elasticsearch.action.admin.indices.datastream.DeleteDataStreamAction;
33+
import org.elasticsearch.action.admin.indices.datastream.GetDataStreamsAction;
34+
import org.elasticsearch.action.admin.indices.datastream.CreateDataStreamAction;
3135
import org.elasticsearch.action.admin.cluster.health.ClusterHealthAction;
3236
import org.elasticsearch.action.admin.cluster.health.TransportClusterHealthAction;
3337
import org.elasticsearch.action.admin.cluster.node.hotthreads.NodesHotThreadsAction;
@@ -597,6 +601,7 @@ public <Request extends ActionRequest, Response extends ActionResponse> void reg
597601
actions.register(ClearScrollAction.INSTANCE, TransportClearScrollAction.class);
598602
actions.register(RecoveryAction.INSTANCE, TransportRecoveryAction.class);
599603
actions.register(NodesReloadSecureSettingsAction.INSTANCE, TransportNodesReloadSecureSettingsAction.class);
604+
actions.register(AutoCreateAction.INSTANCE, AutoCreateAction.TransportAction.class);
600605

601606
//Indexed scripts
602607
actions.register(PutStoredScriptAction.INSTANCE, TransportPutStoredScriptAction.class);
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.elasticsearch.action.admin.indices.create;
20+
21+
import org.elasticsearch.action.ActionListener;
22+
import org.elasticsearch.action.ActionType;
23+
import org.elasticsearch.action.support.ActionFilters;
24+
import org.elasticsearch.action.support.master.TransportMasterNodeAction;
25+
import org.elasticsearch.cluster.ClusterState;
26+
import org.elasticsearch.cluster.block.ClusterBlockException;
27+
import org.elasticsearch.cluster.block.ClusterBlockLevel;
28+
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
29+
import org.elasticsearch.cluster.metadata.MetadataCreateIndexService;
30+
import org.elasticsearch.cluster.service.ClusterService;
31+
import org.elasticsearch.common.inject.Inject;
32+
import org.elasticsearch.common.io.stream.StreamInput;
33+
import org.elasticsearch.threadpool.ThreadPool;
34+
import org.elasticsearch.transport.TransportService;
35+
36+
import java.io.IOException;
37+
38+
/**
39+
* Api that auto creates an index that originate from requests that write into an index that doesn't yet exist.
40+
*/
41+
public final class AutoCreateAction extends ActionType<CreateIndexResponse> {
42+
43+
public static final AutoCreateAction INSTANCE = new AutoCreateAction();
44+
public static final String NAME = "indices:admin/auto_create";
45+
46+
private AutoCreateAction() {
47+
super(NAME, CreateIndexResponse::new);
48+
}
49+
50+
public static final class TransportAction extends TransportMasterNodeAction<CreateIndexRequest, CreateIndexResponse> {
51+
52+
private final MetadataCreateIndexService createIndexService;
53+
54+
@Inject
55+
public TransportAction(TransportService transportService, ClusterService clusterService, ThreadPool threadPool,
56+
ActionFilters actionFilters, IndexNameExpressionResolver indexNameExpressionResolver,
57+
MetadataCreateIndexService createIndexService) {
58+
super(NAME, transportService, clusterService, threadPool, actionFilters, CreateIndexRequest::new, indexNameExpressionResolver);
59+
this.createIndexService = createIndexService;
60+
}
61+
62+
@Override
63+
protected String executor() {
64+
return ThreadPool.Names.SAME;
65+
}
66+
67+
@Override
68+
protected CreateIndexResponse read(StreamInput in) throws IOException {
69+
return new CreateIndexResponse(in);
70+
}
71+
72+
@Override
73+
protected void masterOperation(CreateIndexRequest request,
74+
ClusterState state,
75+
ActionListener<CreateIndexResponse> listener) throws Exception {
76+
TransportCreateIndexAction.innerCreateIndex(request, listener, indexNameExpressionResolver, createIndexService);
77+
}
78+
79+
@Override
80+
protected ClusterBlockException checkBlock(CreateIndexRequest request, ClusterState state) {
81+
return state.blocks().indexBlockedException(ClusterBlockLevel.METADATA_WRITE, request.index());
82+
}
83+
}
84+
85+
}

server/src/main/java/org/elasticsearch/action/admin/indices/create/TransportCreateIndexAction.java

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,14 +70,20 @@ protected ClusterBlockException checkBlock(CreateIndexRequest request, ClusterSt
7070
@Override
7171
protected void masterOperation(final CreateIndexRequest request, final ClusterState state,
7272
final ActionListener<CreateIndexResponse> listener) {
73-
String cause = request.cause();
74-
if (cause.length() == 0) {
75-
cause = "api";
73+
if (request.cause().length() == 0) {
74+
request.cause("api");
7675
}
7776

77+
innerCreateIndex(request, listener, indexNameExpressionResolver, createIndexService);
78+
}
79+
80+
static void innerCreateIndex(CreateIndexRequest request,
81+
ActionListener<CreateIndexResponse> listener,
82+
IndexNameExpressionResolver indexNameExpressionResolver,
83+
MetadataCreateIndexService createIndexService) {
7884
final String indexName = indexNameExpressionResolver.resolveDateMathExpression(request.index());
7985
final CreateIndexClusterStateUpdateRequest updateRequest =
80-
new CreateIndexClusterStateUpdateRequest(cause, indexName, request.index())
86+
new CreateIndexClusterStateUpdateRequest(request.cause(), indexName, request.index())
8187
.ackTimeout(request.timeout()).masterNodeTimeout(request.masterNodeTimeout())
8288
.settings(request.settings()).mappings(request.mappings())
8389
.aliases(request.aliases())

server/src/main/java/org/elasticsearch/action/bulk/TransportBulkAction.java

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import org.elasticsearch.action.DocWriteRequest;
3434
import org.elasticsearch.action.DocWriteResponse;
3535
import org.elasticsearch.action.RoutingMissingException;
36+
import org.elasticsearch.action.admin.indices.create.AutoCreateAction;
3637
import org.elasticsearch.action.admin.indices.create.CreateIndexRequest;
3738
import org.elasticsearch.action.admin.indices.create.CreateIndexResponse;
3839
import org.elasticsearch.action.index.IndexRequest;
@@ -241,7 +242,8 @@ protected void doExecute(Task task, BulkRequest bulkRequest, ActionListener<Bulk
241242
} else {
242243
final AtomicInteger counter = new AtomicInteger(autoCreateIndices.size());
243244
for (String index : autoCreateIndices) {
244-
createIndex(index, bulkRequest.preferV2Templates(), bulkRequest.timeout(), new ActionListener<CreateIndexResponse>() {
245+
createIndex(index, bulkRequest.preferV2Templates(), bulkRequest.timeout(), minNodeVersion,
246+
new ActionListener<CreateIndexResponse>() {
245247
@Override
246248
public void onResponse(CreateIndexResponse result) {
247249
if (counter.decrementAndGet() == 0) {
@@ -385,13 +387,21 @@ boolean shouldAutoCreate(String index, ClusterState state) {
385387
return autoCreateIndex.shouldAutoCreate(index, state);
386388
}
387389

388-
void createIndex(String index, Boolean preferV2Templates, TimeValue timeout, ActionListener<CreateIndexResponse> listener) {
390+
void createIndex(String index,
391+
Boolean preferV2Templates,
392+
TimeValue timeout,
393+
Version minNodeVersion,
394+
ActionListener<CreateIndexResponse> listener) {
389395
CreateIndexRequest createIndexRequest = new CreateIndexRequest();
390396
createIndexRequest.index(index);
391397
createIndexRequest.cause("auto(bulk api)");
392398
createIndexRequest.masterNodeTimeout(timeout);
393399
createIndexRequest.preferV2Templates(preferV2Templates);
394-
client.admin().indices().create(createIndexRequest, listener);
400+
if (minNodeVersion.onOrAfter(Version.V_7_8_0)) {
401+
client.execute(AutoCreateAction.INSTANCE, createIndexRequest, listener);
402+
} else {
403+
client.admin().indices().create(createIndexRequest, listener);
404+
}
395405
}
396406

397407
private boolean setResponseFailureIfIndexMatches(AtomicArray<BulkItemResponse> responses, int idx, DocWriteRequest<?> request,

server/src/test/java/org/elasticsearch/action/bulk/TransportBulkActionIndicesThatCannotBeCreatedTests.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import org.elasticsearch.index.VersionType;
3939
import org.elasticsearch.tasks.Task;
4040
import org.elasticsearch.test.ESTestCase;
41+
import org.elasticsearch.test.VersionUtils;
4142
import org.elasticsearch.threadpool.ThreadPool;
4243
import org.elasticsearch.transport.TransportService;
4344

@@ -111,7 +112,7 @@ private void indicesThatCannotBeCreatedTestCase(Set<String> expected,
111112
when(clusterService.state()).thenReturn(state);
112113
DiscoveryNodes discoveryNodes = mock(DiscoveryNodes.class);
113114
when(state.getNodes()).thenReturn(discoveryNodes);
114-
when(discoveryNodes.getMinNodeVersion()).thenReturn(Version.CURRENT);
115+
when(discoveryNodes.getMinNodeVersion()).thenReturn(VersionUtils.randomCompatibleVersion(random(), Version.CURRENT));
115116
DiscoveryNode localNode = mock(DiscoveryNode.class);
116117
when(clusterService.localNode()).thenReturn(localNode);
117118
when(localNode.isIngestNode()).thenReturn(randomBoolean());
@@ -138,7 +139,7 @@ boolean shouldAutoCreate(String index, ClusterState state) {
138139

139140
@Override
140141
void createIndex(String index, Boolean preferV2Templates,
141-
TimeValue timeout, ActionListener<CreateIndexResponse> listener) {
142+
TimeValue timeout, Version minNodeVersion, ActionListener<CreateIndexResponse> listener) {
142143
// If we try to create an index just immediately assume it worked
143144
listener.onResponse(new CreateIndexResponse(true, true, index) {});
144145
}

server/src/test/java/org/elasticsearch/action/bulk/TransportBulkActionIngestTests.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
import org.elasticsearch.ingest.IngestService;
5555
import org.elasticsearch.tasks.Task;
5656
import org.elasticsearch.test.ESTestCase;
57+
import org.elasticsearch.test.VersionUtils;
5758
import org.elasticsearch.threadpool.ThreadPool;
5859
import org.elasticsearch.transport.TransportResponseHandler;
5960
import org.elasticsearch.transport.TransportService;
@@ -157,7 +158,8 @@ void executeBulk(Task task, final BulkRequest bulkRequest, final long startTimeN
157158

158159
@Override
159160
void createIndex(String index, Boolean preferV2Templates,
160-
TimeValue timeout, ActionListener<CreateIndexResponse> listener) {
161+
TimeValue timeout, Version minNodeVersion,
162+
ActionListener<CreateIndexResponse> listener) {
161163
indexCreated = true;
162164
listener.onResponse(null);
163165
}
@@ -192,7 +194,7 @@ public void setupAction() {
192194
ImmutableOpenMap<String, DiscoveryNode> ingestNodes = ImmutableOpenMap.<String, DiscoveryNode>builder(2)
193195
.fPut("node1", remoteNode1).fPut("node2", remoteNode2).build();
194196
when(nodes.getIngestNodes()).thenReturn(ingestNodes);
195-
when(nodes.getMinNodeVersion()).thenReturn(Version.CURRENT);
197+
when(nodes.getMinNodeVersion()).thenReturn(VersionUtils.randomCompatibleVersion(random(), Version.CURRENT));
196198
ClusterState state = mock(ClusterState.class);
197199
when(state.getNodes()).thenReturn(nodes);
198200
Metadata metadata = Metadata.builder().indices(ImmutableOpenMap.<String, IndexMetadata>builder()

server/src/test/java/org/elasticsearch/action/bulk/TransportBulkActionTests.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
import org.elasticsearch.cluster.metadata.IndexMetadata;
3333
import org.elasticsearch.cluster.metadata.IndexTemplateMetadata;
3434
import org.elasticsearch.cluster.metadata.Metadata;
35+
import org.elasticsearch.cluster.node.DiscoveryNode;
36+
import org.elasticsearch.cluster.node.DiscoveryNodeRole;
3537
import org.elasticsearch.cluster.service.ClusterService;
3638
import org.elasticsearch.common.settings.Settings;
3739
import org.elasticsearch.common.unit.TimeValue;
@@ -40,6 +42,7 @@
4042
import org.elasticsearch.index.VersionType;
4143
import org.elasticsearch.ingest.IngestService;
4244
import org.elasticsearch.test.ESTestCase;
45+
import org.elasticsearch.test.VersionUtils;
4346
import org.elasticsearch.test.transport.CapturingTransport;
4447
import org.elasticsearch.threadpool.TestThreadPool;
4548
import org.elasticsearch.threadpool.ThreadPool;
@@ -80,7 +83,7 @@ protected boolean needToCheck() {
8083

8184
@Override
8285
void createIndex(String index, Boolean preferV2Templates,
83-
TimeValue timeout, ActionListener<CreateIndexResponse> listener) {
86+
TimeValue timeout, Version minNodeVersion, ActionListener<CreateIndexResponse> listener) {
8487
indexCreated = true;
8588
listener.onResponse(null);
8689
}
@@ -90,7 +93,9 @@ void createIndex(String index, Boolean preferV2Templates,
9093
public void setUp() throws Exception {
9194
super.setUp();
9295
threadPool = new TestThreadPool(getClass().getName());
93-
clusterService = createClusterService(threadPool);
96+
DiscoveryNode discoveryNode = new DiscoveryNode("node", ESTestCase.buildNewFakeTransportAddress(), Collections.emptyMap(),
97+
DiscoveryNodeRole.BUILT_IN_ROLES, VersionUtils.randomCompatibleVersion(random(), Version.CURRENT));
98+
clusterService = createClusterService(threadPool, discoveryNode);
9499
CapturingTransport capturingTransport = new CapturingTransport();
95100
transportService = capturingTransport.createTransportService(clusterService.getSettings(), threadPool,
96101
TransportService.NOOP_TRANSPORT_INTERCEPTOR,

server/src/test/java/org/elasticsearch/action/bulk/TransportBulkActionTookTests.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
import org.apache.lucene.util.Constants;
2424
import org.elasticsearch.action.ActionType;
25+
import org.elasticsearch.Version;
2526
import org.elasticsearch.action.ActionListener;
2627
import org.elasticsearch.action.ActionRequest;
2728
import org.elasticsearch.action.ActionResponse;
@@ -32,6 +33,8 @@
3233
import org.elasticsearch.client.node.NodeClient;
3334
import org.elasticsearch.cluster.ClusterState;
3435
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
36+
import org.elasticsearch.cluster.node.DiscoveryNode;
37+
import org.elasticsearch.cluster.node.DiscoveryNodeRole;
3538
import org.elasticsearch.cluster.service.ClusterService;
3639
import org.elasticsearch.common.Strings;
3740
import org.elasticsearch.common.settings.Settings;
@@ -41,6 +44,7 @@
4144
import org.elasticsearch.rest.action.document.RestBulkAction;
4245
import org.elasticsearch.tasks.Task;
4346
import org.elasticsearch.test.ESTestCase;
47+
import org.elasticsearch.test.VersionUtils;
4448
import org.elasticsearch.test.transport.CapturingTransport;
4549
import org.elasticsearch.threadpool.TestThreadPool;
4650
import org.elasticsearch.threadpool.ThreadPool;
@@ -82,7 +86,9 @@ public static void afterClass() {
8286
@Before
8387
public void setUp() throws Exception {
8488
super.setUp();
85-
clusterService = createClusterService(threadPool);
89+
DiscoveryNode discoveryNode = new DiscoveryNode("node", ESTestCase.buildNewFakeTransportAddress(), Collections.emptyMap(),
90+
DiscoveryNodeRole.BUILT_IN_ROLES, VersionUtils.randomCompatibleVersion(random(), Version.CURRENT));
91+
clusterService = createClusterService(threadPool, discoveryNode);
8692
}
8793

8894
@After

x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/privilege/IndexPrivilege.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import org.elasticsearch.action.admin.indices.alias.exists.AliasesExistAction;
1313
import org.elasticsearch.action.admin.indices.alias.get.GetAliasesAction;
1414
import org.elasticsearch.action.admin.indices.close.CloseIndexAction;
15+
import org.elasticsearch.action.admin.indices.create.AutoCreateAction;
1516
import org.elasticsearch.action.admin.indices.create.CreateIndexAction;
1617
import org.elasticsearch.action.admin.indices.delete.DeleteIndexAction;
1718
import org.elasticsearch.action.admin.indices.exists.indices.IndicesExistsAction;
@@ -60,7 +61,7 @@ public final class IndexPrivilege extends Privilege {
6061
private static final Automaton MONITOR_AUTOMATON = patterns("indices:monitor/*");
6162
private static final Automaton MANAGE_AUTOMATON =
6263
unionAndMinimize(Arrays.asList(MONITOR_AUTOMATON, patterns("indices:admin/*")));
63-
private static final Automaton CREATE_INDEX_AUTOMATON = patterns(CreateIndexAction.NAME);
64+
private static final Automaton CREATE_INDEX_AUTOMATON = patterns(CreateIndexAction.NAME, AutoCreateAction.NAME);
6465
private static final Automaton DELETE_INDEX_AUTOMATON = patterns(DeleteIndexAction.NAME);
6566
private static final Automaton VIEW_METADATA_AUTOMATON = patterns(GetAliasesAction.NAME, AliasesExistAction.NAME,
6667
GetIndexAction.NAME, IndicesExistsAction.NAME, GetFieldMappingsAction.NAME + "*", GetMappingsAction.NAME,

x-pack/plugin/ml/qa/ml-with-security/roles.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ minimal:
99
# non-ML indices
1010
- names: [ 'airline-data', 'index-*', 'unavailable-data', 'utopia' ]
1111
privileges:
12-
- indices:admin/create
12+
- create_index
1313
- indices:admin/refresh
1414
- read
1515
- index

0 commit comments

Comments
 (0)