diff --git a/server/src/main/java/org/opensearch/index/engine/InternalEngine.java b/server/src/main/java/org/opensearch/index/engine/InternalEngine.java index e93f8e6cdb1c7..1e4b2183185b4 100644 --- a/server/src/main/java/org/opensearch/index/engine/InternalEngine.java +++ b/server/src/main/java/org/opensearch/index/engine/InternalEngine.java @@ -1418,6 +1418,13 @@ private boolean assertDocDoesNotExist(final Index index, final boolean allowDele } private void updateDocs(final Term uid, final List docs, final IndexWriter indexWriter) throws IOException { + if (engineConfig.getIndexSettings().getIndexMetadata().isAppendOnlyIndex()) { + failEngine( + "Failing shard as update operation is not allowed for append only index ", + new EngineException(shardId, "Unable to update document as it is an append only index") + ); + } + if (docs.size() > 1) { indexWriter.softUpdateDocuments(uid, docs, softDeletesField); } else { diff --git a/server/src/test/java/org/opensearch/index/engine/InternalEngineTests.java b/server/src/test/java/org/opensearch/index/engine/InternalEngineTests.java index 11ad355e22afd..96efd0ea8dfcb 100644 --- a/server/src/test/java/org/opensearch/index/engine/InternalEngineTests.java +++ b/server/src/test/java/org/opensearch/index/engine/InternalEngineTests.java @@ -1084,6 +1084,55 @@ public void testConcurrentGetAndFlush() throws Exception { latestGetResult.get().close(); } + public void testUpdateOperationForAppendOnlyIndex() throws Exception { + Settings.Builder settings = Settings.builder() + .put(defaultSettings.getSettings()) + .put(IndexMetadata.INDEX_APPEND_ONLY_ENABLED_SETTING.getKey(), "true"); + final IndexMetadata indexMetadata = IndexMetadata.builder(defaultSettings.getIndexMetadata()).settings(settings).build(); + final IndexSettings indexSettings = IndexSettingsModule.newIndexSettings(indexMetadata); + final AtomicLong globalCheckpoint = new AtomicLong(SequenceNumbers.NO_OPS_PERFORMED); + try ( + Store store = createStore(); + InternalEngine engine = createUpdateOnlyEngine( + config(indexSettings, store, createTempDir(), newMergePolicy(), null, null, globalCheckpoint::get) + ) + ) { + engine.refresh("warm_up"); + Engine.Searcher searchResult = engine.acquireSearcher("test"); + searchResult.close(); + + final BiFunction searcherFactory = engine::acquireSearcher; + + // create a document + Document document = testDocumentWithTextField(); + document.add(new Field(SourceFieldMapper.NAME, BytesReference.toBytes(B_1), SourceFieldMapper.Defaults.FIELD_TYPE)); + ParsedDocument doc = testParsedDocument("1", null, document, B_1, null); + expectThrows(AlreadyClosedException.class, () -> engine.index(indexForDoc(doc))); + } + } + + private InternalEngine createUpdateOnlyEngine(EngineConfig config) throws IOException { + final Store store = config.getStore(); + final Directory directory = store.directory(); + if (Lucene.indexExists(directory) == false) { + store.createEmpty(config.getIndexSettings().getIndexVersionCreated().luceneVersion); + final String translogUuid = Translog.createEmptyTranslog( + config.getTranslogConfig().getTranslogPath(), + SequenceNumbers.NO_OPS_PERFORMED, + shardId, + primaryTerm.get() + ); + store.associateIndexWithNewTranslog(translogUuid); + } + + return new InternalEngine(config) { + @Override + protected IndexingStrategy indexingStrategyForOperation(Index index) throws IOException { + return IndexingStrategy.processNormally(false, 0, 0); + } + }; + } + public void testSimpleOperations() throws Exception { engine.refresh("warm_up"); Engine.Searcher searchResult = engine.acquireSearcher("test");