use of org.opensearch.index.engine.EngineFactory in project OpenSearch by opensearch-project.
the class IndexLevelReplicationTests method testDocumentFailureReplication.
/**
* test document failures (failures after seq_no generation) are added as noop operation to the translog
* for primary and replica shards
*/
public void testDocumentFailureReplication() throws Exception {
final IOException indexException = new IOException("simulated indexing failure");
final EngineFactory engineFactory = config -> InternalEngineTests.createInternalEngine((dir, iwc) -> new IndexWriter(dir, iwc) {
@Override
public long addDocument(Iterable<? extends IndexableField> doc) throws IOException {
boolean isTombstone = false;
for (IndexableField field : doc) {
if (SeqNoFieldMapper.TOMBSTONE_NAME.equals(field.name())) {
isTombstone = true;
}
}
if (isTombstone) {
// allow to add Noop
return super.addDocument(doc);
} else {
throw indexException;
}
}
}, null, null, config);
try (ReplicationGroup shards = new ReplicationGroup(buildIndexMetadata(0)) {
@Override
protected EngineFactory getEngineFactory(ShardRouting routing) {
return engineFactory;
}
}) {
// start with the primary only so two first failures are replicated to replicas via recovery from the translog of the primary.
shards.startPrimary();
long primaryTerm = shards.getPrimary().getPendingPrimaryTerm();
List<Translog.Operation> expectedTranslogOps = new ArrayList<>();
BulkItemResponse indexResp = shards.index(new IndexRequest(index.getName()).id("1").source("{}", XContentType.JSON));
assertThat(indexResp.isFailed(), equalTo(true));
assertThat(indexResp.getFailure().getCause(), equalTo(indexException));
expectedTranslogOps.add(new Translog.NoOp(0, primaryTerm, indexException.toString()));
try (Translog.Snapshot snapshot = getTranslog(shards.getPrimary()).newSnapshot()) {
assertThat(snapshot, SnapshotMatchers.containsOperationsInAnyOrder(expectedTranslogOps));
}
shards.assertAllEqual(0);
int nReplica = randomIntBetween(1, 3);
for (int i = 0; i < nReplica; i++) {
shards.addReplica();
}
shards.startReplicas(nReplica);
for (IndexShard shard : shards) {
try (Translog.Snapshot snapshot = getTranslog(shard).newSnapshot()) {
// we flush at the end of peer recovery
if (shard.routingEntry().primary() || shard.indexSettings().isSoftDeleteEnabled() == false) {
assertThat(snapshot, SnapshotMatchers.containsOperationsInAnyOrder(expectedTranslogOps));
} else {
assertThat(snapshot.totalOperations(), equalTo(0));
}
}
try (Translog.Snapshot snapshot = shard.newChangesSnapshot("test", 0, Long.MAX_VALUE, false)) {
assertThat(snapshot, SnapshotMatchers.containsOperationsInAnyOrder(expectedTranslogOps));
}
}
// the failure replicated directly from the replication channel.
indexResp = shards.index(new IndexRequest(index.getName()).id("any").source("{}", XContentType.JSON));
assertThat(indexResp.getFailure().getCause(), equalTo(indexException));
Translog.NoOp noop2 = new Translog.NoOp(1, primaryTerm, indexException.toString());
expectedTranslogOps.add(noop2);
for (IndexShard shard : shards) {
try (Translog.Snapshot snapshot = getTranslog(shard).newSnapshot()) {
if (shard.routingEntry().primary() || shard.indexSettings().isSoftDeleteEnabled() == false) {
assertThat(snapshot, SnapshotMatchers.containsOperationsInAnyOrder(expectedTranslogOps));
} else {
assertThat(snapshot, SnapshotMatchers.containsOperationsInAnyOrder(Collections.singletonList(noop2)));
}
}
try (Translog.Snapshot snapshot = shard.newChangesSnapshot("test", 0, Long.MAX_VALUE, false)) {
assertThat(snapshot, SnapshotMatchers.containsOperationsInAnyOrder(expectedTranslogOps));
}
}
shards.assertAllEqual(0);
}
}
use of org.opensearch.index.engine.EngineFactory in project OpenSearch by opensearch-project.
the class IndicesService method getEngineFactory.
private EngineFactory getEngineFactory(final IndexSettings idxSettings) {
final IndexMetadata indexMetadata = idxSettings.getIndexMetadata();
if (indexMetadata != null && indexMetadata.getState() == IndexMetadata.State.CLOSE) {
// NoOpEngine takes precedence as long as the index is closed
return NoOpEngine::new;
}
final List<Optional<EngineFactory>> engineFactories = engineFactoryProviders.stream().map(engineFactoryProvider -> engineFactoryProvider.apply(idxSettings)).filter(maybe -> Objects.requireNonNull(maybe).isPresent()).collect(Collectors.toList());
if (engineFactories.isEmpty()) {
return new InternalEngineFactory();
} else if (engineFactories.size() == 1) {
assert engineFactories.get(0).isPresent();
return engineFactories.get(0).get();
} else {
final String message = String.format(Locale.ROOT, "multiple engine factories provided for %s: %s", idxSettings.getIndex(), engineFactories.stream().map(t -> {
assert t.isPresent();
return "[" + t.get().getClass().getName() + "]";
}).collect(Collectors.joining(",")));
throw new IllegalStateException(message);
}
}
use of org.opensearch.index.engine.EngineFactory in project OpenSearch by opensearch-project.
the class IndexService method createShard.
public synchronized IndexShard createShard(final ShardRouting routing, final Consumer<ShardId> globalCheckpointSyncer, final RetentionLeaseSyncer retentionLeaseSyncer) throws IOException {
Objects.requireNonNull(retentionLeaseSyncer);
/*
* TODO: we execute this in parallel but it's a synced method. Yet, we might
* be able to serialize the execution via the cluster state in the future. for now we just
* keep it synced.
*/
if (closed.get()) {
throw new IllegalStateException("Can't create shard " + routing.shardId() + ", closed");
}
final Settings indexSettings = this.indexSettings.getSettings();
final ShardId shardId = routing.shardId();
boolean success = false;
Store store = null;
IndexShard indexShard = null;
ShardLock lock = null;
try {
lock = nodeEnv.shardLock(shardId, "starting shard", TimeUnit.SECONDS.toMillis(5));
eventListener.beforeIndexShardCreated(shardId, indexSettings);
ShardPath path;
try {
path = ShardPath.loadShardPath(logger, nodeEnv, shardId, this.indexSettings.customDataPath());
} catch (IllegalStateException ex) {
logger.warn("{} failed to load shard path, trying to remove leftover", shardId);
try {
ShardPath.deleteLeftoverShardDirectory(logger, nodeEnv, lock, this.indexSettings);
path = ShardPath.loadShardPath(logger, nodeEnv, shardId, this.indexSettings.customDataPath());
} catch (Exception inner) {
ex.addSuppressed(inner);
throw ex;
}
}
if (path == null) {
// TODO: we should, instead, hold a "bytes reserved" of how large we anticipate this shard will be, e.g. for a shard
// that's being relocated/replicated we know how large it will become once it's done copying:
// Count up how many shards are currently on each data path:
Map<Path, Integer> dataPathToShardCount = new HashMap<>();
for (IndexShard shard : this) {
Path dataPath = shard.shardPath().getRootStatePath();
Integer curCount = dataPathToShardCount.get(dataPath);
if (curCount == null) {
curCount = 0;
}
dataPathToShardCount.put(dataPath, curCount + 1);
}
path = ShardPath.selectNewPathForShard(nodeEnv, shardId, this.indexSettings, routing.getExpectedShardSize() == ShardRouting.UNAVAILABLE_EXPECTED_SHARD_SIZE ? getAvgShardSizeInBytes() : routing.getExpectedShardSize(), dataPathToShardCount);
logger.debug("{} creating using a new path [{}]", shardId, path);
} else {
logger.debug("{} creating using an existing path [{}]", shardId, path);
}
if (shards.containsKey(shardId.id())) {
throw new IllegalStateException(shardId + " already exists");
}
logger.debug("creating shard_id {}", shardId);
// if we are on a shared FS we only own the shard (ie. we can safely delete it) if we are the primary.
final Engine.Warmer engineWarmer = (reader) -> {
IndexShard shard = getShardOrNull(shardId.getId());
if (shard != null) {
warmer.warm(reader, shard, IndexService.this.indexSettings);
}
};
Directory directory = directoryFactory.newDirectory(this.indexSettings, path);
store = new Store(shardId, this.indexSettings, directory, lock, new StoreCloseListener(shardId, () -> eventListener.onStoreClosed(shardId)));
eventListener.onStoreCreated(shardId);
indexShard = new IndexShard(routing, this.indexSettings, path, store, indexSortSupplier, indexCache, mapperService, similarityService, engineFactory, engineConfigFactory, eventListener, readerWrapper, threadPool, bigArrays, engineWarmer, searchOperationListeners, indexingOperationListeners, () -> globalCheckpointSyncer.accept(shardId), retentionLeaseSyncer, circuitBreakerService);
eventListener.indexShardStateChanged(indexShard, null, indexShard.state(), "shard created");
eventListener.afterIndexShardCreated(indexShard);
shards = newMapBuilder(shards).put(shardId.id(), indexShard).immutableMap();
success = true;
return indexShard;
} catch (ShardLockObtainFailedException e) {
throw new IOException("failed to obtain in-memory shard lock", e);
} finally {
if (success == false) {
if (lock != null) {
IOUtils.closeWhileHandlingException(lock);
}
closeShard("initialization failed", shardId, indexShard, store, eventListener);
}
}
}
use of org.opensearch.index.engine.EngineFactory in project OpenSearch by opensearch-project.
the class IndexLevelReplicationTests method testAppendOnlyRecoveryThenReplication.
public void testAppendOnlyRecoveryThenReplication() throws Exception {
CountDownLatch indexedOnPrimary = new CountDownLatch(1);
CountDownLatch recoveryDone = new CountDownLatch(1);
try (ReplicationGroup shards = new ReplicationGroup(buildIndexMetadata(1)) {
@Override
protected EngineFactory getEngineFactory(ShardRouting routing) {
return config -> new InternalEngine(config) {
@Override
public IndexResult index(Index op) throws IOException {
IndexResult result = super.index(op);
if (op.origin() == Operation.Origin.PRIMARY) {
indexedOnPrimary.countDown();
// to make sure that this operation is replicated to the replica via recovery, then via replication.
try {
recoveryDone.await();
} catch (InterruptedException e) {
throw new AssertionError(e);
}
}
return result;
}
};
}
}) {
shards.startAll();
Thread thread = new Thread(() -> {
IndexRequest indexRequest = new IndexRequest(index.getName()).source("{}", XContentType.JSON);
try {
shards.index(indexRequest);
} catch (Exception e) {
throw new AssertionError(e);
}
});
thread.start();
IndexShard replica = shards.addReplica();
Future<Void> fut = shards.asyncRecoverReplica(replica, (shard, node) -> new RecoveryTarget(shard, node, recoveryListener) {
@Override
public void prepareForTranslogOperations(int totalTranslogOps, ActionListener<Void> listener) {
try {
indexedOnPrimary.await();
} catch (InterruptedException e) {
throw new AssertionError(e);
}
super.prepareForTranslogOperations(totalTranslogOps, listener);
}
});
fut.get();
recoveryDone.countDown();
thread.join();
shards.assertAllEqual(1);
}
}
use of org.opensearch.index.engine.EngineFactory in project OpenSearch by opensearch-project.
the class RecoveryTests method testFailsToIndexDuringPeerRecovery.
public void testFailsToIndexDuringPeerRecovery() throws Exception {
AtomicReference<IOException> throwExceptionDuringIndexing = new AtomicReference<>(new IOException("simulated"));
try (ReplicationGroup group = new ReplicationGroup(buildIndexMetadata(0)) {
@Override
protected EngineFactory getEngineFactory(ShardRouting routing) {
if (routing.primary()) {
return new InternalEngineFactory();
} else {
return config -> InternalEngineTests.createInternalEngine((dir, iwc) -> new IndexWriter(dir, iwc) {
@Override
public long addDocument(Iterable<? extends IndexableField> doc) throws IOException {
final IOException error = throwExceptionDuringIndexing.getAndSet(null);
if (error != null) {
throw error;
}
return super.addDocument(doc);
}
}, null, null, config);
}
}
}) {
group.startAll();
group.indexDocs(randomIntBetween(1, 10));
allowShardFailures();
IndexShard replica = group.addReplica();
expectThrows(Exception.class, () -> group.recoverReplica(replica, (shard, sourceNode) -> new RecoveryTarget(shard, sourceNode, new PeerRecoveryTargetService.RecoveryListener() {
@Override
public void onRecoveryDone(RecoveryState state) {
throw new AssertionError("recovery must fail");
}
@Override
public void onRecoveryFailure(RecoveryState state, RecoveryFailedException e, boolean sendShardFailure) {
assertThat(ExceptionsHelper.unwrap(e, IOException.class).getMessage(), equalTo("simulated"));
}
})));
expectThrows(AlreadyClosedException.class, () -> replica.refresh("test"));
group.removeReplica(replica);
replica.store().close();
closeShards(replica);
}
}
Aggregations