Skip to content
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

add support for write index resolution when creating/updating documents #31520

Merged
merged 30 commits into from
Jul 19, 2018
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
9e86921
add support for write index resolution when creating/updating documents
talevy Jun 20, 2018
df8fabd
refactor to use new resolveWriteIndex flag instead of IndicesOptions
talevy Jun 25, 2018
7c75916
Merge branch 'master' into indices-options-write-index
talevy Jun 26, 2018
113ebc5
comment out test question
talevy Jun 26, 2018
fb7c168
whitespace cleanup
talevy Jun 26, 2018
c1d2d4e
Merge branch 'master' into indices-options-write-index
talevy Jun 27, 2018
b585835
cleanup and return existing alias routing resolution
talevy Jun 27, 2018
42893bf
Merge branch 'master' into indices-options-write-index
talevy Jun 27, 2018
54d0a3d
fix typo
talevy Jun 27, 2018
b5e5e81
add successful getWriteIndex test
talevy Jun 27, 2018
d4a5ebd
simplify metadata test
talevy Jun 27, 2018
2f8a004
ease up index routing resolution.
talevy Jun 27, 2018
9238cb5
fix IT test
talevy Jun 28, 2018
8ae0967
Merge branch 'master' into indices-options-write-index
talevy Jun 28, 2018
e8cf8c8
add security test for is_write_index resolution
talevy Jun 29, 2018
2f2af95
Merge branch 'master' into indices-options-write-index
talevy Jun 29, 2018
3df26bd
Merge branch 'master' into indices-options-write-index
talevy Jul 9, 2018
5d47213
Merge branch 'master' into indices-options-write-index
talevy Jul 10, 2018
00eb394
respond to comments
talevy Jul 10, 2018
7c753db
add tests for different index expressions and indices options
talevy Jul 10, 2018
f1032e4
fix checkstyle
talevy Jul 10, 2018
347149e
fix metadata index routing tests
talevy Jul 10, 2018
e58a3fb
ignore routing on aliases pointing to multiple indices in write opera…
talevy Jul 10, 2018
222ae13
Merge branch 'master' into indices-options-write-index
talevy Jul 15, 2018
ae372f3
update routing to evaluate to write index's alias metadata for write …
talevy Jul 16, 2018
b54efe4
Merge branch 'master' into indices-options-write-index
talevy Jul 17, 2018
cda911f
add more integration tests and small cleanup
talevy Jul 17, 2018
eb097c2
fix metadata randomness
talevy Jul 17, 2018
cc969d1
cleanup bulk write routing IT test and remove Unit tests for this
talevy Jul 18, 2018
284d3ee
add randomization to bulkintegrationIT test
talevy Jul 19, 2018
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 @@ -46,13 +46,15 @@
*/
public class ReindexSourceTargetValidationTests extends ESTestCase {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This class needs more work, but I am not sure whether we want reindex target-aliases to resolve to
the write index. It feels right, but it also sounds scary. what do you think?

Copy link
Contributor Author

@talevy talevy Jun 26, 2018

Choose a reason for hiding this comment

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

oh nevermind, it must check details since even a single index with is_write_index=false should not be chosen by reindex api, and that would be awkward

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure I follow. Reindex should be orthogonal to the write alias and should just work ™️ ? what am I missing?

private static final ClusterState STATE = ClusterState.builder(new ClusterName("test")).metaData(MetaData.builder()
.put(index("target", "target_alias", "target_multi"), true)
.put(index("target2", "target_multi"), true)
.put(index("foo"), true)
.put(index("bar"), true)
.put(index("baz"), true)
.put(index("source", "source_multi"), true)
.put(index("source2", "source_multi"), true)).build();
.put(index("target", false, "target_alias", "target_multi"), true)
.put(index("target2", false, "target_multi"), true)
.put(index("target3", true, "target_multi_with_write"), true)
.put(index("target4", false, "target_multi_with_write"), true)
.put(index("foo", false), true)
.put(index("bar", false), true)
.put(index("baz", false), true)
.put(index("source", false, "source_multi"), true)
.put(index("source2", false, "source_multi"), true)).build();
private static final IndexNameExpressionResolver INDEX_NAME_EXPRESSION_RESOLVER = new IndexNameExpressionResolver(Settings.EMPTY);
private static final AutoCreateIndex AUTO_CREATE_INDEX = new AutoCreateIndex(Settings.EMPTY,
new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS), INDEX_NAME_EXPRESSION_RESOLVER);
Expand Down Expand Up @@ -84,6 +86,7 @@ public void testTargetIsAlias() {
// The index names can come in either order
assertThat(e.getMessage(), containsString("target"));
assertThat(e.getMessage(), containsString("target2"));
// NORELEASE: succeeds("target_multi_with_write", "foo"); should this be allowed?
}

