From 4d4b962fd100e8f85fdfccf744d6dbd83fd8eca4 Mon Sep 17 00:00:00 2001 From: Carlos Delgado <6339205+carlosdelest@users.noreply.github.com> Date: Tue, 22 Apr 2025 17:23:06 +0200 Subject: [PATCH] Synonyms API - Add refresh parameter to check synonyms index and reload analyzers (#126935) * Add timeout to SynonymsManagementAPIService put synonyms * Remove replicas 0, as that may impact serverless * Add timeout to put synonyms action, fix tests * Fix number of replicas * Remove cluster.health checks for synonyms index * Revert debugging * Add integration test for timeouts * Use TimeValue instead of an int * Add YAML tests and REST API specs * Fix a validation bug in put synonym rule * Spotless * Update docs/changelog/126314.yaml * Remove unnecessary checks for null * Fix equals / HashCode * Checks that timeout is passed correctly to the check health method * Use correctly the default timeout * spotless * Add monitor cluster privilege to internal synonyms user * [CI] Auto commit changes from spotless * Add capabilities to avoid failing on bwc tests * Replace timeout for refresh param * Add param to specs * Add YAML tests * Fix changelog * [CI] Auto commit changes from spotless * Use BWC serialization tests * Fix bug in test parser * Spotless * Delete doesn't need reloading :facepalm: removing it * Revert "Delete doesn't need reloading :facepalm: removing it" This reverts commit 9c8e0b62beaba2f2894756c959cf9df9e20c4b0f. * [CI] Auto commit changes from spotless * Fix refresh for delete synonym rule * Fix tests * Update docs/changelog/126935.yaml * Add reload analyzers test * reload_analyzers is not available on serverless --------- Co-authored-by: elasticsearchmachine --- docs/changelog/126314.yaml | 6 + docs/changelog/126935.yaml | 6 + .../api/synonyms.delete_synonym_rule.json | 6 + .../api/synonyms.put_synonym.json | 6 + .../api/synonyms.put_synonym_rule.json | 6 + .../test/synonyms/10_synonyms_put.yml | 36 ++- .../test/synonyms/110_synonyms_invalid.yml | 19 -- .../test/synonyms/20_synonyms_get.yml | 6 - .../test/synonyms/30_synonyms_delete.yml | 7 - .../test/synonyms/40_synonyms_sets_get.yml | 6 - .../test/synonyms/50_synonym_rule_put.yml | 71 ++++- .../test/synonyms/60_synonym_rule_get.yml | 6 - .../test/synonyms/70_synonym_rule_delete.yml | 27 +- .../test/synonyms/80_synonyms_from_index.yml | 6 - .../90_synonyms_reloading_for_synset.yml | 65 ++++- .../SynonymsManagementAPIServiceIT.java | 260 +++++++++++++----- .../org/elasticsearch/TransportVersions.java | 1 + .../synonyms/DeleteSynonymRuleAction.java | 18 +- .../action/synonyms/PutSynonymRuleAction.java | 28 +- .../action/synonyms/PutSynonymsAction.java | 26 +- .../synonyms/SynonymUpdateResponse.java | 41 ++- .../TransportDeleteSynonymRuleAction.java | 1 + .../TransportPutSynonymRuleAction.java | 4 +- .../synonyms/TransportPutSynonymsAction.java | 1 + .../synonyms/RestDeleteSynonymRuleAction.java | 9 +- .../synonyms/RestPutSynonymRuleAction.java | 7 + .../synonyms/RestPutSynonymsAction.java | 7 + .../action/synonyms/SynonymCapabilities.java | 26 ++ .../SynonymsManagementAPIService.java | 111 ++++++-- .../analyze/ReloadAnalyzersResponseTests.java | 4 +- ...onymRuleActionRequestSerializingTests.java | 2 +- ...onymRuleActionRequestSerializingTests.java | 2 +- ...SynonymsActionRequestSerializingTests.java | 2 +- ...SynonymUpdateResponseSerializingTests.java | 79 +++++- .../core/security/user/InternalUsers.java | 2 +- 35 files changed, 719 insertions(+), 191 deletions(-) create mode 100644 docs/changelog/126314.yaml create mode 100644 docs/changelog/126935.yaml create mode 100644 server/src/main/java/org/elasticsearch/rest/action/synonyms/SynonymCapabilities.java diff --git a/docs/changelog/126314.yaml b/docs/changelog/126314.yaml new file mode 100644 index 000000000000..8d16788f8b0c --- /dev/null +++ b/docs/changelog/126314.yaml @@ -0,0 +1,6 @@ +pr: 126314 +summary: Add refresh to synonyms put / delete APIs to wait for synonyms to be accessible and reload analyzers +area: Analysis +type: bug +issues: + - 121441 diff --git a/docs/changelog/126935.yaml b/docs/changelog/126935.yaml new file mode 100644 index 000000000000..7ef231ffa83c --- /dev/null +++ b/docs/changelog/126935.yaml @@ -0,0 +1,6 @@ +pr: 126935 +summary: Synonyms API - Add refresh parameter to check synonyms index and reload analyzers +area: Analysis +type: enhancement +issues: + - 121441 diff --git a/rest-api-spec/src/main/resources/rest-api-spec/api/synonyms.delete_synonym_rule.json b/rest-api-spec/src/main/resources/rest-api-spec/api/synonyms.delete_synonym_rule.json index 5a0de4ab94a7..e2285bbd6d4a 100644 --- a/rest-api-spec/src/main/resources/rest-api-spec/api/synonyms.delete_synonym_rule.json +++ b/rest-api-spec/src/main/resources/rest-api-spec/api/synonyms.delete_synonym_rule.json @@ -33,6 +33,12 @@ } } ] + }, + "params": { + "refresh": { + "type": "boolean", + "description": "Refresh search analyzers to update synonyms" + } } } } 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 e09bbb7e428a..3e700163e173 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": { + "refresh": { + "type": "boolean", + "description": "Refresh search analyzers to update synonyms" + } + }, "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 51503b581986..55edd65a8beb 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": { + "refresh": { + "type": "boolean", + "description": "Refresh search analyzers to update synonyms" + } + }, "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 4f36514f833d..69b04e92d1d5 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,31 @@ setup: - match: { count: 0 } - match: { synonyms_set: [] } +--- +"Refresh can be specified": + + - requires: + test_runner_features: [ capabilities ] + capabilities: + - method: PUT + path: /_synonyms/{rule_id} + capabilities: [ synonyms_refresh_param ] + reason: "synonyms refresh param capability needed" + + - do: + synonyms.put_synonym: + id: test-update-synonyms + refresh: false + body: + synonyms_set: + - synonyms: "hello, hi" + - synonyms: "bye => goodbye" + id: "test-id" + + - match: { result: "created" } + # Reload analyzers info is not included + - not_exists: reload_analyzers_details + --- "Validation fails tests": - do: @@ -116,3 +131,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 f4578290788f..131c7c0e826f 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 8ab97b3ec779..20f76e31e43a 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 ea27267c518a..51d2a9c5a693 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 e68c93077bde..9d6540c118ce 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 c8f463ba5cbe..3e618176eed2 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,68 @@ setup: rule_id: "test-id-0" body: synonyms: "i-phone, iphone" + +--- +"Refresh can be specified": + + - requires: + test_runner_features: [ capabilities ] + capabilities: + - method: PUT + path: /_synonyms/{rule_id} + capabilities: [ synonyms_refresh_param ] + reason: "synonyms refresh param capability needed" + + - do: + synonyms.put_synonym_rule: + refresh: false + set_id: "test-synonyms" + rule_id: "test-id-2" + body: + synonyms: "bye, goodbye, seeya" + + - match: { result: "updated" } + # Reload analyzers info is not included + - not_exists: reload_analyzers_details + +--- +"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 1754467e89b2..413337072e16 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 b24ed799bfd8..50d36b5c5db6 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: @@ -50,6 +44,27 @@ setup: - synonyms: "test => check" id: "test-id-3" +--- +"Refresh can be specified": + + - requires: + test_runner_features: [ capabilities ] + capabilities: + - method: PUT + path: /_synonyms/{rule_id} + capabilities: [ synonyms_refresh_param ] + reason: "synonyms refresh param capability needed" + + - do: + synonyms.delete_synonym_rule: + set_id: test-synonyms + rule_id: test-id-2 + refresh: false + + - match: { result: "deleted" } + # Reload analyzers info is not included + - not_exists: reload_analyzers_details + --- "Delete synonym rule - missing synonym set": - 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 a99635789659..15cb1000e2ad 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 02db799e52e5..50df0ad2c632 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: @@ -156,3 +150,62 @@ setup: my_field: query: salute - match: { hits.total.value: 0 } + +--- +"Reload analyzers with refresh false": + - requires: + test_runner_features: [ capabilities ] + capabilities: + - method: PUT + path: /_synonyms/{rule_id} + capabilities: [ synonyms_refresh_param ] + reason: "synonyms refresh param capability needed" + + - do: + synonyms.put_synonym: + id: synonyms_set1 + refresh: false + body: + synonyms_set: + - synonyms: "hello, salute" + + - match: { result: "updated" } + - not_exists: reload_analyzers_details + + # Confirm that the index analyzers are not reloaded for my_index1 + - do: + search: + index: my_index1 + body: + query: + match: + my_field: + query: salute + - match: { hits.total.value: 0 } + + # Reloading analyzers makes synonyms refresh + - do: + synonyms.put_synonym: + id: synonyms_set1 + refresh: true + body: + synonyms_set: + - synonyms: "hello, salute" + - synonyms: "ciao => goodbye" + + - match: { result: "updated" } + - gt: { reload_analyzers_details._shards.total: 0 } + - gt: { reload_analyzers_details._shards.successful: 0 } + - length: { reload_analyzers_details.reload_details: 1 } + + - do: + search: + index: my_index1 + body: + query: + match: + my_field: + query: salute + + - match: { hits.total.value: 1 } + diff --git a/server/src/internalClusterTest/java/org/elasticsearch/synonyms/SynonymsManagementAPIServiceIT.java b/server/src/internalClusterTest/java/org/elasticsearch/synonyms/SynonymsManagementAPIServiceIT.java index a7b98b463913..9b428321fa6a 100644 --- a/server/src/internalClusterTest/java/org/elasticsearch/synonyms/SynonymsManagementAPIServiceIT.java +++ b/server/src/internalClusterTest/java/org/elasticsearch/synonyms/SynonymsManagementAPIServiceIT.java @@ -11,8 +11,12 @@ package org.elasticsearch.synonyms; 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.index.mapper.extras.MapperExtrasPlugin; +import org.elasticsearch.indices.IndexCreationException; import org.elasticsearch.plugins.Plugin; import org.elasticsearch.reindex.ReindexPlugin; import org.elasticsearch.test.ESIntegTestCase; @@ -50,22 +54,29 @@ public class SynonymsManagementAPIServiceIT extends ESIntegTestCase { public void testCreateManySynonyms() throws Exception { CountDownLatch putLatch = new CountDownLatch(1); String synonymSetId = randomIdentifier(); + boolean refresh = randomBoolean(); 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), + refresh, + new ActionListener<>() { + @Override + public void onResponse(SynonymsManagementAPIService.SynonymsReloadResult synonymsReloadResult) { + assertEquals( + SynonymsManagementAPIService.UpdateSynonymsResultStatus.CREATED, + synonymsReloadResult.synonymsOperationResult() + ); + assertEquals(refresh, synonymsReloadResult.reloadAnalyzersResponse() != null); + 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 class SynonymsManagementAPIServiceIT extends ESIntegTestCase { synonymsManagementAPIService.putSynonymsSet( randomIdentifier(), randomSynonymsSet(maxSynonymSets + 1, maxSynonymSets * 2), + randomBoolean(), new ActionListener<>() { @Override public void onResponse(SynonymsManagementAPIService.SynonymsReloadResult synonymsReloadResult) { @@ -120,67 +132,73 @@ public class SynonymsManagementAPIServiceIT extends ESIntegTestCase { 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), + randomBoolean(), + 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], true, 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); - } - updatedRulesLatch.countDown(); + 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], + randomBoolean(), + 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(); + } + } + ); + } + 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 class SynonymsManagementAPIServiceIT extends ESIntegTestCase { CountDownLatch latch = new CountDownLatch(1); String synonymSetId = randomIdentifier(); SynonymRule[] synonymsSet = randomSynonymsSet(maxSynonymSets, maxSynonymSets); - synonymsManagementAPIService.putSynonymsSet(synonymSetId, synonymsSet, new ActionListener<>() { + synonymsManagementAPIService.putSynonymsSet(synonymSetId, synonymsSet, true, new ActionListener<>() { @Override public void onResponse(SynonymsManagementAPIService.SynonymsReloadResult synonymsReloadResult) { // Updating a rule fails synonymsManagementAPIService.putSynonymRule( synonymSetId, synonymsSet[randomIntBetween(0, maxSynonymSets - 1)], + randomBoolean(), new ActionListener<>() { @Override public void onResponse(SynonymsManagementAPIService.SynonymsReloadResult synonymsReloadResult) { @@ -224,11 +243,11 @@ public class SynonymsManagementAPIServiceIT extends ESIntegTestCase { String synonymSetId = randomIdentifier(); String ruleId = randomIdentifier(); SynonymRule[] synonymsSet = randomSynonymsSet(maxSynonymSets, maxSynonymSets); - synonymsManagementAPIService.putSynonymsSet(synonymSetId, synonymsSet, new ActionListener<>() { + synonymsManagementAPIService.putSynonymsSet(synonymSetId, synonymsSet, randomBoolean(), new ActionListener<>() { @Override public void onResponse(SynonymsManagementAPIService.SynonymsReloadResult synonymsReloadResult) { // Updating a rule fails - synonymsManagementAPIService.putSynonymRule(synonymSetId, randomSynonymRule(ruleId), new ActionListener<>() { + synonymsManagementAPIService.putSynonymRule(synonymSetId, randomSynonymRule(ruleId), true, new ActionListener<>() { @Override public void onResponse(SynonymsManagementAPIService.SynonymsReloadResult synonymsReloadResult) { fail("Should not create a new rule that does not exist when at max capacity"); @@ -289,4 +308,113 @@ public class SynonymsManagementAPIServiceIT extends ESIntegTestCase { readLatch.await(5, TimeUnit.SECONDS); verify(logger).warn(anyString(), eq(synonymSetId)); } + + public void testCreateSynonymsWithYellowSynonymsIndex() throws Exception { + + // Override health method check to simulate a timeout in checking the synonyms index + synonymsManagementAPIService = new SynonymsManagementAPIService(client()) { + @Override + void checkSynonymsIndexHealth(ActionListener listener) { + 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), true, new ActionListener<>() { + @Override + public void onResponse(SynonymsManagementAPIService.SynonymsReloadResult synonymsReloadResult) { + fail("Shouldn't have been able to create synonyms with refresh 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); + String synonymRuleId = randomIdentifier(); + synonymsManagementAPIService.putSynonymRule(synonymSetId, randomSynonymRule(synonymRuleId), true, new ActionListener<>() { + @Override + public void onResponse(SynonymsManagementAPIService.SynonymsReloadResult synonymsReloadResult) { + fail("Shouldn't have been able to update synonyms with refresh 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); + + // Delete a rule does not fail + CountDownLatch deleteLatch = new CountDownLatch(1); + synonymsManagementAPIService.deleteSynonymRule(synonymSetId, synonymRuleId, true, new ActionListener<>() { + @Override + public void onResponse(SynonymsManagementAPIService.SynonymsReloadResult synonymsReloadResult) { + updateLatch.countDown(); + } + + @Override + public void onFailure(Exception e) { + // Expected + fail("Should have been able to delete a synonym rule"); + } + }); + + deleteLatch.await(5, TimeUnit.SECONDS); + + // But, we can still create a synonyms set without refresh + CountDownLatch putNoRefreshLatch = new CountDownLatch(1); + synonymsManagementAPIService.putSynonymsSet(synonymSetId, randomSynonymsSet(1, 1), false, new ActionListener<>() { + @Override + public void onResponse(SynonymsManagementAPIService.SynonymsReloadResult synonymsReloadResult) { + // Expected + putLatch.countDown(); + } + + @Override + public void onFailure(Exception e) { + fail(e); + } + }); + + putNoRefreshLatch.await(5, TimeUnit.SECONDS); + + // Same for update + CountDownLatch putRuleNoRefreshLatch = new CountDownLatch(1); + synonymsManagementAPIService.putSynonymRule(synonymSetId, randomSynonymRule(synonymRuleId), false, new ActionListener<>() { + @Override + public void onResponse(SynonymsManagementAPIService.SynonymsReloadResult synonymsReloadResult) { + // Expected + putRuleNoRefreshLatch.countDown(); + } + + @Override + public void onFailure(Exception e) { + fail(e); + } + }); + + putRuleNoRefreshLatch.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 73081b3db0ee..e60fef23dc3f 100644 --- a/server/src/main/java/org/elasticsearch/TransportVersions.java +++ b/server/src/main/java/org/elasticsearch/TransportVersions.java @@ -232,6 +232,7 @@ public class TransportVersions { public static final TransportVersion BATCHED_QUERY_EXECUTION_DELAYABLE_WRITABLE = def(9_057_0_00); public static final TransportVersion SEARCH_INCREMENTAL_TOP_DOCS_NULL = def(9_058_0_00); public static final TransportVersion COMPRESS_DELAYABLE_WRITEABLE = def(9_059_0_00); + public static final TransportVersion SYNONYMS_REFRESH_PARAM = def(9_060_0_00); /* * STOP! READ THIS FIRST! No, really, diff --git a/server/src/main/java/org/elasticsearch/action/synonyms/DeleteSynonymRuleAction.java b/server/src/main/java/org/elasticsearch/action/synonyms/DeleteSynonymRuleAction.java index 9f52f1552e42..a485ed19ac74 100644 --- a/server/src/main/java/org/elasticsearch/action/synonyms/DeleteSynonymRuleAction.java +++ b/server/src/main/java/org/elasticsearch/action/synonyms/DeleteSynonymRuleAction.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; @@ -31,18 +32,24 @@ public class DeleteSynonymRuleAction extends ActionType { public static class Request extends ActionRequest { private final String synonymsSetId; - private final String synonymRuleId; + private final boolean refresh; public Request(StreamInput in) throws IOException { super(in); this.synonymsSetId = in.readString(); this.synonymRuleId = in.readString(); + if (in.getTransportVersion().onOrAfter(TransportVersions.SYNONYMS_REFRESH_PARAM)) { + this.refresh = in.readBoolean(); + } else { + this.refresh = true; + } } - public Request(String synonymsSetId, String synonymRuleId) { + public Request(String synonymsSetId, String synonymRuleId, boolean refresh) { this.synonymsSetId = synonymsSetId; this.synonymRuleId = synonymRuleId; + this.refresh = refresh; } @Override @@ -63,6 +70,9 @@ public class DeleteSynonymRuleAction extends ActionType { super.writeTo(out); out.writeString(synonymsSetId); out.writeString(synonymRuleId); + if (out.getTransportVersion().onOrAfter(TransportVersions.SYNONYMS_REFRESH_PARAM)) { + out.writeBoolean(refresh); + } } public String synonymsSetId() { @@ -73,6 +83,10 @@ public class DeleteSynonymRuleAction extends ActionType { return synonymRuleId; } + public boolean refresh() { + return refresh; + } + @Override public boolean equals(Object o) { if (this == o) return true; 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 6d0d24dbfee0..a504ecdc6f8a 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; @@ -40,8 +41,8 @@ public class PutSynonymRuleAction extends ActionType { public static class Request extends ActionRequest { private final String synonymsSetId; - private final SynonymRule synonymRule; + private final boolean refresh; public static final ParseField SYNONYMS_FIELD = new ParseField(SynonymsManagementAPIService.SYNONYMS_FIELD); private static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>( @@ -58,20 +59,28 @@ public class PutSynonymRuleAction extends ActionType { super(in); this.synonymsSetId = in.readString(); this.synonymRule = new SynonymRule(in); + if (in.getTransportVersion().onOrAfter(TransportVersions.SYNONYMS_REFRESH_PARAM)) { + this.refresh = in.readBoolean(); + } else { + this.refresh = true; + } } - public Request(String synonymsSetId, String synonymRuleId, BytesReference content, XContentType contentType) throws IOException { + public Request(String synonymsSetId, String synonymRuleId, boolean refresh, BytesReference content, XContentType contentType) + throws IOException { 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.refresh = refresh; } - Request(String synonymsSetId, SynonymRule synonymRule) { + Request(String synonymsSetId, SynonymRule synonymRule, boolean refresh) { this.synonymsSetId = synonymsSetId; this.synonymRule = synonymRule; + this.refresh = refresh; } @Override @@ -96,6 +105,9 @@ public class PutSynonymRuleAction extends ActionType { super.writeTo(out); out.writeString(synonymsSetId); synonymRule.writeTo(out); + if (out.getTransportVersion().onOrAfter(TransportVersions.SYNONYMS_REFRESH_PARAM)) { + out.writeBoolean(refresh); + } } public String synonymsSetId() { @@ -106,17 +118,23 @@ public class PutSynonymRuleAction extends ActionType { return synonymRule; } + public boolean refresh() { + return refresh; + } + @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(refresh, request.refresh) + && Objects.equals(synonymsSetId, request.synonymsSetId) + && Objects.equals(synonymRule, request.synonymRule); } @Override public int hashCode() { - return Objects.hash(synonymsSetId, synonymRule); + return Objects.hash(synonymsSetId, synonymRule, refresh); } } } 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 5e806ef2ef54..1b9b6b36cb2f 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; @@ -43,6 +44,7 @@ public class PutSynonymsAction extends ActionType { public static class Request extends ActionRequest { private final String synonymsSetId; private final SynonymRule[] synonymRules; + private final boolean refresh; 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 +61,16 @@ public class PutSynonymsAction extends ActionType { super(in); this.synonymsSetId = in.readString(); this.synonymRules = in.readArray(SynonymRule::new, SynonymRule[]::new); + if (in.getTransportVersion().onOrAfter(TransportVersions.SYNONYMS_REFRESH_PARAM)) { + this.refresh = in.readBoolean(); + } else { + this.refresh = false; + } } - public Request(String synonymsSetId, BytesReference content, XContentType contentType) throws IOException { + public Request(String synonymsSetId, boolean refresh, BytesReference content, XContentType contentType) throws IOException { this.synonymsSetId = synonymsSetId; + this.refresh = refresh; try (XContentParser parser = XContentHelper.createParser(XContentParserConfiguration.EMPTY, content, contentType)) { this.synonymRules = PARSER.apply(parser, null); } catch (Exception e) { @@ -70,9 +78,10 @@ public class PutSynonymsAction extends ActionType { } } - Request(String synonymsSetId, SynonymRule[] synonymRules) { + Request(String synonymsSetId, SynonymRule[] synonymRules, boolean refresh) { this.synonymsSetId = synonymsSetId; this.synonymRules = synonymRules; + this.refresh = refresh; } @Override @@ -95,12 +104,19 @@ public class PutSynonymsAction extends ActionType { super.writeTo(out); out.writeString(synonymsSetId); out.writeArray(synonymRules); + if (out.getTransportVersion().onOrAfter(TransportVersions.SYNONYMS_REFRESH_PARAM)) { + out.writeBoolean(refresh); + } } public String synonymsSetId() { return synonymsSetId; } + public boolean refresh() { + return refresh; + } + public SynonymRule[] synonymRules() { return synonymRules; } @@ -110,12 +126,14 @@ public class PutSynonymsAction extends ActionType { 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(refresh, request.refresh) + && 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), refresh); } } } diff --git a/server/src/main/java/org/elasticsearch/action/synonyms/SynonymUpdateResponse.java b/server/src/main/java/org/elasticsearch/action/synonyms/SynonymUpdateResponse.java index c1a9f2a80c47..5c55bd54a00d 100644 --- a/server/src/main/java/org/elasticsearch/action/synonyms/SynonymUpdateResponse.java +++ b/server/src/main/java/org/elasticsearch/action/synonyms/SynonymUpdateResponse.java @@ -9,6 +9,7 @@ package org.elasticsearch.action.synonyms; +import org.elasticsearch.TransportVersions; import org.elasticsearch.action.ActionResponse; import org.elasticsearch.action.admin.indices.analyze.ReloadAnalyzersResponse; import org.elasticsearch.common.io.stream.StreamInput; @@ -20,17 +21,27 @@ import org.elasticsearch.xcontent.ToXContentObject; import org.elasticsearch.xcontent.XContentBuilder; import java.io.IOException; +import java.util.List; import java.util.Locale; +import java.util.Map; import java.util.Objects; public class SynonymUpdateResponse extends ActionResponse implements ToXContentObject { + public static final String RESULT_FIELD = "result"; + public static final String RELOAD_ANALYZERS_DETAILS_FIELD = "reload_analyzers_details"; + static final ReloadAnalyzersResponse EMPTY_RELOAD_ANALYZER_RESPONSE = new ReloadAnalyzersResponse(0, 0, 0, List.of(), Map.of()); + private final UpdateSynonymsResultStatus updateStatus; private final ReloadAnalyzersResponse reloadAnalyzersResponse; public SynonymUpdateResponse(StreamInput in) throws IOException { this.updateStatus = in.readEnum(UpdateSynonymsResultStatus.class); - this.reloadAnalyzersResponse = new ReloadAnalyzersResponse(in); + if (in.getTransportVersion().onOrAfter(TransportVersions.SYNONYMS_REFRESH_PARAM)) { + this.reloadAnalyzersResponse = in.readOptionalWriteable(ReloadAnalyzersResponse::new); + } else { + this.reloadAnalyzersResponse = new ReloadAnalyzersResponse(in); + } } public SynonymUpdateResponse(SynonymsReloadResult synonymsReloadResult) { @@ -38,7 +49,6 @@ public class SynonymUpdateResponse extends ActionResponse implements ToXContentO UpdateSynonymsResultStatus updateStatus = synonymsReloadResult.synonymsOperationResult(); Objects.requireNonNull(updateStatus, "Update status must not be null"); ReloadAnalyzersResponse reloadResponse = synonymsReloadResult.reloadAnalyzersResponse(); - Objects.requireNonNull(reloadResponse, "Reload analyzers response must not be null"); this.updateStatus = updateStatus; this.reloadAnalyzersResponse = reloadResponse; @@ -48,9 +58,11 @@ public class SynonymUpdateResponse extends ActionResponse implements ToXContentO public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { builder.startObject(); { - builder.field("result", updateStatus.name().toLowerCase(Locale.ENGLISH)); - builder.field("reload_analyzers_details"); - reloadAnalyzersResponse.toXContent(builder, params); + builder.field(RESULT_FIELD, updateStatus.name().toLowerCase(Locale.ENGLISH)); + if (reloadAnalyzersResponse != null) { + builder.field(RELOAD_ANALYZERS_DETAILS_FIELD); + reloadAnalyzersResponse.toXContent(builder, params); + } } builder.endObject(); @@ -60,7 +72,16 @@ public class SynonymUpdateResponse extends ActionResponse implements ToXContentO @Override public void writeTo(StreamOutput out) throws IOException { out.writeEnum(updateStatus); - reloadAnalyzersResponse.writeTo(out); + if (out.getTransportVersion().onOrAfter(TransportVersions.SYNONYMS_REFRESH_PARAM)) { + out.writeOptionalWriteable(reloadAnalyzersResponse); + } else { + if (reloadAnalyzersResponse == null) { + // Nulls will be written as empty reload analyzer responses for older versions + EMPTY_RELOAD_ANALYZER_RESPONSE.writeTo(out); + } else { + reloadAnalyzersResponse.writeTo(out); + } + } } public RestStatus status() { @@ -70,6 +91,14 @@ public class SynonymUpdateResponse extends ActionResponse implements ToXContentO }; } + UpdateSynonymsResultStatus updateStatus() { + return updateStatus; + } + + ReloadAnalyzersResponse reloadAnalyzersResponse() { + return reloadAnalyzersResponse; + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/server/src/main/java/org/elasticsearch/action/synonyms/TransportDeleteSynonymRuleAction.java b/server/src/main/java/org/elasticsearch/action/synonyms/TransportDeleteSynonymRuleAction.java index 0d55774489c4..9fed5e5716c5 100644 --- a/server/src/main/java/org/elasticsearch/action/synonyms/TransportDeleteSynonymRuleAction.java +++ b/server/src/main/java/org/elasticsearch/action/synonyms/TransportDeleteSynonymRuleAction.java @@ -41,6 +41,7 @@ public class TransportDeleteSynonymRuleAction extends HandledTransportAction new SynonymUpdateResponse(updateResponse)) + request.refresh(), + 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 b0095ebee6a0..290f62e7bad5 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 @@ public class TransportPutSynonymsAction extends HandledTransportAction client.execute(DeleteSynonymRuleAction.INSTANCE, request, new RestToXContentListener<>(channel)); } + + @Override + public Set supportedCapabilities() { + return SynonymCapabilities.CAPABILITIES; + } } 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 db912f9d81d7..0af0dad3696e 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 @@ -20,6 +20,7 @@ import org.elasticsearch.rest.action.RestToXContentListener; import java.io.IOException; import java.util.List; +import java.util.Set; import static org.elasticsearch.rest.RestRequest.Method.PUT; @@ -41,6 +42,7 @@ public class RestPutSynonymRuleAction extends BaseRestHandler { PutSynonymRuleAction.Request request = new PutSynonymRuleAction.Request( restRequest.param("synonymsSet"), restRequest.param("synonymRuleId"), + restRequest.paramAsBoolean("refresh", true), restRequest.content(), restRequest.getXContentType() ); @@ -50,4 +52,9 @@ public class RestPutSynonymRuleAction extends BaseRestHandler { new RestToXContentListener<>(channel, SynonymUpdateResponse::status, r -> null) ); } + + @Override + public Set supportedCapabilities() { + return SynonymCapabilities.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 3244f7960bdc..46a63a12d0e0 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 @@ -20,6 +20,7 @@ import org.elasticsearch.rest.action.RestToXContentListener; import java.io.IOException; import java.util.List; +import java.util.Set; import static org.elasticsearch.rest.RestRequest.Method.PUT; @@ -40,6 +41,7 @@ public class RestPutSynonymsAction extends BaseRestHandler { protected RestChannelConsumer prepareRequest(RestRequest restRequest, NodeClient client) throws IOException { PutSynonymsAction.Request request = new PutSynonymsAction.Request( restRequest.param("synonymsSet"), + restRequest.paramAsBoolean("refresh", true), restRequest.content(), restRequest.getXContentType() ); @@ -49,4 +51,9 @@ public class RestPutSynonymsAction extends BaseRestHandler { new RestToXContentListener<>(channel, SynonymUpdateResponse::status, r -> null) ); } + + @Override + public Set supportedCapabilities() { + return SynonymCapabilities.CAPABILITIES; + } } diff --git a/server/src/main/java/org/elasticsearch/rest/action/synonyms/SynonymCapabilities.java b/server/src/main/java/org/elasticsearch/rest/action/synonyms/SynonymCapabilities.java new file mode 100644 index 000000000000..670065739270 --- /dev/null +++ b/server/src/main/java/org/elasticsearch/rest/action/synonyms/SynonymCapabilities.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 SynonymCapabilities { + + private SynonymCapabilities() { + throw new UnsupportedOperationException("This is a utility class and cannot be instantiated"); + } + + private static final String SYNONYMS_REFRESH_PARAM = "synonyms_refresh_param"; + + public static final Set CAPABILITIES = Set.of(SYNONYMS_REFRESH_PARAM); +} diff --git a/server/src/main/java/org/elasticsearch/synonyms/SynonymsManagementAPIService.java b/server/src/main/java/org/elasticsearch/synonyms/SynonymsManagementAPIService.java index a3d3af70de28..70b020eb66ab 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.ActionListener; 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.client.internal.OriginSettingClient; 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 @@ -90,6 +95,7 @@ public class SynonymsManagementAPIService { private static final String SYNONYM_RULE_ID_FIELD = SynonymRule.ID_FIELD.getPreferredName(); private static final String SYNONYM_SETS_AGG_NAME = "synonym_sets_aggr"; private static final int SYNONYMS_INDEX_MAPPINGS_VERSION = 1; + public static final int INDEX_SEARCHABLE_TIMEOUT_SECONDS = 30; private final int maxSynonymsSets; // Package private for testing @@ -301,7 +307,12 @@ public class SynonymsManagementAPIService { }); } - public void putSynonymsSet(String synonymSetId, SynonymRule[] synonymsSet, ActionListener listener) { + public void putSynonymsSet( + String synonymSetId, + SynonymRule[] synonymsSet, + boolean refresh, + ActionListener listener + ) { if (synonymsSet.length > maxSynonymsSets) { listener.onFailure( new IllegalArgumentException("The number of synonyms rules in a synonym set cannot exceed " + maxSynonymsSets) @@ -343,7 +354,13 @@ public class SynonymsManagementAPIService { ? UpdateSynonymsResultStatus.CREATED : UpdateSynonymsResultStatus.UPDATED; - reloadAnalyzers(synonymSetId, false, bulkInsertResponseListener, updateSynonymsResultStatus); + checkIndexSearchableAndReloadAnalyzers( + synonymSetId, + refresh, + false, + updateSynonymsResultStatus, + bulkInsertResponseListener + ); }) ); })); @@ -366,7 +383,12 @@ public class SynonymsManagementAPIService { bulkRequestBuilder.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE).execute(listener); } - public void putSynonymRule(String synonymsSetId, SynonymRule synonymRule, ActionListener listener) { + public void putSynonymRule( + String synonymsSetId, + SynonymRule synonymRule, + boolean refresh, + ActionListener listener + ) { checkSynonymSetExists(synonymsSetId, listener.delegateFailureAndWrap((l1, obj) -> { // Count synonym rules to check if we're at maximum BoolQueryBuilder queryFilter = QueryBuilders.boolQuery() @@ -388,14 +410,18 @@ public class SynonymsManagementAPIService { new IllegalArgumentException("The number of synonym rules in a synonyms set cannot exceed " + maxSynonymsSets) ); } else { - indexSynonymRule(synonymsSetId, synonymRule, searchListener); + indexSynonymRule(synonymsSetId, synonymRule, refresh, searchListener); } })); })); } - private void indexSynonymRule(String synonymsSetId, SynonymRule synonymRule, ActionListener listener) - throws IOException { + private void indexSynonymRule( + String synonymsSetId, + SynonymRule synonymRule, + boolean refresh, + ActionListener listener + ) throws IOException { IndexRequest indexRequest = createSynonymRuleIndexRequest(synonymsSetId, synonymRule).setRefreshPolicy( WriteRequest.RefreshPolicy.IMMEDIATE ); @@ -404,7 +430,7 @@ public class SynonymsManagementAPIService { ? UpdateSynonymsResultStatus.CREATED : UpdateSynonymsResultStatus.UPDATED; - reloadAnalyzers(synonymsSetId, false, l2, updateStatus); + checkIndexSearchableAndReloadAnalyzers(synonymsSetId, refresh, false, updateStatus, l2); })); } @@ -424,7 +450,12 @@ public class SynonymsManagementAPIService { ); } - public void deleteSynonymRule(String synonymsSetId, String synonymRuleId, ActionListener listener) { + public void deleteSynonymRule( + String synonymsSetId, + String synonymRuleId, + boolean refresh, + ActionListener listener + ) { client.prepareDelete(SYNONYMS_ALIAS_NAME, internalSynonymRuleId(synonymsSetId, synonymRuleId)) .setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE) .execute(new DelegatingIndexNotFoundActionListener<>(synonymsSetId, listener, (l, deleteResponse) -> { @@ -443,7 +474,11 @@ public class SynonymsManagementAPIService { return; } - reloadAnalyzers(synonymsSetId, false, listener, UpdateSynonymsResultStatus.DELETED); + if (refresh) { + reloadAnalyzers(synonymsSetId, false, UpdateSynonymsResultStatus.DELETED, listener); + } else { + listener.onResponse(new SynonymsReloadResult(UpdateSynonymsResultStatus.DELETED, null)); + } })); } @@ -501,7 +536,7 @@ public class SynonymsManagementAPIService { public void deleteSynonymsSet(String synonymSetId, ActionListener listener) { // Previews reloading the resource to understand its usage on indices - reloadAnalyzers(synonymSetId, true, listener.delegateFailure((reloadListener, reloadResult) -> { + reloadAnalyzers(synonymSetId, true, null, listener.delegateFailure((reloadListener, reloadResult) -> { Map reloadDetails = reloadResult.reloadAnalyzersResponse.getReloadDetails(); if (reloadDetails.isEmpty() == false) { Set indices = reloadDetails.entrySet() @@ -538,14 +573,48 @@ public class SynonymsManagementAPIService { deleteObjectsListener.onResponse(AcknowledgedResponse.of(true)); })); - }), null); + })); } - private void reloadAnalyzers( + private void checkIndexSearchableAndReloadAnalyzers( + String synonymSetId, + boolean refresh, + boolean preview, + UpdateSynonymsResultStatus synonymsOperationResult, + ActionListener listener + ) { + + if (refresh == false) { + // If not refreshing, we don't need to reload analyzers + listener.onResponse(new SynonymsReloadResult(synonymsOperationResult, null)); + return; + } + + // Check synonyms index is searchable before reloading, to ensure analyzers are able to load the changed information + checkSynonymsIndexHealth(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, synonymsOperationResult, listener); + })); + } + + private void reloadAnalyzers( String synonymSetId, boolean preview, - ActionListener listener, - UpdateSynonymsResultStatus synonymsOperationResult + UpdateSynonymsResultStatus synonymsOperationResult, + ActionListener listener ) { // auto-reload all reloadable analyzers (currently only those that use updateable synonym or keyword_marker filters) ReloadAnalyzersRequest reloadAnalyzersRequest = new ReloadAnalyzersRequest(synonymSetId, preview, "*"); @@ -556,13 +625,23 @@ public class SynonymsManagementAPIService { ); } + // Allows checking failures in tests + void checkSynonymsIndexHealth(ActionListener listener) { + ClusterHealthRequest healthRequest = new ClusterHealthRequest( + TimeValue.timeValueSeconds(INDEX_SEARCHABLE_TIMEOUT_SECONDS), + SYNONYMS_ALIAS_NAME + ).waitForGreenStatus(); + + client.execute(TransportClusterHealthAction.TYPE, healthRequest, listener); + } + // Retrieves the internal synonym rule ID to store it in the index. As the same synonym rule ID // can be used in different synonym sets, we prefix the ID with the synonym set to avoid collisions private static String internalSynonymRuleId(String synonymsSetId, String synonymRuleId) { 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/admin/indices/analyze/ReloadAnalyzersResponseTests.java b/server/src/test/java/org/elasticsearch/action/admin/indices/analyze/ReloadAnalyzersResponseTests.java index 57e56e090761..036d7273900a 100644 --- a/server/src/test/java/org/elasticsearch/action/admin/indices/analyze/ReloadAnalyzersResponseTests.java +++ b/server/src/test/java/org/elasticsearch/action/admin/indices/analyze/ReloadAnalyzersResponseTests.java @@ -31,7 +31,7 @@ import static org.elasticsearch.xcontent.ConstructingObjectParser.constructorArg public class ReloadAnalyzersResponseTests extends AbstractBroadcastResponseTestCase { @SuppressWarnings({ "unchecked" }) - private static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>( + public static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>( "reload_analyzer", true, arg -> { @@ -67,8 +67,8 @@ public class ReloadAnalyzersResponseTests extends AbstractBroadcastResponseTestC declareBroadcastFields(PARSER); PARSER.declareObjectArray(constructorArg(), ENTRY_PARSER, ReloadAnalyzersResponse.RELOAD_DETAILS_FIELD); ENTRY_PARSER.declareString(constructorArg(), ReloadAnalyzersResponse.INDEX_FIELD); - ENTRY_PARSER.declareStringArray(constructorArg(), ReloadAnalyzersResponse.RELOADED_ANALYZERS_FIELD); ENTRY_PARSER.declareStringArray(constructorArg(), ReloadAnalyzersResponse.RELOADED_NODE_IDS_FIELD); + ENTRY_PARSER.declareStringArray(constructorArg(), ReloadAnalyzersResponse.RELOADED_ANALYZERS_FIELD); } @Override diff --git a/server/src/test/java/org/elasticsearch/action/synonyms/DeleteSynonymRuleActionRequestSerializingTests.java b/server/src/test/java/org/elasticsearch/action/synonyms/DeleteSynonymRuleActionRequestSerializingTests.java index 68adf4034ce5..97ca4e22869e 100644 --- a/server/src/test/java/org/elasticsearch/action/synonyms/DeleteSynonymRuleActionRequestSerializingTests.java +++ b/server/src/test/java/org/elasticsearch/action/synonyms/DeleteSynonymRuleActionRequestSerializingTests.java @@ -23,7 +23,7 @@ public class DeleteSynonymRuleActionRequestSerializingTests extends AbstractWire @Override protected DeleteSynonymRuleAction.Request createTestInstance() { - return new DeleteSynonymRuleAction.Request(randomIdentifier(), randomIdentifier()); + return new DeleteSynonymRuleAction.Request(randomIdentifier(), randomIdentifier(), randomBoolean()); } @Override 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 0f7a2b08fcda..1c5a15d30347 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 @@ public class PutSynonymRuleActionRequestSerializingTests extends AbstractWireSer @Override protected PutSynonymRuleAction.Request createTestInstance() { - return new PutSynonymRuleAction.Request(randomIdentifier(), SynonymsTestUtils.randomSynonymRule()); + return new PutSynonymRuleAction.Request(randomIdentifier(), SynonymsTestUtils.randomSynonymRule(), randomBoolean()); } @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 d8e73c517f84..ced6e18e9cca 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 @@ public class PutSynonymsActionRequestSerializingTests extends AbstractWireSerial @Override protected PutSynonymsAction.Request createTestInstance() { - return new PutSynonymsAction.Request(randomIdentifier(), randomSynonymsSet()); + return new PutSynonymsAction.Request(randomIdentifier(), randomSynonymsSet(), randomBoolean()); } @Override diff --git a/server/src/test/java/org/elasticsearch/action/synonyms/SynonymUpdateResponseSerializingTests.java b/server/src/test/java/org/elasticsearch/action/synonyms/SynonymUpdateResponseSerializingTests.java index 3f7ca5037e4a..c1a5b85783e5 100644 --- a/server/src/test/java/org/elasticsearch/action/synonyms/SynonymUpdateResponseSerializingTests.java +++ b/server/src/test/java/org/elasticsearch/action/synonyms/SynonymUpdateResponseSerializingTests.java @@ -9,23 +9,54 @@ package org.elasticsearch.action.synonyms; +import org.elasticsearch.TransportVersion; +import org.elasticsearch.TransportVersions; import org.elasticsearch.action.admin.indices.analyze.ReloadAnalyzersResponse; import org.elasticsearch.action.admin.indices.analyze.ReloadAnalyzersResponseTests; import org.elasticsearch.common.Strings; import org.elasticsearch.common.io.stream.Writeable; import org.elasticsearch.common.xcontent.XContentHelper; +import org.elasticsearch.synonyms.SynonymsManagementAPIService; import org.elasticsearch.synonyms.SynonymsManagementAPIService.SynonymsReloadResult; -import org.elasticsearch.test.AbstractWireSerializingTestCase; +import org.elasticsearch.test.AbstractBWCSerializationTestCase; +import org.elasticsearch.xcontent.ConstructingObjectParser; +import org.elasticsearch.xcontent.ParseField; +import org.elasticsearch.xcontent.XContentParser; import java.io.IOException; import java.util.Collections; +import java.util.Locale; import java.util.Map; +import static org.elasticsearch.action.synonyms.SynonymUpdateResponse.EMPTY_RELOAD_ANALYZER_RESPONSE; import static org.elasticsearch.synonyms.SynonymsManagementAPIService.UpdateSynonymsResultStatus.CREATED; import static org.elasticsearch.synonyms.SynonymsManagementAPIService.UpdateSynonymsResultStatus.DELETED; import static org.elasticsearch.synonyms.SynonymsManagementAPIService.UpdateSynonymsResultStatus.UPDATED; +import static org.elasticsearch.xcontent.ConstructingObjectParser.constructorArg; +import static org.elasticsearch.xcontent.ConstructingObjectParser.optionalConstructorArg; -public class SynonymUpdateResponseSerializingTests extends AbstractWireSerializingTestCase { +public class SynonymUpdateResponseSerializingTests extends AbstractBWCSerializationTestCase { + + private static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>( + "synonyms_update_response", + true, + arg -> { + SynonymsManagementAPIService.UpdateSynonymsResultStatus status = SynonymsManagementAPIService.UpdateSynonymsResultStatus + .valueOf(((String) arg[0]).toUpperCase(Locale.ROOT)); + ReloadAnalyzersResponse reloadAnalyzersResponse = (ReloadAnalyzersResponse) arg[1]; + return new SynonymUpdateResponse(new SynonymsReloadResult(status, reloadAnalyzersResponse)); + } + ); + + static { + PARSER.declareString(constructorArg(), new ParseField(SynonymUpdateResponse.RESULT_FIELD)); + PARSER.declareObjectOrNull( + optionalConstructorArg(), + (p, c) -> ReloadAnalyzersResponseTests.PARSER.parse(p, null), + null, + new ParseField(SynonymUpdateResponse.RELOAD_ANALYZERS_DETAILS_FIELD) + ); + } @Override protected Writeable.Reader instanceReader() { @@ -34,9 +65,22 @@ public class SynonymUpdateResponseSerializingTests extends AbstractWireSerializi @Override protected SynonymUpdateResponse createTestInstance() { - Map reloadedIndicesDetails = ReloadAnalyzersResponseTests - .createRandomReloadDetails(); - ReloadAnalyzersResponse reloadAnalyzersResponse = new ReloadAnalyzersResponse(10, 10, 0, null, reloadedIndicesDetails); + return createTestInstance(randomBoolean()); + } + + private SynonymUpdateResponse createTestInstance(boolean includeReloadInfo) { + ReloadAnalyzersResponse reloadAnalyzersResponse = null; + if (includeReloadInfo) { + Map reloadedIndicesDetails = ReloadAnalyzersResponseTests + .createRandomReloadDetails(); + reloadAnalyzersResponse = new ReloadAnalyzersResponse( + randomIntBetween(0, 10), + randomIntBetween(0, 10), + randomIntBetween(0, 5), + null, + reloadedIndicesDetails + ); + } return new SynonymUpdateResponse(new SynonymsReloadResult(randomFrom(CREATED, UPDATED, DELETED), reloadAnalyzersResponse)); } @@ -45,6 +89,17 @@ public class SynonymUpdateResponseSerializingTests extends AbstractWireSerializi return randomValueOtherThan(instance, this::createTestInstance); } + @Override + protected SynonymUpdateResponse mutateInstanceForVersion(SynonymUpdateResponse instance, TransportVersion version) { + + if (version.before(TransportVersions.SYNONYMS_REFRESH_PARAM) && instance.reloadAnalyzersResponse() == null) { + // Nulls will be written as empty reload analyzer responses for older versions + return new SynonymUpdateResponse(new SynonymsReloadResult(instance.updateStatus(), EMPTY_RELOAD_ANALYZER_RESPONSE)); + } + + return instance; + } + public void testToXContent() throws IOException { Map reloadedIndicesNodes = Collections.singletonMap( "index", @@ -73,4 +128,18 @@ public class SynonymUpdateResponseSerializingTests extends AbstractWireSerializi } }"""), output); } + + public void testToXContentWithNoReloadResult() throws IOException { + SynonymUpdateResponse response = new SynonymUpdateResponse(new SynonymsReloadResult(CREATED, null)); + String output = Strings.toString(response); + assertEquals(XContentHelper.stripWhitespace(""" + { + "result": "created" + }"""), output); + } + + @Override + protected SynonymUpdateResponse doParseInstance(XContentParser parser) throws IOException { + return PARSER.apply(parser, null); + } } 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 52bc6eb140e7..a2610d639236 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 @@ -282,7 +282,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(), },