Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

IndexCreator.java 9.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2019 SonarSource SA
  4. * mailto:info AT sonarsource DOT com
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU Lesser General Public
  8. * License as published by the Free Software Foundation; either
  9. * version 3 of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program; if not, write to the Free Software Foundation,
  18. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  19. */
  20. package org.sonar.server.es;
  21. import java.util.Arrays;
  22. import java.util.Collection;
  23. import java.util.List;
  24. import java.util.Set;
  25. import java.util.stream.Collectors;
  26. import org.apache.commons.lang.StringUtils;
  27. import org.elasticsearch.action.admin.indices.create.CreateIndexResponse;
  28. import org.elasticsearch.action.admin.indices.settings.get.GetSettingsRequest;
  29. import org.elasticsearch.action.admin.indices.settings.put.UpdateSettingsRequest;
  30. import org.elasticsearch.action.support.master.AcknowledgedResponse;
  31. import org.elasticsearch.cluster.health.ClusterHealthStatus;
  32. import org.elasticsearch.common.settings.Settings;
  33. import org.picocontainer.Startable;
  34. import org.sonar.api.config.Configuration;
  35. import org.sonar.api.server.ServerSide;
  36. import org.sonar.api.utils.log.Logger;
  37. import org.sonar.api.utils.log.Loggers;
  38. import org.sonar.process.ProcessProperties;
  39. import org.sonar.server.es.metadata.EsDbCompatibility;
  40. import org.sonar.server.es.metadata.MetadataIndex;
  41. import org.sonar.server.es.metadata.MetadataIndexDefinition;
  42. import org.sonar.server.es.newindex.BuiltIndex;
  43. import org.sonar.server.es.newindex.NewIndex;
  44. import org.sonar.server.platform.db.migration.es.MigrationEsClient;
  45. import static org.sonar.server.es.metadata.MetadataIndexDefinition.DESCRIPTOR;
  46. import static org.sonar.server.es.metadata.MetadataIndexDefinition.TYPE_METADATA;
  47. /**
  48. * Creates/deletes all indices in Elasticsearch during server startup.
  49. */
  50. @ServerSide
  51. public class IndexCreator implements Startable {
  52. private static final Logger LOGGER = Loggers.get(IndexCreator.class);
  53. private final MetadataIndexDefinition metadataIndexDefinition;
  54. private final MetadataIndex metadataIndex;
  55. private final EsClient client;
  56. private final MigrationEsClient migrationEsClient;
  57. private final IndexDefinitions definitions;
  58. private final EsDbCompatibility esDbCompatibility;
  59. private final Configuration configuration;
  60. public IndexCreator(EsClient client, IndexDefinitions definitions, MetadataIndexDefinition metadataIndexDefinition,
  61. MetadataIndex metadataIndex, MigrationEsClient migrationEsClient, EsDbCompatibility esDbCompatibility, Configuration configuration) {
  62. this.client = client;
  63. this.definitions = definitions;
  64. this.metadataIndexDefinition = metadataIndexDefinition;
  65. this.metadataIndex = metadataIndex;
  66. this.migrationEsClient = migrationEsClient;
  67. this.esDbCompatibility = esDbCompatibility;
  68. this.configuration = configuration;
  69. }
  70. @Override
  71. public void start() {
  72. // create the "metadata" index first
  73. IndexType.IndexMainType metadataMainType = TYPE_METADATA;
  74. if (!client.prepareIndicesExist(metadataMainType.getIndex()).get().isExists()) {
  75. IndexDefinition.IndexDefinitionContext context = new IndexDefinition.IndexDefinitionContext();
  76. metadataIndexDefinition.define(context);
  77. NewIndex index = context.getIndices().values().iterator().next();
  78. createIndex(index.build(), false);
  79. } else {
  80. ensureWritable(metadataMainType);
  81. }
  82. checkDbCompatibility(definitions.getIndices().values());
  83. // create indices that do not exist or that have a new definition (different mapping, cluster enabled, ...)
  84. definitions.getIndices().values().stream()
  85. .filter(i -> !i.getMainType().equals(metadataMainType))
  86. .forEach(index -> {
  87. boolean exists = client.prepareIndicesExist(index.getMainType().getIndex()).get().isExists();
  88. if (!exists) {
  89. createIndex(index, true);
  90. } else if (hasDefinitionChange(index)) {
  91. updateIndex(index);
  92. } else {
  93. ensureWritable(index.getMainType());
  94. }
  95. });
  96. }
  97. private void ensureWritable(IndexType.IndexMainType mainType) {
  98. if (isReadOnly(mainType)) {
  99. removeReadOnly(mainType);
  100. }
  101. }
  102. private boolean isReadOnly(IndexType.IndexMainType mainType) {
  103. String indexName = mainType.getIndex().getName();
  104. String readOnly = client.nativeClient().admin().indices().getSettings(new GetSettingsRequest().indices(indexName)).actionGet()
  105. .getSetting(indexName, "index.blocks.read_only_allow_delete");
  106. return readOnly != null && "true".equalsIgnoreCase(readOnly);
  107. }
  108. private void removeReadOnly(IndexType.IndexMainType mainType) {
  109. LOGGER.info("Index [{}] is read-only. Making it writable...", mainType.getIndex().getName());
  110. String indexName = mainType.getIndex().getName();
  111. Settings.Builder builder = Settings.builder();
  112. builder.putNull("index.blocks.read_only_allow_delete");
  113. client.nativeClient().admin().indices()
  114. .updateSettings(new UpdateSettingsRequest().indices(indexName).settings(builder.build()))
  115. .actionGet();
  116. }
  117. @Override
  118. public void stop() {
  119. // nothing to do
  120. }
  121. private void createIndex(BuiltIndex<?> builtIndex, boolean useMetadata) {
  122. Index index = builtIndex.getMainType().getIndex();
  123. LOGGER.info(String.format("Create index [%s]", index.getName()));
  124. Settings.Builder settings = Settings.builder();
  125. settings.put(builtIndex.getSettings());
  126. if (useMetadata) {
  127. metadataIndex.setHash(index, IndexDefinitionHash.of(builtIndex));
  128. metadataIndex.setInitialized(builtIndex.getMainType(), false);
  129. builtIndex.getRelationTypes().forEach(relationType -> metadataIndex.setInitialized(relationType, false));
  130. }
  131. CreateIndexResponse indexResponse = client
  132. .prepareCreate(index)
  133. .setSettings(settings)
  134. .get();
  135. if (!indexResponse.isAcknowledged()) {
  136. throw new IllegalStateException("Failed to create index [" + index.getName() + "]");
  137. }
  138. client.waitForStatus(ClusterHealthStatus.YELLOW);
  139. // create types
  140. LOGGER.info("Create type {}", builtIndex.getMainType().format());
  141. AcknowledgedResponse mappingResponse = client.preparePutMapping(index)
  142. .setType(builtIndex.getMainType().getType())
  143. .setSource(builtIndex.getAttributes())
  144. .get();
  145. if (!mappingResponse.isAcknowledged()) {
  146. throw new IllegalStateException("Failed to create type " + builtIndex.getMainType().getType());
  147. }
  148. client.waitForStatus(ClusterHealthStatus.YELLOW);
  149. }
  150. private void deleteIndex(String indexName) {
  151. client.nativeClient().admin().indices().prepareDelete(indexName).get();
  152. }
  153. private void updateIndex(BuiltIndex<?> index) {
  154. boolean blueGreen = configuration.getBoolean(ProcessProperties.Property.BLUE_GREEN_ENABLED.getKey()).orElse(false);
  155. String indexName = index.getMainType().getIndex().getName();
  156. if (blueGreen) {
  157. // SonarCloud
  158. if (migrationEsClient.getUpdatedIndices().contains(indexName)) {
  159. LOGGER.info("Resetting definition hash of Elasticsearch index [{}]", indexName);
  160. metadataIndex.setHash(index.getMainType().getIndex(), IndexDefinitionHash.of(index));
  161. } else {
  162. throw new IllegalStateException("Blue/green deployment is not supported. Elasticsearch index [" + indexName + "] changed and needs to be dropped.");
  163. }
  164. } else {
  165. // SonarQube
  166. LOGGER.info("Delete Elasticsearch index {} (structure changed)", indexName);
  167. deleteIndex(indexName);
  168. createIndex(index, true);
  169. }
  170. }
  171. private boolean hasDefinitionChange(BuiltIndex<?> index) {
  172. return metadataIndex.getHash(index.getMainType().getIndex())
  173. .map(hash -> {
  174. String defHash = IndexDefinitionHash.of(index);
  175. return !StringUtils.equals(hash, defHash);
  176. }).orElse(true);
  177. }
  178. private void checkDbCompatibility(Collection<BuiltIndex> definitions) {
  179. List<String> existingIndices = loadExistingIndicesExceptMetadata(definitions);
  180. if (!existingIndices.isEmpty()) {
  181. boolean delete = false;
  182. if (!esDbCompatibility.hasSameDbVendor()) {
  183. LOGGER.info("Delete Elasticsearch indices (DB vendor changed)");
  184. delete = true;
  185. }
  186. if (delete) {
  187. existingIndices.forEach(this::deleteIndex);
  188. }
  189. }
  190. esDbCompatibility.markAsCompatible();
  191. }
  192. private List<String> loadExistingIndicesExceptMetadata(Collection<BuiltIndex> definitions) {
  193. Set<String> definedNames = definitions.stream()
  194. .map(t -> t.getMainType().getIndex().getName())
  195. .collect(Collectors.toSet());
  196. return Arrays.stream(client.nativeClient().admin().indices().prepareGetIndex().get().getIndices())
  197. .filter(definedNames::contains)
  198. .filter(index -> !DESCRIPTOR.getName().equals(index))
  199. .collect(Collectors.toList());
  200. }
  201. }