public void testRemoteInfoSkipsValidation() {
Expand All @@ -109,7 +112,7 @@ private void succeeds(RemoteInfo remoteInfo, String target, String... sources) {
INDEX_NAME_EXPRESSION_RESOLVER, AUTO_CREATE_INDEX, STATE);
}

private static IndexMetaData index(String name, String... aliases) {
private static IndexMetaData index(String name, boolean isWriteIndex, String... aliases) {
Copy link
Contributor

Choose a reason for hiding this comment

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

this param isn't used?

IndexMetaData.Builder builder = IndexMetaData.builder(name).settings(Settings.builder()
.put("index.version.created", Version.CURRENT.id)
.put("index.number_of_shards", 1)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ protected void doRun() throws Exception {
TransportUpdateAction.resolveAndValidateRouting(metaData, concreteIndex.getName(), (UpdateRequest) docWriteRequest);
break;
case DELETE:
docWriteRequest.routing(metaData.resolveIndexRouting(docWriteRequest.routing(), docWriteRequest.index()));
docWriteRequest.routing(metaData.resolveIndexRouting(docWriteRequest.routing(), docWriteRequest.index(), true));
Copy link
Contributor

Choose a reason for hiding this comment

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

double checking - do we have a test for multi routing values in alias and make sure we use the right value (I hate this API but we have to test it works)?

Copy link
Contributor

Choose a reason for hiding this comment

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

With some more thinking, I don't think we should make this feature (routing value in an alias) work with the write index flag. The problem is that if we allow to write to multi index aliases with this feature turned on, there is no way for get to work through those aliases without us changing the get behavior of resolving multi-index aliased base on the index write flag which is unexpected. Bottom line, can we see what it takes to make sure resolveIndexRouting explodes as soon as you touch more than one index via an alias?

// check if routing is required, if so, throw error if routing wasn't specified
if (docWriteRequest.routing() == null && metaData.routingRequired(concreteIndex.getName(), docWriteRequest.type())) {
throw new RoutingMissingException(concreteIndex.getName(), docWriteRequest.type(), docWriteRequest.id());
Expand Down Expand Up @@ -474,7 +474,7 @@ Index getConcreteIndex(String indexOrAlias) {
Index resolveIfAbsent(DocWriteRequest<?> request) {
Index concreteIndex = indices.get(request.index());
if (concreteIndex == null) {
concreteIndex = indexNameExpressionResolver.concreteSingleIndex(state, request);
concreteIndex = indexNameExpressionResolver.concreteWriteIndex(state, request);
indices.put(request.index(), concreteIndex);
}
return concreteIndex;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ protected ShardIterator shards(ClusterState state, InternalRequest request) {
@Override
protected void resolveRequest(ClusterState state, InternalRequest request) {
// update the routing (request#index here is possibly an alias)
request.request().routing(state.metaData().resolveIndexRouting(request.request().routing(), request.request().index()));
request.request().routing(state.metaData().resolveIndexRouting(request.request().routing(), request.request().index(), false));
// Fail fast on the node that received the request.
if (request.request().routing() == null && state.getMetaData().routingRequired(request.concreteIndex(), request.request().type())) {
throw new RoutingMissingException(request.concreteIndex(), request.request().type(), request.request().id());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ protected void doExecute(Task task, final MultiGetRequest request, final ActionL
try {
concreteSingleIndex = indexNameExpressionResolver.concreteSingleIndex(clusterState, item).getName();

item.routing(clusterState.metaData().resolveIndexRouting(item.routing(), item.index()));
item.routing(clusterState.metaData().resolveIndexRouting(item.routing(), item.index(), false));
if ((item.routing() == null) && (clusterState.getMetaData().routingRequired(concreteSingleIndex, item.type()))) {
String message = "routing is required for [" + concreteSingleIndex + "]/[" + item.type() + "]/[" + item.id() + "]";
responses.set(i, newItemFailure(concreteSingleIndex, item.type(), item.id(), new IllegalArgumentException(message)));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -496,7 +496,7 @@ public void process(Version indexCreatedVersion, @Nullable MappingMetaData mappi

/* resolve the routing if needed */
public void resolveRouting(MetaData metaData) {
routing(metaData.resolveIndexRouting(routing, index));
routing(metaData.resolveIndexRouting(routing, index, true));
}

@Override
Expand Down Expand Up @@ -603,5 +603,4 @@ public long getAutoGeneratedTimestamp() {
public IndexRequest setShardId(ShardId shardId) {
throw new UnsupportedOperationException("shard id should never be set on IndexRequest");
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ protected void doExecute(Task task, final MultiTermVectorsRequest request, final
Map<ShardId, MultiTermVectorsShardRequest> shardRequests = new HashMap<>();
for (int i = 0; i < request.requests.size(); i++) {
TermVectorsRequest termVectorsRequest = request.requests.get(i);
termVectorsRequest.routing(clusterState.metaData().resolveIndexRouting(termVectorsRequest.routing(), termVectorsRequest.index()));
termVectorsRequest.routing(clusterState.metaData().resolveIndexRouting(termVectorsRequest.routing(), termVectorsRequest.index(), false));
if (!clusterState.metaData().hasConcreteIndex(termVectorsRequest.index())) {
responses.set(i, new MultiTermVectorsItemResponse(null, new MultiTermVectorsResponse.Failure(termVectorsRequest.index(),
termVectorsRequest.type(), termVectorsRequest.id(), new IndexNotFoundException(termVectorsRequest.index()))));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ protected boolean resolveIndex(TermVectorsRequest request) {
@Override
protected void resolveRequest(ClusterState state, InternalRequest request) {
// update the routing (request#index here is possibly an alias or a parent)
request.request().routing(state.metaData().resolveIndexRouting(request.request().routing(), request.request().index()));
request.request().routing(state.metaData().resolveIndexRouting(request.request().routing(), request.request().index(), false));
// Fail fast on the node that received the request.
if (request.request().routing() == null && state.getMetaData().routingRequired(request.concreteIndex(), request.request().type())) {
throw new RoutingMissingException(request.concreteIndex(), request.request().type(), request.request().id());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ protected void resolveRequest(ClusterState state, UpdateRequest request) {
}

public static void resolveAndValidateRouting(MetaData metaData, String concreteIndex, UpdateRequest request) {
request.routing((metaData.resolveIndexRouting(request.routing(), request.index())));
request.routing((metaData.resolveIndexRouting(request.routing(), request.index(), true)));
// Fail fast on the node that received the request, rather than failing when translating on the index or delete request.
if (request.routing() == null && metaData.routingRequired(concreteIndex, request.type())) {
throw new RoutingMissingException(concreteIndex, request.type(), request.id());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ public String[] concreteIndexNames(ClusterState state, IndicesOptions options, S
return concreteIndexNames(context, indexExpressions);
}

/**
/**
* Translates the provided index expression into actual concrete indices, properly deduplicated.
*
* @param state the cluster state containing all the data to resolve to expressions to concrete indices
Expand All @@ -117,7 +117,26 @@ public String[] concreteIndexNames(ClusterState state, IndicesOptions options, S
* indices options in the context don't allow such a case.
*/
public Index[] concreteIndices(ClusterState state, IndicesOptions options, String... indexExpressions) {
Context context = new Context(state, options);
Context context = new Context(state, options, false, false);
return concreteIndices(context, indexExpressions);
}

/**
* Translates the provided index expression into actual concrete indices, properly deduplicated.
*
* @param state the cluster state containing all the data to resolve to expressions to concrete indices
* @param options defines how the aliases or indices need to be resolved to concrete indices
* @param resolveToWriteIndex defines whether to require that aliases resolve to their respective write indices
* @param indexExpressions expressions that can be resolved to alias or index names.
* @return the resolved concrete indices based on the cluster state, indices options and index expressions
* @throws IndexNotFoundException if one of the index expressions is pointing to a missing index or alias and the
* provided indices options in the context don't allow such a case, or if the final result of the indices resolution
* contains no indices and the indices options in the context don't allow such a case.
* @throws IllegalArgumentException if one of the aliases resolve to multiple indices and the provided
* indices options in the context don't allow such a case.
*/
public Index[] concreteIndices(ClusterState state, IndicesOptions options, boolean resolveToWriteIndex, String... indexExpressions) {
Copy link
Contributor

Choose a reason for hiding this comment

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

why does this method need to be public ? can we remove it and inline the implementation with it's single caller? it's just two lines.

Context context = new Context(state, options, false, resolveToWriteIndex);
return concreteIndices(context, indexExpressions);
}

Expand Down Expand Up @@ -194,29 +213,43 @@ Index[] concreteIndices(Context context, String... indexExpressions) {
}

Collection<IndexMetaData> resolvedIndices = aliasOrIndex.getIndices();
Copy link
Contributor

Choose a reason for hiding this comment

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

can we make if clause have this structure and inline this variable in the second else (see marker):

 if (aliasOrIndex.isAlias() && context.isResolveToWriteIndex()) {
    .. write operation path
} else {
  ... read path ..
  start the old code here and put the resolvedIndices variable definition here.
  
}

Copy link
Contributor

Choose a reason for hiding this comment

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

ping about inlining this variable.

if (resolvedIndices.size() > 1 && !options.allowAliasesToMultipleIndices()) {

if (aliasOrIndex.isAlias() && context.isResolveToWriteIndex()) {
AliasOrIndex.Alias alias = (AliasOrIndex.Alias) aliasOrIndex;
IndexMetaData writeIndex = alias.getWriteIndex();
if (writeIndex == null) {
if (alias.getIndices().size() > 1) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think you can make this statement - it may be that all of them have their write index flag set to false. I think we can have a generic statement like "no write index is defined for alias X. The write index may be explicitly disabled using is_write_index=false or the alias points to multiple indices without one being designated as a write index".

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I will make more generic

Copy link
Contributor Author

Choose a reason for hiding this comment

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

it may be that all of them have their write index flag set to false

saying it points to multiple indices with none set as a write-index [is_write_index=true] does not conflict with that statement?

Copy link
Contributor

Choose a reason for hiding this comment

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

isn't this covered by "the alias points to multiple indices without one being designated as a write index"?

throw new IllegalArgumentException("Alias [" + alias.getAliasName() +
"] points to multiple indices with none set as a write-index [is_write_index=true]");
} else {
throw new IllegalArgumentException("Alias [" + alias.getAliasName() + "] points to an index ["
+ alias.getIndices().get(0).getIndex().getName() + "] with [is_write_index=false]");
}
}
concreteIndices.add(writeIndex.getIndex());
} else if (resolvedIndices.size() > 1 && options.allowAliasesToMultipleIndices() == false) {
String[] indexNames = new String[resolvedIndices.size()];
int i = 0;
for (IndexMetaData indexMetaData : resolvedIndices) {
indexNames[i++] = indexMetaData.getIndex().getName();
}
throw new IllegalArgumentException("Alias [" + expression + "] has more than one indices associated with it [" +
Arrays.toString(indexNames) + "], can't execute a single index op");
}

for (IndexMetaData index : resolvedIndices) {
if (index.getState() == IndexMetaData.State.CLOSE) {
if (failClosed) {
throw new IndexClosedException(index.getIndex());
} else {
if (options.forbidClosedIndices() == false) {
concreteIndices.add(index.getIndex());
Arrays.toString(indexNames) + "], can't execute a single index op");
} else {
for (IndexMetaData index : resolvedIndices) {
if (index.getState() == IndexMetaData.State.CLOSE) {
if (failClosed) {
throw new IndexClosedException(index.getIndex());
} else {
if (options.forbidClosedIndices() == false) {
concreteIndices.add(index.getIndex());
}
}
} else if (index.getState() == IndexMetaData.State.OPEN) {
concreteIndices.add(index.getIndex());
} else {
throw new IllegalStateException("index state [" + index.getState() + "] not supported");
}
} else if (index.getState() == IndexMetaData.State.OPEN) {
concreteIndices.add(index.getIndex());
} else {
throw new IllegalStateException("index state [" + index.getState() + "] not supported");
}
}
}
Expand Down Expand Up @@ -255,6 +288,24 @@ public Index concreteSingleIndex(ClusterState state, IndicesRequest request) {
return indices[0];
}

/**
* Utility method that allows to resolve an index expression to its corresponding single write index.
*
* @param state the cluster state containing all the data to resolve to expression to a concrete index
* @param request The request that defines how the an alias or an index need to be resolved to a concrete index
* and the expression that can be resolved to an alias or an index name.
* @throws IllegalArgumentException if the index resolution does not lead to an index, or leads to more than one index
* @return the write index obtained as a result of the index resolution
*/
public Index concreteWriteIndex(ClusterState state, IndicesRequest request) {
String indexExpression = request.indices() != null && request.indices().length > 0 ? request.indices()[0] : null;
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think null is good hear (it translates to an all).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

why would this be different than concreteSingleIndex?

Index[] indices = concreteIndices(state, request.indicesOptions(), true, indexExpression);
if (indices.length != 1) {
Copy link
Contributor

Choose a reason for hiding this comment

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

💯

throw new IllegalArgumentException("unable to return a single index as the index and options provided got resolved to multiple indices");
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we need to adapt this message for more things that can go wrong (single index with no write flag etc.)

}
return indices[0];
}

/**
* @return whether the specified alias or index exists. If the alias or index contains datemath then that is resolved too.
*/
Expand Down Expand Up @@ -292,7 +343,7 @@ public String[] indexAliases(ClusterState state, String index, Predicate<AliasMe
String... expressions) {
// expand the aliases wildcard
List<String> resolvedExpressions = expressions != null ? Arrays.asList(expressions) : Collections.emptyList();
Context context = new Context(state, IndicesOptions.lenientExpandOpen(), true);
Context context = new Context(state, IndicesOptions.lenientExpandOpen(), true, false);
for (ExpressionResolver expressionResolver : expressionResolvers) {
resolvedExpressions = expressionResolver.resolve(context, resolvedExpressions);
}
Expand Down Expand Up @@ -512,24 +563,26 @@ static final class Context {
private final IndicesOptions options;
private final long startTime;
private final boolean preserveAliases;
private final boolean resolveToWriteIndex;

Context(ClusterState state, IndicesOptions options) {
this(state, options, System.currentTimeMillis());
}

Context(ClusterState state, IndicesOptions options, boolean preserveAliases) {
this(state, options, System.currentTimeMillis(), preserveAliases);
Context(ClusterState state, IndicesOptions options, boolean preserveAliases, boolean resolveToWriteIndex) {
this(state, options, System.currentTimeMillis(), preserveAliases, resolveToWriteIndex);
}

Context(ClusterState state, IndicesOptions options, long startTime) {
this(state, options, startTime, false);
this(state, options, startTime, false, false);
}

Context(ClusterState state, IndicesOptions options, long startTime, boolean preserveAliases) {
Context(ClusterState state, IndicesOptions options, long startTime, boolean preserveAliases, boolean resolveToWriteIndex) {
this.state = state;
this.options = options;
this.startTime = startTime;
this.preserveAliases = preserveAliases;
this.resolveToWriteIndex = resolveToWriteIndex;
}

public ClusterState getState() {
Expand All @@ -552,6 +605,14 @@ public long getStartTime() {
boolean isPreserveAliases() {
return preserveAliases;
}

/**
* This is used to require that aliases resolve to their write-index. It is currently not used in conjunction
* with <code>preserveAliases</code>.
*/
boolean isResolveToWriteIndex() {
return resolveToWriteIndex;
}
}

private interface ExpressionResolver {
Expand Down
Loading