You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ProjectsInWarningDaemon.java 6.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  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.qualitygate;
  21. import com.google.common.util.concurrent.ThreadFactoryBuilder;
  22. import java.util.Optional;
  23. import java.util.concurrent.Executors;
  24. import java.util.concurrent.ScheduledExecutorService;
  25. import java.util.concurrent.ThreadFactory;
  26. import java.util.concurrent.TimeUnit;
  27. import org.picocontainer.Startable;
  28. import org.sonar.api.config.Configuration;
  29. import org.sonar.api.utils.log.Logger;
  30. import org.sonar.api.utils.log.Loggers;
  31. import org.sonar.db.DbClient;
  32. import org.sonar.db.DbSession;
  33. import org.sonar.server.es.SearchOptions;
  34. import org.sonar.server.measure.index.ProjectMeasuresIndex;
  35. import org.sonar.server.measure.index.ProjectMeasuresQuery;
  36. import org.sonar.server.util.GlobalLockManager;
  37. import static org.sonar.api.measures.Metric.Level.WARN;
  38. /**
  39. * This class is regularly checking the number of projects in warning state, in order to not return the "Warning" value in the quality gate facet of the Projects page when there are no more projects in warning.
  40. *
  41. * @see <a href="https://jira.sonarsource.com/browse/SONAR-12140">SONAR-12140</a> for more information
  42. */
  43. public class ProjectsInWarningDaemon implements Startable {
  44. final static String PROJECTS_IN_WARNING_INTERNAL_PROPERTY = "projectsInWarning";
  45. private static final Logger LOG = Loggers.get(ProjectsInWarningDaemon.class);
  46. private static final String FREQUENCY_IN_SECONDS_PROPERTY = "sonar.projectsInWarning.frequencyInSeconds";
  47. private static final int DEFAULT_FREQUENCY_IN_SECONDS = 60 * 60 * 24;
  48. private static final String THREAD_NAME_PREFIX = "sq-projects-in-warning-service-";
  49. private static final String LOCK_NAME = "ProjectsInWarn";
  50. private static final int LOCK_DURATION_IN_SECOND = 60 * 60;
  51. private final DbClient dbClient;
  52. private final ProjectMeasuresIndex projectMeasuresIndex;
  53. private final Configuration config;
  54. private final GlobalLockManager lockManager;
  55. private final ProjectsInWarning projectsInWarning;
  56. private ScheduledExecutorService executorService;
  57. public ProjectsInWarningDaemon(DbClient dbClient, ProjectMeasuresIndex projectMeasuresIndex, Configuration config, GlobalLockManager lockManager,
  58. ProjectsInWarning projectsInWarning) {
  59. this.dbClient = dbClient;
  60. this.projectMeasuresIndex = projectMeasuresIndex;
  61. this.config = config;
  62. this.lockManager = lockManager;
  63. this.projectsInWarning = projectsInWarning;
  64. }
  65. public void notifyStart() {
  66. try (DbSession dbSession = dbClient.openSession(false)) {
  67. Optional<String> internalProperty = dbClient.internalPropertiesDao().selectByKey(dbSession, PROJECTS_IN_WARNING_INTERNAL_PROPERTY);
  68. if (internalProperty.isPresent() && internalProperty.get().equals("0")) {
  69. projectsInWarning.update(0L);
  70. LOG.info("Counting number of projects in warning is not started as there are no projects in this situation.");
  71. return;
  72. }
  73. }
  74. LOG.info("Counting number of projects in warning is enabled.");
  75. executorService = Executors.newSingleThreadScheduledExecutor(newThreadFactory());
  76. executorService.scheduleWithFixedDelay(countProjectsInWarning(), 0, frequency(), TimeUnit.SECONDS);
  77. }
  78. private int frequency() {
  79. return config.getInt(FREQUENCY_IN_SECONDS_PROPERTY).orElse(DEFAULT_FREQUENCY_IN_SECONDS);
  80. }
  81. private Runnable countProjectsInWarning() {
  82. return () -> {
  83. try (DbSession dbSession = dbClient.openSession(false)) {
  84. long nbProjectsInWarning = projectMeasuresIndex.search(
  85. new ProjectMeasuresQuery()
  86. .setQualityGateStatus(WARN)
  87. .setIgnoreAuthorization(true),
  88. // We only need the number of projects in warning
  89. new SearchOptions().setLimit(1)).getTotal();
  90. projectsInWarning.update(nbProjectsInWarning);
  91. updateProjectsInWarningInDb(dbSession, nbProjectsInWarning);
  92. if (nbProjectsInWarning == 0L) {
  93. LOG.info("Counting number of projects in warning will be disabled as there are no more projects in warning.");
  94. executorService.shutdown();
  95. }
  96. } catch (Exception e) {
  97. LOG.error("Error while counting number of projects in warning: {}", e);
  98. }
  99. };
  100. }
  101. private void updateProjectsInWarningInDb(DbSession dbSession, long nbProjectsInWarning) {
  102. // Only one web node should do the update in db to avoid any collision
  103. if (!lockManager.tryLock(LOCK_NAME, LOCK_DURATION_IN_SECOND)) {
  104. return;
  105. }
  106. dbClient.internalPropertiesDao().save(dbSession, PROJECTS_IN_WARNING_INTERNAL_PROPERTY, Long.toString(nbProjectsInWarning));
  107. dbSession.commit();
  108. }
  109. @Override
  110. public void start() {
  111. // Nothing is done here, as this component needs to be started after ES indexing. See PlatformLevelStartup for more info.
  112. }
  113. @Override
  114. public void stop() {
  115. if (executorService == null) {
  116. return;
  117. }
  118. try {
  119. executorService.shutdown();
  120. executorService.awaitTermination(5, TimeUnit.SECONDS);
  121. } catch (InterruptedException e) {
  122. Thread.currentThread().interrupt();
  123. }
  124. }
  125. private static ThreadFactory newThreadFactory() {
  126. return new ThreadFactoryBuilder()
  127. .setNameFormat(THREAD_NAME_PREFIX + "%d")
  128. .setPriority(Thread.MIN_PRIORITY)
  129. .build();
  130. }
  131. }