From 72c6c208735094891d46b35445956805bff37c8d Mon Sep 17 00:00:00 2001 From: =?utf8?q?S=C3=A9bastien=20Lesaint?= Date: Fri, 19 Aug 2016 17:46:01 +0200 Subject: [PATCH] SONAR-7825 alter CE HTTP server for general use --- ...Module.java => CeConfigurationModule.java} | 16 +--- .../main/java/org/sonar/ce/CeHttpModule.java | 33 ++++++++ .../org/sonar/ce/CeTaskCommonsModule.java | 38 +++++++++ .../container/ComputeEngineContainerImpl.java | 10 ++- .../org/sonar/ce/httpd/CeHttpServer.java} | 78 ++++++++++++----- .../java/org/sonar/ce/httpd/HttpAction.java | 51 +++++++++++ .../java/org/sonar/ce/httpd/package-info.java | 23 +++++ .../ce/systeminfo/SystemInfoHttpAction.java | 57 +++++++++++++ .../org/sonar/ce/systeminfo/package-info.java | 23 +++++ .../ComputeEngineContainerImplTest.java | 7 +- .../org/sonar/ce/httpd/CeHttpServerTest.java} | 84 ++++++++++++++----- .../systeminfo/SystemInfoHttpActionTest.java | 70 ++++++++++++++++ .../monitoring/ProcessSystemInfoClient.java | 2 +- 13 files changed, 432 insertions(+), 60 deletions(-) rename server/sonar-ce/src/main/java/org/sonar/ce/{CeModule.java => CeConfigurationModule.java} (71%) create mode 100644 server/sonar-ce/src/main/java/org/sonar/ce/CeHttpModule.java create mode 100644 server/sonar-ce/src/main/java/org/sonar/ce/CeTaskCommonsModule.java rename server/{sonar-process/src/main/java/org/sonar/process/systeminfo/SystemInfoHttpServer.java => sonar-ce/src/main/java/org/sonar/ce/httpd/CeHttpServer.java} (51%) create mode 100644 server/sonar-ce/src/main/java/org/sonar/ce/httpd/HttpAction.java create mode 100644 server/sonar-ce/src/main/java/org/sonar/ce/httpd/package-info.java create mode 100644 server/sonar-ce/src/main/java/org/sonar/ce/systeminfo/SystemInfoHttpAction.java create mode 100644 server/sonar-ce/src/main/java/org/sonar/ce/systeminfo/package-info.java rename server/{sonar-process/src/test/java/org/sonar/process/systeminfo/SystemInfoHttpServerTest.java => sonar-ce/src/test/java/org/sonar/ce/httpd/CeHttpServerTest.java} (51%) create mode 100644 server/sonar-ce/src/test/java/org/sonar/ce/systeminfo/SystemInfoHttpActionTest.java diff --git a/server/sonar-ce/src/main/java/org/sonar/ce/CeModule.java b/server/sonar-ce/src/main/java/org/sonar/ce/CeConfigurationModule.java similarity index 71% rename from server/sonar-ce/src/main/java/org/sonar/ce/CeModule.java rename to server/sonar-ce/src/main/java/org/sonar/ce/CeConfigurationModule.java index 03797845073..7d4f81bf98c 100644 --- a/server/sonar-ce/src/main/java/org/sonar/ce/CeModule.java +++ b/server/sonar-ce/src/main/java/org/sonar/ce/CeConfigurationModule.java @@ -21,29 +21,17 @@ package org.sonar.ce; import org.sonar.ce.log.CeLogging; import org.sonar.core.platform.Module; -import org.sonar.db.purge.period.DefaultPeriodCleaner; import org.sonar.process.systeminfo.ProcessStateSystemInfo; -import org.sonar.process.systeminfo.SystemInfoHttpServer; import org.sonar.server.computation.configuration.CeConfigurationImpl; -import org.sonar.server.computation.dbcleaner.IndexPurgeListener; -import org.sonar.server.computation.dbcleaner.ProjectCleaner; import org.sonar.server.computation.monitoring.CeDatabaseMBeanImpl; -/** - * Globally available components in CE - */ -public class CeModule extends Module { +public class CeConfigurationModule extends Module { @Override protected void configureModule() { add( CeConfigurationImpl.class, CeLogging.class, CeDatabaseMBeanImpl.class, - SystemInfoHttpServer.class, - new ProcessStateSystemInfo("Compute Engine State"), - - DefaultPeriodCleaner.class, - ProjectCleaner.class, - IndexPurgeListener.class); + new ProcessStateSystemInfo("Compute Engine State")); } } diff --git a/server/sonar-ce/src/main/java/org/sonar/ce/CeHttpModule.java b/server/sonar-ce/src/main/java/org/sonar/ce/CeHttpModule.java new file mode 100644 index 00000000000..69afca02955 --- /dev/null +++ b/server/sonar-ce/src/main/java/org/sonar/ce/CeHttpModule.java @@ -0,0 +1,33 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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.ce; + +import org.sonar.ce.httpd.CeHttpServer; +import org.sonar.ce.systeminfo.SystemInfoHttpAction; +import org.sonar.core.platform.Module; + +public class CeHttpModule extends Module { + @Override + protected void configureModule() { + add( + CeHttpServer.class, + SystemInfoHttpAction.class); + } +} diff --git a/server/sonar-ce/src/main/java/org/sonar/ce/CeTaskCommonsModule.java b/server/sonar-ce/src/main/java/org/sonar/ce/CeTaskCommonsModule.java new file mode 100644 index 00000000000..1f062b82f22 --- /dev/null +++ b/server/sonar-ce/src/main/java/org/sonar/ce/CeTaskCommonsModule.java @@ -0,0 +1,38 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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.ce; + +import org.sonar.core.platform.Module; +import org.sonar.db.purge.period.DefaultPeriodCleaner; +import org.sonar.server.computation.dbcleaner.IndexPurgeListener; +import org.sonar.server.computation.dbcleaner.ProjectCleaner; + +/** + * Globally available components in CE for tasks to use. + */ +public class CeTaskCommonsModule extends Module { + @Override + protected void configureModule() { + add( + DefaultPeriodCleaner.class, + ProjectCleaner.class, + IndexPurgeListener.class); + } +} diff --git a/server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java b/server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java index 1acda5bf67a..7d6524dcf48 100644 --- a/server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java +++ b/server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java @@ -39,6 +39,10 @@ import org.sonar.api.utils.Durations; import org.sonar.api.utils.System2; import org.sonar.api.utils.UriReader; import org.sonar.api.utils.Version; +import org.sonar.ce.CeConfigurationModule; +import org.sonar.ce.CeHttpModule; +import org.sonar.ce.CeQueueModule; +import org.sonar.ce.CeTaskCommonsModule; import org.sonar.ce.db.ReadOnlyPropertiesDao; import org.sonar.ce.es.EsIndexerEnabler; import org.sonar.ce.platform.ComputeEngineExtensionInstaller; @@ -69,8 +73,6 @@ import org.sonar.server.activity.index.ActivityIndexer; import org.sonar.server.component.ComponentCleanerService; import org.sonar.server.component.ComponentFinder; import org.sonar.server.component.ComponentService; -import org.sonar.ce.CeModule; -import org.sonar.ce.CeQueueModule; import org.sonar.server.computation.task.projectanalysis.ProjectAnalysisTaskModule; import org.sonar.server.computation.taskprocessor.CeTaskProcessorModule; import org.sonar.server.debt.DebtModelPluginRepository; @@ -622,8 +624,10 @@ public class ComputeEngineContainerImpl implements ComputeEngineContainer { PrivilegedPluginsStopper.class, // Compute engine (must be after Views and Developer Cockpit) - CeModule.class, + CeConfigurationModule.class, CeQueueModule.class, + CeHttpModule.class, + CeTaskCommonsModule.class, ProjectAnalysisTaskModule.class, CeTaskProcessorModule.class, // CeWsModule.class, no Web Service in CE diff --git a/server/sonar-process/src/main/java/org/sonar/process/systeminfo/SystemInfoHttpServer.java b/server/sonar-ce/src/main/java/org/sonar/ce/httpd/CeHttpServer.java similarity index 51% rename from server/sonar-process/src/main/java/org/sonar/process/systeminfo/SystemInfoHttpServer.java rename to server/sonar-ce/src/main/java/org/sonar/ce/httpd/CeHttpServer.java index b8c08f3b90c..2a90c8192e6 100644 --- a/server/sonar-process/src/main/java/org/sonar/process/systeminfo/SystemInfoHttpServer.java +++ b/server/sonar-ce/src/main/java/org/sonar/ce/httpd/CeHttpServer.java @@ -17,21 +17,28 @@ * 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.process.systeminfo; +package org.sonar.ce.httpd; import fi.iki.elonen.NanoHTTPD; -import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.net.InetAddress; import java.net.UnknownHostException; +import java.util.HashMap; import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; import java.util.Properties; import org.slf4j.LoggerFactory; import org.sonar.process.DefaultProcessCommands; -import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo; +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkState; +import static fi.iki.elonen.NanoHTTPD.Response.Status.INTERNAL_ERROR; +import static fi.iki.elonen.NanoHTTPD.Response.Status.NOT_FOUND; import static java.lang.Integer.parseInt; +import static java.util.Objects.requireNonNull; import static org.sonar.process.ProcessEntryPoint.PROPERTY_PROCESS_INDEX; import static org.sonar.process.ProcessEntryPoint.PROPERTY_SHARED_PATH; @@ -39,24 +46,25 @@ import static org.sonar.process.ProcessEntryPoint.PROPERTY_SHARED_PATH; * This HTTP server exports data required for display of System Info page (and the related web service). * It listens on loopback address only, so it does not need to be secure (no HTTPS, no authentication). */ -public class SystemInfoHttpServer { - - private static final String PROTOBUF_MIME_TYPE = "application/x-protobuf"; +public class CeHttpServer { private final Properties processProps; - private final List sectionProviders; - private final SystemInfoNanoHttpd nanoHttpd; + private final List actions; + private final ActionRegistryImpl actionRegistry; + private final CeNanoHttpd nanoHttpd; - public SystemInfoHttpServer(Properties processProps, List sectionProviders) throws UnknownHostException { + public CeHttpServer(Properties processProps, List actions) throws UnknownHostException { this.processProps = processProps; - this.sectionProviders = sectionProviders; + this.actions = actions; + this.actionRegistry = new ActionRegistryImpl(); InetAddress loopbackAddress = InetAddress.getByName(null); - this.nanoHttpd = new SystemInfoNanoHttpd(loopbackAddress.getHostAddress(), 0); + this.nanoHttpd = new CeNanoHttpd(loopbackAddress.getHostAddress(), 0, actionRegistry); } // do not rename. This naming convention is required for picocontainer. public void start() { try { + registerActions(); nanoHttpd.start(); registerHttpUrl(); } catch (IOException e) { @@ -64,6 +72,10 @@ public class SystemInfoHttpServer { } } + private void registerActions() { + actions.forEach(action -> action.register(this.actionRegistry)); + } + private void registerHttpUrl() { int processNumber = parseInt(processProps.getProperty(PROPERTY_PROCESS_INDEX)); File shareDir = new File(processProps.getProperty(PROPERTY_SHARED_PATH)); @@ -84,21 +96,49 @@ public class SystemInfoHttpServer { return "http://" + nanoHttpd.getHostname() + ":" + nanoHttpd.getListeningPort(); } - private class SystemInfoNanoHttpd extends NanoHTTPD { + private class CeNanoHttpd extends NanoHTTPD { + private final NanoHTTPD.Response response404 = newFixedLengthResponse(NOT_FOUND, MIME_PLAINTEXT, "Error 404, not found."); + + private final ActionRegistryImpl actionRegistry; - SystemInfoNanoHttpd(String hostname, int port) { + CeNanoHttpd(String hostname, int port, ActionRegistryImpl actionRegistry) { super(hostname, port); + this.actionRegistry = actionRegistry; } @Override public Response serve(IHTTPSession session) { - ProtobufSystemInfo.SystemInfo.Builder infoBuilder = ProtobufSystemInfo.SystemInfo.newBuilder(); - for (SystemInfoSection sectionProvider : sectionProviders) { - ProtobufSystemInfo.Section section = sectionProvider.toProtobuf(); - infoBuilder.addSections(section); + return actionRegistry.getAction(session) + .map(action -> serveFromAction(session, action)) + .orElse(response404); + } + + private Response serveFromAction(IHTTPSession session, HttpAction action) { + try { + return action.serve(session); + } catch (Exception e) { + return newFixedLengthResponse(INTERNAL_ERROR, MIME_PLAINTEXT, e.getMessage()); } - byte[] bytes = infoBuilder.build().toByteArray(); - return newFixedLengthResponse(Response.Status.OK, PROTOBUF_MIME_TYPE, new ByteArrayInputStream(bytes), bytes.length); + } + } + + private static final class ActionRegistryImpl implements HttpAction.ActionRegistry { + private final Map actionsByPath = new HashMap<>(); + + @Override + public void register(String path, HttpAction action) { + requireNonNull(path, "path can't be null"); + requireNonNull(action, "action can't be null"); + checkArgument(!path.isEmpty(), "path can't be empty"); + checkArgument(!path.startsWith("/"), "path must not start with '/'"); + String fixedPath = path.toLowerCase(Locale.ENGLISH); + HttpAction existingAction = actionsByPath.put(fixedPath, action); + checkState(existingAction == null, "Action '%s' already registered for path '%s'", existingAction, fixedPath); + } + + Optional getAction(NanoHTTPD.IHTTPSession session) { + String path = session.getUri().substring(1).toLowerCase(Locale.ENGLISH); + return Optional.ofNullable(actionsByPath.get(path)); } } } diff --git a/server/sonar-ce/src/main/java/org/sonar/ce/httpd/HttpAction.java b/server/sonar-ce/src/main/java/org/sonar/ce/httpd/HttpAction.java new file mode 100644 index 00000000000..c4d633269a1 --- /dev/null +++ b/server/sonar-ce/src/main/java/org/sonar/ce/httpd/HttpAction.java @@ -0,0 +1,51 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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.ce.httpd; + +import fi.iki.elonen.NanoHTTPD.IHTTPSession; +import fi.iki.elonen.NanoHTTPD.Response; + +/** + * a Http action of the CE's HTTP server handles a request for a specified path. + * + *

+ * Method {@link #register(ActionRegistry)} of the action will be called right before the HTTP server is started (server + * is started by the Pico Container). It's the action's responsability to call the method + * {@link ActionRegistry#register(ActionRegistry)} to register itself for a given path. + *

+ *

+ * Method {@link #serve(IHTTPSession)} will be called each time a request matching the path the action registered itself + * for. + *

+ */ +public interface HttpAction { + void register(ActionRegistry registry); + + Response serve(IHTTPSession session); + + interface ActionRegistry { + /** + * @throws NullPointerException if {@code path} of {@code action} is {@code null} + * @throws IllegalArgumentException if {@code path} is empty or starts with {@code /} + * @throws IllegalStateException if an action is already registered for the specified path (case is ignored) + */ + void register(String path, HttpAction action); + } +} diff --git a/server/sonar-ce/src/main/java/org/sonar/ce/httpd/package-info.java b/server/sonar-ce/src/main/java/org/sonar/ce/httpd/package-info.java new file mode 100644 index 00000000000..43a83872081 --- /dev/null +++ b/server/sonar-ce/src/main/java/org/sonar/ce/httpd/package-info.java @@ -0,0 +1,23 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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. + */ +@ParametersAreNonnullByDefault +package org.sonar.ce.httpd; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/server/sonar-ce/src/main/java/org/sonar/ce/systeminfo/SystemInfoHttpAction.java b/server/sonar-ce/src/main/java/org/sonar/ce/systeminfo/SystemInfoHttpAction.java new file mode 100644 index 00000000000..59bba1c6e96 --- /dev/null +++ b/server/sonar-ce/src/main/java/org/sonar/ce/systeminfo/SystemInfoHttpAction.java @@ -0,0 +1,57 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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.ce.systeminfo; + +import fi.iki.elonen.NanoHTTPD; +import java.io.ByteArrayInputStream; +import java.util.List; +import org.sonar.ce.httpd.HttpAction; +import org.sonar.process.systeminfo.SystemInfoSection; +import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo; + +import static fi.iki.elonen.NanoHTTPD.newFixedLengthResponse; + +public class SystemInfoHttpAction implements HttpAction { + + private static final String PATH = "systemInfo"; + private static final String PROTOBUF_MIME_TYPE = "application/x-protobuf"; + + private final List sectionProviders; + + public SystemInfoHttpAction(List sectionProviders) { + this.sectionProviders = sectionProviders; + } + + @Override + public void register(ActionRegistry registry) { + registry.register(PATH, this); + } + + @Override + public NanoHTTPD.Response serve(NanoHTTPD.IHTTPSession session) { + ProtobufSystemInfo.SystemInfo.Builder infoBuilder = ProtobufSystemInfo.SystemInfo.newBuilder(); + for (SystemInfoSection sectionProvider : sectionProviders) { + ProtobufSystemInfo.Section section = sectionProvider.toProtobuf(); + infoBuilder.addSections(section); + } + byte[] bytes = infoBuilder.build().toByteArray(); + return newFixedLengthResponse(NanoHTTPD.Response.Status.OK, PROTOBUF_MIME_TYPE, new ByteArrayInputStream(bytes), bytes.length); + } +} diff --git a/server/sonar-ce/src/main/java/org/sonar/ce/systeminfo/package-info.java b/server/sonar-ce/src/main/java/org/sonar/ce/systeminfo/package-info.java new file mode 100644 index 00000000000..1cd7a4e100c --- /dev/null +++ b/server/sonar-ce/src/main/java/org/sonar/ce/systeminfo/package-info.java @@ -0,0 +1,23 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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. + */ +@ParametersAreNonnullByDefault +package org.sonar.ce.systeminfo; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java b/server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java index 22db63201ce..e220ecfb360 100644 --- a/server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java +++ b/server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java @@ -89,9 +89,10 @@ public class ComputeEngineContainerImplTest { .hasSize( CONTAINER_ITSELF + 75 // level 4 - + 6 // content of CeModule - + 7 // content of CeQueueModule - + 3 // content of ProjectAnalysisTaskModule + + 7 // content of CeConfigurationModule + + 2 // content of CeHttpModule + + 5 // content of CeQueueModule + + 4 // content of ProjectAnalysisTaskModule + 4 // content of CeTaskProcessorModule ); assertThat(picoContainer.getParent().getComponentAdapters()).hasSize( diff --git a/server/sonar-process/src/test/java/org/sonar/process/systeminfo/SystemInfoHttpServerTest.java b/server/sonar-ce/src/test/java/org/sonar/ce/httpd/CeHttpServerTest.java similarity index 51% rename from server/sonar-process/src/test/java/org/sonar/process/systeminfo/SystemInfoHttpServerTest.java rename to server/sonar-ce/src/test/java/org/sonar/ce/httpd/CeHttpServerTest.java index 04df2a6d850..2723279a291 100644 --- a/server/sonar-process/src/test/java/org/sonar/process/systeminfo/SystemInfoHttpServerTest.java +++ b/server/sonar-ce/src/test/java/org/sonar/ce/httpd/CeHttpServerTest.java @@ -17,45 +17,48 @@ * 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.process.systeminfo; +package org.sonar.ce.httpd; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.Response; +import fi.iki.elonen.NanoHTTPD; +import java.io.File; import java.io.IOException; import java.net.ConnectException; -import java.util.Arrays; import java.util.Properties; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import org.apache.commons.io.IOUtils; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.junit.rules.TemporaryFolder; -import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo; +import org.sonar.process.DefaultProcessCommands; +import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; import static org.sonar.process.ProcessEntryPoint.PROPERTY_PROCESS_INDEX; import static org.sonar.process.ProcessEntryPoint.PROPERTY_SHARED_PATH; -public class SystemInfoHttpServerTest { +public class CeHttpServerTest { @Rule public TemporaryFolder temp = new TemporaryFolder(); - @Rule public ExpectedException expectedException = ExpectedException.none(); - SystemInfoSection stateProvider1 = new ProcessStateSystemInfo("state1"); - SystemInfoSection stateProvider2 = new ProcessStateSystemInfo("state2"); - SystemInfoHttpServer underTest; + private CeHttpServer underTest; + private File sharedDir; @Before public void setUp() throws Exception { + this.sharedDir = temp.newFolder(); Properties properties = new Properties(); properties.setProperty(PROPERTY_PROCESS_INDEX, "1"); - properties.setProperty(PROPERTY_SHARED_PATH, temp.newFolder().getAbsolutePath()); - underTest = new SystemInfoHttpServer(properties, Arrays.asList(stateProvider1, stateProvider2)); + properties.setProperty(PROPERTY_SHARED_PATH, sharedDir.getAbsolutePath()); + underTest = new CeHttpServer(properties, singletonList(new PomPomAction())); + underTest.start(); } @After @@ -63,20 +66,49 @@ public class SystemInfoHttpServerTest { underTest.stop(); } + @Test + public void start_publishes_URL_in_IPC() throws Exception { + try (DefaultProcessCommands commands = DefaultProcessCommands.secondary(this.sharedDir, 1)) { + assertThat(commands.getSystemInfoUrl()).startsWith("http://127.0.0.1:"); + } + } + + @Test + public void unkownUrl_return_a_404() throws IOException { + assertThat(call(underTest.getUrl() + "/dfdsfdsfsdsd").code()).isEqualTo(404); + } + + @Test + public void action_is_matched_on_exact_URL() throws IOException { + Response response = call(underTest.getUrl() + "/pompom"); + assertIsPomPomResponse(response); + } + + @Test + public void action_is_matched_on_URL_ignoring_case() throws IOException { + Response response = call(underTest.getUrl() + "/pOMpoM"); + assertIsPomPomResponse(response); + } + + @Test + public void action_is_matched_on_URL_with_parameters() throws IOException { + Response response = call(underTest.getUrl() + "/pompom?toto=2"); + assertIsPomPomResponse(response); + } + @Test public void start_starts_http_server_and_publishes_URL_in_IPC() throws Exception { - underTest.start(); - Response response = call(underTest.getUrl()); + Response response = call(underTest.getUrl() + "/pompom?toto=2"); + assertIsPomPomResponse(response); + } + + private void assertIsPomPomResponse(Response response) throws IOException { assertThat(response.code()).isEqualTo(200); - ProtobufSystemInfo.SystemInfo systemInfo = ProtobufSystemInfo.SystemInfo.parseFrom(response.body().bytes()); - assertThat(systemInfo.getSectionsCount()).isEqualTo(2); - assertThat(systemInfo.getSections(0).getName()).isEqualTo("state1"); - assertThat(systemInfo.getSections(1).getName()).isEqualTo("state2"); + assertThat(IOUtils.toString(response.body().byteStream())).isEqualTo("ok"); } @Test public void stop_stops_http_server() throws Exception { - underTest.start(); underTest.stop(); expectedException.expect(ConnectException.class); call(underTest.getUrl()); @@ -86,4 +118,16 @@ public class SystemInfoHttpServerTest { Request request = new Request.Builder().get().url(url).build(); return new OkHttpClient().newCall(request).execute(); } + + private static class PomPomAction implements HttpAction { + @Override + public void register(ActionRegistry registry) { + registry.register("pompom", this); + } + + @Override + public NanoHTTPD.Response serve(NanoHTTPD.IHTTPSession session) { + return NanoHTTPD.newFixedLengthResponse(NanoHTTPD.Response.Status.OK, NanoHTTPD.MIME_PLAINTEXT, "ok"); + } + } } diff --git a/server/sonar-ce/src/test/java/org/sonar/ce/systeminfo/SystemInfoHttpActionTest.java b/server/sonar-ce/src/test/java/org/sonar/ce/systeminfo/SystemInfoHttpActionTest.java new file mode 100644 index 00000000000..cf0d5b31974 --- /dev/null +++ b/server/sonar-ce/src/test/java/org/sonar/ce/systeminfo/SystemInfoHttpActionTest.java @@ -0,0 +1,70 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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.ce.systeminfo; + +import fi.iki.elonen.NanoHTTPD; +import java.util.Arrays; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.ce.httpd.HttpAction; +import org.sonar.process.systeminfo.ProcessStateSystemInfo; +import org.sonar.process.systeminfo.SystemInfoSection; +import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +public class SystemInfoHttpActionTest { + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private SystemInfoSection stateProvider1 = new ProcessStateSystemInfo("state1"); + private SystemInfoSection stateProvider2 = new ProcessStateSystemInfo("state2"); + private SystemInfoHttpAction underTest; + + @Before + public void setUp() throws Exception { + underTest = new SystemInfoHttpAction(Arrays.asList(stateProvider1, stateProvider2)); + } + + @Test + public void register_to_path_systemInfo() { + HttpAction.ActionRegistry actionRegistry = mock(HttpAction.ActionRegistry.class); + + underTest.register(actionRegistry); + + verify(actionRegistry).register("systemInfo", underTest); + } + + @Test + public void start_starts_http_server_and_publishes_URL_in_IPC() throws Exception { + NanoHTTPD.Response response = underTest.serve(mock(NanoHTTPD.IHTTPSession.class)); + assertThat(response.getStatus()).isEqualTo(NanoHTTPD.Response.Status.OK); + ProtobufSystemInfo.SystemInfo systemInfo = ProtobufSystemInfo.SystemInfo.parseFrom(response.getData()); + assertThat(systemInfo.getSectionsCount()).isEqualTo(2); + assertThat(systemInfo.getSections(0).getName()).isEqualTo("state1"); + assertThat(systemInfo.getSections(1).getName()).isEqualTo("state2"); + } + +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/ProcessSystemInfoClient.java b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/ProcessSystemInfoClient.java index 3968a2792a0..9e319f28651 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/ProcessSystemInfoClient.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/ProcessSystemInfoClient.java @@ -49,7 +49,7 @@ public class ProcessSystemInfoClient { public Optional connect(ProcessId processId) { try (DefaultProcessCommands commands = DefaultProcessCommands.secondary(ipcSharedDir, processId.getIpcIndex())) { if (commands.isUp()) { - String url = commands.getSystemInfoUrl(); + String url = commands.getSystemInfoUrl() + "/systemInfo"; byte[] protobuf = IOUtils.toByteArray(new URI(url)); return Optional.of(ProtobufSystemInfo.SystemInfo.parseFrom(protobuf)); } -- 2.39.5