|
35 | 35 | import org.elasticsearch.common.util.iterable.Iterables; |
36 | 36 | import org.elasticsearch.common.xcontent.XContentType; |
37 | 37 | import org.elasticsearch.index.IndexSettings; |
| 38 | +import org.elasticsearch.index.VersionType; |
38 | 39 | import org.elasticsearch.index.engine.Engine; |
39 | | -import org.elasticsearch.index.engine.EngineConfig; |
40 | 40 | import org.elasticsearch.index.engine.EngineFactory; |
41 | 41 | import org.elasticsearch.index.engine.InternalEngine; |
42 | 42 | import org.elasticsearch.index.engine.InternalEngineTests; |
|
47 | 47 | import org.elasticsearch.index.shard.IndexShard; |
48 | 48 | import org.elasticsearch.index.shard.IndexShardTests; |
49 | 49 | import org.elasticsearch.index.store.Store; |
| 50 | +import org.elasticsearch.index.translog.SnapshotMatchers; |
50 | 51 | import org.elasticsearch.index.translog.Translog; |
51 | 52 | import org.elasticsearch.indices.recovery.RecoveryTarget; |
52 | 53 | import org.elasticsearch.threadpool.TestThreadPool; |
53 | 54 | import org.elasticsearch.threadpool.ThreadPool; |
54 | 55 | import org.hamcrest.Matcher; |
55 | 56 |
|
56 | 57 | import java.io.IOException; |
| 58 | +import java.nio.charset.StandardCharsets; |
57 | 59 | import java.util.ArrayList; |
58 | 60 | import java.util.Collections; |
59 | 61 | import java.util.List; |
@@ -338,38 +340,75 @@ public void testReplicaOperationWithConcurrentPrimaryPromotion() throws Exceptio |
338 | 340 | * for primary and replica shards |
339 | 341 | */ |
340 | 342 | public void testDocumentFailureReplication() throws Exception { |
341 | | - final String failureMessage = "simulated document failure"; |
342 | | - final ThrowingDocumentFailureEngineFactory throwingDocumentFailureEngineFactory = |
343 | | - new ThrowingDocumentFailureEngineFactory(failureMessage); |
| 343 | + final IOException indexException = new IOException("simulated indexing failure"); |
| 344 | + final IOException deleteException = new IOException("simulated deleting failure"); |
| 345 | + final EngineFactory engineFactory = config -> InternalEngineTests.createInternalEngine((dir, iwc) -> |
| 346 | + new IndexWriter(dir, iwc) { |
| 347 | + final AtomicBoolean throwAfterIndexedOneDoc = new AtomicBoolean(); // need one document to trigger delete in IW. |
| 348 | + @Override |
| 349 | + public long addDocument(Iterable<? extends IndexableField> doc) throws IOException { |
| 350 | + if (throwAfterIndexedOneDoc.getAndSet(true)) { |
| 351 | + throw indexException; |
| 352 | + } else { |
| 353 | + return super.addDocument(doc); |
| 354 | + } |
| 355 | + } |
| 356 | + @Override |
| 357 | + public long deleteDocuments(Term... terms) throws IOException { |
| 358 | + throw deleteException; |
| 359 | + } |
| 360 | + }, null, null, config); |
344 | 361 | try (ReplicationGroup shards = new ReplicationGroup(buildIndexMetaData(0)) { |
345 | 362 | @Override |
346 | | - protected EngineFactory getEngineFactory(ShardRouting routing) { |
347 | | - return throwingDocumentFailureEngineFactory; |
348 | | - }}) { |
| 363 | + protected EngineFactory getEngineFactory(ShardRouting routing) { return engineFactory; }}) { |
349 | 364 |
|
350 | | - // test only primary |
| 365 | + // start with the primary only so two first failures are replicated to replicas via recovery from the translog of the primary. |
351 | 366 | shards.startPrimary(); |
352 | | - BulkItemResponse response = shards.index( |
353 | | - new IndexRequest(index.getName(), "type", "1") |
354 | | - .source("{}", XContentType.JSON) |
355 | | - ); |
356 | | - assertTrue(response.isFailed()); |
357 | | - assertNoOpTranslogOperationForDocumentFailure(shards, 1, shards.getPrimary().getPendingPrimaryTerm(), failureMessage); |
358 | | - shards.assertAllEqual(0); |
| 367 | + long primaryTerm = shards.getPrimary().getPendingPrimaryTerm(); |
| 368 | + List<Translog.Operation> expectedTranslogOps = new ArrayList<>(); |
| 369 | + BulkItemResponse indexResp = shards.index(new IndexRequest(index.getName(), "type", "1") |
| 370 | + .source("{}", XContentType.JSON).version(1).versionType(VersionType.EXTERNAL)); |
| 371 | + assertThat(indexResp.isFailed(), equalTo(false)); |
| 372 | + expectedTranslogOps.add(new Translog.Index("type", "1", 0, primaryTerm, 1, VersionType.EXTERNAL, |
| 373 | + "{}".getBytes(StandardCharsets.UTF_8), null, null, -1)); |
| 374 | + try (Translog.Snapshot snapshot = getTranslog(shards.getPrimary()).newSnapshot()) { |
| 375 | + assertThat(snapshot, SnapshotMatchers.containsOperationsInAnyOrder(expectedTranslogOps)); |
| 376 | + } |
| 377 | + |
| 378 | + indexResp = shards.index(new IndexRequest(index.getName(), "type", "any").source("{}", XContentType.JSON)); |
| 379 | + assertThat(indexResp.getFailure().getCause(), equalTo(indexException)); |
| 380 | + expectedTranslogOps.add(new Translog.NoOp(1, primaryTerm, indexException.toString())); |
| 381 | + |
| 382 | + BulkItemResponse deleteResp = shards.delete(new DeleteRequest(index.getName(), "type", "1")); |
| 383 | + assertThat(deleteResp.getFailure().getCause(), equalTo(deleteException)); |
| 384 | + expectedTranslogOps.add(new Translog.NoOp(2, primaryTerm, deleteException.toString())); |
| 385 | + shards.assertAllEqual(1); |
359 | 386 |
|
360 | | - // add some replicas |
361 | 387 | int nReplica = randomIntBetween(1, 3); |
362 | 388 | for (int i = 0; i < nReplica; i++) { |
363 | 389 | shards.addReplica(); |
364 | 390 | } |
365 | 391 | shards.startReplicas(nReplica); |
366 | | - response = shards.index( |
367 | | - new IndexRequest(index.getName(), "type", "1") |
368 | | - .source("{}", XContentType.JSON) |
369 | | - ); |
370 | | - assertTrue(response.isFailed()); |
371 | | - assertNoOpTranslogOperationForDocumentFailure(shards, 2, shards.getPrimary().getPendingPrimaryTerm(), failureMessage); |
372 | | - shards.assertAllEqual(0); |
| 392 | + for (IndexShard shard : shards) { |
| 393 | + try (Translog.Snapshot snapshot = getTranslog(shard).newSnapshot()) { |
| 394 | + assertThat(snapshot, SnapshotMatchers.containsOperationsInAnyOrder(expectedTranslogOps)); |
| 395 | + } |
| 396 | + } |
| 397 | + // unlike previous failures, these two failures replicated directly from the replication channel. |
| 398 | + indexResp = shards.index(new IndexRequest(index.getName(), "type", "any").source("{}", XContentType.JSON)); |
| 399 | + assertThat(indexResp.getFailure().getCause(), equalTo(indexException)); |
| 400 | + expectedTranslogOps.add(new Translog.NoOp(3, primaryTerm, indexException.toString())); |
| 401 | + |
| 402 | + deleteResp = shards.delete(new DeleteRequest(index.getName(), "type", "1")); |
| 403 | + assertThat(deleteResp.getFailure().getCause(), equalTo(deleteException)); |
| 404 | + expectedTranslogOps.add(new Translog.NoOp(4, primaryTerm, deleteException.toString())); |
| 405 | + |
| 406 | + for (IndexShard shard : shards) { |
| 407 | + try (Translog.Snapshot snapshot = getTranslog(shard).newSnapshot()) { |
| 408 | + assertThat(snapshot, SnapshotMatchers.containsOperationsInAnyOrder(expectedTranslogOps)); |
| 409 | + } |
| 410 | + } |
| 411 | + shards.assertAllEqual(1); |
373 | 412 | } |
374 | 413 | } |
375 | 414 |
|
@@ -541,47 +580,4 @@ public void testOutOfOrderDeliveryForAppendOnlyOperations() throws Exception { |
541 | 580 | shards.assertAllEqual(0); |
542 | 581 | } |
543 | 582 | } |
544 | | - |
545 | | - /** Throws <code>documentFailure</code> on every indexing operation */ |
546 | | - static class ThrowingDocumentFailureEngineFactory implements EngineFactory { |
547 | | - final String documentFailureMessage; |
548 | | - |
549 | | - ThrowingDocumentFailureEngineFactory(String documentFailureMessage) { |
550 | | - this.documentFailureMessage = documentFailureMessage; |
551 | | - } |
552 | | - |
553 | | - @Override |
554 | | - public Engine newReadWriteEngine(EngineConfig config) { |
555 | | - return InternalEngineTests.createInternalEngine((directory, writerConfig) -> |
556 | | - new IndexWriter(directory, writerConfig) { |
557 | | - @Override |
558 | | - public long addDocument(Iterable<? extends IndexableField> doc) throws IOException { |
559 | | - assert documentFailureMessage != null; |
560 | | - throw new IOException(documentFailureMessage); |
561 | | - } |
562 | | - }, null, null, config); |
563 | | - } |
564 | | - } |
565 | | - |
566 | | - private static void assertNoOpTranslogOperationForDocumentFailure( |
567 | | - Iterable<IndexShard> replicationGroup, |
568 | | - int expectedOperation, |
569 | | - long expectedPrimaryTerm, |
570 | | - String failureMessage) throws IOException { |
571 | | - for (IndexShard indexShard : replicationGroup) { |
572 | | - try(Translog.Snapshot snapshot = getTranslog(indexShard).newSnapshot()) { |
573 | | - assertThat(snapshot.totalOperations(), equalTo(expectedOperation)); |
574 | | - long expectedSeqNo = 0L; |
575 | | - Translog.Operation op = snapshot.next(); |
576 | | - do { |
577 | | - assertThat(op.opType(), equalTo(Translog.Operation.Type.NO_OP)); |
578 | | - assertThat(op.seqNo(), equalTo(expectedSeqNo)); |
579 | | - assertThat(op.primaryTerm(), equalTo(expectedPrimaryTerm)); |
580 | | - assertThat(((Translog.NoOp) op).reason(), containsString(failureMessage)); |
581 | | - op = snapshot.next(); |
582 | | - expectedSeqNo++; |
583 | | - } while (op != null); |
584 | | - } |
585 | | - } |
586 | | - } |
587 | 583 | } |
0 commit comments