@@ -20,10 +20,10 @@ | |||
package org.sonar.server.platform.db.migration.es; | |||
public interface ElasticsearchClient { | |||
public interface MigrationEsClient { | |||
/** | |||
* This method is reentrant and does not fail if indexName or otherIndexNames does not exist | |||
* This method is re-entrant and does not fail if indexName or otherIndexNames do not exist | |||
*/ | |||
void deleteIndexes(String... otherIndexNames); | |||
void deleteIndexes(String name, String... otherNames); | |||
} |
@@ -22,6 +22,7 @@ package org.sonar.server.platform.db.migration.version.v72; | |||
import java.sql.SQLException; | |||
import org.sonar.db.Database; | |||
import org.sonar.server.platform.db.migration.es.MigrationEsClient; | |||
import org.sonar.server.platform.db.migration.sql.RenameColumnsBuilder; | |||
import org.sonar.server.platform.db.migration.step.DdlChange; | |||
@@ -29,8 +30,11 @@ import static org.sonar.server.platform.db.migration.def.VarcharColumnDef.newVar | |||
public class RenameSubmitterLoginToSubmitterUuidOnTableCeActivity extends DdlChange { | |||
public RenameSubmitterLoginToSubmitterUuidOnTableCeActivity(Database db) { | |||
private final MigrationEsClient esClient; | |||
public RenameSubmitterLoginToSubmitterUuidOnTableCeActivity(Database db, MigrationEsClient esClient) { | |||
super(db); | |||
this.esClient = esClient; | |||
} | |||
@Override | |||
@@ -44,6 +48,6 @@ public class RenameSubmitterLoginToSubmitterUuidOnTableCeActivity extends DdlCha | |||
.build()) | |||
.build()); | |||
context.deleteIndexes("users"); | |||
esClient.deleteIndexes("users"); | |||
} | |||
} |
@@ -1,51 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 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.platform.db.migration.step; | |||
import java.sql.SQLException; | |||
import org.junit.Test; | |||
import org.mockito.ArgumentCaptor; | |||
import org.sonar.server.platform.db.migration.es.ElasticsearchClient; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.mockito.Mockito.mock; | |||
import static org.mockito.Mockito.times; | |||
import static org.mockito.Mockito.verify; | |||
public class ElasticsearchChangeTest { | |||
private ElasticsearchClient client = mock(ElasticsearchClient.class); | |||
@Test | |||
public void deleteIndice_call() throws SQLException { | |||
ArgumentCaptor<String> indices = ArgumentCaptor.forClass(String.class); | |||
new ElasticsearchChange(client) { | |||
@Override | |||
protected void execute(Context context) throws SQLException { | |||
context.deleteIndice("a", "b", "c"); | |||
} | |||
}.execute(); | |||
verify(client, times(1)).deleteIndice(indices.capture()); | |||
assertThat(indices.getAllValues()).containsExactly("a", "b", "c"); | |||
} | |||
} |
@@ -25,8 +25,11 @@ import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.junit.rules.ExpectedException; | |||
import org.sonar.db.CoreDbTester; | |||
import org.sonar.server.platform.db.migration.es.MigrationEsClient; | |||
import static java.sql.Types.VARCHAR; | |||
import static org.mockito.Mockito.mock; | |||
import static org.mockito.Mockito.verify; | |||
public class RenameSubmitterLoginToSubmitterUuidOnTableCeActivityTest { | |||
@@ -36,7 +39,8 @@ public class RenameSubmitterLoginToSubmitterUuidOnTableCeActivityTest { | |||
@Rule | |||
public ExpectedException expectedException = ExpectedException.none(); | |||
private RenameSubmitterLoginToSubmitterUuidOnTableCeActivity underTest = new RenameSubmitterLoginToSubmitterUuidOnTableCeActivity(db.database()); | |||
private MigrationEsClient esClient = mock(MigrationEsClient.class); | |||
private RenameSubmitterLoginToSubmitterUuidOnTableCeActivity underTest = new RenameSubmitterLoginToSubmitterUuidOnTableCeActivity(db.database(), esClient); | |||
@Test | |||
public void rename_column() throws SQLException { | |||
@@ -44,6 +48,8 @@ public class RenameSubmitterLoginToSubmitterUuidOnTableCeActivityTest { | |||
db.assertColumnDefinition("ce_activity", "submitter_uuid", VARCHAR, 255, true); | |||
db.assertColumnDoesNotExist("ce_activity", "submitter_login"); | |||
verify(esClient).deleteIndexes("users"); | |||
} | |||
public void migration_is_not_reentrant() throws SQLException { |
@@ -0,0 +1,46 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 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 com.google.common.collect.Sets; | |||
import java.util.Arrays; | |||
import java.util.Set; | |||
import java.util.stream.Stream; | |||
import org.elasticsearch.action.admin.indices.mapping.get.GetMappingsResponse; | |||
import org.sonar.server.platform.db.migration.es.MigrationEsClient; | |||
public class MigrationEsClientImpl implements MigrationEsClient { | |||
private final EsClient client; | |||
public MigrationEsClientImpl(EsClient client) { | |||
this.client = client; | |||
} | |||
@Override | |||
public void deleteIndexes(String name, String... otherNames) { | |||
GetMappingsResponse mappings = client.nativeClient().admin().indices().prepareGetMappings("_all").get(); | |||
Set<String> existingIndices = Sets.newHashSet(mappings.mappings().keysIt()); | |||
Stream.concat(Stream.of(name), Arrays.stream(otherNames)) | |||
.distinct() | |||
.filter(existingIndices::contains) | |||
.forEach(index -> client.nativeClient().admin().indices().prepareDelete(index).get()); | |||
} | |||
} |
@@ -1,57 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2018 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 java.util.Arrays; | |||
import org.elasticsearch.action.admin.indices.mapping.get.GetMappingsResponse; | |||
import org.elasticsearch.client.Client; | |||
import org.sonar.server.platform.db.migration.es.ElasticsearchClient; | |||
import static java.util.Objects.requireNonNull; | |||
public class SimpleEsClientImpl implements ElasticsearchClient { | |||
private final Client nativeClient; | |||
public SimpleEsClientImpl(Client nativeClient) { | |||
this.nativeClient = requireNonNull(nativeClient); | |||
} | |||
/** | |||
* This method is reentrant and does not fail if indexName or otherIndexNames does not exist | |||
*/ | |||
public void deleteIndexes(String... indexNames) { | |||
if (indexNames.length == 0) { | |||
return; | |||
} | |||
GetMappingsResponse getMappingsResponse = nativeClient.admin().indices().prepareGetMappings("_all").get(); | |||
String[] allIndexes = getMappingsResponse.mappings().keys().toArray(String.class); | |||
String[] intersection = intersection(indexNames, allIndexes); | |||
nativeClient.admin().indices().prepareDelete(intersection).get(); | |||
} | |||
private String[] intersection(String[] a, String[] b) { | |||
return Arrays.stream(a) | |||
.distinct() | |||
.filter(x -> Arrays.stream(b).anyMatch(y -> y != null && y.equals(x))) | |||
.toArray(String[]::new); | |||
} | |||
} |
@@ -26,6 +26,7 @@ import org.sonar.core.platform.PluginLoader; | |||
import org.sonar.core.extension.CoreExtensionRepositoryImpl; | |||
import org.sonar.core.extension.CoreExtensionsLoader; | |||
import org.sonar.server.l18n.ServerI18n; | |||
import org.sonar.server.es.MigrationEsClientImpl; | |||
import org.sonar.server.platform.DatabaseServerCompatibility; | |||
import org.sonar.server.platform.DefaultServerUpgradeStatus; | |||
import org.sonar.server.platform.StartupMetadataProvider; | |||
@@ -56,6 +57,7 @@ public class PlatformLevel2 extends PlatformLevel { | |||
MigrationConfigurationModule.class, | |||
DatabaseVersion.class, | |||
DatabaseServerCompatibility.class, | |||
MigrationEsClientImpl.class, | |||
new StartupMetadataProvider(), | |||
DefaultServerUpgradeStatus.class, |
@@ -19,53 +19,48 @@ | |||
*/ | |||
package org.sonar.server.es; | |||
import org.elasticsearch.client.Client; | |||
import java.util.Iterator; | |||
import org.elasticsearch.cluster.metadata.IndexMetaData; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.sonar.api.config.internal.MapSettings; | |||
import org.sonar.server.platform.db.migration.es.MigrationEsClient; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.mockito.Mockito.mock; | |||
import static org.mockito.Mockito.never; | |||
import static org.mockito.Mockito.verify; | |||
import static org.sonar.server.es.NewIndex.SettingsConfiguration.newBuilder; | |||
public class SimpleEsClientImplTest { | |||
public class MigrationEsClientImplTest { | |||
@Rule | |||
public EsTester es = EsTester.createCustom(new FakeIndexDefinition(), | |||
public EsTester es = EsTester.createCustom( | |||
new SimpleIndexDefinition("a"), | |||
new SimpleIndexDefinition("b"), | |||
new SimpleIndexDefinition("c")); | |||
private Client client = es.client().nativeClient(); | |||
private SimpleEsClientImpl underTest = new SimpleEsClientImpl(client); | |||
private MigrationEsClient underTest = new MigrationEsClientImpl(es.client()); | |||
@Test | |||
public void call_without_arguments_does_not_generate_an_elasticsearch_call() { | |||
Client client = mock(Client.class); | |||
SimpleEsClientImpl underTest = new SimpleEsClientImpl(client); | |||
underTest.deleteIndexes(); | |||
public void delete_existing_index() { | |||
underTest.deleteIndexes("a"); | |||
verify(client, never()).admin(); | |||
assertThat(loadExistingIndices()) | |||
.doesNotContain("a") | |||
.contains("b", "c"); | |||
} | |||
@Test | |||
public void delete_known_indice_must_delete_the_index() { | |||
underTest.deleteIndexes("fakes"); | |||
public void ignore_indices_that_do_not_exist() { | |||
underTest.deleteIndexes("a", "xxx", "c"); | |||
assertThat(es.client().nativeClient().admin().indices().prepareGetMappings().get().mappings().get("fakes")).isNull(); | |||
assertThat(loadExistingIndices()) | |||
.doesNotContain("a", "c") | |||
.contains("b"); | |||
} | |||
@Test | |||
public void delete_unknown_indice_must_delete_all_existing_indexes() { | |||
underTest.deleteIndexes("a", "xxx", "c"); | |||
assertThat(es.client().nativeClient().admin().indices().prepareGetMappings().get().mappings().get("a")).isNull(); | |||
assertThat(es.client().nativeClient().admin().indices().prepareGetMappings().get().mappings().get("c")).isNull(); | |||
private Iterator<String> loadExistingIndices() { | |||
return es.client().nativeClient().admin().indices().prepareGetMappings().get().mappings().keysIt(); | |||
} | |||
public class SimpleIndexDefinition implements IndexDefinition { | |||
private static class SimpleIndexDefinition implements IndexDefinition { | |||
private final String indexName; | |||
public SimpleIndexDefinition(String indexName) { |