]> source.dussan.org Git - sonarqube.git/blob
7b91c40c1528d1005de5396067ed06c9b4c0f74f
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2024 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.qualityprofile.index;
21
22 import com.google.common.collect.ImmutableSet;
23 import java.util.Collection;
24 import java.util.HashMap;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.Set;
28 import java.util.stream.Collectors;
29 import javax.annotation.Nullable;
30 import org.elasticsearch.action.index.IndexRequest;
31 import org.elasticsearch.action.search.SearchRequest;
32 import org.elasticsearch.index.query.QueryBuilders;
33 import org.elasticsearch.search.builder.SearchSourceBuilder;
34 import org.slf4j.Logger;
35 import org.slf4j.LoggerFactory;
36 import org.sonar.db.DbClient;
37 import org.sonar.db.DbSession;
38 import org.sonar.db.es.EsQueueDto;
39 import org.sonar.db.qualityprofile.IndexedActiveRuleDto;
40 import org.sonar.db.qualityprofile.QProfileDto;
41 import org.sonar.db.qualityprofile.RulesProfileDto;
42 import org.sonar.db.rule.SeverityUtil;
43 import org.sonar.server.es.BulkIndexer;
44 import org.sonar.server.es.BulkIndexer.Size;
45 import org.sonar.server.es.EsClient;
46 import org.sonar.server.es.IndexType;
47 import org.sonar.server.es.IndexingListener;
48 import org.sonar.server.es.IndexingResult;
49 import org.sonar.server.es.OneToOneResilientIndexingListener;
50 import org.sonar.server.es.ResilientIndexer;
51 import org.sonar.server.qualityprofile.ActiveRuleChange;
52 import org.sonar.server.qualityprofile.ActiveRuleInheritance;
53
54 import static org.elasticsearch.index.query.QueryBuilders.termQuery;
55 import static org.sonar.server.qualityprofile.index.ActiveRuleDoc.docIdOf;
56 import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_ACTIVE_RULE_PROFILE_UUID;
57 import static org.sonar.server.rule.index.RuleIndexDefinition.TYPE_ACTIVE_RULE;
58
59 public class ActiveRuleIndexer implements ResilientIndexer {
60
61   private static final Logger LOGGER = LoggerFactory.getLogger(ActiveRuleIndexer.class);
62   private static final String ID_TYPE_ACTIVE_RULE_UUID = "activeRuleUuid";
63   private static final String ID_TYPE_RULE_PROFILE_UUID = "ruleProfileUuid";
64
65   private final DbClient dbClient;
66   private final EsClient esClient;
67
68   public ActiveRuleIndexer(DbClient dbClient, EsClient esClient) {
69     this.dbClient = dbClient;
70     this.esClient = esClient;
71   }
72
73   @Override
74   public void indexOnStartup(Set<IndexType> uninitializedIndexTypes) {
75     indexAll(Size.LARGE);
76   }
77
78   public void indexAll() {
79     indexAll(Size.REGULAR);
80   }
81
82   private void indexAll(Size bulkSize) {
83     try (DbSession dbSession = dbClient.openSession(false)) {
84       BulkIndexer bulkIndexer = createBulkIndexer(bulkSize, IndexingListener.FAIL_ON_ERROR);
85       bulkIndexer.start();
86       dbClient.activeRuleDao().scrollAllForIndexing(dbSession, ar -> bulkIndexer.add(newIndexRequest(ar)));
87       bulkIndexer.stop();
88     }
89   }
90
91   @Override
92   public Set<IndexType> getIndexTypes() {
93     return ImmutableSet.of(TYPE_ACTIVE_RULE);
94   }
95
96   public void commitAndIndex(DbSession dbSession, Collection<ActiveRuleChange> changes) {
97     List<EsQueueDto> items = changes.stream()
98       .map(ActiveRuleChange::getActiveRule)
99       .map(ar -> newQueueDto(docIdOf(ar.getUuid()), ID_TYPE_ACTIVE_RULE_UUID, ar.getRuleUuid()))
100       .toList();
101
102     dbClient.esQueueDao().insert(dbSession, items);
103     dbSession.commit();
104     postCommit(dbSession, items);
105   }
106
107   public void commitDeletionOfProfiles(DbSession dbSession, Collection<QProfileDto> profiles) {
108     List<EsQueueDto> items = profiles.stream()
109       .map(QProfileDto::getRulesProfileUuid)
110       .distinct()
111       .map(ruleProfileUuid -> newQueueDto(ruleProfileUuid, ID_TYPE_RULE_PROFILE_UUID, null))
112       .toList();
113
114     dbClient.esQueueDao().insert(dbSession, items);
115     dbSession.commit();
116     postCommit(dbSession, items);
117   }
118
119   /**
120    * Entry point for Byteman tests. See directory tests/resilience.
121    */
122   private void postCommit(DbSession dbSession, Collection<EsQueueDto> items) {
123     index(dbSession, items);
124   }
125
126   /**
127    * @return the number of items that have been successfully indexed
128    */
129   @Override
130   public IndexingResult index(DbSession dbSession, Collection<EsQueueDto> items) {
131     IndexingResult result = new IndexingResult();
132
133     if (items.isEmpty()) {
134       return result;
135     }
136
137     Map<String, EsQueueDto> activeRuleItems = new HashMap<>();
138     Map<String, EsQueueDto> ruleProfileItems = new HashMap<>();
139     items.forEach(i -> {
140       if (ID_TYPE_RULE_PROFILE_UUID.equals(i.getDocIdType())) {
141         ruleProfileItems.put(i.getDocId(), i);
142       } else if (ID_TYPE_ACTIVE_RULE_UUID.equals(i.getDocIdType())) {
143         activeRuleItems.put(i.getDocId(), i);
144       } else {
145         LOGGER.error("Unsupported es_queue.doc_id_type. Removing row from queue: " + i);
146         deleteQueueDto(dbSession, i);
147       }
148     });
149
150     if (!activeRuleItems.isEmpty()) {
151       result.add(doIndexActiveRules(dbSession, activeRuleItems));
152     }
153     if (!ruleProfileItems.isEmpty()) {
154       result.add(doIndexRuleProfiles(dbSession, ruleProfileItems));
155     }
156     return result;
157   }
158
159   private IndexingResult doIndexActiveRules(DbSession dbSession, Map<String, EsQueueDto> activeRuleItems) {
160     OneToOneResilientIndexingListener listener = new OneToOneResilientIndexingListener(dbClient, dbSession, activeRuleItems.values());
161     BulkIndexer bulkIndexer = createBulkIndexer(Size.REGULAR, listener);
162     bulkIndexer.start();
163     Map<String, EsQueueDto> remaining = new HashMap<>(activeRuleItems);
164     dbClient.activeRuleDao().scrollByUuidsForIndexing(dbSession, toActiveRuleUuids(activeRuleItems),
165       i -> {
166         remaining.remove(docIdOf(i.getUuid()));
167         bulkIndexer.add(newIndexRequest(i));
168       });
169
170     // the remaining ids reference rows that don't exist in db. They must
171     // be deleted from index.
172     remaining.values().forEach(item -> bulkIndexer.addDeletion(TYPE_ACTIVE_RULE,
173       item.getDocId(), item.getDocRouting()));
174     return bulkIndexer.stop();
175   }
176
177   private static Collection<String> toActiveRuleUuids(Map<String, EsQueueDto> activeRuleItems) {
178     Set<String> docIds = activeRuleItems.keySet();
179     return docIds.stream()
180       .map(ActiveRuleDoc::activeRuleUuidOf)
181       .collect(Collectors.toSet());
182   }
183
184   private IndexingResult doIndexRuleProfiles(DbSession dbSession, Map<String, EsQueueDto> ruleProfileItems) {
185     IndexingResult result = new IndexingResult();
186
187     for (Map.Entry<String, EsQueueDto> entry : ruleProfileItems.entrySet()) {
188       String ruleProfileUUid = entry.getKey();
189       EsQueueDto item = entry.getValue();
190       IndexingResult profileResult;
191
192       RulesProfileDto profile = dbClient.qualityProfileDao().selectRuleProfile(dbSession, ruleProfileUUid);
193       if (profile == null) {
194         // profile does not exist anymore in db --> related documents must be deleted from index rules/activeRule
195         SearchRequest search = EsClient.prepareSearch(TYPE_ACTIVE_RULE.getMainType())
196           .source(new SearchSourceBuilder().query(QueryBuilders.boolQuery().must(termQuery(FIELD_ACTIVE_RULE_PROFILE_UUID, ruleProfileUUid))));
197         profileResult = BulkIndexer.delete(esClient, TYPE_ACTIVE_RULE, search);
198
199       } else {
200         BulkIndexer bulkIndexer = createBulkIndexer(Size.REGULAR, IndexingListener.FAIL_ON_ERROR);
201         bulkIndexer.start();
202         dbClient.activeRuleDao().scrollByRuleProfileForIndexing(dbSession, ruleProfileUUid, i -> bulkIndexer.add(newIndexRequest(i)));
203         profileResult = bulkIndexer.stop();
204       }
205
206       if (profileResult.isSuccess()) {
207         deleteQueueDto(dbSession, item);
208       }
209       result.add(profileResult);
210     }
211
212     return result;
213   }
214
215   private void deleteQueueDto(DbSession dbSession, EsQueueDto item) {
216     dbClient.esQueueDao().delete(dbSession, item);
217     dbSession.commit();
218   }
219
220   private BulkIndexer createBulkIndexer(Size size, IndexingListener listener) {
221     return new BulkIndexer(esClient, TYPE_ACTIVE_RULE, size, listener);
222   }
223
224   private static IndexRequest newIndexRequest(IndexedActiveRuleDto dto) {
225     ActiveRuleDoc doc = new ActiveRuleDoc(dto.getUuid())
226       .setRuleUuid(dto.getRuleUuid())
227       .setRuleProfileUuid(dto.getRuleProfileUuid())
228       .setSeverity(SeverityUtil.getSeverityFromOrdinal(dto.getSeverity()));
229     // all the fields must be present, even if value is null
230     String inheritance = dto.getInheritance();
231     doc.setInheritance(inheritance == null ? ActiveRuleInheritance.NONE.name() : inheritance);
232     return doc.toIndexRequest();
233   }
234
235   private static EsQueueDto newQueueDto(String docId, String docIdType, @Nullable String routing) {
236     return EsQueueDto.create(TYPE_ACTIVE_RULE.format(), docId, docIdType, routing);
237   }
238 }