*/
package org.sonar.server.es;
+import java.util.Arrays;
+import java.util.List;
import java.util.Map;
+import java.util.stream.Collectors;
import org.apache.commons.lang.StringUtils;
import org.elasticsearch.action.admin.indices.create.CreateIndexResponse;
import org.elasticsearch.action.admin.indices.mapping.put.PutMappingResponse;
import org.elasticsearch.cluster.health.ClusterHealthStatus;
import org.elasticsearch.common.settings.Settings;
import org.picocontainer.Startable;
+import org.sonar.api.config.Configuration;
import org.sonar.api.server.ServerSide;
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;
import org.sonar.server.es.IndexDefinitions.Index;
+import org.sonar.server.es.metadata.EsDbCompatibility;
import org.sonar.server.es.metadata.MetadataIndex;
import org.sonar.server.es.metadata.MetadataIndexDefinition;
public class IndexCreator implements Startable {
private static final Logger LOGGER = Loggers.get(IndexCreator.class);
+ private static final String PROPERY_DISABLE_CHECK = "sonar.search.disableDropOnDbMigration";
private final MetadataIndexDefinition metadataIndexDefinition;
private final MetadataIndex metadataIndex;
private final EsClient client;
private final IndexDefinitions definitions;
+ private final EsDbCompatibility esDbCompatibility;
+ private final Configuration configuration;
- public IndexCreator(EsClient client, IndexDefinitions definitions, MetadataIndexDefinition metadataIndexDefinition, MetadataIndex metadataIndex) {
+ public IndexCreator(EsClient client, IndexDefinitions definitions, MetadataIndexDefinition metadataIndexDefinition,
+ MetadataIndex metadataIndex, EsDbCompatibility esDbCompatibility, Configuration configuration) {
this.client = client;
this.definitions = definitions;
this.metadataIndexDefinition = metadataIndexDefinition;
this.metadataIndex = metadataIndex;
+ this.esDbCompatibility = esDbCompatibility;
+ this.configuration = configuration;
}
@Override
public void start() {
-
// create the "metadata" index first
if (!client.prepareIndicesExist(MetadataIndexDefinition.INDEX_TYPE_METADATA.getIndex()).get().isExists()) {
IndexDefinition.IndexDefinitionContext context = new IndexDefinition.IndexDefinitionContext();
createIndex(new Index(index), false);
}
+ checkDbCompatibility();
+
// create indices that do not exist or that have a new definition (different mapping, cluster enabled, ...)
for (Index index : definitions.getIndices().values()) {
boolean exists = client.prepareIndicesExist(index.getName()).get().isExists();
- if (exists && !index.getName().equals(MetadataIndexDefinition.INDEX_TYPE_METADATA.getIndex()) && needsToDeleteIndex(index)) {
- LOGGER.info(String.format("Delete index %s (settings changed)", index.getName()));
+ if (exists && !index.getName().equals(MetadataIndexDefinition.INDEX_TYPE_METADATA.getIndex()) && hasDefinitionChange(index)) {
+ LOGGER.info("Delete Elasticsearch index {} (structure changed)", index.getName());
deleteIndex(index.getName());
exists = false;
}
client.nativeClient().admin().indices().prepareDelete(indexName).get();
}
- private boolean needsToDeleteIndex(Index index) {
+ private boolean hasDefinitionChange(Index index) {
return metadataIndex.getHash(index.getName())
.map(hash -> {
String defHash = IndexDefinitionHash.of(index);
return !StringUtils.equals(hash, defHash);
}).orElse(true);
}
+
+ private void checkDbCompatibility() {
+ boolean disabledCheck = configuration.getBoolean(PROPERY_DISABLE_CHECK).orElse(false);
+ if (disabledCheck) {
+ LOGGER.warn("Automatic drop of search indices in turned off (see property " + PROPERY_DISABLE_CHECK + ")");
+ }
+
+ List<String> existingIndices = loadExistingIndicesExceptMetadata();
+ if (!disabledCheck && !existingIndices.isEmpty()) {
+ boolean delete = false;
+ if (!esDbCompatibility.hasSameDbVendor()) {
+ LOGGER.info("Delete Elasticsearch indices (DB vendor changed)");
+ delete = true;
+ } else if (!esDbCompatibility.hasSameDbSchemaVersion()) {
+ LOGGER.info("Delete Elasticsearch indices (DB schema changed)");
+ delete = true;
+ }
+ if (delete) {
+ existingIndices.forEach(this::deleteIndex);
+ }
+ }
+ esDbCompatibility.markAsCompatible();
+ }
+
+ private List<String> loadExistingIndicesExceptMetadata() {
+ return Arrays.stream(client.nativeClient().admin().indices().prepareGetIndex().get().getIndices())
+ .filter(index -> !MetadataIndexDefinition.INDEX_TYPE_METADATA.getIndex().equals(index))
+ .collect(Collectors.toList());
+ }
}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.es.metadata;
+
+/**
+ * Checks when Elasticsearch indices must be dropped because
+ * of changes in database
+ */
+public interface EsDbCompatibility {
+
+ /**
+ * Whether the effective DB vendor equals the vendor
+ * registered in Elasticsearch metadata.
+ * Return {@code false} if at least one of the values is absent
+ */
+ boolean hasSameDbVendor();
+
+ /**
+ * Whether the effective DB schema version equals the version
+ * registered in Elasticsearch metadata.
+ * Return {@code false} if at least one of the values is absent
+ */
+ boolean hasSameDbSchemaVersion();
+
+ /**
+ * Stores in Elasticsearch the metadata about database
+ */
+ void markAsCompatible();
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.es.metadata;
+
+import java.util.Objects;
+import java.util.Optional;
+import org.sonar.db.DbClient;
+import org.sonar.server.platform.db.migration.history.MigrationHistory;
+
+public class EsDbCompatibilityImpl implements EsDbCompatibility {
+
+ private final DbClient dbClient;
+ private final MetadataIndex metadataIndex;
+ private final MigrationHistory dbMigrationHistory;
+
+ public EsDbCompatibilityImpl(DbClient dbClient, MetadataIndex metadataIndex, MigrationHistory dbMigrationHistory) {
+ this.dbClient = dbClient;
+ this.metadataIndex = metadataIndex;
+ this.dbMigrationHistory = dbMigrationHistory;
+ }
+
+ @Override
+ public boolean hasSameDbVendor() {
+ Optional<String> registeredDbVendor = metadataIndex.getDbVendor();
+ return registeredDbVendor.isPresent() && registeredDbVendor.get().equals(getDbVendor());
+ }
+
+ @Override
+ public boolean hasSameDbSchemaVersion() {
+ Optional<Long> registeredVersion = metadataIndex.getDbSchemaVersion();
+ if (!registeredVersion.isPresent()) {
+ return false;
+ }
+ return getDbSchemaVersion()
+ .filter(effectiveVersion -> Objects.equals(registeredVersion.get(), effectiveVersion))
+ .isPresent();
+ }
+
+ @Override
+ public void markAsCompatible() {
+ metadataIndex.setDbMetadata(getDbVendor(), getDbSchemaVersion()
+ .orElseThrow(() -> new IllegalStateException("DB schema version is not present in database")));
+ }
+
+ private String getDbVendor() {
+ return dbClient.getDatabase().getDialect().getId();
+ }
+
+ private Optional<Long> getDbSchemaVersion() {
+ return dbMigrationHistory.getLastMigrationNumber();
+ }
+}
public class MetadataIndex {
+ private static final String DB_VENDOR_KEY = "dbVendor";
+ private static final String DB_SCHEMA_VERSION_KEY = "dbSchemaVersion";
+
private final EsClient esClient;
public MetadataIndex(EsClient esClient) {
}
public void setHash(String index, String hash) {
- setMetadata(hash, hashId(index));
+ setMetadata(hashId(index), hash);
}
private static String hashId(String index) {
}
public void setInitialized(IndexType indexType, boolean initialized) {
- setMetadata(String.valueOf(initialized), initializedId(indexType));
+ setMetadata(initializedId(indexType), String.valueOf(initialized));
}
private static String initializedId(IndexType indexType) {
return indexType.getIndex() + "." + indexType.getType() + ".initialized";
}
+ public Optional<String> getDbVendor() {
+ return getMetadata(DB_VENDOR_KEY);
+ }
+
+ public Optional<Long> getDbSchemaVersion() {
+ return getMetadata(DB_SCHEMA_VERSION_KEY).map(Long::parseLong);
+ }
+
+ public void setDbMetadata(String vendor, long schemaVersion) {
+ setMetadata(DB_VENDOR_KEY, vendor);
+ setMetadata(DB_SCHEMA_VERSION_KEY, String.valueOf(schemaVersion));
+ }
+
private Optional<String> getMetadata(String id) {
GetRequestBuilder request = esClient.prepareGet(MetadataIndexDefinition.INDEX_TYPE_METADATA, id)
.setStoredFields(MetadataIndexDefinition.FIELD_VALUE);
return Optional.empty();
}
- private void setMetadata(String hash, String id) {
+ private void setMetadata(String id, String value) {
esClient.prepareIndex(MetadataIndexDefinition.INDEX_TYPE_METADATA)
.setId(id)
- .setSource(MetadataIndexDefinition.FIELD_VALUE, hash)
+ .setSource(MetadataIndexDefinition.FIELD_VALUE, value)
.setRefreshPolicy(REFRESH_IMMEDIATE)
.get();
}
import org.sonar.server.es.IndexDefinitions;
import org.sonar.server.es.ProjectIndexersImpl;
import org.sonar.server.es.RecoveryIndexer;
+import org.sonar.server.es.metadata.EsDbCompatibilityImpl;
import org.sonar.server.es.metadata.MetadataIndex;
import org.sonar.server.es.metadata.MetadataIndexDefinition;
import org.sonar.server.event.NewAlerts;
addIfStartupLeader(
IndexCreator.class,
MetadataIndexDefinition.class,
- MetadataIndex.class);
+ MetadataIndex.class,
+ EsDbCompatibilityImpl.class);
add(
PluginDownloader.class,
container.addSingleton(IndexCreator.class);
container.addSingleton(MetadataIndex.class);
container.addSingleton(MetadataIndexDefinition.class);
+ container.addSingleton(TestEsDbCompatibility.class);
container.startComponents();
container.stopComponents();
client().close();
private void afterTest() throws Exception {
if (cluster != null) {
MetaData metaData = cluster.client().admin().cluster().prepareState().execute().actionGet().getState().getMetaData();
- assertEquals("test leaves persistent cluster metadata behind: " + metaData.persistentSettings().getAsMap(), metaData
- .persistentSettings().getAsMap().size(), 0);
- assertEquals("test leaves transient cluster metadata behind: " + metaData.transientSettings().getAsMap(), metaData
- .transientSettings().getAsMap().size(), 0);
+ assertEquals("test leaves persistent cluster metadata behind: " + metaData.persistentSettings().getAsMap(),
+ 0,
+ metaData.persistentSettings().getAsMap().size());
+ assertEquals("test leaves transient cluster metadata behind: " + metaData.transientSettings().getAsMap(), 0, metaData
+ .transientSettings().getAsMap().size());
ensureClusterSizeConsistency();
ensureClusterStateConsistency();
cluster.beforeIndexDeletion();
}
}
+ public void putDocuments(IndexType indexType, Map<String,Object>... docs) {
+ try {
+ BulkRequestBuilder bulk = cluster.client().prepareBulk()
+ .setRefreshPolicy(REFRESH_IMMEDIATE);
+ for (Map<String,Object> doc : docs) {
+ bulk.add(new IndexRequest(indexType.getIndex(), indexType.getType())
+ .source(doc));
+ }
+ BulkResponse bulkResponse = bulk.get();
+ if (bulkResponse.hasFailures()) {
+ throw new IllegalStateException(bulkResponse.buildFailureMessage());
+ }
+ } catch (Exception e) {
+ throw Throwables.propagate(e);
+ }
+ }
+
public long countDocuments(String index, String type) {
return countDocuments(new IndexType(index, type));
}
*/
package org.sonar.server.es;
+import com.google.common.collect.ImmutableMap;
import java.io.IOException;
import java.util.Map;
+import java.util.function.Consumer;
import javax.annotation.CheckForNull;
import org.elasticsearch.action.support.WriteRequest;
import org.elasticsearch.cluster.metadata.MappingMetaData;
import org.junit.Rule;
import org.junit.Test;
import org.sonar.api.config.internal.MapSettings;
+import org.sonar.api.utils.log.LogTester;
+import org.sonar.api.utils.log.LoggerLevel;
import org.sonar.server.es.metadata.MetadataIndex;
import org.sonar.server.es.metadata.MetadataIndexDefinition;
import static org.assertj.core.api.Assertions.assertThat;
import static org.elasticsearch.action.support.WriteRequest.RefreshPolicy.IMMEDIATE;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.sonar.server.es.NewIndex.SettingsConfiguration.newBuilder;
public class IndexCreatorTest {
- private static final NewIndex.SettingsConfiguration settingsConfiguration = newBuilder(new MapSettings().asConfig()).build();
+ private static final NewIndex.SettingsConfiguration SETTINGS_CONFIGURATION = newBuilder(new MapSettings().asConfig()).build();
+ private static final String LOG_DB_VENDOR_CHANGED = "Delete Elasticsearch indices (DB vendor changed)";
+ private static final String LOG_DB_SCHEMA_CHANGED = "Delete Elasticsearch indices (DB schema changed)";
+
+ @Rule
+ public LogTester logTester = new LogTester();
@Rule
public EsTester es = new EsTester();
private MetadataIndexDefinition metadataIndexDefinition = new MetadataIndexDefinition(new MapSettings().asConfig());
private MetadataIndex metadataIndex = new MetadataIndex(es.client());
+ private TestEsDbCompatibility esDbCompatibility = new TestEsDbCompatibility();
+ private MapSettings settings = new MapSettings();
@Test
public void create_index() throws Exception {
assertThat(mappings()).isEmpty();
- IndexDefinitions registry = new IndexDefinitions(new IndexDefinition[] {new FakeIndexDefinition()}, new MapSettings().asConfig());
- registry.start();
- IndexCreator creator = new IndexCreator(es.client(), registry, metadataIndexDefinition, metadataIndex);
- creator.start();
+ IndexCreator underTest = startNewCreator(new FakeIndexDefinition());
// check that index is created with related mapping
ImmutableOpenMap<String, ImmutableOpenMap<String, MappingMetaData>> mappings = mappings();
assertThat(field(mapping, "updatedAt").get("type")).isEqualTo("date");
// of course do not delete indices on stop
- creator.stop();
+ underTest.stop();
assertThat(mappings()).isNotEmpty();
}
@Test
public void mark_all_non_existing_index_types_as_uninitialized() throws Exception {
- MetadataIndex metadataIndexMock = mock(MetadataIndex.class);
- IndexDefinitions registry = new IndexDefinitions(new IndexDefinition[] {context -> {
-
- NewIndex i = context.create("i", settingsConfiguration);
+ startNewCreator(context -> {
+ NewIndex i = context.create("i", SETTINGS_CONFIGURATION);
i.createType("t1");
i.createType("t2");
- }}, new MapSettings().asConfig());
- registry.start();
- IndexCreator creator = new IndexCreator(es.client(), registry, metadataIndexDefinition, metadataIndexMock);
- creator.start();
+ });
- verify(metadataIndexMock).setHash(eq("i"), anyString());
- verify(metadataIndexMock).setInitialized(eq(new IndexType("i", "t1")), eq(false));
- verify(metadataIndexMock).setInitialized(eq(new IndexType("i", "t2")), eq(false));
- verifyNoMoreInteractions(metadataIndexMock);
+ assertThat(metadataIndex.getHash("i")).isNotEmpty();
+ assertThat(metadataIndex.getInitialized(new IndexType("i", "t1"))).isFalse();
+ assertThat(metadataIndex.getInitialized(new IndexType("i", "t2"))).isFalse();
}
@Test
public void recreate_index_on_definition_changes() throws Exception {
- assertThat(mappings()).isEmpty();
-
// v1
- IndexDefinitions registry = new IndexDefinitions(new IndexDefinition[] {new FakeIndexDefinition()}, new MapSettings().asConfig());
- registry.start();
- IndexCreator creator = new IndexCreator(es.client(), registry, metadataIndexDefinition, metadataIndex);
- creator.start();
- creator.stop();
+ startNewCreator(new FakeIndexDefinition());
IndexType fakeIndexType = new IndexType("fakes", "fake");
String id = "1";
assertThat(es.client().prepareGet(fakeIndexType, id).get().isExists()).isTrue();
// v2
- registry = new IndexDefinitions(new IndexDefinition[] {new FakeIndexDefinitionV2()}, new MapSettings().asConfig());
- registry.start();
- creator = new IndexCreator(es.client(), registry, metadataIndexDefinition, metadataIndex);
- creator.start();
+ startNewCreator(new FakeIndexDefinitionV2());
+
ImmutableOpenMap<String, ImmutableOpenMap<String, MappingMetaData>> mappings = mappings();
MappingMetaData mapping = mappings.get("fakes").get("fake");
assertThat(countMappingFields(mapping)).isEqualTo(3);
assertThat(field(mapping, "updatedAt").get("type")).isEqualTo("date");
assertThat(field(mapping, "newField").get("type")).isEqualTo("integer");
- creator.stop();
assertThat(es.client().prepareGet(fakeIndexType, id).get().isExists()).isFalse();
}
@Test
public void do_not_recreate_index_on_unchanged_definition() throws Exception {
- assertThat(mappings()).isEmpty();
-
// v1
- IndexDefinitions registry = new IndexDefinitions(new IndexDefinition[] {new FakeIndexDefinition()}, new MapSettings().asConfig());
- registry.start();
- IndexCreator creator = new IndexCreator(es.client(), registry, metadataIndexDefinition, metadataIndex);
- creator.start();
- creator.stop();
-
+ startNewCreator(new FakeIndexDefinition());
IndexType fakeIndexType = new IndexType("fakes", "fake");
String id = "1";
es.client().prepareIndex(fakeIndexType).setId(id).setSource(new FakeDoc().getFields()).setRefreshPolicy(IMMEDIATE).get();
assertThat(es.client().prepareGet(fakeIndexType, id).get().isExists()).isTrue();
// v1
- registry = new IndexDefinitions(new IndexDefinition[] {new FakeIndexDefinition()}, new MapSettings().asConfig());
- registry.start();
- creator = new IndexCreator(es.client(), registry, metadataIndexDefinition, metadataIndex);
- creator.start();
- creator.stop();
-
+ startNewCreator(new FakeIndexDefinition());
assertThat(es.client().prepareGet(fakeIndexType, id).get().isExists()).isTrue();
}
+ @Test
+ public void delete_existing_indices_if_db_vendor_changed() {
+ testDeleteOnDbChange(LOG_DB_VENDOR_CHANGED,
+ c -> c.setHasSameDbVendor(false));
+ }
+
+ @Test
+ public void delete_existing_indices_if_db_schema_changed() {
+ testDeleteOnDbChange(LOG_DB_SCHEMA_CHANGED,
+ c -> c.setHasSameDbSchemaVersion(false));
+ }
+
+ @Test
+ public void do_not_check_db_compatibility_on_fresh_es() {
+ // supposed to be ignored
+ esDbCompatibility.setHasSameDbVendor(false);
+ esDbCompatibility.setHasSameDbSchemaVersion(false);
+
+ startNewCreator(new FakeIndexDefinition());
+
+ assertThat(logTester.logs(LoggerLevel.INFO))
+ .doesNotContain(LOG_DB_VENDOR_CHANGED)
+ .doesNotContain(LOG_DB_SCHEMA_CHANGED)
+ .contains("Create type fakes/fake")
+ .contains("Create type metadatas/metadata");
+ }
+
+ @Test
+ public void do_not_check_db_compatibility_if_disabled_by_configuration() {
+ settings.setProperty("sonar.search.disableDropOnDbMigration", true);
+
+ // initial startup, automatic drop may be ignored because indices do not exist
+ startNewCreator(new FakeIndexDefinition());
+ logTester.clear();
+
+ // second startup, automatic drop can be disabled only by configuration
+
+ // supposed to be ignored
+ esDbCompatibility.setHasSameDbVendor(false);
+ esDbCompatibility.setHasSameDbSchemaVersion(false);
+
+ startNewCreator(new FakeIndexDefinition());
+
+ assertThat(logTester.logs(LoggerLevel.INFO))
+ .doesNotContain(LOG_DB_VENDOR_CHANGED)
+ .doesNotContain(LOG_DB_SCHEMA_CHANGED);
+ assertThat(logTester.logs(LoggerLevel.WARN)).contains("Automatic drop of search indices in turned off (see property sonar.search.disableDropOnDbMigration)");
+ }
+
+ private void testDeleteOnDbChange(String expectedLog, Consumer<TestEsDbCompatibility> afterFirstStart) {
+ startNewCreator(new FakeIndexDefinition());
+ assertThat(logTester.logs(LoggerLevel.INFO))
+ .doesNotContain(expectedLog)
+ .contains("Create type fakes/fake")
+ .contains("Create type metadatas/metadata");
+ putFakeDocument();
+ assertThat(es.countDocuments(FakeIndexDefinition.INDEX_TYPE)).isEqualTo(1);
+
+ afterFirstStart.accept(esDbCompatibility);
+ logTester.clear();
+ startNewCreator(new FakeIndexDefinition());
+
+ assertThat(logTester.logs(LoggerLevel.INFO))
+ .contains(expectedLog)
+ .contains("Create type fakes/fake")
+ // keep existing metadata
+ .doesNotContain("Create type metadatas/metadata");
+ // index has been dropped and re-created
+ assertThat(es.countDocuments(FakeIndexDefinition.INDEX_TYPE)).isEqualTo(0);
+ }
+
private ImmutableOpenMap<String, ImmutableOpenMap<String, MappingMetaData>> mappings() {
return es.client().nativeClient().admin().indices().prepareGetMappings().get().mappings();
}
@CheckForNull
+ @SuppressWarnings("unchecked")
private Map<String, Object> field(MappingMetaData mapping, String field) throws IOException {
- Map<String, Object> props = (Map) mapping.getSourceAsMap().get("properties");
+ Map<String, Object> props = (Map<String, Object>) mapping.getSourceAsMap().get("properties");
return (Map<String, Object>) props.get(field);
}
return ((Map) mapping.getSourceAsMap().get("properties")).size();
}
- public static class FakeIndexDefinition implements IndexDefinition {
+ private IndexCreator startNewCreator(IndexDefinition... definitions) {
+ IndexDefinitions defs = new IndexDefinitions(definitions, new MapSettings().asConfig());
+ defs.start();
+ IndexCreator creator = new IndexCreator(es.client(), defs, metadataIndexDefinition, metadataIndex, esDbCompatibility, settings.asConfig());
+ creator.start();
+ return creator;
+ }
+
+ private void putFakeDocument() {
+ es.putDocuments(FakeIndexDefinition.INDEX_TYPE, ImmutableMap.of("key", "foo"));
+ }
+
+ private static class FakeIndexDefinition implements IndexDefinition {
+ private static final IndexType INDEX_TYPE = new IndexType("fakes", "fake");
+
@Override
public void define(IndexDefinitionContext context) {
- NewIndex index = context.create("fakes", settingsConfiguration);
+ NewIndex index = context.create("fakes", SETTINGS_CONFIGURATION);
NewIndex.NewIndexType mapping = index.createType("fake");
mapping.keywordFieldBuilder("key").build();
mapping.createDateTimeField("updatedAt");
}
}
- public static class FakeIndexDefinitionV2 implements IndexDefinition {
+ private static class FakeIndexDefinitionV2 implements IndexDefinition {
@Override
public void define(IndexDefinitionContext context) {
- NewIndex index = context.create("fakes", settingsConfiguration);
+ NewIndex index = context.create("fakes", SETTINGS_CONFIGURATION);
NewIndex.NewIndexType mapping = index.createType("fake");
mapping.keywordFieldBuilder("key").build();
mapping.createDateTimeField("updatedAt");
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.es;
+
+import org.sonar.server.es.metadata.EsDbCompatibility;
+
+public class TestEsDbCompatibility implements EsDbCompatibility {
+
+ private boolean hasSameDbVendor = true;
+ private boolean hasSameDbSchemaVersion = true;
+
+ public TestEsDbCompatibility setHasSameDbVendor(boolean b) {
+ this.hasSameDbVendor = b;
+ return this;
+ }
+
+ public TestEsDbCompatibility setHasSameDbSchemaVersion(boolean b) {
+ this.hasSameDbSchemaVersion = b;
+ return this;
+ }
+
+ @Override
+ public boolean hasSameDbVendor() {
+ return hasSameDbVendor;
+ }
+
+ @Override
+ public boolean hasSameDbSchemaVersion() {
+ return hasSameDbSchemaVersion;
+ }
+
+ @Override
+ public void markAsCompatible() {
+
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.es.metadata;
+
+import java.util.Optional;
+import javax.annotation.Nullable;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.mockito.Mockito;
+import org.sonar.db.DbClient;
+import org.sonar.server.es.EsTester;
+import org.sonar.server.es.FakeIndexDefinition;
+import org.sonar.server.platform.db.migration.history.MigrationHistory;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class EsDbCompatibilityImplTest {
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+ @Rule
+ public EsTester es = new EsTester(new FakeIndexDefinition());
+ private DbClient dbClient = mock(DbClient.class, Mockito.RETURNS_DEEP_STUBS);
+ private MigrationHistory migrationHistory = mock(MigrationHistory.class);
+ private MetadataIndex metadataIndex = new MetadataIndex(es.client());
+ private EsDbCompatibilityImpl underTest = new EsDbCompatibilityImpl(dbClient, metadataIndex, migrationHistory);
+
+ @Test
+ public void hasSameDbVendor_is_true_if_values_match() {
+ prepareDb("mysql", 1_800L);
+ prepareEs("mysql", 2_000L);
+
+ assertThat(underTest.hasSameDbVendor()).isTrue();
+ }
+
+ @Test
+ public void hasSameDbVendor_is_false_if_values_dont_match() {
+ prepareDb("mysql", 1_800L);
+ prepareEs("postgres", 1_800L);
+
+ assertThat(underTest.hasSameDbVendor()).isFalse();
+ }
+
+ @Test
+ public void hasSameDbVendor_is_false_if_value_is_absent_from_es() {
+ prepareDb("mysql", 1_800L);
+
+ assertThat(underTest.hasSameDbVendor()).isFalse();
+ }
+
+ @Test
+ public void hasSameDbSchemaVersion_is_true_if_values_match() {
+ prepareDb("mysql", 1_800L);
+ prepareEs("postgres", 1_800L);
+
+ assertThat(underTest.hasSameDbSchemaVersion()).isTrue();
+ }
+
+ @Test
+ public void hasSameDbSchemaVersion_is_false_if_values_dont_match() {
+ prepareDb("mysql", 1_800L);
+ prepareEs("postgres", 2_000L);
+
+ assertThat(underTest.hasSameDbSchemaVersion()).isFalse();
+ }
+
+ @Test
+ public void hasSameDbSchemaVersion_is_false_if_value_is_absent_from_db() {
+ prepareDb("mysql", null);
+ prepareEs("postgres", 1_800L);
+
+ assertThat(underTest.hasSameDbSchemaVersion()).isFalse();
+ }
+
+ @Test
+ public void hasSameDbSchemaVersion_is_false_if_value_is_absent_from_es() {
+ prepareDb("mysql", 1_800L);
+
+ assertThat(underTest.hasSameDbSchemaVersion()).isFalse();
+ }
+
+ @Test
+ public void store_db_metadata_in_es() {
+ prepareDb("mysql", 1_800L);
+
+ underTest.markAsCompatible();
+
+ assertThat(metadataIndex.getDbVendor()).hasValue("mysql");
+ assertThat(metadataIndex.getDbSchemaVersion()).hasValue(1_800L);
+ }
+
+ @Test
+ public void store_updates_db_metadata_in_es() {
+ prepareEs("mysql", 1_800L);
+ prepareDb("postgres", 2_000L);
+
+ underTest.markAsCompatible();
+
+ assertThat(metadataIndex.getDbVendor()).hasValue("postgres");
+ assertThat(metadataIndex.getDbSchemaVersion()).hasValue(2_000L);
+ }
+
+ @Test
+ public void store_throws_ISE_if_metadata_cant_be_loaded_from_db() {
+ prepareDb("postgres", null);
+
+ expectedException.expect(IllegalStateException.class);
+ expectedException.expectMessage("DB schema version is not present in database");
+
+ underTest.markAsCompatible();
+ }
+
+ @Test
+ public void store_marks_es_as_compatible_with_db() {
+ prepareDb("postgres", 1_800L);
+
+ underTest.markAsCompatible();
+
+ assertThat(underTest.hasSameDbSchemaVersion()).isTrue();
+ assertThat(underTest.hasSameDbVendor()).isTrue();
+ }
+
+ private void prepareDb(String dbVendor, @Nullable Long dbSchemaVersion) {
+ when(dbClient.getDatabase().getDialect().getId()).thenReturn(dbVendor);
+ when(migrationHistory.getLastMigrationNumber()).thenReturn(Optional.ofNullable(dbSchemaVersion));
+ }
+
+ private void prepareEs(String dbVendor, long dbSchemaVersion) {
+ metadataIndex.setDbMetadata(dbVendor, dbSchemaVersion);
+ }
+}
public EsTester es = new EsTester(new FakeIndexDefinition());
private final MetadataIndex underTest = new MetadataIndex(es.client());
private final String index = randomAlphanumeric(20);
- private final String type = randomAlphanumeric(20);
-
@Test
public void type_should_be_not_initialized_by_default() throws Exception {
underTest.setHash(index, hash);
assertThat(underTest.getHash(index)).hasValue(hash);
}
-}
\ No newline at end of file
+
+ @Test
+ public void database_metadata_are_empty_if_absent_from_index() {
+ assertThat(underTest.getDbVendor()).isNotPresent();
+ assertThat(underTest.getDbSchemaVersion()).isNotPresent();
+ }
+
+ @Test
+ public void database_metadata_are_present_from_index() {
+ underTest.setDbMetadata("postgres", 1_800);
+
+ assertThat(underTest.getDbVendor()).hasValue("postgres");
+ assertThat(underTest.getDbSchemaVersion()).hasValue(1_800L);
+ }
+}