|
9 | 9 | package org.elasticsearch.action.bulk;
|
10 | 10 |
|
11 | 11 | import org.elasticsearch.ElasticsearchException;
|
| 12 | +import org.elasticsearch.action.ActionFuture; |
12 | 13 | import org.elasticsearch.action.ActionRequestValidationException;
|
13 | 14 | import org.elasticsearch.action.admin.indices.alias.Alias;
|
14 | 15 | import org.elasticsearch.action.admin.indices.mapping.get.GetMappingsResponse;
|
|
19 | 20 | import org.elasticsearch.action.support.replication.ReplicationRequest;
|
20 | 21 | import org.elasticsearch.cluster.metadata.IndexMetadata;
|
21 | 22 | import org.elasticsearch.common.bytes.BytesReference;
|
| 23 | +import org.elasticsearch.common.settings.Settings; |
| 24 | +import org.elasticsearch.common.util.concurrent.ConcurrentCollections; |
| 25 | +import org.elasticsearch.core.Tuple; |
22 | 26 | import org.elasticsearch.ingest.IngestTestPlugin;
|
23 | 27 | import org.elasticsearch.plugins.Plugin;
|
24 | 28 | import org.elasticsearch.rest.RestStatus;
|
25 | 29 | import org.elasticsearch.test.ESIntegTestCase;
|
| 30 | +import org.elasticsearch.test.InternalSettingsPlugin; |
| 31 | +import org.elasticsearch.test.transport.MockTransportService; |
| 32 | +import org.elasticsearch.test.transport.StubbableTransport; |
| 33 | +import org.elasticsearch.transport.Transport; |
| 34 | +import org.elasticsearch.transport.TransportRequest; |
| 35 | +import org.elasticsearch.transport.TransportRequestOptions; |
| 36 | +import org.elasticsearch.transport.TransportService; |
26 | 37 | import org.elasticsearch.xcontent.XContentBuilder;
|
27 | 38 | import org.elasticsearch.xcontent.XContentType;
|
28 | 39 |
|
|
32 | 43 | import java.util.Collection;
|
33 | 44 | import java.util.Collections;
|
34 | 45 | import java.util.Map;
|
| 46 | +import java.util.Set; |
| 47 | +import java.util.concurrent.BrokenBarrierException; |
| 48 | +import java.util.concurrent.CountDownLatch; |
| 49 | +import java.util.concurrent.CyclicBarrier; |
35 | 50 | import java.util.concurrent.ExecutionException;
|
36 | 51 | import java.util.concurrent.atomic.AtomicBoolean;
|
37 | 52 | import java.util.concurrent.atomic.AtomicInteger;
|
|
51 | 66 | public class BulkIntegrationIT extends ESIntegTestCase {
|
52 | 67 | @Override
|
53 | 68 | protected Collection<Class<? extends Plugin>> nodePlugins() {
|
54 |
| - return Arrays.asList(IngestTestPlugin.class); |
| 69 | + return Arrays.asList(IngestTestPlugin.class, MockTransportService.TestPlugin.class); |
55 | 70 | }
|
56 | 71 |
|
57 | 72 | public void testBulkIndexCreatesMapping() throws Exception {
|
@@ -197,4 +212,64 @@ public void testDeleteIndexWhileIndexing() throws Exception {
|
197 | 212 | }
|
198 | 213 | }
|
199 | 214 |
|
| 215 | + // tests that we abandon one of two conflicting transactions. |
| 216 | + public void testPrepareConflict() throws Exception { |
| 217 | + int shards = between(1, 5); |
| 218 | + createIndex("test", Settings.builder().put(IndexMetadata.SETTING_NUMBER_OF_SHARDS, shards).build()); |
| 219 | + String coordinating = internalCluster().startCoordinatingOnlyNode(Settings.EMPTY); |
| 220 | + Iterable<TransportService> transportServiceIterable = internalCluster().getInstances( |
| 221 | + TransportService.class |
| 222 | + ); |
| 223 | + CountDownLatch ready = new CountDownLatch(1); |
| 224 | + Set<TxID> txes = ConcurrentCollections.newConcurrentSet(); |
| 225 | + // todo: only really need to do this on coordinator. |
| 226 | + transportServiceIterable.forEach(ts -> ((MockTransportService) ts).addSendBehavior(new StubbableTransport.SendRequestBehavior() { |
| 227 | + @Override |
| 228 | + public void sendRequest(Transport.Connection connection, long requestId, String action, TransportRequest request, TransportRequestOptions options) throws IOException { |
| 229 | + if (action.startsWith(ShardPrepareCommitAction.NAME)) { |
| 230 | + txes.add(((ShardPrepareCommitRequest) request).txid()); |
| 231 | + new Thread(() -> { |
| 232 | + try { |
| 233 | + ready.await(); |
| 234 | + } catch (InterruptedException e) { |
| 235 | + fail(); |
| 236 | + } |
| 237 | + try { |
| 238 | + connection.sendRequest(requestId, action, request, options); |
| 239 | + } catch (IOException e) { |
| 240 | + fail(); |
| 241 | + } |
| 242 | + }).start(); |
| 243 | + } else { |
| 244 | + connection.sendRequest(requestId, action, request, options); |
| 245 | + } |
| 246 | + } |
| 247 | + })); |
| 248 | + |
| 249 | + ActionFuture<IndexResponse> future1 = client(coordinating).prepareIndex("test") |
| 250 | + .setId("1") |
| 251 | + .setSource(Map.of("f" + randomIntBetween(1, 10), randomNonNegativeLong()), XContentType.JSON) |
| 252 | + .execute(); |
| 253 | + ActionFuture<IndexResponse> future2 = client(coordinating).prepareIndex("test") |
| 254 | + .setId("1") |
| 255 | + .setSource(Map.of("g" + randomIntBetween(1, 10), randomNonNegativeLong()), XContentType.JSON) |
| 256 | + .execute(); |
| 257 | + assertBusy(() -> assertThat(txes.size(), equalTo(2))); |
| 258 | + |
| 259 | + ready.countDown(); |
| 260 | + Tuple<IndexResponse, Exception> response1 = resultOrException(future1); |
| 261 | + Tuple<IndexResponse, Exception> response2 = resultOrException(future2); |
| 262 | + |
| 263 | + // one failure |
| 264 | + assertThat(response1.v2() != null, is(response2.v2() == null)); |
| 265 | + |
| 266 | + } |
| 267 | + |
| 268 | + Tuple<IndexResponse, Exception> resultOrException(ActionFuture<IndexResponse> future) { |
| 269 | + try { |
| 270 | + return Tuple.tuple(future.actionGet(), null); |
| 271 | + } catch (Exception e) { |
| 272 | + return Tuple.tuple(null, e); |
| 273 | + } |
| 274 | + } |
200 | 275 | }
|
0 commit comments