--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.common.health;
+
+import java.util.Arrays;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.sonar.process.cluster.health.NodeDetails;
+import org.sonar.process.cluster.health.NodeHealth;
+import org.sonar.server.health.Health;
+
+import static org.sonar.process.cluster.health.NodeHealth.Status.GREEN;
+import static org.sonar.process.cluster.health.NodeHealth.Status.RED;
+import static org.sonar.process.cluster.health.NodeHealth.Status.YELLOW;
+
+public class AppNodeClusterCheck implements ClusterHealthCheck {
+
+ @Override
+ public Health check(Set<NodeHealth> nodeHealths) {
+ Set<NodeHealth> appNodes = nodeHealths.stream()
+ .filter(s -> s.getDetails().getType() == NodeDetails.Type.APPLICATION)
+ .collect(Collectors.toSet());
+
+ return Arrays.stream(AppNodeClusterHealthSubChecks.values())
+ .map(s -> s.check(appNodes))
+ .reduce(Health.GREEN, HealthReducer::merge);
+ }
+
+ private enum AppNodeClusterHealthSubChecks implements ClusterHealthSubCheck {
+ NO_APPLICATION_NODE() {
+ @Override
+ public Health check(Set<NodeHealth> appNodes) {
+ int appNodeCount = appNodes.size();
+ if (appNodeCount == 0) {
+ return Health.builder()
+ .setStatus(Health.Status.RED)
+ .addCause("No application node")
+ .build();
+ }
+ return Health.GREEN;
+ }
+ },
+ MIN_APPLICATION_NODE_COUNT() {
+ @Override
+ public Health check(Set<NodeHealth> appNodes) {
+ int appNodeCount = appNodes.size();
+ if (appNodeCount == 1) {
+ return Health.builder()
+ .setStatus(Health.Status.YELLOW)
+ .addCause("There should be at least two application nodes")
+ .build();
+ }
+ return Health.GREEN;
+ }
+ },
+ REPORT_RED_OR_YELLOW_NODES() {
+ @Override
+ public Health check(Set<NodeHealth> appNodes) {
+ int appNodeCount = appNodes.size();
+ if (appNodeCount == 0) {
+ // skipping this check
+ return Health.GREEN;
+ }
+
+ long redNodesCount = withStatus(appNodes, RED).count();
+ long yellowNodesCount = withStatus(appNodes, YELLOW).count();
+ if (redNodesCount == 0 && yellowNodesCount == 0) {
+ return Health.GREEN;
+ }
+
+ Health.Builder builder = Health.builder();
+ if (redNodesCount == appNodeCount) {
+ return builder
+ .setStatus(Health.Status.RED)
+ .addCause("Status of all application nodes is RED")
+ .build();
+ } else if (redNodesCount > 0) {
+ builder.addCause("At least one application node is RED");
+ }
+ if (yellowNodesCount == appNodeCount) {
+ return builder
+ .setStatus(Health.Status.YELLOW)
+ .addCause("Status of all application nodes is YELLOW")
+ .build();
+ } else if (yellowNodesCount > 0) {
+ builder.addCause("At least one application node is YELLOW");
+ }
+ long greenNodesCount = withStatus(appNodes, GREEN).count();
+ builder.setStatus(greenNodesCount > 0 || yellowNodesCount > 0 ? Health.Status.YELLOW : Health.Status.RED);
+
+ return builder.build();
+ }
+ }
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.common.health;
+
+import org.sonar.server.app.ProcessCommandWrapper;
+import org.sonar.server.health.Health;
+
+public class CeStatusNodeCheck implements NodeHealthCheck {
+ private static final Health RED_HEALTH = Health.builder()
+ .setStatus(Health.Status.RED)
+ .addCause("Compute Engine is not operational")
+ .build();
+
+ private final ProcessCommandWrapper processCommandWrapper;
+
+ public CeStatusNodeCheck(ProcessCommandWrapper processCommandWrapper) {
+ this.processCommandWrapper = processCommandWrapper;
+ }
+
+ @Override
+ public Health check() {
+ if (processCommandWrapper.isCeOperational()) {
+ return Health.GREEN;
+ }
+
+ return RED_HEALTH;
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.common.health;
+
+import java.util.Set;
+import org.sonar.process.cluster.health.NodeHealth;
+import org.sonar.server.health.Health;
+
+public interface ClusterHealthCheck {
+ Health check(Set<NodeHealth> nodeHealths);
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.common.health;
+
+import java.util.Set;
+import java.util.stream.Stream;
+import org.sonar.process.cluster.health.NodeHealth;
+
+interface ClusterHealthSubCheck extends ClusterHealthCheck {
+
+ default Stream<NodeHealth> withStatus(Set<NodeHealth> searchNodes, NodeHealth.Status... statuses) {
+ return searchNodes.stream()
+ .filter(t -> {
+ for (NodeHealth.Status status : statuses) {
+ if (status == t.getStatus()) {
+ return true;
+ }
+ }
+ return false;
+ });
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.common.health;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.IsAliveMapper;
+import org.sonar.server.health.Health;
+
+/**
+ * Checks Web Server can connect to the Database.
+ */
+public class DbConnectionNodeCheck implements NodeHealthCheck {
+ private static final Logger LOGGER = LoggerFactory.getLogger(DbConnectionNodeCheck.class);
+ private static final Health RED_HEALTH = Health.builder().setStatus(Health.Status.RED).addCause("Can't connect to DB").build();
+
+ private final DbClient dbClient;
+
+ public DbConnectionNodeCheck(DbClient dbClient) {
+ this.dbClient = dbClient;
+ }
+
+ @Override
+ public Health check() {
+ if (isConnectedToDB()) {
+ return Health.GREEN;
+ }
+ return RED_HEALTH;
+ }
+
+ private boolean isConnectedToDB() {
+ try (DbSession dbSession = dbClient.openSession(false)) {
+ return dbSession.getMapper(IsAliveMapper.class).isAlive() == IsAliveMapper.IS_ALIVE_RETURNED_VALUE;
+ } catch (RuntimeException e) {
+ LOGGER.trace("DB connection is down: {}", e.getMessage(), e);
+ return false;
+ }
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.common.health;
+
+import org.elasticsearch.action.admin.cluster.health.ClusterHealthRequest;
+import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse;
+import org.elasticsearch.cluster.health.ClusterHealthStatus;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.server.es.EsClient;
+import org.sonar.server.health.Health;
+
+abstract class EsStatusCheck {
+ private static final Logger LOG = LoggerFactory.getLogger(EsStatusCheck.class);
+
+ private static final Health YELLOW_HEALTH = Health.builder()
+ .setStatus(Health.Status.YELLOW)
+ .addCause("Elasticsearch status is YELLOW")
+ .build();
+ private static final Health RED_HEALTH = Health.builder()
+ .setStatus(Health.Status.RED)
+ .addCause("Elasticsearch status is RED")
+ .build();
+ protected static final Health RED_HEALTH_UNAVAILABLE = Health.builder()
+ .setStatus(Health.Status.RED)
+ .addCause("Elasticsearch status is RED (unavailable)")
+ .build();
+
+ private final EsClient esClient;
+
+ EsStatusCheck(EsClient esClient) {
+ this.esClient = esClient;
+ }
+
+ protected ClusterHealthResponse getEsClusterHealth() {
+ try {
+ return esClient.clusterHealth(new ClusterHealthRequest());
+ } catch (Exception e) {
+ LOG.error("Failed to query ES status", e);
+ return null;
+ }
+ }
+
+ protected static Health extractStatusHealth(ClusterHealthResponse healthResponse) {
+ ClusterHealthStatus esStatus = healthResponse.getStatus();
+ if (esStatus == null) {
+ return RED_HEALTH_UNAVAILABLE;
+ }
+ switch (esStatus) {
+ case GREEN:
+ return Health.GREEN;
+ case YELLOW:
+ return YELLOW_HEALTH;
+ case RED:
+ return RED_HEALTH;
+ default:
+ throw new IllegalArgumentException("Unsupported Elasticsearch status " + esStatus);
+ }
+ }
+
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.common.health;
+
+import java.util.Set;
+import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse;
+import org.sonar.process.cluster.health.NodeHealth;
+import org.sonar.server.es.EsClient;
+import org.sonar.server.health.Health;
+
+public class EsStatusClusterCheck extends EsStatusCheck implements ClusterHealthCheck {
+ private static final String MINIMUM_NODE_MESSAGE = "There should be at least three search nodes";
+ private static final int RECOMMENDED_MIN_NUMBER_OF_ES_NODES = 3;
+
+ public EsStatusClusterCheck(EsClient esClient) {
+ super(esClient);
+ }
+
+ @Override
+ public Health check(Set<NodeHealth> nodeHealths) {
+ ClusterHealthResponse esClusterHealth = this.getEsClusterHealth();
+ if (esClusterHealth != null) {
+ Health minimumNodes = checkMinimumNodes(esClusterHealth);
+ Health clusterStatus = extractStatusHealth(esClusterHealth);
+ return HealthReducer.merge(minimumNodes, clusterStatus);
+ }
+ return RED_HEALTH_UNAVAILABLE;
+ }
+
+ private static Health checkMinimumNodes(ClusterHealthResponse esClusterHealth) {
+ int nodeCount = esClusterHealth.getNumberOfNodes();
+ if (nodeCount < RECOMMENDED_MIN_NUMBER_OF_ES_NODES) {
+ return Health.builder()
+ .setStatus(Health.Status.YELLOW)
+ .addCause(MINIMUM_NODE_MESSAGE)
+ .build();
+ }
+ return Health.GREEN;
+ }
+
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.common.health;
+
+import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse;
+import org.sonar.server.es.EsClient;
+import org.sonar.server.health.Health;
+
+/**
+ * Checks the ElasticSearch cluster status.
+ */
+public class EsStatusNodeCheck extends EsStatusCheck implements NodeHealthCheck {
+
+ public EsStatusNodeCheck(EsClient esClient) {
+ super(esClient);
+ }
+
+ @Override
+ public Health check() {
+ ClusterHealthResponse healthResponse = getEsClusterHealth();
+ return healthResponse != null ? extractStatusHealth(healthResponse) : RED_HEALTH_UNAVAILABLE;
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.common.health;
+
+
+import org.sonar.server.health.Health;
+
+public class HealthReducer {
+
+ private HealthReducer() {
+ // no public constructor
+ }
+
+ public static Health merge(Health left, Health right) {
+ Health.Builder builder = Health.builder()
+ .setStatus(worseOf(left.getStatus(), right.getStatus()));
+ left.getCauses().forEach(builder::addCause);
+ right.getCauses().forEach(builder::addCause);
+ return builder.build();
+ }
+
+ private static Health.Status worseOf(Health.Status left, Health.Status right) {
+ if (left == right) {
+ return left;
+ }
+ if (left.ordinal() > right.ordinal()) {
+ return left;
+ }
+ return right;
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.common.health;
+
+import org.sonar.server.health.Health;
+
+public interface NodeHealthCheck {
+ Health check();
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.common.health;
+
+import org.sonar.server.health.Health;
+
+/**
+ * Checks the running status of the WebServer when the WebServer is in safemode.
+ * Obviously, it statically returns a red health status.
+ */
+public class WebServerSafemodeNodeCheck implements NodeHealthCheck {
+
+ private static final Health RED_HEALTH = Health.builder()
+ .setStatus(Health.Status.RED)
+ .addCause("SonarQube webserver is not up")
+ .build();
+
+ @Override
+ public Health check() {
+ return RED_HEALTH;
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.common.health;
+
+import java.util.EnumSet;
+import org.sonar.server.app.RestartFlagHolder;
+import org.sonar.server.health.Health;
+import org.sonar.server.platform.Platform;
+import org.sonar.server.platform.db.migration.DatabaseMigrationState;
+
+/**
+ * Checks the running status of the WebServer when it is not anymore in safemode.
+ */
+public class WebServerStatusNodeCheck implements NodeHealthCheck {
+ private static final EnumSet<DatabaseMigrationState.Status> VALID_DATABASEMIGRATION_STATUSES = EnumSet.of(
+ DatabaseMigrationState.Status.NONE, DatabaseMigrationState.Status.SUCCEEDED);
+
+ private final DatabaseMigrationState migrationState;
+ private final Platform platform;
+ private final RestartFlagHolder restartFlagHolder;
+
+ public WebServerStatusNodeCheck(DatabaseMigrationState migrationState, Platform platform, RestartFlagHolder restartFlagHolder) {
+ this.migrationState = migrationState;
+ this.platform = platform;
+ this.restartFlagHolder = restartFlagHolder;
+ }
+
+ @Override
+ public Health check() {
+ Platform.Status platformStatus = platform.status();
+ if (platformStatus == Platform.Status.UP
+ && VALID_DATABASEMIGRATION_STATUSES.contains(migrationState.getStatus())
+ && !restartFlagHolder.isRestarting()) {
+ return Health.GREEN;
+ }
+ return Health.builder()
+ .setStatus(Health.Status.RED)
+ .addCause("SonarQube webserver is not up")
+ .build();
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.common.platform;
+
+public interface LivenessChecker {
+ boolean liveness();
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.common.platform;
+
+import javax.annotation.Nullable;
+import org.sonar.server.common.health.CeStatusNodeCheck;
+import org.sonar.server.common.health.DbConnectionNodeCheck;
+import org.sonar.server.common.health.EsStatusNodeCheck;
+import org.sonar.server.common.health.WebServerStatusNodeCheck;
+import org.sonar.server.health.Health;
+
+public class LivenessCheckerImpl implements LivenessChecker {
+
+ private final DbConnectionNodeCheck dbConnectionNodeCheck;
+ private final CeStatusNodeCheck ceStatusNodeCheck;
+ @Nullable
+ private final EsStatusNodeCheck esStatusNodeCheck;
+ private final WebServerStatusNodeCheck webServerStatusNodeCheck;
+
+ public LivenessCheckerImpl(DbConnectionNodeCheck dbConnectionNodeCheck,
+ WebServerStatusNodeCheck webServerStatusNodeCheck, CeStatusNodeCheck ceStatusNodeCheck, @Nullable EsStatusNodeCheck esStatusNodeCheck) {
+ this.dbConnectionNodeCheck = dbConnectionNodeCheck;
+ this.webServerStatusNodeCheck = webServerStatusNodeCheck;
+ this.ceStatusNodeCheck = ceStatusNodeCheck;
+ this.esStatusNodeCheck = esStatusNodeCheck;
+ }
+
+ public boolean liveness() {
+
+ if (!Health.Status.GREEN.equals(dbConnectionNodeCheck.check().getStatus())) {
+ return false;
+ }
+
+ if (!Health.Status.GREEN.equals(webServerStatusNodeCheck.check().getStatus())) {
+ return false;
+ }
+
+ if (!Health.Status.GREEN.equals(ceStatusNodeCheck.check().getStatus())) {
+ return false;
+ }
+
+ if (esStatusNodeCheck != null && Health.Status.RED.equals(esStatusNodeCheck.check().getStatus())) {
+ return false;
+ }
+
+ return true;
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.common.platform;
+
+import org.sonar.server.common.health.DbConnectionNodeCheck;
+import org.sonar.server.health.Health;
+
+public class SafeModeLivenessCheckerImpl implements LivenessChecker {
+
+ private final DbConnectionNodeCheck dbConnectionNodeCheck;
+
+ public SafeModeLivenessCheckerImpl(DbConnectionNodeCheck dbConnectionNodeCheck) {
+ this.dbConnectionNodeCheck = dbConnectionNodeCheck;
+ }
+
+ public boolean liveness() {
+ return Health.Status.GREEN.equals(dbConnectionNodeCheck.check().getStatus());
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.common.health;
+
+import java.util.Arrays;
+import java.util.Random;
+import java.util.Set;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+import org.junit.Test;
+import org.sonar.process.cluster.health.NodeDetails;
+import org.sonar.process.cluster.health.NodeHealth;
+import org.sonar.server.health.Health;
+
+import static java.util.stream.Collectors.toSet;
+import static java.util.stream.Stream.of;
+import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
+import static org.sonar.process.cluster.health.NodeHealth.Status.GREEN;
+import static org.sonar.process.cluster.health.NodeHealth.Status.RED;
+import static org.sonar.process.cluster.health.NodeHealth.Status.YELLOW;
+import static org.sonar.server.common.health.HealthAssert.assertThat;
+
+public class AppNodeClusterCheckTest {
+ private final Random random = new Random();
+
+ private AppNodeClusterCheck underTest = new AppNodeClusterCheck();
+
+ @Test
+ public void status_RED_when_no_application_node() {
+ Set<NodeHealth> nodeHealths = nodeHealths().collect(toSet());
+
+ Health check = underTest.check(nodeHealths);
+
+ assertThat(check)
+ .forInput(nodeHealths)
+ .hasStatus(Health.Status.RED)
+ .andCauses("No application node");
+ }
+
+ @Test
+ public void status_RED_when_single_RED_application_node() {
+ Set<NodeHealth> nodeHealths = nodeHealths(RED).collect(toSet());
+
+ Health check = underTest.check(nodeHealths);
+
+ assertThat(check)
+ .forInput(nodeHealths)
+ .hasStatus(Health.Status.RED)
+ .andCauses("Status of all application nodes is RED",
+ "There should be at least two application nodes");
+ }
+
+ @Test
+ public void status_YELLOW_when_single_YELLOW_application_node() {
+ Set<NodeHealth> nodeHealths = nodeHealths(YELLOW).collect(toSet());
+
+ Health check = underTest.check(nodeHealths);
+
+ assertThat(check)
+ .forInput(nodeHealths)
+ .hasStatus(Health.Status.YELLOW)
+ .andCauses(
+ "Status of all application nodes is YELLOW",
+ "There should be at least two application nodes");
+ }
+
+ @Test
+ public void status_YELLOW_when_single_GREEN_application_node() {
+ Set<NodeHealth> nodeHealths = nodeHealths(GREEN).collect(toSet());
+
+ Health check = underTest.check(nodeHealths);
+
+ assertThat(check)
+ .forInput(nodeHealths)
+ .hasStatus(Health.Status.YELLOW)
+ .andCauses("There should be at least two application nodes");
+ }
+
+ @Test
+ public void status_RED_when_two_RED_application_nodes() {
+ Set<NodeHealth> nodeHealths = nodeHealths(RED, RED).collect(toSet());
+
+ Health check = underTest.check(nodeHealths);
+
+ assertThat(check)
+ .forInput(nodeHealths)
+ .hasStatus(Health.Status.RED)
+ .andCauses("Status of all application nodes is RED");
+ }
+
+ @Test
+ public void status_YELLOW_when_two_YELLOW_application_nodes() {
+ Set<NodeHealth> nodeHealths = nodeHealths(YELLOW, YELLOW).collect(toSet());
+
+ Health check = underTest.check(nodeHealths);
+
+ assertThat(check)
+ .forInput(nodeHealths)
+ .hasStatus(Health.Status.YELLOW)
+ .andCauses("Status of all application nodes is YELLOW");
+ }
+
+ @Test
+ public void status_YELLOW_when_one_RED_node_and_one_YELLOW_application_node() {
+ Set<NodeHealth> nodeHealths = nodeHealths(RED, YELLOW).collect(toSet());
+
+ Health check = underTest.check(nodeHealths);
+
+ assertThat(check)
+ .forInput(nodeHealths)
+ .hasStatus(Health.Status.YELLOW)
+ .andCauses(
+ "At least one application node is RED",
+ "At least one application node is YELLOW");
+ }
+
+ @Test
+ public void status_YELLOW_when_one_RED_node_and_one_GREEN_application_node() {
+ Set<NodeHealth> nodeHealths = nodeHealths(RED, GREEN).collect(toSet());
+
+ Health check = underTest.check(nodeHealths);
+
+ assertThat(check)
+ .forInput(nodeHealths)
+ .hasStatus(Health.Status.YELLOW)
+ .andCauses("At least one application node is RED");
+ }
+
+ @Test
+ public void status_YELLOW_when_one_YELLOW_node_and_one_GREEN_application_node() {
+ Set<NodeHealth> nodeHealths = nodeHealths(YELLOW, GREEN).collect(toSet());
+
+ Health check = underTest.check(nodeHealths);
+
+ assertThat(check)
+ .forInput(nodeHealths)
+ .hasStatus(Health.Status.YELLOW)
+ .andCauses("At least one application node is YELLOW");
+ }
+
+ @Test
+ public void status_GREEN_when_two_GREEN_application_node() {
+ Set<NodeHealth> nodeHealths = nodeHealths(GREEN, GREEN).collect(toSet());
+
+ Health check = underTest.check(nodeHealths);
+
+ assertThat(check)
+ .forInput(nodeHealths)
+ .hasStatus(Health.Status.GREEN)
+ .andCauses();
+ }
+
+ @Test
+ public void status_GREEN_when_two_GREEN_application_node_and_any_number_of_other_is_GREEN() {
+ Set<NodeHealth> nodeHealths = of(
+ // at least 1 extra GREEN
+ of(appNodeHealth(GREEN)),
+ // 0 to 10 GREEN
+ randomNumberOfAppNodeHealthOfAnyStatus(GREEN),
+ // 2 GREEN
+ nodeHealths(GREEN, GREEN))
+ .flatMap(s -> s)
+ .collect(toSet());
+
+ Health check = underTest.check(nodeHealths);
+
+ assertThat(check)
+ .forInput(nodeHealths)
+ .hasStatus(Health.Status.GREEN)
+ .andCauses();
+ }
+
+ @Test
+ public void status_YELLOW_when_two_GREEN_application_node_and_any_number_of_other_is_YELLOW_or_GREEN() {
+ Set<NodeHealth> nodeHealths = of(
+ // at least 1 YELLOW
+ of(appNodeHealth(YELLOW)),
+ // 0 to 10 YELLOW/GREEN
+ randomNumberOfAppNodeHealthOfAnyStatus(GREEN, YELLOW),
+ // 2 GREEN
+ nodeHealths(GREEN, GREEN))
+ .flatMap(s -> s)
+ .collect(toSet());
+ Health check = underTest.check(nodeHealths);
+
+ assertThat(check)
+ .forInput(nodeHealths)
+ .hasStatus(Health.Status.YELLOW)
+ .andCauses("At least one application node is YELLOW");
+ }
+
+ @Test
+ public void status_YELLOW_when_two_GREEN_application_node_and_any_number_of_other_is_RED_or_GREEN() {
+ Set<NodeHealth> nodeHealths = of(
+ // at least 1 RED
+ of(appNodeHealth(RED)),
+ // 0 to 10 RED/GREEN
+ randomNumberOfAppNodeHealthOfAnyStatus(GREEN, RED),
+ // 2 GREEN
+ nodeHealths(GREEN, GREEN))
+ .flatMap(s -> s)
+ .collect(toSet());
+ Health check = underTest.check(nodeHealths);
+
+ assertThat(check)
+ .forInput(nodeHealths)
+ .hasStatus(Health.Status.YELLOW)
+ .andCauses("At least one application node is RED");
+ }
+
+ @Test
+ public void status_YELLOW_when_two_GREEN_application_node_and_any_number_of_other_is_either_RED_or_YELLOW() {
+ Set<NodeHealth> nodeHealths = of(
+ // at least 1 RED
+ of(appNodeHealth(RED)),
+ // at least 1 YELLOW
+ of(appNodeHealth(YELLOW)),
+ // 0 to 10 RED/YELLOW/GREEN
+ randomNumberOfAppNodeHealthOfAnyStatus(RED, YELLOW, GREEN),
+ // 2 GREEN
+ nodeHealths(GREEN, GREEN))
+ .flatMap(s -> s)
+ .collect(toSet());
+
+ Health check = underTest.check(nodeHealths);
+
+ assertThat(check)
+ .forInput(nodeHealths)
+ .hasStatus(Health.Status.YELLOW)
+ .andCauses(
+ "At least one application node is YELLOW",
+ "At least one application node is RED");
+ }
+
+ /**
+ * Between 0 and 10 NodeHealth of Application node with any of the specified statuses.
+ */
+ private Stream<NodeHealth> randomNumberOfAppNodeHealthOfAnyStatus(NodeHealth.Status... randomStatuses) {
+ return IntStream.range(0, random.nextInt(10))
+ .mapToObj(i -> appNodeHealth(randomStatuses[random.nextInt(randomStatuses.length)]));
+ }
+
+ private Stream<NodeHealth> nodeHealths(NodeHealth.Status... appNodeStatuses) {
+ return of(
+ // random number of Search nodes with random status
+ IntStream.range(0, random.nextInt(3))
+ .mapToObj(i -> appNodeHealth(NodeDetails.Type.SEARCH, NodeHealth.Status.values()[random.nextInt(NodeHealth.Status.values().length)])),
+ Arrays.stream(appNodeStatuses).map(this::appNodeHealth))
+ .flatMap(s -> s);
+ }
+
+ private NodeHealth appNodeHealth(NodeHealth.Status status) {
+ return appNodeHealth(NodeDetails.Type.APPLICATION, status);
+ }
+
+ private NodeHealth appNodeHealth(NodeDetails.Type type, NodeHealth.Status status) {
+ return NodeHealth.newNodeHealthBuilder()
+ .setStatus(status)
+ .setDetails(NodeDetails.newNodeDetailsBuilder()
+ .setType(type)
+ .setHost(randomAlphanumeric(32))
+ .setName(randomAlphanumeric(32))
+ .setPort(1 + random.nextInt(88))
+ .setStartedAt(1 + random.nextInt(54))
+ .build())
+ .build();
+ }
+
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.common.health;
+
+import org.junit.Test;
+import org.sonar.server.app.ProcessCommandWrapper;
+import org.sonar.server.common.health.CeStatusNodeCheck;
+import org.sonar.server.health.Health;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class CeStatusNodeCheckTest {
+ private ProcessCommandWrapper processCommandWrapper = mock(ProcessCommandWrapper.class);
+ private CeStatusNodeCheck underTest = new CeStatusNodeCheck(processCommandWrapper);
+
+ @Test
+ public void check_returns_GREEN_status_without_cause_if_ce_is_operational() {
+ when(processCommandWrapper.isCeOperational()).thenReturn(true);
+
+ Health health = underTest.check();
+
+ assertThat(health).isEqualTo(Health.GREEN);
+ }
+
+ @Test
+ public void check_returns_RED_status_with_cause_if_ce_is_not_operational() {
+ when(processCommandWrapper.isCeOperational()).thenReturn(false);
+
+ Health health = underTest.check();
+
+ assertThat(health.getStatus()).isEqualTo(Health.Status.RED);
+ assertThat(health.getCauses()).containsOnly("Compute Engine is not operational");
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.common.health;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.IsAliveMapper;
+import org.sonar.server.common.health.DbConnectionNodeCheck;
+import org.sonar.server.health.Health;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class DbConnectionNodeCheckTest {
+ private DbClient dbClient = mock(DbClient.class);
+ private DbSession dbSession = mock(DbSession.class);
+ private IsAliveMapper isAliveMapper = mock(IsAliveMapper.class);
+
+ private DbConnectionNodeCheck underTest = new DbConnectionNodeCheck(dbClient);
+
+ @Before
+ public void wireMocks() {
+ when(dbClient.openSession(anyBoolean())).thenReturn(dbSession);
+ when(dbSession.getMapper(IsAliveMapper.class)).thenReturn(isAliveMapper);
+ }
+
+ @Test
+ public void status_is_GREEN_without_cause_if_isAlive_returns_1() {
+ when(isAliveMapper.isAlive()).thenReturn(1);
+
+ Health health = underTest.check();
+
+ assertThat(health).isEqualTo(Health.GREEN);
+ }
+
+ @Test
+ public void status_is_RED_with_single_cause_if_any_error_occurs_when_checking_DB() {
+ when(isAliveMapper.isAlive()).thenThrow(new RuntimeException("simulated runtime exception when querying DB"));
+
+ Health health = underTest.check();
+
+ verifyRedStatus(health);
+ }
+
+ /**
+ * By contract {@link IsAliveMapper#isAlive()} can not return anything but 1. Still we write this test as a
+ * protection against change in this contract.
+ */
+ @Test
+ public void status_is_RED_with_single_cause_if_isAlive_does_not_return_1() {
+ when(isAliveMapper.isAlive()).thenReturn(12);
+
+ Health health = underTest.check();
+
+ verifyRedStatus(health);
+ }
+
+ private void verifyRedStatus(Health health) {
+ assertThat(health.getStatus()).isEqualTo(Health.Status.RED);
+ assertThat(health.getCauses()).containsOnly("Can't connect to DB");
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.common.health;
+
+import com.google.common.collect.ImmutableSet;
+import java.util.Random;
+import java.util.Set;
+import org.elasticsearch.cluster.health.ClusterHealthStatus;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.sonar.process.cluster.health.NodeDetails;
+import org.sonar.process.cluster.health.NodeHealth;
+import org.sonar.server.common.health.EsStatusClusterCheck;
+import org.sonar.server.es.EsClient;
+import org.sonar.server.health.Health;
+
+import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
+import static org.mockito.Mockito.when;
+import static org.sonar.process.cluster.health.NodeHealth.Status.GREEN;
+
+public class EsStatusClusterCheckTest {
+
+ private EsClient esClient = Mockito.mock(EsClient.class, RETURNS_DEEP_STUBS);
+ private Random random = new Random();
+ private EsStatusClusterCheck underTest = new EsStatusClusterCheck(esClient);
+
+ @Test
+ public void check_ignores_NodeHealth_arg_and_returns_RED_with_cause_if_an_exception_occurs_checking_ES_cluster_status() {
+ Set<NodeHealth> nodeHealths = ImmutableSet.of(newNodeHealth(NodeHealth.Status.GREEN));
+ when(esClient.clusterHealth(any())).thenThrow(new RuntimeException("Faking an exception occurring while using the EsClient"));
+
+ Health health = new EsStatusClusterCheck(esClient).check(nodeHealths);
+
+ assertThat(health.getStatus()).isEqualTo(Health.Status.RED);
+ assertThat(health.getCauses()).containsOnly("Elasticsearch status is RED (unavailable)");
+ }
+
+ @Test
+ public void check_ignores_NodeHealth_arg_and_returns_GREEN_without_cause_if_ES_cluster_status_is_GREEN() {
+ Set<NodeHealth> nodeHealths = ImmutableSet.of(newNodeHealth(NodeHealth.Status.YELLOW));
+ when(esClient.clusterHealth(any()).getStatus()).thenReturn(ClusterHealthStatus.GREEN);
+ when(esClient.clusterHealth(any()).getNumberOfNodes()).thenReturn(3);
+
+ Health health = underTest.check(nodeHealths);
+
+ assertThat(health).isEqualTo(Health.GREEN);
+ }
+
+ @Test
+ public void check_returns_YELLOW_with_cause_if_ES_cluster_has_less_than_three_nodes_but_status_is_green() {
+ Set<NodeHealth> nodeHealths = ImmutableSet.of(newNodeHealth(GREEN));
+ when(esClient.clusterHealth(any()).getStatus()).thenReturn(ClusterHealthStatus.GREEN);
+ when(esClient.clusterHealth(any()).getNumberOfNodes()).thenReturn(2);
+
+ Health health = underTest.check(nodeHealths);
+
+ assertThat(health.getStatus()).isEqualTo(Health.Status.YELLOW);
+ assertThat(health.getCauses()).containsOnly("There should be at least three search nodes");
+ }
+
+ @Test
+ public void check_returns_RED_with_cause_if_ES_cluster_has_less_than_three_nodes_and_status_is_RED() {
+ Set<NodeHealth> nodeHealths = ImmutableSet.of(newNodeHealth(GREEN));
+ when(esClient.clusterHealth(any()).getStatus()).thenReturn(ClusterHealthStatus.RED);
+ when(esClient.clusterHealth(any()).getNumberOfNodes()).thenReturn(2);
+
+ Health health = underTest.check(nodeHealths);
+
+ assertThat(health.getStatus()).isEqualTo(Health.Status.RED);
+ assertThat(health.getCauses()).contains("Elasticsearch status is RED", "There should be at least three search nodes");
+ }
+
+
+ private NodeHealth newNodeHealth(NodeHealth.Status status) {
+ return NodeHealth.newNodeHealthBuilder()
+ .setStatus(status)
+ .setDetails(NodeDetails.newNodeDetailsBuilder()
+ .setType(random.nextBoolean() ? NodeDetails.Type.APPLICATION : NodeDetails.Type.SEARCH)
+ .setName(randomAlphanumeric(23))
+ .setHost(randomAlphanumeric(23))
+ .setPort(1 + random.nextInt(96))
+ .setStartedAt(1 + random.nextInt(966))
+ .build())
+ .build();
+ }
+
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.common.health;
+
+import org.elasticsearch.cluster.health.ClusterHealthStatus;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.sonar.server.common.health.EsStatusNodeCheck;
+import org.sonar.server.es.EsClient;
+import org.sonar.server.health.Health;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class EsStatusNodeCheckTest {
+
+ private EsClient esClient = mock(EsClient.class, Mockito.RETURNS_DEEP_STUBS);
+ private EsStatusNodeCheck underTest = new EsStatusNodeCheck(esClient);
+
+ @Test
+ public void check_ignores_NodeHealth_arg_and_returns_RED_with_cause_if_an_exception_occurs_checking_ES_cluster_status() {
+ EsClient esClient = mock(EsClient.class);
+ when(esClient.clusterHealth(any())).thenThrow(new RuntimeException("Faking an exception occurring while using the EsClient"));
+
+ Health health = new EsStatusNodeCheck(esClient).check();
+
+ assertThat(health.getStatus()).isEqualTo(Health.Status.RED);
+ assertThat(health.getCauses()).containsOnly("Elasticsearch status is RED (unavailable)");
+ }
+
+ @Test
+ public void check_returns_GREEN_without_cause_if_ES_cluster_status_is_GREEN() {
+ when(esClient.clusterHealth(any()).getStatus()).thenReturn(ClusterHealthStatus.GREEN);
+
+ Health health = underTest.check();
+
+ assertThat(health).isEqualTo(Health.GREEN);
+ }
+
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.common.health;
+
+import com.google.common.collect.ImmutableList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+import javax.annotation.Nullable;
+import org.assertj.core.api.AbstractAssert;
+import org.sonar.process.cluster.health.NodeHealth;
+import org.sonar.server.health.Health;
+
+final class HealthAssert extends AbstractAssert<HealthAssert, Health> {
+ private Set<NodeHealth> nodeHealths;
+
+ private HealthAssert(Health actual) {
+ super(actual, HealthAssert.class);
+ }
+
+ public static HealthAssert assertThat(Health actual) {
+ return new HealthAssert(actual);
+ }
+
+ public HealthAssert forInput(Set<NodeHealth> nodeHealths) {
+ this.nodeHealths = nodeHealths;
+
+ return this;
+ }
+
+ public HealthAssert hasStatus(Health.Status expected) {
+ isNotNull();
+
+ if (actual.getStatus() != expected) {
+ failWithMessage(
+ "Expected Status of Health to be <%s> but was <%s> for NodeHealth \n%s",
+ expected,
+ actual.getStatus(),
+ printStatusesAndTypes(this.nodeHealths));
+ }
+
+ return this;
+ }
+
+ public HealthAssert andCauses(String... causes) {
+ isNotNull();
+
+ if (!checkCauses(causes)) {
+ failWithMessage(
+ "Expected causes of Health to contain only \n%s\n but was \n%s\n for NodeHealth \n%s",
+ Arrays.asList(causes),
+ actual.getCauses(),
+ printStatusesAndTypes(this.nodeHealths));
+ }
+
+ return this;
+ }
+
+ private String printStatusesAndTypes(@Nullable Set<NodeHealth> nodeHealths) {
+ if (nodeHealths == null) {
+ return "<null>";
+ }
+ return nodeHealths.stream()
+ // sort by type then status for debugging convenience
+ .sorted(Comparator.<NodeHealth>comparingInt(s1 -> s1.getDetails().getType().ordinal())
+ .thenComparingInt(s -> s.getStatus().ordinal()))
+ .map(s -> ImmutableList.of(s.getDetails().getType().name(), s.getStatus().name()))
+ .map(String::valueOf)
+ .collect(Collectors.joining(","));
+ }
+
+ private boolean checkCauses(String... causes) {
+ if (causes.length != this.actual.getCauses().size()) {
+ return false;
+ }
+ return Objects.equals(new HashSet<>(Arrays.asList(causes)), this.actual.getCauses());
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.common.health;
+
+import org.junit.Test;
+import org.sonar.server.common.health.WebServerSafemodeNodeCheck;
+import org.sonar.server.health.Health;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class WebServerSafemodeNodeCheckTest {
+ private WebServerSafemodeNodeCheck underTest = new WebServerSafemodeNodeCheck();
+
+ @Test
+ public void always_returns_RED_status_with_cause() {
+ Health health = underTest.check();
+
+ assertThat(health.getStatus()).isEqualTo(Health.Status.RED);
+ assertThat(health.getCauses()).containsOnly("SonarQube webserver is not up");
+
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.common.health;
+
+import java.util.Arrays;
+import java.util.Random;
+import org.junit.Test;
+import org.sonar.server.app.RestartFlagHolder;
+import org.sonar.server.common.health.WebServerStatusNodeCheck;
+import org.sonar.server.health.Health;
+import org.sonar.server.platform.Platform;
+import org.sonar.server.platform.db.migration.DatabaseMigrationState;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class WebServerStatusNodeCheckTest {
+ private final DatabaseMigrationState migrationState = mock(DatabaseMigrationState.class);
+ private final Platform platform = mock(Platform.class);
+ private final RestartFlagHolder restartFlagHolder = mock(RestartFlagHolder.class);
+
+ private final Random random = new Random();
+
+ private WebServerStatusNodeCheck underTest = new WebServerStatusNodeCheck(migrationState, platform, restartFlagHolder);
+
+ @Test
+ public void returns_RED_status_with_cause_if_platform_status_is_not_UP() {
+ Platform.Status[] statusesButUp = Arrays.stream(Platform.Status.values())
+ .filter(s -> s != Platform.Status.UP)
+ .toArray(Platform.Status[]::new);
+ Platform.Status randomStatusButUp = statusesButUp[random.nextInt(statusesButUp.length)];
+ when(platform.status()).thenReturn(randomStatusButUp);
+
+ Health health = underTest.check();
+
+ verifyRedHealthWithCause(health);
+ }
+
+ @Test
+ public void returns_RED_status_with_cause_if_platform_status_is_UP_but_migrationStatus_is_neither_NONE_nor_SUCCEED() {
+ when(platform.status()).thenReturn(Platform.Status.UP);
+ DatabaseMigrationState.Status[] statusesButValidOnes = Arrays.stream(DatabaseMigrationState.Status.values())
+ .filter(s -> s != DatabaseMigrationState.Status.NONE)
+ .filter(s -> s != DatabaseMigrationState.Status.SUCCEEDED)
+ .toArray(DatabaseMigrationState.Status[]::new);
+ DatabaseMigrationState.Status randomInvalidStatus = statusesButValidOnes[random.nextInt(statusesButValidOnes.length)];
+ when(migrationState.getStatus()).thenReturn(randomInvalidStatus);
+
+ Health health = underTest.check();
+
+ verifyRedHealthWithCause(health);
+ }
+
+ @Test
+ public void returns_RED_with_cause_if_platform_status_is_UP_migration_status_is_valid_but_SQ_is_restarting() {
+ when(platform.status()).thenReturn(Platform.Status.UP);
+ when(migrationState.getStatus()).thenReturn(random.nextBoolean() ? DatabaseMigrationState.Status.NONE : DatabaseMigrationState.Status.SUCCEEDED);
+ when(restartFlagHolder.isRestarting()).thenReturn(true);
+
+ Health health = underTest.check();
+
+ verifyRedHealthWithCause(health);
+ }
+
+ @Test
+ public void returns_GREEN_without_cause_if_platform_status_is_UP_migration_status_is_valid_and_SQ_is_not_restarting() {
+ when(platform.status()).thenReturn(Platform.Status.UP);
+ when(migrationState.getStatus()).thenReturn(random.nextBoolean() ? DatabaseMigrationState.Status.NONE : DatabaseMigrationState.Status.SUCCEEDED);
+ when(restartFlagHolder.isRestarting()).thenReturn(false);
+
+ Health health = underTest.check();
+
+ assertThat(health).isEqualTo(Health.GREEN);
+ }
+
+ private void verifyRedHealthWithCause(Health health) {
+ assertThat(health.getStatus()).isEqualTo(Health.Status.RED);
+ assertThat(health.getCauses()).containsOnly("SonarQube webserver is not up");
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.common.platform;
+
+import org.assertj.core.api.Assertions;
+import org.junit.Test;
+import org.sonar.server.common.health.CeStatusNodeCheck;
+import org.sonar.server.common.health.DbConnectionNodeCheck;
+import org.sonar.server.common.health.EsStatusNodeCheck;
+import org.sonar.server.common.health.WebServerStatusNodeCheck;
+import org.sonar.server.health.Health;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class LivenessCheckerImplTest {
+
+ public static final Health RED = Health.builder().setStatus(Health.Status.RED).build();
+
+ private final DbConnectionNodeCheck dbConnectionNodeCheck = mock(DbConnectionNodeCheck.class);
+ private final WebServerStatusNodeCheck webServerStatusNodeCheck = mock(WebServerStatusNodeCheck.class);
+ private final CeStatusNodeCheck ceStatusNodeCheck = mock(CeStatusNodeCheck.class);
+ private final EsStatusNodeCheck esStatusNodeCheck = mock(EsStatusNodeCheck.class);
+
+ LivenessCheckerImpl underTest = new LivenessCheckerImpl(dbConnectionNodeCheck, webServerStatusNodeCheck, ceStatusNodeCheck, esStatusNodeCheck);
+ LivenessCheckerImpl underTestDCE = new LivenessCheckerImpl(dbConnectionNodeCheck, webServerStatusNodeCheck, ceStatusNodeCheck, null);
+
+ @Test
+ public void fail_when_db_connection_check_fail() {
+ when(dbConnectionNodeCheck.check()).thenReturn(RED);
+
+ Assertions.assertThat(underTest.liveness()).isFalse();
+ }
+
+ @Test
+ public void fail_when_web_check_fail() {
+ when(dbConnectionNodeCheck.check()).thenReturn(Health.GREEN);
+ when(webServerStatusNodeCheck.check()).thenReturn(RED);
+
+ Assertions.assertThat(underTest.liveness()).isFalse();
+ }
+
+ @Test
+ public void fail_when_ce_check_fail() {
+ when(dbConnectionNodeCheck.check()).thenReturn(Health.GREEN);
+ when(webServerStatusNodeCheck.check()).thenReturn(Health.GREEN);
+ when(ceStatusNodeCheck.check()).thenReturn(RED);
+
+ Assertions.assertThat(underTest.liveness()).isFalse();
+ }
+
+ @Test
+ public void fail_when_es_check_fail() {
+ when(dbConnectionNodeCheck.check()).thenReturn(Health.GREEN);
+ when(webServerStatusNodeCheck.check()).thenReturn(Health.GREEN);
+ when(ceStatusNodeCheck.check()).thenReturn(Health.GREEN);
+ when(esStatusNodeCheck.check()).thenReturn(RED);
+
+ Assertions.assertThat(underTest.liveness()).isFalse();
+ }
+
+ @Test
+ public void success_when_db_web_ce_es_succeed() {
+ when(dbConnectionNodeCheck.check()).thenReturn(Health.GREEN);
+ when(webServerStatusNodeCheck.check()).thenReturn(Health.GREEN);
+ when(ceStatusNodeCheck.check()).thenReturn(Health.GREEN);
+ when(esStatusNodeCheck.check()).thenReturn(Health.GREEN);
+
+ Assertions.assertThat(underTest.liveness()).isTrue();
+ }
+
+ @Test
+ public void success_when_db_web_ce_succeed() {
+ when(dbConnectionNodeCheck.check()).thenReturn(Health.GREEN);
+ when(webServerStatusNodeCheck.check()).thenReturn(Health.GREEN);
+ when(ceStatusNodeCheck.check()).thenReturn(Health.GREEN);
+
+ Assertions.assertThat(underTestDCE.liveness()).isTrue();
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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.common.platform;
+
+import org.assertj.core.api.Assertions;
+import org.junit.Test;
+import org.sonar.server.common.health.DbConnectionNodeCheck;
+import org.sonar.server.common.platform.SafeModeLivenessCheckerImpl;
+import org.sonar.server.health.Health;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class SafeModeLivenessCheckerImplTest {
+
+ public static final Health RED = Health.builder().setStatus(Health.Status.RED).build();
+ private final DbConnectionNodeCheck dbConnectionNodeCheck = mock(DbConnectionNodeCheck.class);
+ private final SafeModeLivenessCheckerImpl underTest = new SafeModeLivenessCheckerImpl(dbConnectionNodeCheck);
+
+ @Test
+ public void fail_when_db_connection_check_fail() {
+ when(dbConnectionNodeCheck.check()).thenReturn(RED);
+
+ Assertions.assertThat(underTest.liveness()).isFalse();
+ }
+
+ @Test
+ public void succeed_when_db_connection_check_success() {
+ when(dbConnectionNodeCheck.check()).thenReturn(Health.GREEN);
+
+ Assertions.assertThat(underTest.liveness()).isTrue();
+ }
+
+}
api project(':server:sonar-db-dao')
api project(':server:sonar-webserver-common')
- // We are not supposed to have a v1 dependency. The ideal would be to have another common module between webapi and webapi-v2 but that needs a lot of refactoring.
- api project(':server:sonar-webserver-webapi')
+ testImplementation 'javax.servlet:javax.servlet-api'
testImplementation 'org.mockito:mockito-core'
testImplementation 'org.springframework:spring-test'
testImplementation 'org.skyscreamer:jsonassert:1.5.1'
package org.sonar.server.v2.api.system.controller;
import javax.annotation.Nullable;
+import org.sonar.server.common.platform.LivenessChecker;
import org.sonar.server.exceptions.ForbiddenException;
-import org.sonar.server.platform.ws.LivenessChecker;
import org.sonar.server.user.SystemPasscode;
import org.sonar.server.user.UserSession;
package org.sonar.server.v2.config;
import javax.annotation.Nullable;
+import org.sonar.server.common.health.CeStatusNodeCheck;
+import org.sonar.server.common.health.DbConnectionNodeCheck;
+import org.sonar.server.common.health.EsStatusNodeCheck;
+import org.sonar.server.common.health.WebServerStatusNodeCheck;
+import org.sonar.server.common.platform.LivenessChecker;
+import org.sonar.server.common.platform.LivenessCheckerImpl;
import org.sonar.server.common.user.service.UserService;
-import org.sonar.server.health.CeStatusNodeCheck;
-import org.sonar.server.health.DbConnectionNodeCheck;
-import org.sonar.server.health.EsStatusNodeCheck;
import org.sonar.server.health.HealthChecker;
-import org.sonar.server.health.WebServerStatusNodeCheck;
import org.sonar.server.platform.NodeInformation;
-import org.sonar.server.platform.ws.LivenessChecker;
-import org.sonar.server.platform.ws.LivenessCheckerImpl;
import org.sonar.server.user.SystemPasscode;
import org.sonar.server.user.UserSession;
import org.sonar.server.v2.api.system.controller.DefaultLivenessController;
*/
package org.sonar.server.v2.config;
-import org.sonar.server.health.DbConnectionNodeCheck;
+import org.sonar.server.common.health.DbConnectionNodeCheck;
+import org.sonar.server.common.platform.LivenessChecker;
+import org.sonar.server.common.platform.SafeModeLivenessCheckerImpl;
import org.sonar.server.health.HealthChecker;
-import org.sonar.server.platform.ws.LivenessChecker;
-import org.sonar.server.platform.ws.SafeModeLivenessCheckerImpl;
import org.sonar.server.user.SystemPasscode;
import org.sonar.server.v2.api.system.controller.DefaultLivenessController;
import org.sonar.server.v2.api.system.controller.HealthController;
import org.junit.Rule;
import org.junit.Test;
-import org.sonar.server.platform.ws.LivenessChecker;
+import org.sonar.server.common.platform.LivenessChecker;
import org.sonar.server.tester.UserSessionRule;
import org.sonar.server.user.SystemPasscode;
import org.sonar.server.v2.api.ControllerTester;
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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.health;
-
-import java.util.Arrays;
-import java.util.Set;
-import java.util.stream.Collectors;
-import org.sonar.process.cluster.health.NodeDetails;
-import org.sonar.process.cluster.health.NodeHealth;
-
-import static org.sonar.process.cluster.health.NodeHealth.Status.GREEN;
-import static org.sonar.process.cluster.health.NodeHealth.Status.RED;
-import static org.sonar.process.cluster.health.NodeHealth.Status.YELLOW;
-
-public class AppNodeClusterCheck implements ClusterHealthCheck {
-
- @Override
- public Health check(Set<NodeHealth> nodeHealths) {
- Set<NodeHealth> appNodes = nodeHealths.stream()
- .filter(s -> s.getDetails().getType() == NodeDetails.Type.APPLICATION)
- .collect(Collectors.toSet());
-
- return Arrays.stream(AppNodeClusterHealthSubChecks.values())
- .map(s -> s.check(appNodes))
- .reduce(Health.GREEN, HealthReducer::merge);
- }
-
- private enum AppNodeClusterHealthSubChecks implements ClusterHealthSubCheck {
- NO_APPLICATION_NODE() {
- @Override
- public Health check(Set<NodeHealth> appNodes) {
- int appNodeCount = appNodes.size();
- if (appNodeCount == 0) {
- return Health.builder()
- .setStatus(Health.Status.RED)
- .addCause("No application node")
- .build();
- }
- return Health.GREEN;
- }
- },
- MIN_APPLICATION_NODE_COUNT() {
- @Override
- public Health check(Set<NodeHealth> appNodes) {
- int appNodeCount = appNodes.size();
- if (appNodeCount == 1) {
- return Health.builder()
- .setStatus(Health.Status.YELLOW)
- .addCause("There should be at least two application nodes")
- .build();
- }
- return Health.GREEN;
- }
- },
- REPORT_RED_OR_YELLOW_NODES() {
- @Override
- public Health check(Set<NodeHealth> appNodes) {
- int appNodeCount = appNodes.size();
- if (appNodeCount == 0) {
- // skipping this check
- return Health.GREEN;
- }
-
- long redNodesCount = withStatus(appNodes, RED).count();
- long yellowNodesCount = withStatus(appNodes, YELLOW).count();
- if (redNodesCount == 0 && yellowNodesCount == 0) {
- return Health.GREEN;
- }
-
- Health.Builder builder = Health.builder();
- if (redNodesCount == appNodeCount) {
- return builder
- .setStatus(Health.Status.RED)
- .addCause("Status of all application nodes is RED")
- .build();
- } else if (redNodesCount > 0) {
- builder.addCause("At least one application node is RED");
- }
- if (yellowNodesCount == appNodeCount) {
- return builder
- .setStatus(Health.Status.YELLOW)
- .addCause("Status of all application nodes is YELLOW")
- .build();
- } else if (yellowNodesCount > 0) {
- builder.addCause("At least one application node is YELLOW");
- }
- long greenNodesCount = withStatus(appNodes, GREEN).count();
- builder.setStatus(greenNodesCount > 0 || yellowNodesCount > 0 ? Health.Status.YELLOW : Health.Status.RED);
-
- return builder.build();
- }
- }
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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.health;
-
-import org.sonar.server.app.ProcessCommandWrapper;
-
-public class CeStatusNodeCheck implements NodeHealthCheck {
- private static final Health RED_HEALTH = Health.builder()
- .setStatus(Health.Status.RED)
- .addCause("Compute Engine is not operational")
- .build();
-
- private final ProcessCommandWrapper processCommandWrapper;
-
- public CeStatusNodeCheck(ProcessCommandWrapper processCommandWrapper) {
- this.processCommandWrapper = processCommandWrapper;
- }
-
- @Override
- public Health check() {
- if (processCommandWrapper.isCeOperational()) {
- return Health.GREEN;
- }
-
- return RED_HEALTH;
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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.health;
-
-import java.util.Set;
-import org.sonar.process.cluster.health.NodeHealth;
-
-public interface ClusterHealthCheck {
- Health check(Set<NodeHealth> nodeHealths);
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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.health;
-
-import java.util.Set;
-import java.util.stream.Stream;
-import org.sonar.process.cluster.health.NodeHealth;
-
-interface ClusterHealthSubCheck extends ClusterHealthCheck {
-
- default Stream<NodeHealth> withStatus(Set<NodeHealth> searchNodes, NodeHealth.Status... statuses) {
- return searchNodes.stream()
- .filter(t -> {
- for (NodeHealth.Status status : statuses) {
- if (status == t.getStatus()) {
- return true;
- }
- }
- return false;
- });
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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.health;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbSession;
-import org.sonar.db.IsAliveMapper;
-
-/**
- * Checks Web Server can connect to the Database.
- */
-public class DbConnectionNodeCheck implements NodeHealthCheck {
- private static final Logger LOGGER = LoggerFactory.getLogger(DbConnectionNodeCheck.class);
- private static final Health RED_HEALTH = Health.builder().setStatus(Health.Status.RED).addCause("Can't connect to DB").build();
-
- private final DbClient dbClient;
-
- public DbConnectionNodeCheck(DbClient dbClient) {
- this.dbClient = dbClient;
- }
-
- @Override
- public Health check() {
- if (isConnectedToDB()) {
- return Health.GREEN;
- }
- return RED_HEALTH;
- }
-
- private boolean isConnectedToDB() {
- try (DbSession dbSession = dbClient.openSession(false)) {
- return dbSession.getMapper(IsAliveMapper.class).isAlive() == IsAliveMapper.IS_ALIVE_RETURNED_VALUE;
- } catch (RuntimeException e) {
- LOGGER.trace("DB connection is down: {}", e.getMessage(), e);
- return false;
- }
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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.health;
-
-import org.elasticsearch.action.admin.cluster.health.ClusterHealthRequest;
-import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse;
-import org.elasticsearch.cluster.health.ClusterHealthStatus;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.sonar.server.es.EsClient;
-
-abstract class EsStatusCheck {
- private static final Logger LOG = LoggerFactory.getLogger(EsStatusCheck.class);
-
- private static final Health YELLOW_HEALTH = Health.builder()
- .setStatus(Health.Status.YELLOW)
- .addCause("Elasticsearch status is YELLOW")
- .build();
- private static final Health RED_HEALTH = Health.builder()
- .setStatus(Health.Status.RED)
- .addCause("Elasticsearch status is RED")
- .build();
- protected static final Health RED_HEALTH_UNAVAILABLE = Health.builder()
- .setStatus(Health.Status.RED)
- .addCause("Elasticsearch status is RED (unavailable)")
- .build();
-
- private final EsClient esClient;
-
- EsStatusCheck(EsClient esClient) {
- this.esClient = esClient;
- }
-
- protected ClusterHealthResponse getEsClusterHealth() {
- try {
- return esClient.clusterHealth(new ClusterHealthRequest());
- } catch (Exception e) {
- LOG.error("Failed to query ES status", e);
- return null;
- }
- }
-
- protected static Health extractStatusHealth(ClusterHealthResponse healthResponse) {
- ClusterHealthStatus esStatus = healthResponse.getStatus();
- if (esStatus == null) {
- return RED_HEALTH_UNAVAILABLE;
- }
- switch (esStatus) {
- case GREEN:
- return Health.GREEN;
- case YELLOW:
- return YELLOW_HEALTH;
- case RED:
- return RED_HEALTH;
- default:
- throw new IllegalArgumentException("Unsupported Elasticsearch status " + esStatus);
- }
- }
-
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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.health;
-
-import java.util.Set;
-import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse;
-import org.sonar.process.cluster.health.NodeHealth;
-import org.sonar.server.es.EsClient;
-
-public class EsStatusClusterCheck extends EsStatusCheck implements ClusterHealthCheck {
- private static final String MINIMUM_NODE_MESSAGE = "There should be at least three search nodes";
- private static final int RECOMMENDED_MIN_NUMBER_OF_ES_NODES = 3;
-
- public EsStatusClusterCheck(EsClient esClient) {
- super(esClient);
- }
-
- @Override
- public Health check(Set<NodeHealth> nodeHealths) {
- ClusterHealthResponse esClusterHealth = this.getEsClusterHealth();
- if (esClusterHealth != null) {
- Health minimumNodes = checkMinimumNodes(esClusterHealth);
- Health clusterStatus = extractStatusHealth(esClusterHealth);
- return HealthReducer.merge(minimumNodes, clusterStatus);
- }
- return RED_HEALTH_UNAVAILABLE;
- }
-
- private static Health checkMinimumNodes(ClusterHealthResponse esClusterHealth) {
- int nodeCount = esClusterHealth.getNumberOfNodes();
- if (nodeCount < RECOMMENDED_MIN_NUMBER_OF_ES_NODES) {
- return Health.builder()
- .setStatus(Health.Status.YELLOW)
- .addCause(MINIMUM_NODE_MESSAGE)
- .build();
- }
- return Health.GREEN;
- }
-
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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.health;
-
-import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse;
-import org.sonar.server.es.EsClient;
-
-/**
- * Checks the ElasticSearch cluster status.
- */
-public class EsStatusNodeCheck extends EsStatusCheck implements NodeHealthCheck {
-
- public EsStatusNodeCheck(EsClient esClient) {
- super(esClient);
- }
-
- @Override
- public Health check() {
- ClusterHealthResponse healthResponse = getEsClusterHealth();
- return healthResponse != null ? extractStatusHealth(healthResponse) : RED_HEALTH_UNAVAILABLE;
- }
-}
import javax.annotation.Nullable;
import org.sonar.process.cluster.health.NodeHealth;
import org.sonar.process.cluster.health.SharedHealthState;
+import org.sonar.server.common.health.ClusterHealthCheck;
+import org.sonar.server.common.health.HealthReducer;
+import org.sonar.server.common.health.NodeHealthCheck;
import org.sonar.server.platform.NodeInformation;
import org.springframework.beans.factory.annotation.Autowired;
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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.health;
-
-
-public class HealthReducer {
-
- private HealthReducer() {
- // no public constructor
- }
-
- public static Health merge(Health left, Health right) {
- Health.Builder builder = Health.builder()
- .setStatus(worseOf(left.getStatus(), right.getStatus()));
- left.getCauses().forEach(builder::addCause);
- right.getCauses().forEach(builder::addCause);
- return builder.build();
- }
-
- private static Health.Status worseOf(Health.Status left, Health.Status right) {
- if (left == right) {
- return left;
- }
- if (left.ordinal() > right.ordinal()) {
- return left;
- }
- return right;
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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.health;
-
-public interface NodeHealthCheck {
- Health check();
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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.health;
-
-/**
- * Checks the running status of the WebServer when the WebServer is in safemode.
- * Obviously, it statically returns a red health status.
- */
-public class WebServerSafemodeNodeCheck implements NodeHealthCheck {
-
- private static final Health RED_HEALTH = Health.builder()
- .setStatus(Health.Status.RED)
- .addCause("SonarQube webserver is not up")
- .build();
-
- @Override
- public Health check() {
- return RED_HEALTH;
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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.health;
-
-import java.util.EnumSet;
-import org.sonar.server.app.RestartFlagHolder;
-import org.sonar.server.platform.Platform;
-import org.sonar.server.platform.db.migration.DatabaseMigrationState;
-
-/**
- * Checks the running status of the WebServer when it is not anymore in safemode.
- */
-public class WebServerStatusNodeCheck implements NodeHealthCheck {
- private static final EnumSet<DatabaseMigrationState.Status> VALID_DATABASEMIGRATION_STATUSES = EnumSet.of(
- DatabaseMigrationState.Status.NONE, DatabaseMigrationState.Status.SUCCEEDED);
-
- private final DatabaseMigrationState migrationState;
- private final Platform platform;
- private final RestartFlagHolder restartFlagHolder;
-
- public WebServerStatusNodeCheck(DatabaseMigrationState migrationState, Platform platform, RestartFlagHolder restartFlagHolder) {
- this.migrationState = migrationState;
- this.platform = platform;
- this.restartFlagHolder = restartFlagHolder;
- }
-
- @Override
- public Health check() {
- Platform.Status platformStatus = platform.status();
- if (platformStatus == Platform.Status.UP
- && VALID_DATABASEMIGRATION_STATUSES.contains(migrationState.getStatus())
- && !restartFlagHolder.isRestarting()) {
- return Health.GREEN;
- }
- return Health.builder()
- .setStatus(Health.Status.RED)
- .addCause("SonarQube webserver is not up")
- .build();
- }
-}
package org.sonar.server.platform.ws;
import org.sonar.core.platform.Module;
-import org.sonar.server.health.AppNodeClusterCheck;
-import org.sonar.server.health.CeStatusNodeCheck;
-import org.sonar.server.health.DbConnectionNodeCheck;
-import org.sonar.server.health.EsStatusClusterCheck;
-import org.sonar.server.health.EsStatusNodeCheck;
+import org.sonar.server.common.health.AppNodeClusterCheck;
+import org.sonar.server.common.health.CeStatusNodeCheck;
+import org.sonar.server.common.health.DbConnectionNodeCheck;
+import org.sonar.server.common.health.EsStatusClusterCheck;
+import org.sonar.server.common.health.EsStatusNodeCheck;
import org.sonar.server.health.HealthCheckerImpl;
-import org.sonar.server.health.WebServerStatusNodeCheck;
+import org.sonar.server.common.health.WebServerStatusNodeCheck;
import org.sonar.server.platform.NodeInformation;
public class HealthCheckerModule extends Module {
import org.sonar.api.server.ws.Response;
import org.sonar.api.server.ws.WebService;
+import org.sonar.server.common.platform.LivenessChecker;
public class LivenessActionSupport {
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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.ws;
-
-public interface LivenessChecker {
- boolean liveness();
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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.ws;
-
-import javax.annotation.Nullable;
-import org.sonar.server.health.CeStatusNodeCheck;
-import org.sonar.server.health.DbConnectionNodeCheck;
-import org.sonar.server.health.EsStatusNodeCheck;
-import org.sonar.server.health.Health;
-import org.sonar.server.health.WebServerStatusNodeCheck;
-
-public class LivenessCheckerImpl implements LivenessChecker {
-
- private final DbConnectionNodeCheck dbConnectionNodeCheck;
- private final CeStatusNodeCheck ceStatusNodeCheck;
- @Nullable
- private final EsStatusNodeCheck esStatusNodeCheck;
- private final WebServerStatusNodeCheck webServerStatusNodeCheck;
-
- public LivenessCheckerImpl(DbConnectionNodeCheck dbConnectionNodeCheck,
- WebServerStatusNodeCheck webServerStatusNodeCheck, CeStatusNodeCheck ceStatusNodeCheck, @Nullable EsStatusNodeCheck esStatusNodeCheck) {
- this.dbConnectionNodeCheck = dbConnectionNodeCheck;
- this.webServerStatusNodeCheck = webServerStatusNodeCheck;
- this.ceStatusNodeCheck = ceStatusNodeCheck;
- this.esStatusNodeCheck = esStatusNodeCheck;
- }
-
- public boolean liveness() {
-
- if (!Health.Status.GREEN.equals(dbConnectionNodeCheck.check().getStatus())) {
- return false;
- }
-
- if (!Health.Status.GREEN.equals(webServerStatusNodeCheck.check().getStatus())) {
- return false;
- }
-
- if (!Health.Status.GREEN.equals(ceStatusNodeCheck.check().getStatus())) {
- return false;
- }
-
- if (esStatusNodeCheck != null && Health.Status.RED.equals(esStatusNodeCheck.check().getStatus())) {
- return false;
- }
-
- return true;
- }
-}
package org.sonar.server.platform.ws;
import org.sonar.core.platform.Module;
-import org.sonar.server.health.DbConnectionNodeCheck;
-import org.sonar.server.health.EsStatusNodeCheck;
+import org.sonar.server.common.health.DbConnectionNodeCheck;
+import org.sonar.server.common.health.EsStatusNodeCheck;
import org.sonar.server.health.HealthCheckerImpl;
-import org.sonar.server.health.WebServerSafemodeNodeCheck;
+import org.sonar.server.common.health.WebServerSafemodeNodeCheck;
public class SafeModeHealthCheckerModule extends Module {
@Override
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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.ws;
-
-import org.sonar.server.health.DbConnectionNodeCheck;
-import org.sonar.server.health.Health;
-
-public class SafeModeLivenessCheckerImpl implements LivenessChecker {
-
- private final DbConnectionNodeCheck dbConnectionNodeCheck;
-
- public SafeModeLivenessCheckerImpl(DbConnectionNodeCheck dbConnectionNodeCheck) {
- this.dbConnectionNodeCheck = dbConnectionNodeCheck;
- }
-
- public boolean liveness() {
- return Health.Status.GREEN.equals(dbConnectionNodeCheck.check().getStatus());
- }
-}
package org.sonar.server.platform.ws;
import org.sonar.core.platform.Module;
+import org.sonar.server.common.platform.SafeModeLivenessCheckerImpl;
import org.sonar.server.monitoring.MonitoringWs;
import org.sonar.server.user.BearerPasscode;
package org.sonar.server.platform.ws;
import org.sonar.core.platform.Module;
+import org.sonar.server.common.platform.LivenessCheckerImpl;
public class SystemWsModule extends Module {
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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.health;
-
-import java.util.Arrays;
-import java.util.Random;
-import java.util.Set;
-import java.util.stream.IntStream;
-import java.util.stream.Stream;
-import org.junit.Test;
-import org.sonar.process.cluster.health.NodeDetails;
-import org.sonar.process.cluster.health.NodeHealth;
-
-import static java.util.stream.Collectors.toSet;
-import static java.util.stream.Stream.of;
-import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
-import static org.sonar.process.cluster.health.NodeHealth.Status.GREEN;
-import static org.sonar.process.cluster.health.NodeHealth.Status.RED;
-import static org.sonar.process.cluster.health.NodeHealth.Status.YELLOW;
-import static org.sonar.server.health.HealthAssert.assertThat;
-
-public class AppNodeClusterCheckTest {
- private final Random random = new Random();
-
- private AppNodeClusterCheck underTest = new AppNodeClusterCheck();
-
- @Test
- public void status_RED_when_no_application_node() {
- Set<NodeHealth> nodeHealths = nodeHealths().collect(toSet());
-
- Health check = underTest.check(nodeHealths);
-
- assertThat(check)
- .forInput(nodeHealths)
- .hasStatus(Health.Status.RED)
- .andCauses("No application node");
- }
-
- @Test
- public void status_RED_when_single_RED_application_node() {
- Set<NodeHealth> nodeHealths = nodeHealths(RED).collect(toSet());
-
- Health check = underTest.check(nodeHealths);
-
- assertThat(check)
- .forInput(nodeHealths)
- .hasStatus(Health.Status.RED)
- .andCauses("Status of all application nodes is RED",
- "There should be at least two application nodes");
- }
-
- @Test
- public void status_YELLOW_when_single_YELLOW_application_node() {
- Set<NodeHealth> nodeHealths = nodeHealths(YELLOW).collect(toSet());
-
- Health check = underTest.check(nodeHealths);
-
- assertThat(check)
- .forInput(nodeHealths)
- .hasStatus(Health.Status.YELLOW)
- .andCauses(
- "Status of all application nodes is YELLOW",
- "There should be at least two application nodes");
- }
-
- @Test
- public void status_YELLOW_when_single_GREEN_application_node() {
- Set<NodeHealth> nodeHealths = nodeHealths(GREEN).collect(toSet());
-
- Health check = underTest.check(nodeHealths);
-
- assertThat(check)
- .forInput(nodeHealths)
- .hasStatus(Health.Status.YELLOW)
- .andCauses("There should be at least two application nodes");
- }
-
- @Test
- public void status_RED_when_two_RED_application_nodes() {
- Set<NodeHealth> nodeHealths = nodeHealths(RED, RED).collect(toSet());
-
- Health check = underTest.check(nodeHealths);
-
- assertThat(check)
- .forInput(nodeHealths)
- .hasStatus(Health.Status.RED)
- .andCauses("Status of all application nodes is RED");
- }
-
- @Test
- public void status_YELLOW_when_two_YELLOW_application_nodes() {
- Set<NodeHealth> nodeHealths = nodeHealths(YELLOW, YELLOW).collect(toSet());
-
- Health check = underTest.check(nodeHealths);
-
- assertThat(check)
- .forInput(nodeHealths)
- .hasStatus(Health.Status.YELLOW)
- .andCauses("Status of all application nodes is YELLOW");
- }
-
- @Test
- public void status_YELLOW_when_one_RED_node_and_one_YELLOW_application_node() {
- Set<NodeHealth> nodeHealths = nodeHealths(RED, YELLOW).collect(toSet());
-
- Health check = underTest.check(nodeHealths);
-
- assertThat(check)
- .forInput(nodeHealths)
- .hasStatus(Health.Status.YELLOW)
- .andCauses(
- "At least one application node is RED",
- "At least one application node is YELLOW");
- }
-
- @Test
- public void status_YELLOW_when_one_RED_node_and_one_GREEN_application_node() {
- Set<NodeHealth> nodeHealths = nodeHealths(RED, GREEN).collect(toSet());
-
- Health check = underTest.check(nodeHealths);
-
- assertThat(check)
- .forInput(nodeHealths)
- .hasStatus(Health.Status.YELLOW)
- .andCauses("At least one application node is RED");
- }
-
- @Test
- public void status_YELLOW_when_one_YELLOW_node_and_one_GREEN_application_node() {
- Set<NodeHealth> nodeHealths = nodeHealths(YELLOW, GREEN).collect(toSet());
-
- Health check = underTest.check(nodeHealths);
-
- assertThat(check)
- .forInput(nodeHealths)
- .hasStatus(Health.Status.YELLOW)
- .andCauses("At least one application node is YELLOW");
- }
-
- @Test
- public void status_GREEN_when_two_GREEN_application_node() {
- Set<NodeHealth> nodeHealths = nodeHealths(GREEN, GREEN).collect(toSet());
-
- Health check = underTest.check(nodeHealths);
-
- assertThat(check)
- .forInput(nodeHealths)
- .hasStatus(Health.Status.GREEN)
- .andCauses();
- }
-
- @Test
- public void status_GREEN_when_two_GREEN_application_node_and_any_number_of_other_is_GREEN() {
- Set<NodeHealth> nodeHealths = of(
- // at least 1 extra GREEN
- of(appNodeHealth(GREEN)),
- // 0 to 10 GREEN
- randomNumberOfAppNodeHealthOfAnyStatus(GREEN),
- // 2 GREEN
- nodeHealths(GREEN, GREEN))
- .flatMap(s -> s)
- .collect(toSet());
-
- Health check = underTest.check(nodeHealths);
-
- assertThat(check)
- .forInput(nodeHealths)
- .hasStatus(Health.Status.GREEN)
- .andCauses();
- }
-
- @Test
- public void status_YELLOW_when_two_GREEN_application_node_and_any_number_of_other_is_YELLOW_or_GREEN() {
- Set<NodeHealth> nodeHealths = of(
- // at least 1 YELLOW
- of(appNodeHealth(YELLOW)),
- // 0 to 10 YELLOW/GREEN
- randomNumberOfAppNodeHealthOfAnyStatus(GREEN, YELLOW),
- // 2 GREEN
- nodeHealths(GREEN, GREEN))
- .flatMap(s -> s)
- .collect(toSet());
- Health check = underTest.check(nodeHealths);
-
- assertThat(check)
- .forInput(nodeHealths)
- .hasStatus(Health.Status.YELLOW)
- .andCauses("At least one application node is YELLOW");
- }
-
- @Test
- public void status_YELLOW_when_two_GREEN_application_node_and_any_number_of_other_is_RED_or_GREEN() {
- Set<NodeHealth> nodeHealths = of(
- // at least 1 RED
- of(appNodeHealth(RED)),
- // 0 to 10 RED/GREEN
- randomNumberOfAppNodeHealthOfAnyStatus(GREEN, RED),
- // 2 GREEN
- nodeHealths(GREEN, GREEN))
- .flatMap(s -> s)
- .collect(toSet());
- Health check = underTest.check(nodeHealths);
-
- assertThat(check)
- .forInput(nodeHealths)
- .hasStatus(Health.Status.YELLOW)
- .andCauses("At least one application node is RED");
- }
-
- @Test
- public void status_YELLOW_when_two_GREEN_application_node_and_any_number_of_other_is_either_RED_or_YELLOW() {
- Set<NodeHealth> nodeHealths = of(
- // at least 1 RED
- of(appNodeHealth(RED)),
- // at least 1 YELLOW
- of(appNodeHealth(YELLOW)),
- // 0 to 10 RED/YELLOW/GREEN
- randomNumberOfAppNodeHealthOfAnyStatus(RED, YELLOW, GREEN),
- // 2 GREEN
- nodeHealths(GREEN, GREEN))
- .flatMap(s -> s)
- .collect(toSet());
-
- Health check = underTest.check(nodeHealths);
-
- assertThat(check)
- .forInput(nodeHealths)
- .hasStatus(Health.Status.YELLOW)
- .andCauses(
- "At least one application node is YELLOW",
- "At least one application node is RED");
- }
-
- /**
- * Between 0 and 10 NodeHealth of Application node with any of the specified statuses.
- */
- private Stream<NodeHealth> randomNumberOfAppNodeHealthOfAnyStatus(NodeHealth.Status... randomStatuses) {
- return IntStream.range(0, random.nextInt(10))
- .mapToObj(i -> appNodeHealth(randomStatuses[random.nextInt(randomStatuses.length)]));
- }
-
- private Stream<NodeHealth> nodeHealths(NodeHealth.Status... appNodeStatuses) {
- return of(
- // random number of Search nodes with random status
- IntStream.range(0, random.nextInt(3))
- .mapToObj(i -> appNodeHealth(NodeDetails.Type.SEARCH, NodeHealth.Status.values()[random.nextInt(NodeHealth.Status.values().length)])),
- Arrays.stream(appNodeStatuses).map(this::appNodeHealth))
- .flatMap(s -> s);
- }
-
- private NodeHealth appNodeHealth(NodeHealth.Status status) {
- return appNodeHealth(NodeDetails.Type.APPLICATION, status);
- }
-
- private NodeHealth appNodeHealth(NodeDetails.Type type, NodeHealth.Status status) {
- return NodeHealth.newNodeHealthBuilder()
- .setStatus(status)
- .setDetails(NodeDetails.newNodeDetailsBuilder()
- .setType(type)
- .setHost(randomAlphanumeric(32))
- .setName(randomAlphanumeric(32))
- .setPort(1 + random.nextInt(88))
- .setStartedAt(1 + random.nextInt(54))
- .build())
- .build();
- }
-
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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.health;
-
-import org.junit.Test;
-import org.sonar.server.app.ProcessCommandWrapper;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-public class CeStatusNodeCheckTest {
- private ProcessCommandWrapper processCommandWrapper = mock(ProcessCommandWrapper.class);
- private CeStatusNodeCheck underTest = new CeStatusNodeCheck(processCommandWrapper);
-
- @Test
- public void check_returns_GREEN_status_without_cause_if_ce_is_operational() {
- when(processCommandWrapper.isCeOperational()).thenReturn(true);
-
- Health health = underTest.check();
-
- assertThat(health).isEqualTo(Health.GREEN);
- }
-
- @Test
- public void check_returns_RED_status_with_cause_if_ce_is_not_operational() {
- when(processCommandWrapper.isCeOperational()).thenReturn(false);
-
- Health health = underTest.check();
-
- assertThat(health.getStatus()).isEqualTo(Health.Status.RED);
- assertThat(health.getCauses()).containsOnly("Compute Engine is not operational");
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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.health;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.sonar.db.DbClient;
-import org.sonar.db.DbSession;
-import org.sonar.db.IsAliveMapper;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-public class DbConnectionNodeCheckTest {
- private DbClient dbClient = mock(DbClient.class);
- private DbSession dbSession = mock(DbSession.class);
- private IsAliveMapper isAliveMapper = mock(IsAliveMapper.class);
-
- private DbConnectionNodeCheck underTest = new DbConnectionNodeCheck(dbClient);
-
- @Before
- public void wireMocks() {
- when(dbClient.openSession(anyBoolean())).thenReturn(dbSession);
- when(dbSession.getMapper(IsAliveMapper.class)).thenReturn(isAliveMapper);
- }
-
- @Test
- public void status_is_GREEN_without_cause_if_isAlive_returns_1() {
- when(isAliveMapper.isAlive()).thenReturn(1);
-
- Health health = underTest.check();
-
- assertThat(health).isEqualTo(Health.GREEN);
- }
-
- @Test
- public void status_is_RED_with_single_cause_if_any_error_occurs_when_checking_DB() {
- when(isAliveMapper.isAlive()).thenThrow(new RuntimeException("simulated runtime exception when querying DB"));
-
- Health health = underTest.check();
-
- verifyRedStatus(health);
- }
-
- /**
- * By contract {@link IsAliveMapper#isAlive()} can not return anything but 1. Still we write this test as a
- * protection against change in this contract.
- */
- @Test
- public void status_is_RED_with_single_cause_if_isAlive_does_not_return_1() {
- when(isAliveMapper.isAlive()).thenReturn(12);
-
- Health health = underTest.check();
-
- verifyRedStatus(health);
- }
-
- private void verifyRedStatus(Health health) {
- assertThat(health.getStatus()).isEqualTo(Health.Status.RED);
- assertThat(health.getCauses()).containsOnly("Can't connect to DB");
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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.health;
-
-import com.google.common.collect.ImmutableSet;
-import java.util.Random;
-import java.util.Set;
-import org.elasticsearch.cluster.health.ClusterHealthStatus;
-import org.junit.Test;
-import org.mockito.Mockito;
-import org.sonar.process.cluster.health.NodeDetails;
-import org.sonar.process.cluster.health.NodeHealth;
-import org.sonar.server.es.EsClient;
-
-import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
-import static org.mockito.Mockito.when;
-import static org.sonar.process.cluster.health.NodeHealth.Status.GREEN;
-
-public class EsStatusClusterCheckTest {
-
- private EsClient esClient = Mockito.mock(EsClient.class, RETURNS_DEEP_STUBS);
- private Random random = new Random();
- private EsStatusClusterCheck underTest = new EsStatusClusterCheck(esClient);
-
- @Test
- public void check_ignores_NodeHealth_arg_and_returns_RED_with_cause_if_an_exception_occurs_checking_ES_cluster_status() {
- Set<NodeHealth> nodeHealths = ImmutableSet.of(newNodeHealth(NodeHealth.Status.GREEN));
- when(esClient.clusterHealth(any())).thenThrow(new RuntimeException("Faking an exception occurring while using the EsClient"));
-
- Health health = new EsStatusClusterCheck(esClient).check(nodeHealths);
-
- assertThat(health.getStatus()).isEqualTo(Health.Status.RED);
- assertThat(health.getCauses()).containsOnly("Elasticsearch status is RED (unavailable)");
- }
-
- @Test
- public void check_ignores_NodeHealth_arg_and_returns_GREEN_without_cause_if_ES_cluster_status_is_GREEN() {
- Set<NodeHealth> nodeHealths = ImmutableSet.of(newNodeHealth(NodeHealth.Status.YELLOW));
- when(esClient.clusterHealth(any()).getStatus()).thenReturn(ClusterHealthStatus.GREEN);
- when(esClient.clusterHealth(any()).getNumberOfNodes()).thenReturn(3);
-
- Health health = underTest.check(nodeHealths);
-
- assertThat(health).isEqualTo(Health.GREEN);
- }
-
- @Test
- public void check_returns_YELLOW_with_cause_if_ES_cluster_has_less_than_three_nodes_but_status_is_green() {
- Set<NodeHealth> nodeHealths = ImmutableSet.of(newNodeHealth(GREEN));
- when(esClient.clusterHealth(any()).getStatus()).thenReturn(ClusterHealthStatus.GREEN);
- when(esClient.clusterHealth(any()).getNumberOfNodes()).thenReturn(2);
-
- Health health = underTest.check(nodeHealths);
-
- assertThat(health.getStatus()).isEqualTo(Health.Status.YELLOW);
- assertThat(health.getCauses()).containsOnly("There should be at least three search nodes");
- }
-
- @Test
- public void check_returns_RED_with_cause_if_ES_cluster_has_less_than_three_nodes_and_status_is_RED() {
- Set<NodeHealth> nodeHealths = ImmutableSet.of(newNodeHealth(GREEN));
- when(esClient.clusterHealth(any()).getStatus()).thenReturn(ClusterHealthStatus.RED);
- when(esClient.clusterHealth(any()).getNumberOfNodes()).thenReturn(2);
-
- Health health = underTest.check(nodeHealths);
-
- assertThat(health.getStatus()).isEqualTo(Health.Status.RED);
- assertThat(health.getCauses()).contains("Elasticsearch status is RED", "There should be at least three search nodes");
- }
-
-
- private NodeHealth newNodeHealth(NodeHealth.Status status) {
- return NodeHealth.newNodeHealthBuilder()
- .setStatus(status)
- .setDetails(NodeDetails.newNodeDetailsBuilder()
- .setType(random.nextBoolean() ? NodeDetails.Type.APPLICATION : NodeDetails.Type.SEARCH)
- .setName(randomAlphanumeric(23))
- .setHost(randomAlphanumeric(23))
- .setPort(1 + random.nextInt(96))
- .setStartedAt(1 + random.nextInt(966))
- .build())
- .build();
- }
-
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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.health;
-
-import org.elasticsearch.cluster.health.ClusterHealthStatus;
-import org.junit.Test;
-import org.mockito.Mockito;
-import org.sonar.server.es.EsClient;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-public class EsStatusNodeCheckTest {
-
- private EsClient esClient = mock(EsClient.class, Mockito.RETURNS_DEEP_STUBS);
- private EsStatusNodeCheck underTest = new EsStatusNodeCheck(esClient);
-
- @Test
- public void check_ignores_NodeHealth_arg_and_returns_RED_with_cause_if_an_exception_occurs_checking_ES_cluster_status() {
- EsClient esClient = mock(EsClient.class);
- when(esClient.clusterHealth(any())).thenThrow(new RuntimeException("Faking an exception occurring while using the EsClient"));
-
- Health health = new EsStatusNodeCheck(esClient).check();
-
- assertThat(health.getStatus()).isEqualTo(Health.Status.RED);
- assertThat(health.getCauses()).containsOnly("Elasticsearch status is RED (unavailable)");
- }
-
- @Test
- public void check_returns_GREEN_without_cause_if_ES_cluster_status_is_GREEN() {
- when(esClient.clusterHealth(any()).getStatus()).thenReturn(ClusterHealthStatus.GREEN);
-
- Health health = underTest.check();
-
- assertThat(health).isEqualTo(Health.GREEN);
- }
-
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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.health;
-
-import com.google.common.collect.ImmutableList;
-import java.util.Arrays;
-import java.util.Comparator;
-import java.util.HashSet;
-import java.util.Objects;
-import java.util.Set;
-import java.util.stream.Collectors;
-import javax.annotation.Nullable;
-import org.assertj.core.api.AbstractAssert;
-import org.sonar.process.cluster.health.NodeHealth;
-
-final class HealthAssert extends AbstractAssert<HealthAssert, Health> {
- private Set<NodeHealth> nodeHealths;
-
- private HealthAssert(Health actual) {
- super(actual, HealthAssert.class);
- }
-
- public static HealthAssert assertThat(Health actual) {
- return new HealthAssert(actual);
- }
-
- public HealthAssert forInput(Set<NodeHealth> nodeHealths) {
- this.nodeHealths = nodeHealths;
-
- return this;
- }
-
- public HealthAssert hasStatus(Health.Status expected) {
- isNotNull();
-
- if (actual.getStatus() != expected) {
- failWithMessage(
- "Expected Status of Health to be <%s> but was <%s> for NodeHealth \n%s",
- expected,
- actual.getStatus(),
- printStatusesAndTypes(this.nodeHealths));
- }
-
- return this;
- }
-
- public HealthAssert andCauses(String... causes) {
- isNotNull();
-
- if (!checkCauses(causes)) {
- failWithMessage(
- "Expected causes of Health to contain only \n%s\n but was \n%s\n for NodeHealth \n%s",
- Arrays.asList(causes),
- actual.getCauses(),
- printStatusesAndTypes(this.nodeHealths));
- }
-
- return this;
- }
-
- private String printStatusesAndTypes(@Nullable Set<NodeHealth> nodeHealths) {
- if (nodeHealths == null) {
- return "<null>";
- }
- return nodeHealths.stream()
- // sort by type then status for debugging convenience
- .sorted(Comparator.<NodeHealth>comparingInt(s1 -> s1.getDetails().getType().ordinal())
- .thenComparingInt(s -> s.getStatus().ordinal()))
- .map(s -> ImmutableList.of(s.getDetails().getType().name(), s.getStatus().name()))
- .map(String::valueOf)
- .collect(Collectors.joining(","));
- }
-
- private boolean checkCauses(String... causes) {
- if (causes.length != this.actual.getCauses().size()) {
- return false;
- }
- return Objects.equals(new HashSet<>(Arrays.asList(causes)), this.actual.getCauses());
- }
-}
import org.sonar.process.cluster.health.NodeDetails;
import org.sonar.process.cluster.health.NodeHealth;
import org.sonar.process.cluster.health.SharedHealthState;
+import org.sonar.server.common.health.ClusterHealthCheck;
+import org.sonar.server.common.health.NodeHealthCheck;
import org.sonar.server.platform.NodeInformation;
import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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.health;
-
-import org.junit.Test;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-public class WebServerSafemodeNodeCheckTest {
- private WebServerSafemodeNodeCheck underTest = new WebServerSafemodeNodeCheck();
-
- @Test
- public void always_returns_RED_status_with_cause() {
- Health health = underTest.check();
-
- assertThat(health.getStatus()).isEqualTo(Health.Status.RED);
- assertThat(health.getCauses()).containsOnly("SonarQube webserver is not up");
-
- }
-}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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.health;
-
-import java.util.Arrays;
-import java.util.Random;
-import org.junit.Test;
-import org.sonar.server.app.RestartFlagHolder;
-import org.sonar.server.platform.Platform;
-import org.sonar.server.platform.db.migration.DatabaseMigrationState;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-public class WebServerStatusNodeCheckTest {
- private final DatabaseMigrationState migrationState = mock(DatabaseMigrationState.class);
- private final Platform platform = mock(Platform.class);
- private final RestartFlagHolder restartFlagHolder = mock(RestartFlagHolder.class);
-
- private final Random random = new Random();
-
- private WebServerStatusNodeCheck underTest = new WebServerStatusNodeCheck(migrationState, platform, restartFlagHolder);
-
- @Test
- public void returns_RED_status_with_cause_if_platform_status_is_not_UP() {
- Platform.Status[] statusesButUp = Arrays.stream(Platform.Status.values())
- .filter(s -> s != Platform.Status.UP)
- .toArray(Platform.Status[]::new);
- Platform.Status randomStatusButUp = statusesButUp[random.nextInt(statusesButUp.length)];
- when(platform.status()).thenReturn(randomStatusButUp);
-
- Health health = underTest.check();
-
- verifyRedHealthWithCause(health);
- }
-
- @Test
- public void returns_RED_status_with_cause_if_platform_status_is_UP_but_migrationStatus_is_neither_NONE_nor_SUCCEED() {
- when(platform.status()).thenReturn(Platform.Status.UP);
- DatabaseMigrationState.Status[] statusesButValidOnes = Arrays.stream(DatabaseMigrationState.Status.values())
- .filter(s -> s != DatabaseMigrationState.Status.NONE)
- .filter(s -> s != DatabaseMigrationState.Status.SUCCEEDED)
- .toArray(DatabaseMigrationState.Status[]::new);
- DatabaseMigrationState.Status randomInvalidStatus = statusesButValidOnes[random.nextInt(statusesButValidOnes.length)];
- when(migrationState.getStatus()).thenReturn(randomInvalidStatus);
-
- Health health = underTest.check();
-
- verifyRedHealthWithCause(health);
- }
-
- @Test
- public void returns_RED_with_cause_if_platform_status_is_UP_migration_status_is_valid_but_SQ_is_restarting() {
- when(platform.status()).thenReturn(Platform.Status.UP);
- when(migrationState.getStatus()).thenReturn(random.nextBoolean() ? DatabaseMigrationState.Status.NONE : DatabaseMigrationState.Status.SUCCEEDED);
- when(restartFlagHolder.isRestarting()).thenReturn(true);
-
- Health health = underTest.check();
-
- verifyRedHealthWithCause(health);
- }
-
- @Test
- public void returns_GREEN_without_cause_if_platform_status_is_UP_migration_status_is_valid_and_SQ_is_not_restarting() {
- when(platform.status()).thenReturn(Platform.Status.UP);
- when(migrationState.getStatus()).thenReturn(random.nextBoolean() ? DatabaseMigrationState.Status.NONE : DatabaseMigrationState.Status.SUCCEEDED);
- when(restartFlagHolder.isRestarting()).thenReturn(false);
-
- Health health = underTest.check();
-
- assertThat(health).isEqualTo(Health.GREEN);
- }
-
- private void verifyRedHealthWithCause(Health health) {
- assertThat(health.getStatus()).isEqualTo(Health.Status.RED);
- assertThat(health.getCauses()).containsOnly("SonarQube webserver is not up");
- }
-}
import java.util.stream.Collectors;
import org.junit.Test;
import org.sonar.core.platform.ListContainer;
-import org.sonar.server.health.AppNodeClusterCheck;
-import org.sonar.server.health.CeStatusNodeCheck;
-import org.sonar.server.health.ClusterHealthCheck;
-import org.sonar.server.health.DbConnectionNodeCheck;
-import org.sonar.server.health.EsStatusClusterCheck;
-import org.sonar.server.health.EsStatusNodeCheck;
+import org.sonar.server.common.health.AppNodeClusterCheck;
+import org.sonar.server.common.health.CeStatusNodeCheck;
+import org.sonar.server.common.health.ClusterHealthCheck;
+import org.sonar.server.common.health.DbConnectionNodeCheck;
+import org.sonar.server.common.health.EsStatusClusterCheck;
+import org.sonar.server.common.health.EsStatusNodeCheck;
import org.sonar.server.health.HealthCheckerImpl;
-import org.sonar.server.health.NodeHealthCheck;
-import org.sonar.server.health.WebServerStatusNodeCheck;
+import org.sonar.server.common.health.NodeHealthCheck;
+import org.sonar.server.common.health.WebServerStatusNodeCheck;
import org.sonar.server.platform.NodeInformation;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.Rule;
import org.junit.Test;
import org.sonar.api.server.ws.WebService;
+import org.sonar.server.common.platform.LivenessChecker;
import org.sonar.server.exceptions.ForbiddenException;
import org.sonar.server.tester.UserSessionRule;
import org.sonar.server.user.SystemPasscode;
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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.ws;
-
-import org.assertj.core.api.Assertions;
-import org.junit.Test;
-import org.sonar.server.health.CeStatusNodeCheck;
-import org.sonar.server.health.DbConnectionNodeCheck;
-import org.sonar.server.health.EsStatusNodeCheck;
-import org.sonar.server.health.Health;
-import org.sonar.server.health.WebServerStatusNodeCheck;
-
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-public class LivenessCheckerImplTest {
-
- public static final Health RED = Health.builder().setStatus(Health.Status.RED).build();
-
- private final DbConnectionNodeCheck dbConnectionNodeCheck = mock(DbConnectionNodeCheck.class);
- private final WebServerStatusNodeCheck webServerStatusNodeCheck = mock(WebServerStatusNodeCheck.class);
- private final CeStatusNodeCheck ceStatusNodeCheck = mock(CeStatusNodeCheck.class);
- private final EsStatusNodeCheck esStatusNodeCheck = mock(EsStatusNodeCheck.class);
-
- LivenessCheckerImpl underTest = new LivenessCheckerImpl(dbConnectionNodeCheck, webServerStatusNodeCheck, ceStatusNodeCheck, esStatusNodeCheck);
- LivenessCheckerImpl underTestDCE = new LivenessCheckerImpl(dbConnectionNodeCheck, webServerStatusNodeCheck, ceStatusNodeCheck, null);
-
- @Test
- public void fail_when_db_connection_check_fail() {
- when(dbConnectionNodeCheck.check()).thenReturn(RED);
-
- Assertions.assertThat(underTest.liveness()).isFalse();
- }
-
- @Test
- public void fail_when_web_check_fail() {
- when(dbConnectionNodeCheck.check()).thenReturn(Health.GREEN);
- when(webServerStatusNodeCheck.check()).thenReturn(RED);
-
- Assertions.assertThat(underTest.liveness()).isFalse();
- }
-
- @Test
- public void fail_when_ce_check_fail() {
- when(dbConnectionNodeCheck.check()).thenReturn(Health.GREEN);
- when(webServerStatusNodeCheck.check()).thenReturn(Health.GREEN);
- when(ceStatusNodeCheck.check()).thenReturn(RED);
-
- Assertions.assertThat(underTest.liveness()).isFalse();
- }
-
- @Test
- public void fail_when_es_check_fail() {
- when(dbConnectionNodeCheck.check()).thenReturn(Health.GREEN);
- when(webServerStatusNodeCheck.check()).thenReturn(Health.GREEN);
- when(ceStatusNodeCheck.check()).thenReturn(Health.GREEN);
- when(esStatusNodeCheck.check()).thenReturn(RED);
-
- Assertions.assertThat(underTest.liveness()).isFalse();
- }
-
- @Test
- public void success_when_db_web_ce_es_succeed() {
- when(dbConnectionNodeCheck.check()).thenReturn(Health.GREEN);
- when(webServerStatusNodeCheck.check()).thenReturn(Health.GREEN);
- when(ceStatusNodeCheck.check()).thenReturn(Health.GREEN);
- when(esStatusNodeCheck.check()).thenReturn(Health.GREEN);
-
- Assertions.assertThat(underTest.liveness()).isTrue();
- }
-
- @Test
- public void success_when_db_web_ce_succeed() {
- when(dbConnectionNodeCheck.check()).thenReturn(Health.GREEN);
- when(webServerStatusNodeCheck.check()).thenReturn(Health.GREEN);
- when(ceStatusNodeCheck.check()).thenReturn(Health.GREEN);
-
- Assertions.assertThat(underTestDCE.liveness()).isTrue();
- }
-}
import java.util.stream.Collectors;
import org.junit.Test;
import org.sonar.core.platform.ListContainer;
-import org.sonar.server.health.DbConnectionNodeCheck;
-import org.sonar.server.health.EsStatusNodeCheck;
+import org.sonar.server.common.health.DbConnectionNodeCheck;
+import org.sonar.server.common.health.EsStatusNodeCheck;
import org.sonar.server.health.HealthCheckerImpl;
-import org.sonar.server.health.NodeHealthCheck;
-import org.sonar.server.health.WebServerSafemodeNodeCheck;
+import org.sonar.server.common.health.NodeHealthCheck;
+import org.sonar.server.common.health.WebServerSafemodeNodeCheck;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.Test;
import org.sonar.api.server.ws.WebService;
+import org.sonar.server.common.platform.LivenessChecker;
import org.sonar.server.exceptions.ForbiddenException;
import org.sonar.server.user.SystemPasscode;
import org.sonar.server.ws.TestRequest;
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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.ws;
-
-import org.assertj.core.api.Assertions;
-import org.junit.Test;
-import org.sonar.server.health.DbConnectionNodeCheck;
-import org.sonar.server.health.Health;
-
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-public class SafeModeLivenessCheckerImplTest {
-
- public static final Health RED = Health.builder().setStatus(Health.Status.RED).build();
- private final DbConnectionNodeCheck dbConnectionNodeCheck = mock(DbConnectionNodeCheck.class);
- private final SafeModeLivenessCheckerImpl underTest = new SafeModeLivenessCheckerImpl(dbConnectionNodeCheck);
-
- @Test
- public void fail_when_db_connection_check_fail() {
- when(dbConnectionNodeCheck.check()).thenReturn(RED);
-
- Assertions.assertThat(underTest.liveness()).isFalse();
- }
-
- @Test
- public void succeed_when_db_connection_check_success() {
- when(dbConnectionNodeCheck.check()).thenReturn(Health.GREEN);
-
- Assertions.assertThat(underTest.liveness()).isTrue();
- }
-
-}