Skip to content

Commit d2da8ec

Browse files
committed
add support for write index resolution when creating/updating documents
This commit introduces a new option to IndicesOptions that requires a write index to exist and then uses this option for index/update requests. This commit also fixes a subtle issue with how write-indices are resolved in Aliases. Before, all aliases pointing to one-and-only-one index had a write index even if is_write_index=false. This should not be the case.
1 parent 7313a98 commit d2da8ec

21 files changed

+282
-74
lines changed

modules/reindex/src/test/java/org/elasticsearch/index/reindex/ReindexSourceTargetValidationTests.java

+1-4
Original file line numberDiff line numberDiff line change
@@ -80,10 +80,7 @@ public void testAliasesContainTarget() {
8080

8181
public void testTargetIsAlias() {
8282
Exception e = expectThrows(IllegalArgumentException.class, () -> succeeds("target_multi", "foo"));
83-
assertThat(e.getMessage(), containsString("Alias [target_multi] has more than one indices associated with it [["));
84-
// The index names can come in either order
85-
assertThat(e.getMessage(), containsString("target"));
86-
assertThat(e.getMessage(), containsString("target2"));
83+
assertThat(e.getMessage(), containsString("Alias [target_multi] points to multiple indices"));
8784
}
8885

8986
public void testRemoteInfoSkipsValidation() {

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

+3-3
Original file line numberDiff line numberDiff line change
@@ -148,8 +148,8 @@ protected void doExecute(Task task, BulkRequest bulkRequest, ActionListener<Bulk
148148
final Set<String> indices = bulkRequest.requests.stream()
149149
// delete requests should not attempt to create the index (if the index does not
150150
// exists), unless an external versioning is used
151-
.filter(request -> request.opType() != DocWriteRequest.OpType.DELETE
152-
|| request.versionType() == VersionType.EXTERNAL
151+
.filter(request -> request.opType() != DocWriteRequest.OpType.DELETE
152+
|| request.versionType() == VersionType.EXTERNAL
153153
|| request.versionType() == VersionType.EXTERNAL_GTE)
154154
.map(DocWriteRequest::index)
155155
.collect(Collectors.toSet());
@@ -300,7 +300,7 @@ protected void doRun() throws Exception {
300300
TransportUpdateAction.resolveAndValidateRouting(metaData, concreteIndex.getName(), (UpdateRequest) docWriteRequest);
301301
break;
302302
case DELETE:
303-
docWriteRequest.routing(metaData.resolveIndexRouting(docWriteRequest.routing(), docWriteRequest.index()));
303+
docWriteRequest.routing(metaData.resolveIndexRouting(docWriteRequest.routing(), docWriteRequest.index(), true));
304304
// check if routing is required, if so, throw error if routing wasn't specified
305305
if (docWriteRequest.routing() == null && metaData.routingRequired(concreteIndex.getName(), docWriteRequest.type())) {
306306
throw new RoutingMissingException(concreteIndex.getName(), docWriteRequest.type(), docWriteRequest.id());

server/src/main/java/org/elasticsearch/action/get/TransportGetAction.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ protected ShardIterator shards(ClusterState state, InternalRequest request) {
6969
@Override
7070
protected void resolveRequest(ClusterState state, InternalRequest request) {
7171
// update the routing (request#index here is possibly an alias)
72-
request.request().routing(state.metaData().resolveIndexRouting(request.request().routing(), request.request().index()));
72+
request.request().routing(state.metaData().resolveIndexRouting(request.request().routing(), request.request().index(), false));
7373
// Fail fast on the node that received the request.
7474
if (request.request().routing() == null && state.getMetaData().routingRequired(request.concreteIndex(), request.request().type())) {
7575
throw new RoutingMissingException(request.concreteIndex(), request.request().type(), request.request().id());

server/src/main/java/org/elasticsearch/action/get/TransportMultiGetAction.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ protected void doExecute(final MultiGetRequest request, final ActionListener<Mul
6767
try {
6868
concreteSingleIndex = indexNameExpressionResolver.concreteSingleIndex(clusterState, item).getName();
6969

70-
item.routing(clusterState.metaData().resolveIndexRouting(item.routing(), item.index()));
70+
item.routing(clusterState.metaData().resolveIndexRouting(item.routing(), item.index(), false));
7171
if ((item.routing() == null) && (clusterState.getMetaData().routingRequired(concreteSingleIndex, item.type()))) {
7272
String message = "routing is required for [" + concreteSingleIndex + "]/[" + item.type() + "]/[" + item.id() + "]";
7373
responses.set(i, newItemFailure(concreteSingleIndex, item.type(), item.id(), new IllegalArgumentException(message)));

server/src/main/java/org/elasticsearch/action/index/IndexRequest.java

+7-2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import org.elasticsearch.action.CompositeIndicesRequest;
2626
import org.elasticsearch.action.DocWriteRequest;
2727
import org.elasticsearch.action.RoutingMissingException;
28+
import org.elasticsearch.action.support.IndicesOptions;
2829
import org.elasticsearch.action.support.replication.ReplicatedWriteRequest;
2930
import org.elasticsearch.action.support.replication.ReplicationRequest;
3031
import org.elasticsearch.client.Requests;
@@ -188,6 +189,11 @@ public ActionRequestValidationException validate() {
188189
return validationException;
189190
}
190191

192+
@Override
193+
public IndicesOptions indicesOptions() {
194+
return IndicesOptions.strictAliasToWriteIndexNoExpandForbidClosed();
195+
}
196+
191197
/**
192198
* The content type. This will be used when generating a document from user provided objects like Maps and when parsing the
193199
* source at index time
@@ -496,7 +502,7 @@ public void process(Version indexCreatedVersion, @Nullable MappingMetaData mappi
496502

497503
/* resolve the routing if needed */
498504
public void resolveRouting(MetaData metaData) {
499-
routing(metaData.resolveIndexRouting(routing, index));
505+
routing(metaData.resolveIndexRouting(routing, index, true));
500506
}
501507

502508
@Override
@@ -603,5 +609,4 @@ public long getAutoGeneratedTimestamp() {
603609
public IndexRequest setShardId(ShardId shardId) {
604610
throw new UnsupportedOperationException("shard id should never be set on IndexRequest");
605611
}
606-
607612
}

server/src/main/java/org/elasticsearch/action/support/IndicesOptions.java

+21-1
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,8 @@ public enum Option {
7777
IGNORE_ALIASES,
7878
ALLOW_NO_INDICES,
7979
FORBID_ALIASES_TO_MULTIPLE_INDICES,
80-
FORBID_CLOSED_INDICES;
80+
FORBID_CLOSED_INDICES,
81+
REQUIRE_ALIASES_TO_WRITE_INDEX;
8182

8283
public static final EnumSet<Option> NONE = EnumSet.noneOf(Option.class);
8384
}
@@ -93,6 +94,9 @@ public enum Option {
9394
public static final IndicesOptions STRICT_SINGLE_INDEX_NO_EXPAND_FORBID_CLOSED =
9495
new IndicesOptions(EnumSet.of(Option.FORBID_ALIASES_TO_MULTIPLE_INDICES, Option.FORBID_CLOSED_INDICES),
9596
EnumSet.noneOf(WildcardStates.class));
97+
public static final IndicesOptions STRICT_ALIAS_TO_WRITE_INDEX_NO_EXPAND_FORBID_CLOSED =
98+
new IndicesOptions(EnumSet.of(Option.REQUIRE_ALIASES_TO_WRITE_INDEX, Option.FORBID_CLOSED_INDICES),
99+
EnumSet.noneOf(WildcardStates.class));
96100

97101
private final EnumSet<Option> options;
98102
private final EnumSet<WildcardStates> expandWildcards;
@@ -231,6 +235,13 @@ public boolean allowAliasesToMultipleIndices() {
231235
return options.contains(Option.FORBID_ALIASES_TO_MULTIPLE_INDICES) == false;
232236
}
233237

238+
/**
239+
* @return whether aliases pointing to a write index should resolve to that index
240+
*/
241+
public boolean requireAliasesToWriteIndex() {
242+
return options.contains(Option.REQUIRE_ALIASES_TO_WRITE_INDEX);
243+
}
244+
234245
/**
235246
* @return whether aliases should be ignored (when resolving a wildcard)
236247
*/
@@ -375,6 +386,14 @@ public static IndicesOptions strictSingleIndexNoExpandForbidClosed() {
375386
return STRICT_SINGLE_INDEX_NO_EXPAND_FORBID_CLOSED;
376387
}
377388

389+
/**
390+
* @return indices option that requires each specified index or alias to exist, doesn't expand wildcards and
391+
* throws error if any of the aliases resolves to multiple indices with none specified as a write-index
392+
*/
393+
public static IndicesOptions strictAliasToWriteIndexNoExpandForbidClosed() {
394+
return STRICT_ALIAS_TO_WRITE_INDEX_NO_EXPAND_FORBID_CLOSED;
395+
}
396+
378397
/**
379398
* @return indices options that ignores unavailable indices, expands wildcards only to open indices and
380399
* allows that no indices are resolved from wildcard expressions (not returning an error).
@@ -413,6 +432,7 @@ public String toString() {
413432
", allow_aliases_to_multiple_indices=" + allowAliasesToMultipleIndices() +
414433
", forbid_closed_indices=" + forbidClosedIndices() +
415434
", ignore_aliases=" + ignoreAliases() +
435+
", require_aliases_to_write_index=" + requireAliasesToWriteIndex() +
416436
']';
417437
}
418438
}

server/src/main/java/org/elasticsearch/action/support/replication/ReplicatedWriteRequest.java

+6
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import org.elasticsearch.action.bulk.BulkShardRequest;
2323
import org.elasticsearch.action.delete.DeleteRequest;
2424
import org.elasticsearch.action.index.IndexRequest;
25+
import org.elasticsearch.action.support.IndicesOptions;
2526
import org.elasticsearch.action.support.WriteRequest;
2627
import org.elasticsearch.common.io.stream.StreamInput;
2728
import org.elasticsearch.common.io.stream.StreamOutput;
@@ -58,6 +59,11 @@ public RefreshPolicy getRefreshPolicy() {
5859
return refreshPolicy;
5960
}
6061

62+
@Override
63+
public IndicesOptions indicesOptions() {
64+
return IndicesOptions.strictAliasToWriteIndexNoExpandForbidClosed();
65+
}
66+
6167
@Override
6268
public void readFrom(StreamInput in) throws IOException {
6369
super.readFrom(in);

server/src/main/java/org/elasticsearch/action/termvectors/TransportMultiTermVectorsAction.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ protected void doExecute(final MultiTermVectorsRequest request, final ActionList
6464
Map<ShardId, MultiTermVectorsShardRequest> shardRequests = new HashMap<>();
6565
for (int i = 0; i < request.requests.size(); i++) {
6666
TermVectorsRequest termVectorsRequest = request.requests.get(i);
67-
termVectorsRequest.routing(clusterState.metaData().resolveIndexRouting(termVectorsRequest.routing(), termVectorsRequest.index()));
67+
termVectorsRequest.routing(clusterState.metaData().resolveIndexRouting(termVectorsRequest.routing(), termVectorsRequest.index(), false));
6868
if (!clusterState.metaData().hasConcreteIndex(termVectorsRequest.index())) {
6969
responses.set(i, new MultiTermVectorsItemResponse(null, new MultiTermVectorsResponse.Failure(termVectorsRequest.index(),
7070
termVectorsRequest.type(), termVectorsRequest.id(), new IndexNotFoundException(termVectorsRequest.index()))));

server/src/main/java/org/elasticsearch/action/termvectors/TransportTermVectorsAction.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ protected boolean resolveIndex(TermVectorsRequest request) {
7878
@Override
7979
protected void resolveRequest(ClusterState state, InternalRequest request) {
8080
// update the routing (request#index here is possibly an alias or a parent)
81-
request.request().routing(state.metaData().resolveIndexRouting(request.request().routing(), request.request().index()));
81+
request.request().routing(state.metaData().resolveIndexRouting(request.request().routing(), request.request().index(), false));
8282
// Fail fast on the node that received the request.
8383
if (request.request().routing() == null && state.getMetaData().routingRequired(request.concreteIndex(), request.request().type())) {
8484
throw new RoutingMissingException(request.concreteIndex(), request.request().type(), request.request().id());

server/src/main/java/org/elasticsearch/action/update/TransportUpdateAction.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ protected void resolveRequest(ClusterState state, UpdateRequest request) {
103103
}
104104

105105
public static void resolveAndValidateRouting(MetaData metaData, String concreteIndex, UpdateRequest request) {
106-
request.routing((metaData.resolveIndexRouting(request.routing(), request.index())));
106+
request.routing((metaData.resolveIndexRouting(request.routing(), request.index(), true)));
107107
// Fail fast on the node that received the request, rather than failing when translating on the index or delete request.
108108
if (request.routing() == null && metaData.routingRequired(concreteIndex, request.type())) {
109109
throw new RoutingMissingException(concreteIndex, request.type(), request.id());

server/src/main/java/org/elasticsearch/action/update/UpdateRequest.java

+6
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import org.elasticsearch.action.DocWriteRequest;
2525
import org.elasticsearch.action.index.IndexRequest;
2626
import org.elasticsearch.action.support.ActiveShardCount;
27+
import org.elasticsearch.action.support.IndicesOptions;
2728
import org.elasticsearch.action.support.WriteRequest;
2829
import org.elasticsearch.action.support.replication.ReplicationRequest;
2930
import org.elasticsearch.action.support.single.instance.InstanceShardOperationRequest;
@@ -169,6 +170,11 @@ public ActionRequestValidationException validate() {
169170
return validationException;
170171
}
171172

173+
@Override
174+
public IndicesOptions indicesOptions() {
175+
return IndicesOptions.strictAliasToWriteIndexNoExpandForbidClosed();
176+
}
177+
172178
/**
173179
* The type of the indexed document.
174180
*/

server/src/main/java/org/elasticsearch/cluster/metadata/AliasOrIndex.java

+12-6
Original file line numberDiff line numberDiff line change
@@ -154,12 +154,18 @@ void addIndex(IndexMetaData indexMetaData) {
154154
}
155155

156156
public void computeAndValidateWriteIndex() {
157-
List<IndexMetaData> writeIndices = referenceIndexMetaDatas.stream()
158-
.filter(idxMeta -> Boolean.TRUE.equals(idxMeta.getAliases().get(aliasName).writeIndex()))
159-
.collect(Collectors.toList());
160-
if (referenceIndexMetaDatas.size() == 1) {
161-
writeIndex.set(referenceIndexMetaDatas.get(0));
162-
} else if (writeIndices.size() == 1) {
157+
final List<IndexMetaData> writeIndices;
158+
if (referenceIndexMetaDatas.size() > 1) {
159+
writeIndices = referenceIndexMetaDatas.stream()
160+
.filter(idxMeta -> Boolean.TRUE.equals(idxMeta.getAliases().get(aliasName).writeIndex()))
161+
.collect(Collectors.toList());
162+
} else if(Boolean.FALSE.equals(referenceIndexMetaDatas.get(0).getAliases().get(aliasName).writeIndex()) == false) {
163+
writeIndices = Collections.singletonList(referenceIndexMetaDatas.get(0));
164+
} else {
165+
writeIndices = Collections.emptyList();
166+
}
167+
168+
if (writeIndices.size() == 1) {
163169
writeIndex.set(writeIndices.get(0));
164170
} else if (writeIndices.size() > 1) {
165171
List<String> writeIndicesStrings = writeIndices.stream()

server/src/main/java/org/elasticsearch/cluster/metadata/IndexNameExpressionResolver.java

+29-15
Original file line numberDiff line numberDiff line change
@@ -194,29 +194,43 @@ Index[] concreteIndices(Context context, String... indexExpressions) {
194194
}
195195

196196
Collection<IndexMetaData> resolvedIndices = aliasOrIndex.getIndices();
197-
if (resolvedIndices.size() > 1 && !options.allowAliasesToMultipleIndices()) {
197+
198+
if (aliasOrIndex.isAlias() && options.requireAliasesToWriteIndex()) {
199+
AliasOrIndex.Alias alias = (AliasOrIndex.Alias) aliasOrIndex;
200+
IndexMetaData writeIndex = alias.getWriteIndex();
201+
if (writeIndex == null) {
202+
if (alias.getIndices().size() > 1) {
203+
throw new IllegalArgumentException("Alias [" + alias.getAliasName() +
204+
"] points to multiple indices with none set as a write-index [is_write_index=true]");
205+
} else {
206+
throw new IllegalArgumentException("Alias [" + alias.getAliasName() + "] points to an index ["
207+
+ alias.getIndices().get(0).getIndex().getName() + "] with [is_write_index=false]");
208+
}
209+
}
210+
concreteIndices.add(writeIndex.getIndex());
211+
} else if (resolvedIndices.size() > 1 && options.allowAliasesToMultipleIndices() == false) {
198212
String[] indexNames = new String[resolvedIndices.size()];
199213
int i = 0;
200214
for (IndexMetaData indexMetaData : resolvedIndices) {
201215
indexNames[i++] = indexMetaData.getIndex().getName();
202216
}
203217
throw new IllegalArgumentException("Alias [" + expression + "] has more than one indices associated with it [" +
204-
Arrays.toString(indexNames) + "], can't execute a single index op");
205-
}
206-
207-
for (IndexMetaData index : resolvedIndices) {
208-
if (index.getState() == IndexMetaData.State.CLOSE) {
209-
if (failClosed) {
210-
throw new IndexClosedException(index.getIndex());
211-
} else {
212-
if (options.forbidClosedIndices() == false) {
213-
concreteIndices.add(index.getIndex());
218+
Arrays.toString(indexNames) + "], can't execute a single index op");
219+
} else {
220+
for (IndexMetaData index : resolvedIndices) {
221+
if (index.getState() == IndexMetaData.State.CLOSE) {
222+
if (failClosed) {
223+
throw new IndexClosedException(index.getIndex());
224+
} else {
225+
if (options.forbidClosedIndices() == false) {
226+
concreteIndices.add(index.getIndex());
227+
}
214228
}
229+
} else if (index.getState() == IndexMetaData.State.OPEN) {
230+
concreteIndices.add(index.getIndex());
231+
} else {
232+
throw new IllegalStateException("index state [" + index.getState() + "] not supported");
215233
}
216-
} else if (index.getState() == IndexMetaData.State.OPEN) {
217-
concreteIndices.add(index.getIndex());
218-
} else {
219-
throw new IllegalStateException("index state [" + index.getState() + "] not supported");
220234
}
221235
}
222236
}

server/src/main/java/org/elasticsearch/cluster/metadata/MetaData.java

+8-3
Original file line numberDiff line numberDiff line change
@@ -477,7 +477,7 @@ public String[] getConcreteAllClosedIndices() {
477477
*/
478478
// TODO: This can be moved to IndexNameExpressionResolver too, but this means that we will support wildcards and other expressions
479479
// in the index,bulk,update and delete apis.
480-
public String resolveIndexRouting(@Nullable String routing, String aliasOrIndex) {
480+
public String resolveIndexRouting(@Nullable String routing, String aliasOrIndex, boolean isWriteOperation) {
481481
if (aliasOrIndex == null) {
482482
return routing;
483483
}
@@ -487,10 +487,15 @@ public String resolveIndexRouting(@Nullable String routing, String aliasOrIndex)
487487
return routing;
488488
}
489489
AliasOrIndex.Alias alias = (AliasOrIndex.Alias) result;
490-
if (result.getIndices().size() > 1) {
490+
if ((isWriteOperation && alias.getWriteIndex() == null) || (isWriteOperation == false && result.getIndices().size() > 1)) {
491491
rejectSingleIndexOperation(aliasOrIndex, result);
492492
}
493-
AliasMetaData aliasMd = alias.getFirstAliasMetaData();
493+
final AliasMetaData aliasMd;
494+
if (isWriteOperation) {
495+
aliasMd = alias.getWriteIndex().getAliases().get(alias.getAliasName());
496+
} else {
497+
aliasMd = alias.getFirstAliasMetaData();
498+
}
494499
if (aliasMd.indexRouting() != null) {
495500
if (aliasMd.indexRouting().indexOf(',') != -1) {
496501
throw new IllegalArgumentException("index/alias [" + aliasOrIndex + "] provided with routing value [" + aliasMd.getIndexRouting() + "] that resolved to several routing values, rejecting operation");

0 commit comments

Comments
 (0)