From: Wojtek Wajerowicz <115081248+wojciech-wajerowicz-sonarsource@users.noreply.github.com> Date: Thu, 27 Jul 2023 13:10:52 +0000 (+0200) Subject: SONAR-19991 Don't depend on sonar-webserver-webapi in sonar-webserver-webapi-v2. X-Git-Tag: 10.2.0.77647~283 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=1af4bdccceb04f2a4e3a357794cbebae9010eebd;p=sonarqube.git SONAR-19991 Don't depend on sonar-webserver-webapi in sonar-webserver-webapi-v2. --- diff --git a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/health/AppNodeClusterCheck.java b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/health/AppNodeClusterCheck.java new file mode 100644 index 00000000000..ac7061e87ff --- /dev/null +++ b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/health/AppNodeClusterCheck.java @@ -0,0 +1,112 @@ +/* + * 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 nodeHealths) { + Set 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 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 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 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(); + } + } + } +} diff --git a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/health/CeStatusNodeCheck.java b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/health/CeStatusNodeCheck.java new file mode 100644 index 00000000000..aa324476a5c --- /dev/null +++ b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/health/CeStatusNodeCheck.java @@ -0,0 +1,45 @@ +/* + * 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; + } +} diff --git a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/health/ClusterHealthCheck.java b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/health/ClusterHealthCheck.java new file mode 100644 index 00000000000..ab1884d2251 --- /dev/null +++ b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/health/ClusterHealthCheck.java @@ -0,0 +1,28 @@ +/* + * 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 nodeHealths); +} diff --git a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/health/ClusterHealthSubCheck.java b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/health/ClusterHealthSubCheck.java new file mode 100644 index 00000000000..fa172f0e833 --- /dev/null +++ b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/health/ClusterHealthSubCheck.java @@ -0,0 +1,39 @@ +/* + * 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 withStatus(Set searchNodes, NodeHealth.Status... statuses) { + return searchNodes.stream() + .filter(t -> { + for (NodeHealth.Status status : statuses) { + if (status == t.getStatus()) { + return true; + } + } + return false; + }); + } +} diff --git a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/health/DbConnectionNodeCheck.java b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/health/DbConnectionNodeCheck.java new file mode 100644 index 00000000000..2510f652325 --- /dev/null +++ b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/health/DbConnectionNodeCheck.java @@ -0,0 +1,58 @@ +/* + * 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; + } + } +} diff --git a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/health/EsStatusCheck.java b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/health/EsStatusCheck.java new file mode 100644 index 00000000000..62aec91839b --- /dev/null +++ b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/health/EsStatusCheck.java @@ -0,0 +1,78 @@ +/* + * 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); + } + } + +} diff --git a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/health/EsStatusClusterCheck.java b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/health/EsStatusClusterCheck.java new file mode 100644 index 00000000000..de8ac07fc16 --- /dev/null +++ b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/health/EsStatusClusterCheck.java @@ -0,0 +1,58 @@ +/* + * 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 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; + } + +} diff --git a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/health/EsStatusNodeCheck.java b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/health/EsStatusNodeCheck.java new file mode 100644 index 00000000000..e03c74ecfb8 --- /dev/null +++ b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/health/EsStatusNodeCheck.java @@ -0,0 +1,40 @@ +/* + * 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; + } +} diff --git a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/health/HealthReducer.java b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/health/HealthReducer.java new file mode 100644 index 00000000000..44b09d0e287 --- /dev/null +++ b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/health/HealthReducer.java @@ -0,0 +1,48 @@ +/* + * 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; + } +} diff --git a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/health/NodeHealthCheck.java b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/health/NodeHealthCheck.java new file mode 100644 index 00000000000..10eafd623e5 --- /dev/null +++ b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/health/NodeHealthCheck.java @@ -0,0 +1,26 @@ +/* + * 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(); +} diff --git a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/health/WebServerSafemodeNodeCheck.java b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/health/WebServerSafemodeNodeCheck.java new file mode 100644 index 00000000000..e4bb09dedd9 --- /dev/null +++ b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/health/WebServerSafemodeNodeCheck.java @@ -0,0 +1,39 @@ +/* + * 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; + } +} diff --git a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/health/WebServerStatusNodeCheck.java b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/health/WebServerStatusNodeCheck.java new file mode 100644 index 00000000000..40013cbe14b --- /dev/null +++ b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/health/WebServerStatusNodeCheck.java @@ -0,0 +1,58 @@ +/* + * 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 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(); + } +} diff --git a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/platform/LivenessChecker.java b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/platform/LivenessChecker.java new file mode 100644 index 00000000000..58de1feea81 --- /dev/null +++ b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/platform/LivenessChecker.java @@ -0,0 +1,24 @@ +/* + * 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(); +} diff --git a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/platform/LivenessCheckerImpl.java b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/platform/LivenessCheckerImpl.java new file mode 100644 index 00000000000..7ae54f06ced --- /dev/null +++ b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/platform/LivenessCheckerImpl.java @@ -0,0 +1,65 @@ +/* + * 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; + } +} diff --git a/server/sonar-webserver-common/src/main/java/org/sonar/server/common/platform/SafeModeLivenessCheckerImpl.java b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/platform/SafeModeLivenessCheckerImpl.java new file mode 100644 index 00000000000..97216c0ce33 --- /dev/null +++ b/server/sonar-webserver-common/src/main/java/org/sonar/server/common/platform/SafeModeLivenessCheckerImpl.java @@ -0,0 +1,36 @@ +/* + * 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()); + } +} diff --git a/server/sonar-webserver-common/src/test/java/org/sonar/server/common/health/AppNodeClusterCheckTest.java b/server/sonar-webserver-common/src/test/java/org/sonar/server/common/health/AppNodeClusterCheckTest.java new file mode 100644 index 00000000000..0687a1e653d --- /dev/null +++ b/server/sonar-webserver-common/src/test/java/org/sonar/server/common/health/AppNodeClusterCheckTest.java @@ -0,0 +1,286 @@ +/* + * 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 randomNumberOfAppNodeHealthOfAnyStatus(NodeHealth.Status... randomStatuses) { + return IntStream.range(0, random.nextInt(10)) + .mapToObj(i -> appNodeHealth(randomStatuses[random.nextInt(randomStatuses.length)])); + } + + private Stream 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(); + } + +} diff --git a/server/sonar-webserver-common/src/test/java/org/sonar/server/common/health/CeStatusNodeCheckTest.java b/server/sonar-webserver-common/src/test/java/org/sonar/server/common/health/CeStatusNodeCheckTest.java new file mode 100644 index 00000000000..d485948c388 --- /dev/null +++ b/server/sonar-webserver-common/src/test/java/org/sonar/server/common/health/CeStatusNodeCheckTest.java @@ -0,0 +1,53 @@ +/* + * 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"); + } +} diff --git a/server/sonar-webserver-common/src/test/java/org/sonar/server/common/health/DbConnectionNodeCheckTest.java b/server/sonar-webserver-common/src/test/java/org/sonar/server/common/health/DbConnectionNodeCheckTest.java new file mode 100644 index 00000000000..b02342d7e34 --- /dev/null +++ b/server/sonar-webserver-common/src/test/java/org/sonar/server/common/health/DbConnectionNodeCheckTest.java @@ -0,0 +1,83 @@ +/* + * 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"); + } +} diff --git a/server/sonar-webserver-common/src/test/java/org/sonar/server/common/health/EsStatusClusterCheckTest.java b/server/sonar-webserver-common/src/test/java/org/sonar/server/common/health/EsStatusClusterCheckTest.java new file mode 100644 index 00000000000..9aa2c7c4c8f --- /dev/null +++ b/server/sonar-webserver-common/src/test/java/org/sonar/server/common/health/EsStatusClusterCheckTest.java @@ -0,0 +1,107 @@ +/* + * 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 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 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 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 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(); + } + +} diff --git a/server/sonar-webserver-common/src/test/java/org/sonar/server/common/health/EsStatusNodeCheckTest.java b/server/sonar-webserver-common/src/test/java/org/sonar/server/common/health/EsStatusNodeCheckTest.java new file mode 100644 index 00000000000..bf272944b45 --- /dev/null +++ b/server/sonar-webserver-common/src/test/java/org/sonar/server/common/health/EsStatusNodeCheckTest.java @@ -0,0 +1,59 @@ +/* + * 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); + } + +} diff --git a/server/sonar-webserver-common/src/test/java/org/sonar/server/common/health/HealthAssert.java b/server/sonar-webserver-common/src/test/java/org/sonar/server/common/health/HealthAssert.java new file mode 100644 index 00000000000..8d8c6b32a30 --- /dev/null +++ b/server/sonar-webserver-common/src/test/java/org/sonar/server/common/health/HealthAssert.java @@ -0,0 +1,98 @@ +/* + * 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 { + private Set nodeHealths; + + private HealthAssert(Health actual) { + super(actual, HealthAssert.class); + } + + public static HealthAssert assertThat(Health actual) { + return new HealthAssert(actual); + } + + public HealthAssert forInput(Set 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 nodeHealths) { + if (nodeHealths == null) { + return ""; + } + return nodeHealths.stream() + // sort by type then status for debugging convenience + .sorted(Comparator.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()); + } +} diff --git a/server/sonar-webserver-common/src/test/java/org/sonar/server/common/health/WebServerSafemodeNodeCheckTest.java b/server/sonar-webserver-common/src/test/java/org/sonar/server/common/health/WebServerSafemodeNodeCheckTest.java new file mode 100644 index 00000000000..fafe5a176ae --- /dev/null +++ b/server/sonar-webserver-common/src/test/java/org/sonar/server/common/health/WebServerSafemodeNodeCheckTest.java @@ -0,0 +1,39 @@ +/* + * 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"); + + } +} diff --git a/server/sonar-webserver-common/src/test/java/org/sonar/server/common/health/WebServerStatusNodeCheckTest.java b/server/sonar-webserver-common/src/test/java/org/sonar/server/common/health/WebServerStatusNodeCheckTest.java new file mode 100644 index 00000000000..680178b8ebf --- /dev/null +++ b/server/sonar-webserver-common/src/test/java/org/sonar/server/common/health/WebServerStatusNodeCheckTest.java @@ -0,0 +1,98 @@ +/* + * 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"); + } +} diff --git a/server/sonar-webserver-common/src/test/java/org/sonar/server/common/platform/LivenessCheckerImplTest.java b/server/sonar-webserver-common/src/test/java/org/sonar/server/common/platform/LivenessCheckerImplTest.java new file mode 100644 index 00000000000..8465cb13011 --- /dev/null +++ b/server/sonar-webserver-common/src/test/java/org/sonar/server/common/platform/LivenessCheckerImplTest.java @@ -0,0 +1,97 @@ +/* + * 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(); + } +} diff --git a/server/sonar-webserver-common/src/test/java/org/sonar/server/common/platform/SafeModeLivenessCheckerImplTest.java b/server/sonar-webserver-common/src/test/java/org/sonar/server/common/platform/SafeModeLivenessCheckerImplTest.java new file mode 100644 index 00000000000..8dbe81cc265 --- /dev/null +++ b/server/sonar-webserver-common/src/test/java/org/sonar/server/common/platform/SafeModeLivenessCheckerImplTest.java @@ -0,0 +1,51 @@ +/* + * 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(); + } + +} diff --git a/server/sonar-webserver-webapi-v2/build.gradle b/server/sonar-webserver-webapi-v2/build.gradle index 04d0150bf86..183c5598094 100644 --- a/server/sonar-webserver-webapi-v2/build.gradle +++ b/server/sonar-webserver-webapi-v2/build.gradle @@ -14,9 +14,8 @@ dependencies { 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' diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/system/controller/DefaultLivenessController.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/system/controller/DefaultLivenessController.java index da32653b58a..2747a7b9f1a 100644 --- a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/system/controller/DefaultLivenessController.java +++ b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/api/system/controller/DefaultLivenessController.java @@ -20,8 +20,8 @@ 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; diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/config/PlatformLevel4WebConfig.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/config/PlatformLevel4WebConfig.java index 74b5c8a6fdf..9231cc10c6b 100644 --- a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/config/PlatformLevel4WebConfig.java +++ b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/config/PlatformLevel4WebConfig.java @@ -20,15 +20,15 @@ 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; diff --git a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/config/SafeModeWebConfig.java b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/config/SafeModeWebConfig.java index 5c2671d016b..9cc8266ddb6 100644 --- a/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/config/SafeModeWebConfig.java +++ b/server/sonar-webserver-webapi-v2/src/main/java/org/sonar/server/v2/config/SafeModeWebConfig.java @@ -19,10 +19,10 @@ */ 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; diff --git a/server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/system/controller/DefaultLivenessControllerTest.java b/server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/system/controller/DefaultLivenessControllerTest.java index 6cd65d5d67b..0daa5a994c2 100644 --- a/server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/system/controller/DefaultLivenessControllerTest.java +++ b/server/sonar-webserver-webapi-v2/src/test/java/org/sonar/server/v2/api/system/controller/DefaultLivenessControllerTest.java @@ -21,7 +21,7 @@ package org.sonar.server.v2.api.system.controller; 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; diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/health/AppNodeClusterCheck.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/health/AppNodeClusterCheck.java deleted file mode 100644 index 565dbc868fb..00000000000 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/health/AppNodeClusterCheck.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * 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 nodeHealths) { - Set 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 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 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 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(); - } - } - } -} diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/health/CeStatusNodeCheck.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/health/CeStatusNodeCheck.java deleted file mode 100644 index 2af77890438..00000000000 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/health/CeStatusNodeCheck.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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; - } -} diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/health/ClusterHealthCheck.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/health/ClusterHealthCheck.java deleted file mode 100644 index 6dca76d623b..00000000000 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/health/ClusterHealthCheck.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * 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 nodeHealths); -} diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/health/ClusterHealthSubCheck.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/health/ClusterHealthSubCheck.java deleted file mode 100644 index 3ecb1b33f81..00000000000 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/health/ClusterHealthSubCheck.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * 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 withStatus(Set searchNodes, NodeHealth.Status... statuses) { - return searchNodes.stream() - .filter(t -> { - for (NodeHealth.Status status : statuses) { - if (status == t.getStatus()) { - return true; - } - } - return false; - }); - } -} diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/health/DbConnectionNodeCheck.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/health/DbConnectionNodeCheck.java deleted file mode 100644 index 01cd0178b16..00000000000 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/health/DbConnectionNodeCheck.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * 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; - } - } -} diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/health/EsStatusCheck.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/health/EsStatusCheck.java deleted file mode 100644 index e2287818f21..00000000000 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/health/EsStatusCheck.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * 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); - } - } - -} diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/health/EsStatusClusterCheck.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/health/EsStatusClusterCheck.java deleted file mode 100644 index 5964ddd3f86..00000000000 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/health/EsStatusClusterCheck.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * 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 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; - } - -} diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/health/EsStatusNodeCheck.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/health/EsStatusNodeCheck.java deleted file mode 100644 index 373f536dcd9..00000000000 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/health/EsStatusNodeCheck.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * 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; - } -} diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/health/HealthCheckerImpl.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/health/HealthCheckerImpl.java index 0d551d284e7..15841346c43 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/health/HealthCheckerImpl.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/health/HealthCheckerImpl.java @@ -25,6 +25,9 @@ import javax.annotation.CheckForNull; 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; diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/health/HealthReducer.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/health/HealthReducer.java deleted file mode 100644 index 984bc2acca2..00000000000 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/health/HealthReducer.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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; - } -} diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/health/NodeHealthCheck.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/health/NodeHealthCheck.java deleted file mode 100644 index 814db0df67c..00000000000 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/health/NodeHealthCheck.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * 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(); -} diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/health/WebServerSafemodeNodeCheck.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/health/WebServerSafemodeNodeCheck.java deleted file mode 100644 index 762055d184d..00000000000 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/health/WebServerSafemodeNodeCheck.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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; - } -} diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/health/WebServerStatusNodeCheck.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/health/WebServerStatusNodeCheck.java deleted file mode 100644 index 490f41ee804..00000000000 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/health/WebServerStatusNodeCheck.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * 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 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(); - } -} diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/platform/ws/HealthCheckerModule.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/platform/ws/HealthCheckerModule.java index 8aa53d337c5..fe872529374 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/platform/ws/HealthCheckerModule.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/platform/ws/HealthCheckerModule.java @@ -20,13 +20,13 @@ 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 { diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/platform/ws/LivenessActionSupport.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/platform/ws/LivenessActionSupport.java index 31cbed8d6c9..278ccdb495c 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/platform/ws/LivenessActionSupport.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/platform/ws/LivenessActionSupport.java @@ -21,6 +21,7 @@ package org.sonar.server.platform.ws; import org.sonar.api.server.ws.Response; import org.sonar.api.server.ws.WebService; +import org.sonar.server.common.platform.LivenessChecker; public class LivenessActionSupport { diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/platform/ws/LivenessChecker.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/platform/ws/LivenessChecker.java deleted file mode 100644 index 372114b9b9a..00000000000 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/platform/ws/LivenessChecker.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * 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(); -} diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/platform/ws/LivenessCheckerImpl.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/platform/ws/LivenessCheckerImpl.java deleted file mode 100644 index 415b4897e79..00000000000 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/platform/ws/LivenessCheckerImpl.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * 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; - } -} diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/platform/ws/SafeModeHealthCheckerModule.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/platform/ws/SafeModeHealthCheckerModule.java index d9a37ba5680..894d7fa3daf 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/platform/ws/SafeModeHealthCheckerModule.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/platform/ws/SafeModeHealthCheckerModule.java @@ -20,10 +20,10 @@ 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 diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/platform/ws/SafeModeLivenessCheckerImpl.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/platform/ws/SafeModeLivenessCheckerImpl.java deleted file mode 100644 index 508045f5961..00000000000 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/platform/ws/SafeModeLivenessCheckerImpl.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * 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()); - } -} diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/platform/ws/SafemodeSystemWsModule.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/platform/ws/SafemodeSystemWsModule.java index 93dcd16ac39..b5dbb0c7940 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/platform/ws/SafemodeSystemWsModule.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/platform/ws/SafemodeSystemWsModule.java @@ -20,6 +20,7 @@ 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; diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/platform/ws/SystemWsModule.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/platform/ws/SystemWsModule.java index 641d4f63430..b1a19ee8660 100644 --- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/platform/ws/SystemWsModule.java +++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/platform/ws/SystemWsModule.java @@ -20,6 +20,7 @@ package org.sonar.server.platform.ws; import org.sonar.core.platform.Module; +import org.sonar.server.common.platform.LivenessCheckerImpl; public class SystemWsModule extends Module { diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/health/AppNodeClusterCheckTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/health/AppNodeClusterCheckTest.java deleted file mode 100644 index 8320c967c79..00000000000 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/health/AppNodeClusterCheckTest.java +++ /dev/null @@ -1,285 +0,0 @@ -/* - * 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 randomNumberOfAppNodeHealthOfAnyStatus(NodeHealth.Status... randomStatuses) { - return IntStream.range(0, random.nextInt(10)) - .mapToObj(i -> appNodeHealth(randomStatuses[random.nextInt(randomStatuses.length)])); - } - - private Stream 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(); - } - -} diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/health/CeStatusNodeCheckTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/health/CeStatusNodeCheckTest.java deleted file mode 100644 index 9482bfffe44..00000000000 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/health/CeStatusNodeCheckTest.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * 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"); - } -} diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/health/DbConnectionNodeCheckTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/health/DbConnectionNodeCheckTest.java deleted file mode 100644 index 7a414bbc1d8..00000000000 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/health/DbConnectionNodeCheckTest.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * 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"); - } -} diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/health/EsStatusClusterCheckTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/health/EsStatusClusterCheckTest.java deleted file mode 100644 index 0d8c4d53eee..00000000000 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/health/EsStatusClusterCheckTest.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * 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 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 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 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 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(); - } - -} diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/health/EsStatusNodeCheckTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/health/EsStatusNodeCheckTest.java deleted file mode 100644 index 8eb1c981ab9..00000000000 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/health/EsStatusNodeCheckTest.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * 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); - } - -} diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/health/HealthAssert.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/health/HealthAssert.java deleted file mode 100644 index d46c309d4b7..00000000000 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/health/HealthAssert.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * 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 { - private Set nodeHealths; - - private HealthAssert(Health actual) { - super(actual, HealthAssert.class); - } - - public static HealthAssert assertThat(Health actual) { - return new HealthAssert(actual); - } - - public HealthAssert forInput(Set 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 nodeHealths) { - if (nodeHealths == null) { - return ""; - } - return nodeHealths.stream() - // sort by type then status for debugging convenience - .sorted(Comparator.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()); - } -} diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/health/HealthCheckerImplTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/health/HealthCheckerImplTest.java index 1b266b5b0ac..f3a801041ab 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/health/HealthCheckerImplTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/health/HealthCheckerImplTest.java @@ -32,6 +32,8 @@ import org.junit.Test; 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; diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/health/WebServerSafemodeNodeCheckTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/health/WebServerSafemodeNodeCheckTest.java deleted file mode 100644 index 6d221cf4e36..00000000000 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/health/WebServerSafemodeNodeCheckTest.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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"); - - } -} diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/health/WebServerStatusNodeCheckTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/health/WebServerStatusNodeCheckTest.java deleted file mode 100644 index a5eb21e4de7..00000000000 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/health/WebServerStatusNodeCheckTest.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * 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"); - } -} diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/platform/ws/HealthCheckerModuleTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/platform/ws/HealthCheckerModuleTest.java index dcf45b6565d..5c886f2d115 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/platform/ws/HealthCheckerModuleTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/platform/ws/HealthCheckerModuleTest.java @@ -24,15 +24,15 @@ import java.util.Random; 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; diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/platform/ws/LivenessActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/platform/ws/LivenessActionTest.java index 1576563b406..cf6b474efd2 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/platform/ws/LivenessActionTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/platform/ws/LivenessActionTest.java @@ -22,6 +22,7 @@ package org.sonar.server.platform.ws; 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; diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/platform/ws/LivenessCheckerImplTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/platform/ws/LivenessCheckerImplTest.java deleted file mode 100644 index 4a22cad54d5..00000000000 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/platform/ws/LivenessCheckerImplTest.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * 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(); - } -} diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/platform/ws/SafeModeHealthCheckerModuleTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/platform/ws/SafeModeHealthCheckerModuleTest.java index 0d13606641e..1c786d8e35e 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/platform/ws/SafeModeHealthCheckerModuleTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/platform/ws/SafeModeHealthCheckerModuleTest.java @@ -23,11 +23,11 @@ import java.util.List; 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; diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/platform/ws/SafeModeLivenessActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/platform/ws/SafeModeLivenessActionTest.java index 56a4011d197..ca11d326bd7 100644 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/platform/ws/SafeModeLivenessActionTest.java +++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/platform/ws/SafeModeLivenessActionTest.java @@ -21,6 +21,7 @@ package org.sonar.server.platform.ws; 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; diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/platform/ws/SafeModeLivenessCheckerImplTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/platform/ws/SafeModeLivenessCheckerImplTest.java deleted file mode 100644 index e0bfc60c30d..00000000000 --- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/platform/ws/SafeModeLivenessCheckerImplTest.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * 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(); - } - -}