From 1ef704d05605f22c736821c8960a697b4c10f75a Mon Sep 17 00:00:00 2001 From: =?utf8?q?S=C3=A9bastien=20Lesaint?= Date: Thu, 24 Aug 2017 15:21:26 +0200 Subject: [PATCH] SONAR-9739 add check of CE's operational status in api/system/health --- .../server/app/ProcessCommandWrapper.java | 5 ++ .../server/app/ProcessCommandWrapperImpl.java | 54 +++++++++---------- .../sonar/server/health/CeStatusCheck.java | 46 ++++++++++++++++ .../platform/ws/HealthActionModule.java | 2 + .../app/ProcessCommandWrapperImplTest.java | 18 +++++++ .../server/health/CeStatusCheckTest.java | 51 ++++++++++++++++++ .../platform/ws/HealthActionModuleTest.java | 12 +++-- 7 files changed, 156 insertions(+), 32 deletions(-) create mode 100644 server/sonar-server/src/main/java/org/sonar/server/health/CeStatusCheck.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/health/CeStatusCheckTest.java diff --git a/server/sonar-server/src/main/java/org/sonar/server/app/ProcessCommandWrapper.java b/server/sonar-server/src/main/java/org/sonar/server/app/ProcessCommandWrapper.java index 949417caad7..ee209ad53a0 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/app/ProcessCommandWrapper.java +++ b/server/sonar-server/src/main/java/org/sonar/server/app/ProcessCommandWrapper.java @@ -35,4 +35,9 @@ public interface ProcessCommandWrapper { */ void notifyOperational(); + /** + * Checks whether the Compute Engine is operational. + */ + boolean isCeOperational(); + } diff --git a/server/sonar-server/src/main/java/org/sonar/server/app/ProcessCommandWrapperImpl.java b/server/sonar-server/src/main/java/org/sonar/server/app/ProcessCommandWrapperImpl.java index 65732bd45c7..74e7b538b4e 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/app/ProcessCommandWrapperImpl.java +++ b/server/sonar-server/src/main/java/org/sonar/server/app/ProcessCommandWrapperImpl.java @@ -21,6 +21,7 @@ package org.sonar.server.app; import java.io.File; import org.sonar.api.config.Configuration; +import org.sonar.process.ProcessId; import org.sonar.process.sharedmemoryfile.DefaultProcessCommands; import org.sonar.process.sharedmemoryfile.ProcessCommands; @@ -28,6 +29,21 @@ import static org.sonar.process.ProcessEntryPoint.PROPERTY_PROCESS_INDEX; import static org.sonar.process.ProcessEntryPoint.PROPERTY_SHARED_PATH; public class ProcessCommandWrapperImpl implements ProcessCommandWrapper { + + private static final ProcessMethod SET_OPERATIONAL = processCommands -> { + processCommands.setOperational(); + return null; + }; + private static final ProcessMethod ASK_FOR_RESTART = processCommands -> { + processCommands.askForRestart(); + return null; + }; + private static final ProcessMethod ASK_FOR_STOP = processCommands -> { + processCommands.askForStop(); + return null; + }; + private static final ProcessMethod IS_OPERATIONAL = ProcessCommands::isOperational; + private final Configuration config; public ProcessCommandWrapperImpl(Configuration config) { @@ -36,53 +52,37 @@ public class ProcessCommandWrapperImpl implements ProcessCommandWrapper { @Override public void requestSQRestart() { - call(VoidMethod.ASK_FOR_RESTART, selfProcessNumber()); + call(ASK_FOR_RESTART, selfProcessNumber()); } @Override public void requestStop() { - call(VoidMethod.ASK_FOR_STOP, selfProcessNumber()); + call(ASK_FOR_STOP, selfProcessNumber()); } @Override public void notifyOperational() { - call(VoidMethod.SET_OPERATIONAL, selfProcessNumber()); + call(SET_OPERATIONAL, selfProcessNumber()); + } + + @Override + public boolean isCeOperational() { + return call(IS_OPERATIONAL, ProcessId.COMPUTE_ENGINE.getIpcIndex()); } private int selfProcessNumber() { return nonNullAsInt(PROPERTY_PROCESS_INDEX); } - private T call(VoidMethod command, int processNumber) { + private T call(ProcessMethod command, int processNumber) { File shareDir = nonNullValueAsFile(PROPERTY_SHARED_PATH); try (DefaultProcessCommands commands = DefaultProcessCommands.secondary(shareDir, processNumber)) { return command.callOn(commands); } } - private enum VoidMethod { - SET_OPERATIONAL() { - @Override - T callOn(ProcessCommands processCommands) { - processCommands.setOperational(); - return null; - } - }, - ASK_FOR_RESTART() { - @Override - T callOn(ProcessCommands processCommands) { - processCommands.askForRestart(); - return null; - } - }, - ASK_FOR_STOP() { - @Override - T callOn(ProcessCommands processCommands) { - processCommands.askForStop(); - return null; - } - }; - abstract T callOn(ProcessCommands processCommands); + private interface ProcessMethod { + T callOn(ProcessCommands processCommands); } private int nonNullAsInt(String key) { diff --git a/server/sonar-server/src/main/java/org/sonar/server/health/CeStatusCheck.java b/server/sonar-server/src/main/java/org/sonar/server/health/CeStatusCheck.java new file mode 100644 index 00000000000..1d457f416fd --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/health/CeStatusCheck.java @@ -0,0 +1,46 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 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; + +import static org.sonar.server.health.Health.newHealthCheckBuilder; + +public class CeStatusCheck implements HealthCheck { + private static final Health RED_HEALTH = newHealthCheckBuilder() + .setStatus(Health.Status.RED) + .addCause("Compute Engine is not operational") + .build(); + + private final ProcessCommandWrapper processCommandWrapper; + + public CeStatusCheck(ProcessCommandWrapper processCommandWrapper) { + this.processCommandWrapper = processCommandWrapper; + } + + @Override + public Health check() { + if (processCommandWrapper.isCeOperational()) { + return Health.GREEN; + } + + return RED_HEALTH; + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/ws/HealthActionModule.java b/server/sonar-server/src/main/java/org/sonar/server/platform/ws/HealthActionModule.java index 3e9b97aa487..8c9144b4407 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/ws/HealthActionModule.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/ws/HealthActionModule.java @@ -20,6 +20,7 @@ package org.sonar.server.platform.ws; import org.sonar.core.platform.Module; +import org.sonar.server.health.CeStatusCheck; import org.sonar.server.health.DbConnectionCheck; import org.sonar.server.health.EsStatusCheck; import org.sonar.server.health.HealthCheckerImpl; @@ -33,6 +34,7 @@ public class HealthActionModule extends Module { WebServerStatusCheck.class, DbConnectionCheck.class, EsStatusCheck.class, + CeStatusCheck.class, HealthCheckerImpl.class, diff --git a/server/sonar-server/src/test/java/org/sonar/server/app/ProcessCommandWrapperImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/app/ProcessCommandWrapperImplTest.java index f328f10de44..3bc51450aad 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/app/ProcessCommandWrapperImplTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/app/ProcessCommandWrapperImplTest.java @@ -21,6 +21,7 @@ package org.sonar.server.app; import java.io.File; import java.io.IOException; +import java.util.Random; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; @@ -137,4 +138,21 @@ public class ProcessCommandWrapperImplTest { } } + @Test + public void isCeOperational_reads_shared_memory_operational_flag_in_location_3() throws IOException { + File tmpDir = temp.newFolder().getAbsoluteFile(); + settings.setProperty(PROPERTY_SHARED_PATH, tmpDir.getAbsolutePath()); + + boolean expected = new Random().nextBoolean(); + if (expected) { + try (DefaultProcessCommands processCommands = DefaultProcessCommands.secondary(tmpDir, 3)) { + processCommands.setOperational(); + } + } + + ProcessCommandWrapperImpl underTest = new ProcessCommandWrapperImpl(settings.asConfig()); + + assertThat(underTest.isCeOperational()).isEqualTo(expected); + } + } diff --git a/server/sonar-server/src/test/java/org/sonar/server/health/CeStatusCheckTest.java b/server/sonar-server/src/test/java/org/sonar/server/health/CeStatusCheckTest.java new file mode 100644 index 00000000000..6de6f017331 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/health/CeStatusCheckTest.java @@ -0,0 +1,51 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 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 CeStatusCheckTest { + private ProcessCommandWrapper processCommandWrapper = mock(ProcessCommandWrapper.class); + private CeStatusCheck underTest = new CeStatusCheck(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-server/src/test/java/org/sonar/server/platform/ws/HealthActionModuleTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/ws/HealthActionModuleTest.java index 32d54bf4b1c..f1bdfec0e7c 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/platform/ws/HealthActionModuleTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/platform/ws/HealthActionModuleTest.java @@ -25,6 +25,7 @@ import java.util.stream.Collectors; import org.junit.Test; import org.picocontainer.ComponentAdapter; import org.sonar.core.platform.ComponentContainer; +import org.sonar.server.health.CeStatusCheck; import org.sonar.server.health.DbConnectionCheck; import org.sonar.server.health.EsStatusCheck; import org.sonar.server.health.HealthCheck; @@ -43,9 +44,9 @@ public class HealthActionModuleTest { underTest.configure(container); assertThat(classesAddedToContainer(container)) - .contains(HealthCheckerImpl.class) - .contains(HealthAction.class) - .doesNotContain(SafeModeHealthAction.class); + .contains(HealthCheckerImpl.class) + .contains(HealthAction.class) + .doesNotContain(SafeModeHealthAction.class); } @Test @@ -56,10 +57,11 @@ public class HealthActionModuleTest { List> checks = classesAddedToContainer(container).stream().filter(HealthCheck.class::isAssignableFrom).collect(Collectors.toList()); assertThat(checks) - .hasSize(3) + .hasSize(4) .contains(WebServerStatusCheck.class) .contains(DbConnectionCheck.class) - .contains(EsStatusCheck.class); + .contains(EsStatusCheck.class) + .contains(CeStatusCheck.class); } private List> classesAddedToContainer(ComponentContainer container) { -- 2.39.5