/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.index.reindex;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.index.IndexRequestBuilder;
import org.elasticsearch.index.engine.VersionConflictEngineException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import static org.apache.lucene.util.TestUtil.randomSimpleString;
import static org.hamcrest.Matchers.either;
import static org.hamcrest.Matchers.equalTo;
/**
* Mutates a document while update-by-query-ing it and asserts that the mutation
* always sticks. Update-by-query should never revert documents.
*/
public class UpdateByQueryWhileModifyingTests extends ReindexTestCase {
private static final int MAX_MUTATIONS = 50;
private static final int MAX_ATTEMPTS = 10;
public void testUpdateWhileReindexing() throws Exception {
AtomicReference<String> value = new AtomicReference<>(randomSimpleString(random()));
indexRandom(true, client().prepareIndex("test", "test", "test").setSource("test", value.get()));
final AtomicReference<Throwable> failure = new AtomicReference<>();
final AtomicBoolean keepUpdating = new AtomicBoolean(true);
Thread updater = new Thread(new Runnable() {
@Override
public void run() {
while (keepUpdating.get()) {
try {
assertThat(updateByQuery().source("test").refresh(true).abortOnVersionConflict(false).get(),
updateByQueryResponseMatcher().updated(either(equalTo(0L)).or(equalTo(1L)))
.versionConflicts(either(equalTo(0L)).or(equalTo(1L))));
} catch (Throwable t) {
failure.set(t);
}
}
}
});
updater.start();
try {
for (int i = 0; i < MAX_MUTATIONS; i++) {
GetResponse get = client().prepareGet("test", "test", "test").get();
assertEquals(value.get(), get.getSource().get("test"));
value.set(randomSimpleString(random()));
IndexRequestBuilder index = client().prepareIndex("test", "test", "test").setSource("test", value.get())
.setRefresh(true);
/*
* Update by query increments the version number so concurrent
* indexes might get version conflict exceptions so we just
* blindly retry.
*/
int attempts = 0;
while (true) {
attempts++;
try {
index.setVersion(get.getVersion()).get();
break;
} catch (VersionConflictEngineException e) {
if (attempts >= MAX_ATTEMPTS) {
throw new RuntimeException(
"Failed to index after [" + MAX_ATTEMPTS + "] attempts. Too many version conflicts!");
}
logger.info(
"Caught expected version conflict trying to perform mutation number {} with version {}. Retrying.",
i, get.getVersion());
get = client().prepareGet("test", "test", "test").get();
}
}
}
} finally {
keepUpdating.set(false);
updater.join(TimeUnit.SECONDS.toMillis(10));
if (failure.get() != null) {
throw new RuntimeException(failure.get());
}
}
}
}