Skip to content

Commit

Permalink
HSEARCH-3934 Support custom settings on index update
Browse files Browse the repository at this point in the history
  • Loading branch information
fax4ever authored and yrodiere committed Jan 22, 2021
1 parent f35434e commit 39591c2
Show file tree
Hide file tree
Showing 3 changed files with 275 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@
*/
package org.hibernate.search.backend.elasticsearch.lowlevel.index.settings.impl;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

import org.hibernate.search.backend.elasticsearch.gson.impl.SerializeExtraProperties;

Expand All @@ -16,7 +20,6 @@

/**
* Settings for an Elasticsearch index.
*
*/
@JsonAdapter(IndexSettingsJsonAdapterFactory.class)
public class IndexSettings {
Expand All @@ -26,6 +29,14 @@ public class IndexSettings {
@SerializeExtraProperties
private Map<String, JsonElement> extraAttributes;

public IndexSettings() {
}

public IndexSettings(Analysis analysis, Map<String, JsonElement> extraAttributes) {
this.analysis = analysis;
this.extraAttributes = extraAttributes;
}

public Analysis getAnalysis() {
return analysis;
}
Expand Down Expand Up @@ -69,4 +80,40 @@ public void merge(IndexSettings overridingIndexSettings) {

extraAttributes = overridingIndexSettings.extraAttributes;
}

/**
* Remove all entries from {@link #extraAttributes} that are present
* with the exact same values on {@code extraAttributesToRemove} parameter.
*
* @param extraAttributesToRemove Other index settings extra attributes
*/
public IndexSettings diff(Map<String, JsonElement> extraAttributesToRemove) {
if ( extraAttributes == null || extraAttributes.isEmpty() ) {
// nothing to do
return this;
}

Set<String> keysToRemove = new HashSet<>();
for ( Map.Entry<String, JsonElement> extraAttribute : extraAttributes.entrySet() ) {
String key = extraAttribute.getKey();
if ( !extraAttributesToRemove.containsKey( key ) ) {
continue;
}

if ( Objects.equals( extraAttributesToRemove.get( key ), extraAttribute.getValue() ) ) {
keysToRemove.add( key );
}
}

if ( keysToRemove.isEmpty() ) {
// nothing to do
return this;
}

Map<String, JsonElement> newExtraAttributes = new HashMap<>( extraAttributes );
for ( String key : keysToRemove ) {
newExtraAttributes.remove( key );
}
return new IndexSettings( analysis, newExtraAttributes );
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,10 @@ public CompletableFuture<?> migrate(URLEncodedString indexName,
settingsMigration = aliasMigration;
}
else {
settingsMigration = aliasMigration
.thenCompose( ignored -> doMigrateSettings( indexName, expectedIndexMetadata.getSettings() ) );
settingsMigration = aliasMigration.thenCompose(
ignored -> doMigrateSettings( indexName, expectedIndexMetadata.getSettings(),
actualIndexMetadata.getSettings()
) );
}

/*
Expand Down Expand Up @@ -92,10 +94,15 @@ private CompletableFuture<?> doMigrateAliases(URLEncodedString indexName, Map<St
return schemaAccessor.updateAliases( indexName, aliases );
}

private CompletableFuture<?> doMigrateSettings(URLEncodedString indexName, IndexSettings settings) {
private CompletableFuture<?> doMigrateSettings(URLEncodedString indexName, IndexSettings expectedSettings,
IndexSettings actualSettings) {

// remove all already present settings extra attributes
IndexSettings indexSettings = expectedSettings.diff( actualSettings.getExtraAttributes() );

return schemaAccessor.closeIndex( indexName )
.thenCompose( ignored -> Futures.whenCompleteExecute(
schemaAccessor.updateSettings( indexName, settings ),
schemaAccessor.updateSettings( indexName, indexSettings ),
// Re-open the index after the settings have been successfully updated
// ... or if the settings update fails.
() -> schemaAccessor.openIndex( indexName )
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
/*
* Hibernate Search, full-text search for your domain model
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.search.integrationtest.backend.elasticsearch.schema.management;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.hibernate.search.util.impl.test.JsonHelper.assertJsonEquals;

import org.hibernate.search.backend.elasticsearch.analysis.ElasticsearchAnalysisConfigurationContext;
import org.hibernate.search.backend.elasticsearch.analysis.ElasticsearchAnalysisConfigurer;
import org.hibernate.search.backend.elasticsearch.cfg.ElasticsearchIndexSettings;
import org.hibernate.search.integrationtest.backend.elasticsearch.testsupport.categories.RequiresIndexOpenClose;
import org.hibernate.search.integrationtest.backend.tck.testsupport.util.rule.SearchSetupHelper;
import org.hibernate.search.util.common.SearchException;
import org.hibernate.search.util.common.impl.Futures;
import org.hibernate.search.util.impl.integrationtest.backend.elasticsearch.rule.TestElasticsearchClient;
import org.hibernate.search.util.impl.integrationtest.mapper.stub.StubMappedIndex;
import org.hibernate.search.util.impl.integrationtest.mapper.stub.StubMappingSchemaManagementStrategy;
import org.hibernate.search.util.impl.test.annotation.TestForIssue;

import org.junit.Rule;
import org.junit.Test;
import org.junit.experimental.categories.Category;

/**
* Tests related to index custom settings when updating indexes.
*/
@Category(RequiresIndexOpenClose.class)
@TestForIssue(jiraKey = "HSEARCH-3934")
public class ElasticsearchIndexSchemaManagerUpdateCustomSettingsIT {

@Rule
public final SearchSetupHelper setupHelper = new SearchSetupHelper();

@Rule
public TestElasticsearchClient elasticsearchClient = new TestElasticsearchClient();

private final StubMappedIndex index = StubMappedIndex.withoutFields();

@Test
public void nothingToDo() {
elasticsearchClient.index( index.name() ).deleteAndCreate( "index",
" { " +
" 'number_of_shards': '3', " +
" 'number_of_replicas': '3', " +
" 'analysis': { " +
" 'analyzer': { " +
" 'my_standard-english': { " +
" 'type': 'standard', " +
" 'stopwords': '_english_' " +
" }, " +
" 'my_analyzer_ngram': { " +
" 'type': 'custom', " +
" 'tokenizer': 'my_analyzer_ngram_tokenizer' " +
" } " +
" }, " +
" 'tokenizer': { " +
" 'my_analyzer_ngram_tokenizer': { " +
" 'type': 'ngram', " +
" 'min_gram': '5', " +
" 'max_gram': '6' " +
" } " +
" } " +
" } " +
" } "
);

setupAndUpdateIndex();

assertJsonEquals(
" { " +
" 'analyzer': { " +
" 'my_standard-english': { " +
" 'type': 'standard', " +
" 'stopwords': '_english_' " +
" }, " +
" 'my_analyzer_ngram': { " +
" 'type': 'custom', " +
" 'tokenizer': 'my_analyzer_ngram_tokenizer' " +
" } " +
" }, " +
" 'tokenizer': { " +
" 'my_analyzer_ngram_tokenizer': { " +
" 'type': 'ngram', " +
" 'min_gram': '5', " +
" 'max_gram': '6' " +
" } " +
" } " +
" } ",
elasticsearchClient.index( index.name() ).settings( "index.analysis" ).get()
);

assertThat( elasticsearchClient.index( index.name() ).settings( "index.number_of_shards" ).get() )
.isEqualTo( "\"3\"" );
assertThat( elasticsearchClient.index( index.name() ).settings( "index.number_of_replicas" ).get() )
.isEqualTo( "\"3\"" );
}

@Test
public void change_analysis() {
elasticsearchClient.index( index.name() ).deleteAndCreate( "index",
" { " +
" 'number_of_shards': '3', " +
" 'number_of_replicas': '3', " +
" 'analysis': { " +
" 'analyzer': { " +
" 'my_standard-english': { " +
" 'type': 'standard', " +
" 'stopwords': '_english_' " +
" }, " +
" 'my_analyzer_ngram': { " +
" 'type': 'custom', " +
" 'tokenizer': 'my_analyzer_ngram_tokenizer' " +
" } " +
" }, " +
" 'tokenizer': { " +
" 'my_analyzer_ngram_tokenizer': { " +
" 'type': 'ngram', " +
" 'min_gram': '2', " +
" 'max_gram': '3' " +
" } " +
" } " +
" } " +
" } "
);

setupAndUpdateIndex();

assertJsonEquals(
" { " +
" 'analyzer': { " +
" 'my_standard-english': { " +
" 'type': 'standard', " +
" 'stopwords': '_english_' " +
" }, " +
" 'my_analyzer_ngram': { " +
" 'type': 'custom', " +
" 'tokenizer': 'my_analyzer_ngram_tokenizer' " +
" } " +
" }, " +
" 'tokenizer': { " +
" 'my_analyzer_ngram_tokenizer': { " +
" 'type': 'ngram', " +
" 'min_gram': '5', " +
" 'max_gram': '6' " +
" } " +
" } " +
" } ",
elasticsearchClient.index( index.name() ).settings( "index.analysis" ).get()
);

assertThat( elasticsearchClient.index( index.name() ).settings( "index.number_of_shards" ).get() )
.isEqualTo( "\"3\"" );
assertThat( elasticsearchClient.index( index.name() ).settings( "index.number_of_replicas" ).get() )
.isEqualTo( "\"3\"" );
}

@Test
public void change_numberOfShards() {
elasticsearchClient.index( index.name() ).deleteAndCreate( "index",
" { " +
" 'number_of_shards': '7', " +
" 'number_of_replicas': '3', " +
" 'analysis': { " +
" 'analyzer': { " +
" 'my_standard-english': { " +
" 'type': 'standard', " +
" 'stopwords': '_english_' " +
" }, " +
" 'my_analyzer_ngram': { " +
" 'type': 'custom', " +
" 'tokenizer': 'my_analyzer_ngram_tokenizer' " +
" } " +
" }, " +
" 'tokenizer': { " +
" 'my_analyzer_ngram_tokenizer': { " +
" 'type': 'ngram', " +
" 'min_gram': '2', " +
" 'max_gram': '3' " +
" } " +
" } " +
" } " +
" } "
);

assertThatThrownBy( () -> setupAndUpdateIndex() )
.isInstanceOf( SearchException.class )
.hasMessageContainingAll(
"Unable to update settings", "index.number_of_shards" );
}

private void setupAndUpdateIndex() {
setupHelper.start()
.withSchemaManagement( StubMappingSchemaManagementStrategy.DROP_ON_SHUTDOWN_ONLY )
.withBackendProperty(
// use an empty analysis configurer,
// so that we have only the custom settings definitions
ElasticsearchIndexSettings.ANALYSIS_CONFIGURER,
(ElasticsearchAnalysisConfigurer) (ElasticsearchAnalysisConfigurationContext context) -> {
// No-op
}
)
.withIndexProperty( index.name(), ElasticsearchIndexSettings.SCHEMA_MANAGEMENT_SETTINGS_FILE,
"custom-index-settings/valid.json"
)
.withIndex( index )
.setup();

Futures.unwrappedExceptionJoin( index.schemaManager().createOrUpdate() );
}

}

0 comments on commit 39591c2

Please sign in to comment.