diff --git a/docs/changelog/126314.yaml b/docs/changelog/126314.yaml new file mode 100644 index 0000000000000..4336c3e39e10e --- /dev/null +++ b/docs/changelog/126314.yaml @@ -0,0 +1,6 @@ +pr: 126314 +summary: Add timeout to synonyms put APIs to wait for synonyms to be accessible +area: Analysis +type: bug +issues: + - 121441 diff --git a/rest-api-spec/src/main/resources/rest-api-spec/api/synonyms.put_synonym.json b/rest-api-spec/src/main/resources/rest-api-spec/api/synonyms.put_synonym.json index e09bbb7e428a1..2ea7b34afc8a4 100644 --- a/rest-api-spec/src/main/resources/rest-api-spec/api/synonyms.put_synonym.json +++ b/rest-api-spec/src/main/resources/rest-api-spec/api/synonyms.put_synonym.json @@ -30,6 +30,12 @@ } ] }, + "params": { + "timeout": { + "type": "time", + "description": "Explicit timeout for the operation to complete" + } + }, "body": { "description": "Synonyms set rules", "required": true diff --git a/rest-api-spec/src/main/resources/rest-api-spec/api/synonyms.put_synonym_rule.json b/rest-api-spec/src/main/resources/rest-api-spec/api/synonyms.put_synonym_rule.json index 51503b5819862..c4bd5ba46c50f 100644 --- a/rest-api-spec/src/main/resources/rest-api-spec/api/synonyms.put_synonym_rule.json +++ b/rest-api-spec/src/main/resources/rest-api-spec/api/synonyms.put_synonym_rule.json @@ -34,6 +34,12 @@ } ] }, + "params": { + "timeout": { + "type": "time", + "description": "Explicit timeout for the operation to complete" + } + }, "body": { "description": "Synonym rule", "required": true diff --git a/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/synonyms/10_synonyms_put.yml b/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/synonyms/10_synonyms_put.yml index 4f36514f833dc..374d47940828f 100644 --- a/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/synonyms/10_synonyms_put.yml +++ b/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/synonyms/10_synonyms_put.yml @@ -15,11 +15,6 @@ setup: - match: { result: "created" } - - do: - cluster.health: - index: .synonyms - wait_for_status: green - - do: synonyms.get_synonym: id: test-update-synonyms @@ -63,11 +58,6 @@ setup: - match: { result: "created" } - - do: - cluster.health: - index: .synonyms - wait_for_status: green - - do: synonyms.get_synonym: id: test-empty-synonyms @@ -75,6 +65,41 @@ setup: - match: { count: 0 } - match: { synonyms_set: [] } +--- +"Timeout can be specified": + + - requires: + test_runner_features: [ capabilities ] + capabilities: + - method: PUT + path: /_synonyms/{rule_id} + capabilities: [ put_synonyms_timeout ] + reason: "synonyms timeout capability needed" + + - do: + synonyms.put_synonym: + id: test-update-synonyms + timeout: 10s + body: + synonyms_set: + - synonyms: "hello, hi" + - synonyms: "bye => goodbye" + id: "test-id" + + - match: { result: "created" } + - match: { reload_analyzers_details._shards.total: 0 } + - length: { reload_analyzers_details.reload_details: 0 } + + # Validate timeout values + - do: + catch: /as a time value:\ negative durations are not supported/ + synonyms.put_synonym: + id: test-update-synonyms + timeout: -100s + body: + synonyms_set: + - synonyms: "bye, goodbye" + --- "Validation fails tests": - do: @@ -116,3 +141,4 @@ setup: body: synonyms_set: - synonyms: "bye, goodbye, " + diff --git a/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/synonyms/110_synonyms_invalid.yml b/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/synonyms/110_synonyms_invalid.yml index f4578290788f1..131c7c0e826fc 100644 --- a/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/synonyms/110_synonyms_invalid.yml +++ b/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/synonyms/110_synonyms_invalid.yml @@ -11,12 +11,6 @@ setup: synonyms_set: synonyms: "foo => bar, baz" - # This is to ensure that all index shards (write and read) are available. In serverless this can take some time. - - do: - cluster.health: - index: .synonyms - wait_for_status: green - - do: indices.create: index: test_index @@ -372,13 +366,6 @@ setup: synonyms_set: synonyms: "foo => bar, baz" - # This is to ensure that all index shards (write and read) are available. In serverless this can take some time. - - do: - cluster.health: - index: .synonyms - wait_for_status: green - - - do: indices.stats: { index: test_index } @@ -441,12 +428,6 @@ setup: synonyms_set: synonyms: "foo => bar, baz" - # This is to ensure that all index shards (write and read) are available. In serverless this can take some time. - - do: - cluster.health: - index: .synonyms - wait_for_status: green - - do: # Warning issued in previous versions allowed_warnings: diff --git a/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/synonyms/20_synonyms_get.yml b/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/synonyms/20_synonyms_get.yml index 8ab97b3ec779d..20f76e31e43a8 100644 --- a/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/synonyms/20_synonyms_get.yml +++ b/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/synonyms/20_synonyms_get.yml @@ -14,12 +14,6 @@ setup: - synonyms: "test => check" id: "test-id-3" - # This is to ensure that all index shards (write and read) are available. In serverless this can take some time. - - do: - cluster.health: - index: .synonyms - wait_for_status: green - --- "Get synonyms set": - do: diff --git a/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/synonyms/30_synonyms_delete.yml b/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/synonyms/30_synonyms_delete.yml index ea27267c518a5..51d2a9c5a6938 100644 --- a/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/synonyms/30_synonyms_delete.yml +++ b/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/synonyms/30_synonyms_delete.yml @@ -12,12 +12,6 @@ setup: - synonyms: "bye => goodbye" id: "test-id-2" - # This is to ensure that all index shards (write and read) are available. In serverless this can take some time. - - do: - cluster.health: - index: .synonyms - wait_for_status: green - --- "Delete synonyms set": - do: @@ -77,7 +71,6 @@ setup: settings: index: number_of_shards: 1 - number_of_replicas: 0 analysis: filter: my_synonym_filter: diff --git a/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/synonyms/40_synonyms_sets_get.yml b/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/synonyms/40_synonyms_sets_get.yml index e68c93077bdec..9d6540c118ce5 100644 --- a/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/synonyms/40_synonyms_sets_get.yml +++ b/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/synonyms/40_synonyms_sets_get.yml @@ -10,12 +10,6 @@ setup: - synonyms: "hello, hi" - synonyms: "goodbye, bye" - # This is to ensure that all index shards (write and read) are available. In serverless this can take some time. - - do: - cluster.health: - index: .synonyms - wait_for_status: green - - do: synonyms.put_synonym: id: test-synonyms-1 diff --git a/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/synonyms/50_synonym_rule_put.yml b/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/synonyms/50_synonym_rule_put.yml index c8f463ba5cbe7..4bda6f4bc0c81 100644 --- a/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/synonyms/50_synonym_rule_put.yml +++ b/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/synonyms/50_synonym_rule_put.yml @@ -14,12 +14,6 @@ setup: - synonyms: "test => check" id: "test-id-3" - # This is to ensure that all index shards (write and read) are available. In serverless this can take some time. - - do: - cluster.health: - index: .synonyms - wait_for_status: green - --- "Update a synonyms rule": - do: @@ -85,3 +79,78 @@ setup: rule_id: "test-id-0" body: synonyms: "i-phone, iphone" + +--- +"Timeout can be specified": + - requires: + test_runner_features: [ capabilities ] + capabilities: + - method: PUT + path: /_synonyms/{set_id}/{rule_id} + capabilities: [ put_synonyms_timeout ] + reason: "synonyms timeout capability needed" + + - do: + synonyms.put_synonym_rule: + set_id: "test-synonyms" + rule_id: "test-id-0" + timeout: 10s + body: + synonyms: "i-phone, iphone" + + - match: { result: "created" } + - match: { reload_analyzers_details._shards.total: 0 } + - length: { reload_analyzers_details.reload_details: 0 } + + # Validates timeout values + - do: + catch: /as a time value:\ negative durations are not supported/ + synonyms.put_synonym_rule: + set_id: "test-synonyms" + rule_id: "test-id-0" + timeout: -100s + body: + synonyms_set: + - synonyms: "bye, goodbye" + +--- +"Validation failure tests": + - do: + catch: /\[synonyms\] field can't be empty/ + synonyms.put_synonym_rule: + set_id: "test-synonyms" + rule_id: "test-id-0" + body: + synonyms: "" + + - do: + catch: /More than one explicit mapping specified in the same synonyms rule/ + synonyms.put_synonym_rule: + set_id: "test-synonyms" + rule_id: "test-id-0" + body: + synonyms: "bye => => goodbye" + + - do: + catch: /Incorrect syntax for \[synonyms\]/ + synonyms.put_synonym_rule: + set_id: "test-synonyms" + rule_id: "test-id-0" + body: + synonyms: " => goodbye" + + - do: + catch: /Incorrect syntax for \[synonyms\]/ + synonyms.put_synonym_rule: + set_id: "test-synonyms" + rule_id: "test-id-0" + body: + synonyms: "bye => " + + - do: + catch: /Incorrect syntax for \[synonyms\]/ + synonyms.put_synonym_rule: + set_id: "test-synonyms" + rule_id: "test-id-0" + body: + synonyms: "bye, goodbye, " diff --git a/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/synonyms/60_synonym_rule_get.yml b/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/synonyms/60_synonym_rule_get.yml index 1754467e89b2f..413337072e160 100644 --- a/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/synonyms/60_synonym_rule_get.yml +++ b/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/synonyms/60_synonym_rule_get.yml @@ -14,12 +14,6 @@ setup: - synonyms: "test => check" id: "test-id-3" - # This is to ensure that all index shards (write and read) are available. In serverless this can take some time. - - do: - cluster.health: - index: .synonyms - wait_for_status: green - --- "Get a synonym rule": - do: diff --git a/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/synonyms/70_synonym_rule_delete.yml b/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/synonyms/70_synonym_rule_delete.yml index b24ed799bfd8f..a4853b0b6d414 100644 --- a/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/synonyms/70_synonym_rule_delete.yml +++ b/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/synonyms/70_synonym_rule_delete.yml @@ -14,12 +14,6 @@ setup: - synonyms: "test => check" id: "test-id-3" - # This is to ensure that all index shards (write and read) are available. In serverless this can take some time. - - do: - cluster.health: - index: .synonyms - wait_for_status: green - --- "Delete synonym rule": - do: diff --git a/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/synonyms/80_synonyms_from_index.yml b/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/synonyms/80_synonyms_from_index.yml index a996357896594..15cb1000e2ad3 100644 --- a/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/synonyms/80_synonyms_from_index.yml +++ b/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/synonyms/80_synonyms_from_index.yml @@ -13,12 +13,6 @@ setup: - synonyms: "bye => goodbye" id: "synonym-rule-2" - # This is to ensure that all index shards (write and read) are available. In serverless this can take some time. - - do: - cluster.health: - index: .synonyms - wait_for_status: green - # Create an index with synonym_filter that uses that synonyms set - do: indices.create: diff --git a/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/synonyms/90_synonyms_reloading_for_synset.yml b/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/synonyms/90_synonyms_reloading_for_synset.yml index 02db799e52e51..579d99e3f2ff7 100644 --- a/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/synonyms/90_synonyms_reloading_for_synset.yml +++ b/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/synonyms/90_synonyms_reloading_for_synset.yml @@ -14,12 +14,6 @@ setup: - synonyms: "bye => goodbye" id: "synonym-rule-2" - # This is to ensure that all index shards (write and read) are available. In serverless this can take some time. - - do: - cluster.health: - index: .synonyms - wait_for_status: green - # Create synonyms synonyms_set2 - do: synonyms.put_synonym: diff --git a/server/src/internalClusterTest/java/org/elasticsearch/synonyms/SynonymsManagementAPIServiceIT.java b/server/src/internalClusterTest/java/org/elasticsearch/synonyms/SynonymsManagementAPIServiceIT.java index a7b98b4639130..0e66d8be65dd7 100644 --- a/server/src/internalClusterTest/java/org/elasticsearch/synonyms/SynonymsManagementAPIServiceIT.java +++ b/server/src/internalClusterTest/java/org/elasticsearch/synonyms/SynonymsManagementAPIServiceIT.java @@ -11,8 +11,13 @@ import org.apache.logging.log4j.Logger; import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse; import org.elasticsearch.action.bulk.BulkResponse; +import org.elasticsearch.cluster.ClusterName; +import org.elasticsearch.cluster.ClusterState; +import org.elasticsearch.core.TimeValue; import org.elasticsearch.index.mapper.extras.MapperExtrasPlugin; +import org.elasticsearch.indices.IndexCreationException; import org.elasticsearch.plugins.Plugin; import org.elasticsearch.reindex.ReindexPlugin; import org.elasticsearch.test.ESIntegTestCase; @@ -23,6 +28,7 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import static org.elasticsearch.action.synonyms.PutSynonymRuleAction.DEFAULT_TIMEOUT; import static org.elasticsearch.action.synonyms.SynonymsTestUtils.randomSynonymRule; import static org.elasticsearch.action.synonyms.SynonymsTestUtils.randomSynonymsSet; import static org.mockito.ArgumentMatchers.anyString; @@ -51,21 +57,26 @@ public void testCreateManySynonyms() throws Exception { CountDownLatch putLatch = new CountDownLatch(1); String synonymSetId = randomIdentifier(); int rulesNumber = randomIntBetween(maxSynonymSets / 2, maxSynonymSets); - synonymsManagementAPIService.putSynonymsSet(synonymSetId, randomSynonymsSet(rulesNumber, rulesNumber), new ActionListener<>() { - @Override - public void onResponse(SynonymsManagementAPIService.SynonymsReloadResult synonymsReloadResult) { - assertEquals( - SynonymsManagementAPIService.UpdateSynonymsResultStatus.CREATED, - synonymsReloadResult.synonymsOperationResult() - ); - putLatch.countDown(); - } + synonymsManagementAPIService.putSynonymsSet( + synonymSetId, + randomSynonymsSet(rulesNumber, rulesNumber), + DEFAULT_TIMEOUT, + new ActionListener<>() { + @Override + public void onResponse(SynonymsManagementAPIService.SynonymsReloadResult synonymsReloadResult) { + assertEquals( + SynonymsManagementAPIService.UpdateSynonymsResultStatus.CREATED, + synonymsReloadResult.synonymsOperationResult() + ); + putLatch.countDown(); + } - @Override - public void onFailure(Exception e) { - fail(e); + @Override + public void onFailure(Exception e) { + fail(e); + } } - }); + ); putLatch.await(5, TimeUnit.SECONDS); @@ -95,6 +106,7 @@ public void testCreateTooManySynonymsAtOnce() throws InterruptedException { synonymsManagementAPIService.putSynonymsSet( randomIdentifier(), randomSynonymsSet(maxSynonymSets + 1, maxSynonymSets * 2), + DEFAULT_TIMEOUT, new ActionListener<>() { @Override public void onResponse(SynonymsManagementAPIService.SynonymsReloadResult synonymsReloadResult) { @@ -120,67 +132,73 @@ public void testCreateTooManySynonymsUsingRuleUpdates() throws InterruptedExcept int rulesToUpdate = randomIntBetween(1, 10); int synonymsToCreate = maxSynonymSets - rulesToUpdate; String synonymSetId = randomIdentifier(); - synonymsManagementAPIService.putSynonymsSet(synonymSetId, randomSynonymsSet(synonymsToCreate), new ActionListener<>() { - @Override - public void onResponse(SynonymsManagementAPIService.SynonymsReloadResult synonymsReloadResult) { - // Create as many rules as should fail - SynonymRule[] rules = randomSynonymsSet(atLeast(rulesToUpdate + 1)); - CountDownLatch updatedRulesLatch = new CountDownLatch(rulesToUpdate); - for (int i = 0; i < rulesToUpdate; i++) { - synonymsManagementAPIService.putSynonymRule(synonymSetId, rules[i], new ActionListener<>() { - @Override - public void onResponse(SynonymsManagementAPIService.SynonymsReloadResult synonymsReloadResult) { - updatedRulesLatch.countDown(); - } - - @Override - public void onFailure(Exception e) { - fail(e); - } - }); - } - try { - updatedRulesLatch.await(5, TimeUnit.SECONDS); - } catch (InterruptedException e) { - fail(e); - } - - // Updating more rules fails - int rulesToInsert = rules.length - rulesToUpdate; - CountDownLatch insertRulesLatch = new CountDownLatch(rulesToInsert); - for (int i = rulesToUpdate; i < rulesToInsert; i++) { - synonymsManagementAPIService.putSynonymRule( - // Error here - synonymSetId, - rules[i], - new ActionListener<>() { + synonymsManagementAPIService.putSynonymsSet( + synonymSetId, + randomSynonymsSet(synonymsToCreate), + DEFAULT_TIMEOUT, + new ActionListener<>() { + @Override + public void onResponse(SynonymsManagementAPIService.SynonymsReloadResult synonymsReloadResult) { + // Create as many rules as should fail + SynonymRule[] rules = randomSynonymsSet(atLeast(rulesToUpdate + 1)); + CountDownLatch updatedRulesLatch = new CountDownLatch(rulesToUpdate); + for (int i = 0; i < rulesToUpdate; i++) { + synonymsManagementAPIService.putSynonymRule(synonymSetId, rules[i], DEFAULT_TIMEOUT, new ActionListener<>() { @Override public void onResponse(SynonymsManagementAPIService.SynonymsReloadResult synonymsReloadResult) { - fail("Shouldn't have been able to update a rule"); + updatedRulesLatch.countDown(); } @Override public void onFailure(Exception e) { - if (e instanceof IllegalArgumentException == false) { - fail(e); + fail(e); + } + }); + } + try { + updatedRulesLatch.await(5, TimeUnit.SECONDS); + } catch (InterruptedException e) { + fail(e); + } + + // Updating more rules fails + int rulesToInsert = rules.length - rulesToUpdate; + CountDownLatch insertRulesLatch = new CountDownLatch(rulesToInsert); + for (int i = rulesToUpdate; i < rulesToInsert; i++) { + synonymsManagementAPIService.putSynonymRule( + // Error here + synonymSetId, + rules[i], + DEFAULT_TIMEOUT, + new ActionListener<>() { + @Override + public void onResponse(SynonymsManagementAPIService.SynonymsReloadResult synonymsReloadResult) { + fail("Shouldn't have been able to update a rule"); + } + + @Override + public void onFailure(Exception e) { + if (e instanceof IllegalArgumentException == false) { + fail(e); + } + updatedRulesLatch.countDown(); } - updatedRulesLatch.countDown(); } - } - ); + ); + } + try { + insertRulesLatch.await(5, TimeUnit.SECONDS); + } catch (InterruptedException e) { + fail(e); + } } - try { - insertRulesLatch.await(5, TimeUnit.SECONDS); - } catch (InterruptedException e) { + + @Override + public void onFailure(Exception e) { fail(e); } } - - @Override - public void onFailure(Exception e) { - fail(e); - } - }); + ); latch.await(5, TimeUnit.SECONDS); } @@ -189,13 +207,14 @@ public void testUpdateRuleWithMaxSynonyms() throws InterruptedException { CountDownLatch latch = new CountDownLatch(1); String synonymSetId = randomIdentifier(); SynonymRule[] synonymsSet = randomSynonymsSet(maxSynonymSets, maxSynonymSets); - synonymsManagementAPIService.putSynonymsSet(synonymSetId, synonymsSet, new ActionListener<>() { + synonymsManagementAPIService.putSynonymsSet(synonymSetId, synonymsSet, DEFAULT_TIMEOUT, new ActionListener<>() { @Override public void onResponse(SynonymsManagementAPIService.SynonymsReloadResult synonymsReloadResult) { // Updating a rule fails synonymsManagementAPIService.putSynonymRule( synonymSetId, synonymsSet[randomIntBetween(0, maxSynonymSets - 1)], + DEFAULT_TIMEOUT, new ActionListener<>() { @Override public void onResponse(SynonymsManagementAPIService.SynonymsReloadResult synonymsReloadResult) { @@ -224,21 +243,26 @@ public void testCreateRuleWithMaxSynonyms() throws InterruptedException { String synonymSetId = randomIdentifier(); String ruleId = randomIdentifier(); SynonymRule[] synonymsSet = randomSynonymsSet(maxSynonymSets, maxSynonymSets); - synonymsManagementAPIService.putSynonymsSet(synonymSetId, synonymsSet, new ActionListener<>() { + synonymsManagementAPIService.putSynonymsSet(synonymSetId, synonymsSet, DEFAULT_TIMEOUT, new ActionListener<>() { @Override public void onResponse(SynonymsManagementAPIService.SynonymsReloadResult synonymsReloadResult) { // Updating a rule fails - synonymsManagementAPIService.putSynonymRule(synonymSetId, randomSynonymRule(ruleId), new ActionListener<>() { - @Override - public void onResponse(SynonymsManagementAPIService.SynonymsReloadResult synonymsReloadResult) { - fail("Should not create a new rule that does not exist when at max capacity"); - } + synonymsManagementAPIService.putSynonymRule( + synonymSetId, + randomSynonymRule(ruleId), + DEFAULT_TIMEOUT, + new ActionListener<>() { + @Override + public void onResponse(SynonymsManagementAPIService.SynonymsReloadResult synonymsReloadResult) { + fail("Should not create a new rule that does not exist when at max capacity"); + } - @Override - public void onFailure(Exception e) { - latch.countDown(); + @Override + public void onFailure(Exception e) { + latch.countDown(); + } } - }); + ); } @Override @@ -289,4 +313,103 @@ public void onFailure(Exception e) { readLatch.await(5, TimeUnit.SECONDS); verify(logger).warn(anyString(), eq(synonymSetId)); } + + public void testCreateSynonymsWithYellowSynonymsIndex() throws Exception { + + TimeValue timeout = randomTimeValue(); + + // Override health method check to simulate a timeout in checking the synonyms index + synonymsManagementAPIService = new SynonymsManagementAPIService(client()) { + @Override + void checkSynonymsIndexHealth(TimeValue actualTimeout, ActionListener listener) { + assertEquals(actualTimeout, timeout); + ClusterState clusterState = ClusterState.builder(ClusterName.DEFAULT).build(); + ClusterHealthResponse response = new ClusterHealthResponse( + randomIdentifier(), + new String[] { SynonymsManagementAPIService.SYNONYMS_INDEX_CONCRETE_NAME }, + clusterState + ); + response.setTimedOut(true); + listener.onResponse(response); + } + }; + + // Create a rule fails + CountDownLatch putLatch = new CountDownLatch(1); + String synonymSetId = randomIdentifier(); + synonymsManagementAPIService.putSynonymsSet(synonymSetId, randomSynonymsSet(1, 1), timeout, new ActionListener<>() { + @Override + public void onResponse(SynonymsManagementAPIService.SynonymsReloadResult synonymsReloadResult) { + fail("Shouldn't have been able to create synonyms with a timeout in synonyms index health"); + } + + @Override + public void onFailure(Exception e) { + // Expected + assertTrue(e instanceof IndexCreationException); + assertTrue(e.getMessage().contains("synonyms index [.synonyms] is not searchable")); + putLatch.countDown(); + } + }); + + putLatch.await(5, TimeUnit.SECONDS); + + // Update a rule fails + CountDownLatch updateLatch = new CountDownLatch(1); + synonymsManagementAPIService.putSynonymRule(synonymSetId, randomSynonymRule(randomIdentifier()), timeout, new ActionListener<>() { + @Override + public void onResponse(SynonymsManagementAPIService.SynonymsReloadResult synonymsReloadResult) { + fail("Shouldn't have been able to update synonyms with a timeout in synonyms index health"); + } + + @Override + public void onFailure(Exception e) { + // Expected + assertTrue(e instanceof IndexCreationException); + assertTrue(e.getMessage().contains("synonyms index [.synonyms] is not searchable")); + updateLatch.countDown(); + } + }); + + updateLatch.await(5, TimeUnit.SECONDS); + + // But, we can still create a synonyms set with timeout -1 + CountDownLatch putWithoutTimeoutLatch = new CountDownLatch(1); + synonymsManagementAPIService.putSynonymsSet(synonymSetId, randomSynonymsSet(1, 1), TimeValue.MINUS_ONE, new ActionListener<>() { + @Override + public void onResponse(SynonymsManagementAPIService.SynonymsReloadResult synonymsReloadResult) { + // Expected + putLatch.countDown(); + } + + @Override + public void onFailure(Exception e) { + fail(e); + } + }); + + putWithoutTimeoutLatch.await(5, TimeUnit.SECONDS); + + // Same for update + CountDownLatch putRuleWithoutTimeoutLatch = new CountDownLatch(1); + synonymsManagementAPIService.putSynonymRule( + synonymSetId, + randomSynonymRule(randomIdentifier()), + TimeValue.MINUS_ONE, + new ActionListener<>() { + @Override + public void onResponse(SynonymsManagementAPIService.SynonymsReloadResult synonymsReloadResult) { + // Expected + putRuleWithoutTimeoutLatch.countDown(); + } + + @Override + public void onFailure(Exception e) { + fail(e); + } + } + ); + + putRuleWithoutTimeoutLatch.await(5, TimeUnit.SECONDS); + } } diff --git a/server/src/main/java/org/elasticsearch/TransportVersions.java b/server/src/main/java/org/elasticsearch/TransportVersions.java index 00943d04275dd..c10784bfec842 100644 --- a/server/src/main/java/org/elasticsearch/TransportVersions.java +++ b/server/src/main/java/org/elasticsearch/TransportVersions.java @@ -214,6 +214,7 @@ static TransportVersion def(int id) { public static final TransportVersion ESQL_REMOVE_AGGREGATE_TYPE = def(9_045_0_00); public static final TransportVersion ADD_PROJECT_ID_TO_DSL_ERROR_INFO = def(9_046_0_00); public static final TransportVersion SEMANTIC_TEXT_CHUNKING_CONFIG = def(9_047_00_0); + public static final TransportVersion SYNONYMS_UPDATE_TIMEOUT = def(9_048_0_00); /* * STOP! READ THIS FIRST! No, really, diff --git a/server/src/main/java/org/elasticsearch/action/synonyms/PutSynonymRuleAction.java b/server/src/main/java/org/elasticsearch/action/synonyms/PutSynonymRuleAction.java index 6d0d24dbfee05..6ec5666f0c13d 100644 --- a/server/src/main/java/org/elasticsearch/action/synonyms/PutSynonymRuleAction.java +++ b/server/src/main/java/org/elasticsearch/action/synonyms/PutSynonymRuleAction.java @@ -9,6 +9,7 @@ package org.elasticsearch.action.synonyms; +import org.elasticsearch.TransportVersions; import org.elasticsearch.action.ActionRequest; import org.elasticsearch.action.ActionRequestValidationException; import org.elasticsearch.action.ActionType; @@ -18,6 +19,7 @@ import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.xcontent.XContentHelper; +import org.elasticsearch.core.TimeValue; import org.elasticsearch.synonyms.SynonymRule; import org.elasticsearch.synonyms.SynonymsManagementAPIService; import org.elasticsearch.xcontent.ConstructingObjectParser; @@ -34,14 +36,16 @@ public class PutSynonymRuleAction extends ActionType { public static final PutSynonymRuleAction INSTANCE = new PutSynonymRuleAction(); public static final String NAME = "cluster:admin/synonym_rules/put"; + public static final TimeValue DEFAULT_TIMEOUT = TimeValue.timeValueSeconds(10); + public PutSynonymRuleAction() { super(NAME); } public static class Request extends ActionRequest { private final String synonymsSetId; - private final SynonymRule synonymRule; + private final TimeValue timeout; public static final ParseField SYNONYMS_FIELD = new ParseField(SynonymsManagementAPIService.SYNONYMS_FIELD); private static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>( @@ -58,20 +62,30 @@ public Request(StreamInput in) throws IOException { super(in); this.synonymsSetId = in.readString(); this.synonymRule = new SynonymRule(in); + if (in.getTransportVersion().onOrAfter(TransportVersions.SYNONYMS_UPDATE_TIMEOUT)) { + this.timeout = in.readTimeValue(); + } else { + this.timeout = DEFAULT_TIMEOUT; + } } - public Request(String synonymsSetId, String synonymRuleId, BytesReference content, XContentType contentType) throws IOException { + public Request(String synonymsSetId, String synonymRuleId, TimeValue timeout, BytesReference content, XContentType contentType) + throws IOException { + Objects.requireNonNull(timeout); this.synonymsSetId = synonymsSetId; try (XContentParser parser = XContentHelper.createParser(XContentParserConfiguration.EMPTY, content, contentType)) { this.synonymRule = PARSER.apply(parser, synonymRuleId); } catch (Exception e) { throw new IllegalArgumentException("Failed to parse: " + content.utf8ToString(), e); } + this.timeout = timeout; } - Request(String synonymsSetId, SynonymRule synonymRule) { + Request(String synonymsSetId, SynonymRule synonymRule, TimeValue timeout) { + Objects.requireNonNull(timeout); this.synonymsSetId = synonymsSetId; this.synonymRule = synonymRule; + this.timeout = timeout; } @Override @@ -96,6 +110,9 @@ public void writeTo(StreamOutput out) throws IOException { super.writeTo(out); out.writeString(synonymsSetId); synonymRule.writeTo(out); + if (out.getTransportVersion().onOrAfter(TransportVersions.SYNONYMS_UPDATE_TIMEOUT)) { + out.writeTimeValue(timeout); + } } public String synonymsSetId() { @@ -106,17 +123,23 @@ public SynonymRule synonymRule() { return synonymRule; } + public TimeValue timeout() { + return timeout; + } + @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Request request = (Request) o; - return Objects.equals(synonymsSetId, request.synonymsSetId) && Objects.equals(synonymRule, request.synonymRule); + return Objects.equals(timeout, request.timeout) + && Objects.equals(synonymsSetId, request.synonymsSetId) + && Objects.equals(synonymRule, request.synonymRule); } @Override public int hashCode() { - return Objects.hash(synonymsSetId, synonymRule); + return Objects.hash(synonymsSetId, synonymRule, timeout); } } } diff --git a/server/src/main/java/org/elasticsearch/action/synonyms/PutSynonymsAction.java b/server/src/main/java/org/elasticsearch/action/synonyms/PutSynonymsAction.java index 5e806ef2ef548..a91e004b346e3 100644 --- a/server/src/main/java/org/elasticsearch/action/synonyms/PutSynonymsAction.java +++ b/server/src/main/java/org/elasticsearch/action/synonyms/PutSynonymsAction.java @@ -9,6 +9,7 @@ package org.elasticsearch.action.synonyms; +import org.elasticsearch.TransportVersions; import org.elasticsearch.action.ActionRequest; import org.elasticsearch.action.ActionRequestValidationException; import org.elasticsearch.action.ActionType; @@ -18,6 +19,7 @@ import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.xcontent.XContentHelper; +import org.elasticsearch.core.TimeValue; import org.elasticsearch.synonyms.SynonymRule; import org.elasticsearch.synonyms.SynonymsManagementAPIService; import org.elasticsearch.xcontent.ConstructingObjectParser; @@ -31,6 +33,8 @@ import java.util.List; import java.util.Objects; +import static org.elasticsearch.action.synonyms.PutSynonymRuleAction.DEFAULT_TIMEOUT; + public class PutSynonymsAction extends ActionType { public static final PutSynonymsAction INSTANCE = new PutSynonymsAction(); @@ -43,6 +47,7 @@ public PutSynonymsAction() { public static class Request extends ActionRequest { private final String synonymsSetId; private final SynonymRule[] synonymRules; + private final TimeValue timeout; public static final ParseField SYNONYMS_SET_FIELD = new ParseField(SynonymsManagementAPIService.SYNONYMS_SET_FIELD); private static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>("synonyms_set", args -> { @@ -59,10 +64,16 @@ public Request(StreamInput in) throws IOException { super(in); this.synonymsSetId = in.readString(); this.synonymRules = in.readArray(SynonymRule::new, SynonymRule[]::new); + if (in.getTransportVersion().onOrAfter(TransportVersions.SYNONYMS_UPDATE_TIMEOUT)) { + this.timeout = in.readTimeValue(); + } else { + this.timeout = DEFAULT_TIMEOUT; + } } - public Request(String synonymsSetId, BytesReference content, XContentType contentType) throws IOException { + public Request(String synonymsSetId, TimeValue timeout, BytesReference content, XContentType contentType) throws IOException { this.synonymsSetId = synonymsSetId; + this.timeout = timeout; try (XContentParser parser = XContentHelper.createParser(XContentParserConfiguration.EMPTY, content, contentType)) { this.synonymRules = PARSER.apply(parser, null); } catch (Exception e) { @@ -70,9 +81,10 @@ public Request(String synonymsSetId, BytesReference content, XContentType conten } } - Request(String synonymsSetId, SynonymRule[] synonymRules) { + Request(String synonymsSetId, SynonymRule[] synonymRules, TimeValue timeout) { this.synonymsSetId = synonymsSetId; this.synonymRules = synonymRules; + this.timeout = timeout; } @Override @@ -95,12 +107,19 @@ public void writeTo(StreamOutput out) throws IOException { super.writeTo(out); out.writeString(synonymsSetId); out.writeArray(synonymRules); + if (out.getTransportVersion().onOrAfter(TransportVersions.SYNONYMS_UPDATE_TIMEOUT)) { + out.writeTimeValue(timeout); + } } public String synonymsSetId() { return synonymsSetId; } + public TimeValue timeout() { + return timeout; + } + public SynonymRule[] synonymRules() { return synonymRules; } @@ -110,12 +129,14 @@ public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Request request = (Request) o; - return Objects.equals(synonymsSetId, request.synonymsSetId) && Arrays.equals(synonymRules, request.synonymRules); + return Objects.equals(timeout, request.timeout) + && Objects.equals(synonymsSetId, request.synonymsSetId) + && Arrays.equals(synonymRules, request.synonymRules); } @Override public int hashCode() { - return Objects.hash(synonymsSetId, Arrays.hashCode(synonymRules)); + return Objects.hash(synonymsSetId, Arrays.hashCode(synonymRules), timeout); } } } diff --git a/server/src/main/java/org/elasticsearch/action/synonyms/TransportPutSynonymRuleAction.java b/server/src/main/java/org/elasticsearch/action/synonyms/TransportPutSynonymRuleAction.java index 33baa7b9081f2..c9e24dfd78e8e 100644 --- a/server/src/main/java/org/elasticsearch/action/synonyms/TransportPutSynonymRuleAction.java +++ b/server/src/main/java/org/elasticsearch/action/synonyms/TransportPutSynonymRuleAction.java @@ -41,8 +41,8 @@ protected void doExecute(Task task, PutSynonymRuleAction.Request request, Action synonymsManagementAPIService.putSynonymRule( request.synonymsSetId(), request.synonymRule(), - listener.map(updateResponse -> new SynonymUpdateResponse(updateResponse)) + request.timeout(), + listener.map(SynonymUpdateResponse::new) ); - } } diff --git a/server/src/main/java/org/elasticsearch/action/synonyms/TransportPutSynonymsAction.java b/server/src/main/java/org/elasticsearch/action/synonyms/TransportPutSynonymsAction.java index b0095ebee6a05..8a738b0ba65b8 100644 --- a/server/src/main/java/org/elasticsearch/action/synonyms/TransportPutSynonymsAction.java +++ b/server/src/main/java/org/elasticsearch/action/synonyms/TransportPutSynonymsAction.java @@ -35,6 +35,7 @@ protected void doExecute(Task task, PutSynonymsAction.Request request, ActionLis synonymsManagementAPIService.putSynonymsSet( request.synonymsSetId(), request.synonymRules(), + request.timeout(), listener.map(SynonymUpdateResponse::new) ); } diff --git a/server/src/main/java/org/elasticsearch/rest/action/synonyms/RestPutSynonymRuleAction.java b/server/src/main/java/org/elasticsearch/rest/action/synonyms/RestPutSynonymRuleAction.java index db912f9d81d76..0adae21c78ba0 100644 --- a/server/src/main/java/org/elasticsearch/rest/action/synonyms/RestPutSynonymRuleAction.java +++ b/server/src/main/java/org/elasticsearch/rest/action/synonyms/RestPutSynonymRuleAction.java @@ -14,13 +14,16 @@ import org.elasticsearch.client.internal.node.NodeClient; import org.elasticsearch.rest.BaseRestHandler; import org.elasticsearch.rest.RestRequest; +import org.elasticsearch.rest.RestUtils; import org.elasticsearch.rest.Scope; import org.elasticsearch.rest.ServerlessScope; import org.elasticsearch.rest.action.RestToXContentListener; import java.io.IOException; import java.util.List; +import java.util.Set; +import static org.elasticsearch.action.synonyms.PutSynonymRuleAction.DEFAULT_TIMEOUT; import static org.elasticsearch.rest.RestRequest.Method.PUT; @ServerlessScope(Scope.PUBLIC) @@ -41,6 +44,7 @@ protected RestChannelConsumer prepareRequest(RestRequest restRequest, NodeClient PutSynonymRuleAction.Request request = new PutSynonymRuleAction.Request( restRequest.param("synonymsSet"), restRequest.param("synonymRuleId"), + restRequest.paramAsTime(RestUtils.REST_TIMEOUT_PARAM, DEFAULT_TIMEOUT), restRequest.content(), restRequest.getXContentType() ); @@ -50,4 +54,9 @@ protected RestChannelConsumer prepareRequest(RestRequest restRequest, NodeClient new RestToXContentListener<>(channel, SynonymUpdateResponse::status, r -> null) ); } + + @Override + public Set supportedCapabilities() { + return SynonymPutCapabilities.CAPABILITIES; + } } diff --git a/server/src/main/java/org/elasticsearch/rest/action/synonyms/RestPutSynonymsAction.java b/server/src/main/java/org/elasticsearch/rest/action/synonyms/RestPutSynonymsAction.java index 3244f7960bdcc..e15c23987fd88 100644 --- a/server/src/main/java/org/elasticsearch/rest/action/synonyms/RestPutSynonymsAction.java +++ b/server/src/main/java/org/elasticsearch/rest/action/synonyms/RestPutSynonymsAction.java @@ -14,13 +14,16 @@ import org.elasticsearch.client.internal.node.NodeClient; import org.elasticsearch.rest.BaseRestHandler; import org.elasticsearch.rest.RestRequest; +import org.elasticsearch.rest.RestUtils; import org.elasticsearch.rest.Scope; import org.elasticsearch.rest.ServerlessScope; import org.elasticsearch.rest.action.RestToXContentListener; import java.io.IOException; import java.util.List; +import java.util.Set; +import static org.elasticsearch.action.synonyms.PutSynonymRuleAction.DEFAULT_TIMEOUT; import static org.elasticsearch.rest.RestRequest.Method.PUT; @ServerlessScope(Scope.PUBLIC) @@ -40,6 +43,7 @@ public List routes() { protected RestChannelConsumer prepareRequest(RestRequest restRequest, NodeClient client) throws IOException { PutSynonymsAction.Request request = new PutSynonymsAction.Request( restRequest.param("synonymsSet"), + restRequest.paramAsTime(RestUtils.REST_TIMEOUT_PARAM, DEFAULT_TIMEOUT), restRequest.content(), restRequest.getXContentType() ); @@ -49,4 +53,9 @@ protected RestChannelConsumer prepareRequest(RestRequest restRequest, NodeClient new RestToXContentListener<>(channel, SynonymUpdateResponse::status, r -> null) ); } + + @Override + public Set supportedCapabilities() { + return SynonymPutCapabilities.CAPABILITIES; + } } diff --git a/server/src/main/java/org/elasticsearch/rest/action/synonyms/SynonymPutCapabilities.java b/server/src/main/java/org/elasticsearch/rest/action/synonyms/SynonymPutCapabilities.java new file mode 100644 index 0000000000000..9d6d0698a8662 --- /dev/null +++ b/server/src/main/java/org/elasticsearch/rest/action/synonyms/SynonymPutCapabilities.java @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +package org.elasticsearch.rest.action.synonyms; + +import java.util.Set; + +/** + * A {@link Set} of "capabilities" supported by the {@link RestPutSynonymsAction} and {@link RestPutSynonymRuleAction}. + */ +public final class SynonymPutCapabilities { + + private SynonymPutCapabilities() { + throw new UnsupportedOperationException("This is a utility class and cannot be instantiated"); + } + + private static final String PUT_SYNONYMS_TIMEOUT = "put_synonyms_timeout"; + + public static final Set CAPABILITIES = Set.of(PUT_SYNONYMS_TIMEOUT); +} diff --git a/server/src/main/java/org/elasticsearch/synonyms/SynonymsManagementAPIService.java b/server/src/main/java/org/elasticsearch/synonyms/SynonymsManagementAPIService.java index a3d3af70de28a..57658a014e72e 100644 --- a/server/src/main/java/org/elasticsearch/synonyms/SynonymsManagementAPIService.java +++ b/server/src/main/java/org/elasticsearch/synonyms/SynonymsManagementAPIService.java @@ -19,6 +19,9 @@ import org.elasticsearch.action.DelegatingActionListener; import org.elasticsearch.action.DocWriteRequest; import org.elasticsearch.action.DocWriteResponse; +import org.elasticsearch.action.admin.cluster.health.ClusterHealthRequest; +import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse; +import org.elasticsearch.action.admin.cluster.health.TransportClusterHealthAction; import org.elasticsearch.action.admin.indices.analyze.ReloadAnalyzersRequest; import org.elasticsearch.action.admin.indices.analyze.ReloadAnalyzersResponse; import org.elasticsearch.action.admin.indices.analyze.TransportReloadAnalyzersAction; @@ -36,12 +39,14 @@ import org.elasticsearch.cluster.metadata.IndexMetadata; import org.elasticsearch.cluster.routing.Preference; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.core.TimeValue; import org.elasticsearch.index.IndexNotFoundException; import org.elasticsearch.index.query.BoolQueryBuilder; import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.index.reindex.BulkByScrollResponse; import org.elasticsearch.index.reindex.DeleteByQueryAction; import org.elasticsearch.index.reindex.DeleteByQueryRequest; +import org.elasticsearch.indices.IndexCreationException; import org.elasticsearch.indices.SystemIndexDescriptor; import org.elasticsearch.rest.RestStatus; import org.elasticsearch.search.aggregations.BucketOrder; @@ -72,7 +77,7 @@ public class SynonymsManagementAPIService { private static final String SYNONYMS_INDEX_NAME_PATTERN = ".synonyms-*"; private static final int SYNONYMS_INDEX_FORMAT = 2; - private static final String SYNONYMS_INDEX_CONCRETE_NAME = ".synonyms-" + SYNONYMS_INDEX_FORMAT; + static final String SYNONYMS_INDEX_CONCRETE_NAME = ".synonyms-" + SYNONYMS_INDEX_FORMAT; private static final String SYNONYMS_ALIAS_NAME = ".synonyms"; public static final String SYNONYMS_FEATURE_NAME = "synonyms"; // Stores the synonym set the rule belongs to @@ -301,7 +306,12 @@ private static void logUniqueFailureMessagesWithIndices(List listener) { + public void putSynonymsSet( + String synonymSetId, + SynonymRule[] synonymsSet, + TimeValue timeout, + ActionListener listener + ) { if (synonymsSet.length > maxSynonymsSets) { listener.onFailure( new IllegalArgumentException("The number of synonyms rules in a synonym set cannot exceed " + maxSynonymsSets) @@ -343,7 +353,13 @@ public void putSynonymsSet(String synonymSetId, SynonymRule[] synonymsSet, Actio ? UpdateSynonymsResultStatus.CREATED : UpdateSynonymsResultStatus.UPDATED; - reloadAnalyzers(synonymSetId, false, bulkInsertResponseListener, updateSynonymsResultStatus); + ensureSynonymsSearchableAndReloadAnalyzers( + synonymSetId, + false, + bulkInsertResponseListener, + updateSynonymsResultStatus, + timeout + ); }) ); })); @@ -366,7 +382,12 @@ void bulkUpdateSynonymsSet(String synonymSetId, SynonymRule[] synonymsSet, Actio bulkRequestBuilder.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE).execute(listener); } - public void putSynonymRule(String synonymsSetId, SynonymRule synonymRule, ActionListener listener) { + public void putSynonymRule( + String synonymsSetId, + SynonymRule synonymRule, + TimeValue timeout, + ActionListener listener + ) { checkSynonymSetExists(synonymsSetId, listener.delegateFailureAndWrap((l1, obj) -> { // Count synonym rules to check if we're at maximum BoolQueryBuilder queryFilter = QueryBuilders.boolQuery() @@ -388,14 +409,18 @@ public void putSynonymRule(String synonymsSetId, SynonymRule synonymRule, Action new IllegalArgumentException("The number of synonym rules in a synonyms set cannot exceed " + maxSynonymsSets) ); } else { - indexSynonymRule(synonymsSetId, synonymRule, searchListener); + indexSynonymRule(synonymsSetId, synonymRule, timeout, searchListener); } })); })); } - private void indexSynonymRule(String synonymsSetId, SynonymRule synonymRule, ActionListener listener) - throws IOException { + private void indexSynonymRule( + String synonymsSetId, + SynonymRule synonymRule, + TimeValue timeout, + ActionListener listener + ) throws IOException { IndexRequest indexRequest = createSynonymRuleIndexRequest(synonymsSetId, synonymRule).setRefreshPolicy( WriteRequest.RefreshPolicy.IMMEDIATE ); @@ -404,7 +429,7 @@ private void indexSynonymRule(String synonymsSetId, SynonymRule synonymRule, Act ? UpdateSynonymsResultStatus.CREATED : UpdateSynonymsResultStatus.UPDATED; - reloadAnalyzers(synonymsSetId, false, l2, updateStatus); + ensureSynonymsSearchableAndReloadAnalyzers(synonymsSetId, false, l2, updateStatus, timeout); })); } @@ -541,12 +566,51 @@ public void deleteSynonymsSet(String synonymSetId, ActionListener void ensureSynonymsSearchableAndReloadAnalyzers( + String synonymSetId, + boolean preview, + ActionListener listener, + UpdateSynonymsResultStatus synonymsOperationResult, + TimeValue timeout + ) { + // Ensure synonyms index is searchable if timeout is present + if (TimeValue.MINUS_ONE.equals(timeout) == false) { + checkSynonymsIndexHealth(timeout, listener.delegateFailure((l, response) -> { + if (response.isTimedOut()) { + l.onFailure( + new IndexCreationException( + "synonyms index [" + + SYNONYMS_ALIAS_NAME + + "] is not searchable. " + + response.getActiveShardsPercent() + + "% shards are active", + null + ) + ); + return; + } + + reloadAnalyzers(synonymSetId, preview, l, synonymsOperationResult); + })); + } else { + reloadAnalyzers(synonymSetId, preview, listener, synonymsOperationResult); + } + } + + // Allows checking failures in tests + void checkSynonymsIndexHealth(TimeValue timeout, ActionListener listener) { + ClusterHealthRequest healthRequest = new ClusterHealthRequest(timeout, SYNONYMS_ALIAS_NAME).waitForGreenStatus(); + + client.execute(TransportClusterHealthAction.TYPE, healthRequest, listener); + } + private void reloadAnalyzers( String synonymSetId, boolean preview, ActionListener listener, UpdateSynonymsResultStatus synonymsOperationResult ) { + // auto-reload all reloadable analyzers (currently only those that use updateable synonym or keyword_marker filters) ReloadAnalyzersRequest reloadAnalyzersRequest = new ReloadAnalyzersRequest(synonymSetId, preview, "*"); client.execute( @@ -562,7 +626,7 @@ private static String internalSynonymRuleId(String synonymsSetId, String synonym return synonymsSetId + SYNONYM_RULE_ID_SEPARATOR + synonymRuleId; } - static Settings settings() { + private static Settings settings() { return Settings.builder() .put(IndexMetadata.SETTING_NUMBER_OF_SHARDS, 1) .put(IndexMetadata.SETTING_AUTO_EXPAND_REPLICAS, "0-1") diff --git a/server/src/test/java/org/elasticsearch/action/synonyms/PutSynonymRuleActionRequestSerializingTests.java b/server/src/test/java/org/elasticsearch/action/synonyms/PutSynonymRuleActionRequestSerializingTests.java index 0f7a2b08fcdae..db7c056557168 100644 --- a/server/src/test/java/org/elasticsearch/action/synonyms/PutSynonymRuleActionRequestSerializingTests.java +++ b/server/src/test/java/org/elasticsearch/action/synonyms/PutSynonymRuleActionRequestSerializingTests.java @@ -22,7 +22,7 @@ protected Writeable.Reader instanceReader() { @Override protected PutSynonymRuleAction.Request createTestInstance() { - return new PutSynonymRuleAction.Request(randomIdentifier(), SynonymsTestUtils.randomSynonymRule()); + return new PutSynonymRuleAction.Request(randomIdentifier(), SynonymsTestUtils.randomSynonymRule(), randomTimeValue()); } @Override diff --git a/server/src/test/java/org/elasticsearch/action/synonyms/PutSynonymsActionRequestSerializingTests.java b/server/src/test/java/org/elasticsearch/action/synonyms/PutSynonymsActionRequestSerializingTests.java index d8e73c517f84b..9c67f32596c47 100644 --- a/server/src/test/java/org/elasticsearch/action/synonyms/PutSynonymsActionRequestSerializingTests.java +++ b/server/src/test/java/org/elasticsearch/action/synonyms/PutSynonymsActionRequestSerializingTests.java @@ -25,7 +25,7 @@ protected Writeable.Reader instanceReader() { @Override protected PutSynonymsAction.Request createTestInstance() { - return new PutSynonymsAction.Request(randomIdentifier(), randomSynonymsSet()); + return new PutSynonymsAction.Request(randomIdentifier(), randomSynonymsSet(), randomTimeValue()); } @Override diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/user/InternalUsers.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/user/InternalUsers.java index 961f363be7958..3a378a1e6d895 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/user/InternalUsers.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/user/InternalUsers.java @@ -283,7 +283,7 @@ public class InternalUsers { UsernamesField.SYNONYMS_USER_NAME, new RoleDescriptor( UsernamesField.SYNONYMS_ROLE_NAME, - null, + new String[] { "monitor" }, new RoleDescriptor.IndicesPrivileges[] { RoleDescriptor.IndicesPrivileges.builder().indices(".synonyms*").privileges("all").allowRestrictedIndices(true).build(), RoleDescriptor.IndicesPrivileges.builder().indices("*").privileges(TransportReloadAnalyzersAction.TYPE.name()).build(), },