]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-10020 Refactor system/info WS
authorDuarte Meneses <duarte.meneses@sonarsource.com>
Tue, 24 Oct 2017 13:54:17 +0000 (15:54 +0200)
committerDuarte Meneses <duarte.meneses@sonarsource.com>
Tue, 24 Oct 2017 15:47:37 +0000 (17:47 +0200)
server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/WebSystemInfoModule.java
server/sonar-server/src/main/java/org/sonar/server/platform/ws/BaseInfoWsAction.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/platform/ws/ClusterInfoAction.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/platform/ws/ClusterSystemInfoWriter.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/platform/ws/InfoAction.java
server/sonar-server/src/main/java/org/sonar/server/platform/ws/StandaloneSystemInfoWriter.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/platform/ws/SystemInfoWriter.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/platform/ws/ClusterSystemInfoWriterTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/platform/ws/InfoActionTest.java
server/sonar-server/src/test/java/org/sonar/server/platform/ws/StandaloneSystemInfoWriterTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/platform/ws/SystemWsTest.java

index fa3a32f3b673385a9ccac1366bfbe7b261eaeaae..fc4914604142e0e8002c2930660fcbeba793fc18 100644 (file)
@@ -30,8 +30,9 @@ import org.sonar.server.platform.monitoring.cluster.NodeSystemSection;
 import org.sonar.server.platform.monitoring.cluster.ProcessInfoProvider;
 import org.sonar.server.platform.monitoring.cluster.EsClusterStateSection;
 import org.sonar.server.platform.monitoring.cluster.SearchNodesInfoLoaderImpl;
-import org.sonar.server.platform.ws.ClusterInfoAction;
+import org.sonar.server.platform.ws.ClusterSystemInfoWriter;
 import org.sonar.server.platform.ws.InfoAction;
+import org.sonar.server.platform.ws.StandaloneSystemInfoWriter;
 
 public class WebSystemInfoModule {
 
@@ -53,7 +54,7 @@ public class WebSystemInfoModule {
       StandaloneSystemSection.class,
 
       OfficialDistribution.class,
-
+      StandaloneSystemInfoWriter.class,
       InfoAction.class
     };
   }
@@ -79,7 +80,8 @@ public class WebSystemInfoModule {
       GlobalInfoLoader.class,
       AppNodesInfoLoaderImpl.class,
       SearchNodesInfoLoaderImpl.class,
-      ClusterInfoAction.class
+      ClusterSystemInfoWriter.class,
+      InfoAction.class
     };
   }
 }
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/ws/BaseInfoWsAction.java b/server/sonar-server/src/main/java/org/sonar/server/platform/ws/BaseInfoWsAction.java
deleted file mode 100644 (file)
index f6c8ff5..0000000
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * 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.platform.ws;
-
-import java.util.Collection;
-import org.sonar.api.server.ws.Request;
-import org.sonar.api.server.ws.Response;
-import org.sonar.api.server.ws.WebService;
-import org.sonar.api.utils.text.JsonWriter;
-import org.sonar.process.systeminfo.SystemInfoUtils;
-import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
-import org.sonar.server.health.Health;
-import org.sonar.server.telemetry.TelemetryDataLoader;
-import org.sonar.server.user.UserSession;
-
-import static org.sonar.server.telemetry.TelemetryDataJsonWriter.writeTelemetryData;
-
-public abstract class BaseInfoWsAction implements SystemWsAction {
-
-  private static final String[] ORDERED_SECTION_NAMES = {
-    // standalone
-    "System", "Database", "Plugins",
-
-    // cluster
-    "Web JVM State", "Web Database Connection", "Web Logging", "Web JVM Properties",
-    "Compute Engine Tasks", "Compute Engine JVM State", "Compute Engine Database Connection", "Compute Engine Logging", "Compute Engine JVM Properties",
-    "Search State", "Search Indexes"};
-
-  private final UserSession userSession;
-  private final TelemetryDataLoader telemetry;
-
-  public BaseInfoWsAction(UserSession userSession, TelemetryDataLoader telemetry) {
-    this.userSession = userSession;
-    this.telemetry = telemetry;
-  }
-
-  @Override
-  public void define(WebService.NewController controller) {
-    controller.createAction("info")
-      .setDescription("Get detailed information about system configuration.<br/>" +
-        "Requires 'Administer' permissions.<br/>" +
-        "Since 5.5, this web service becomes internal in order to more easily update result.")
-      .setSince("5.1")
-      .setInternal(true)
-      .setResponseExample(getClass().getResource("/org/sonar/server/platform/ws/info-example.json"))
-      .setHandler(this);
-  }
-
-  @Override
-  public void handle(Request request, Response response) throws InterruptedException {
-    userSession.checkIsSystemAdministrator();
-    doHandle(request, response);
-  }
-
-  protected abstract void doHandle(Request request, Response response) throws InterruptedException;
-
-  protected void writeSections(Collection<ProtobufSystemInfo.Section> sections, JsonWriter json) {
-    SystemInfoUtils
-      .order(sections, ORDERED_SECTION_NAMES)
-      .forEach(section -> writeSection(section, json));
-  }
-
-  private void writeSection(ProtobufSystemInfo.Section section, JsonWriter json) {
-    json.name(section.getName());
-    json.beginObject();
-    for (ProtobufSystemInfo.Attribute attribute : section.getAttributesList()) {
-      writeAttribute(attribute, json);
-    }
-    json.endObject();
-  }
-
-  private void writeAttribute(ProtobufSystemInfo.Attribute attribute, JsonWriter json) {
-    switch (attribute.getValueCase()) {
-      case BOOLEAN_VALUE:
-        json.prop(attribute.getKey(), attribute.getBooleanValue());
-        break;
-      case LONG_VALUE:
-        json.prop(attribute.getKey(), attribute.getLongValue());
-        break;
-      case DOUBLE_VALUE:
-        json.prop(attribute.getKey(), attribute.getDoubleValue());
-        break;
-      case STRING_VALUE:
-        json.prop(attribute.getKey(), attribute.getStringValue());
-        break;
-      case VALUE_NOT_SET:
-        json.name(attribute.getKey()).beginArray().values(attribute.getStringValuesList()).endArray();
-        break;
-      default:
-        throw new IllegalArgumentException("Unsupported type: " + attribute.getValueCase());
-    }
-  }
-
-  protected void writeHealth(Health health, JsonWriter json) {
-    json.prop("Health", health.getStatus().name());
-    json.name("Health Causes").beginArray().values(health.getCauses()).endArray();
-  }
-
-  protected void writeTelemetry(JsonWriter json) {
-    json.name("Statistics");
-    writeTelemetryData(json, telemetry.load());
-  }
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/ws/ClusterInfoAction.java b/server/sonar-server/src/main/java/org/sonar/server/platform/ws/ClusterInfoAction.java
deleted file mode 100644 (file)
index 35c4386..0000000
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * 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.platform.ws;
-
-import java.util.Collection;
-import org.sonar.api.server.ws.Request;
-import org.sonar.api.server.ws.Response;
-import org.sonar.api.utils.text.JsonWriter;
-import org.sonar.server.health.ClusterHealth;
-import org.sonar.server.health.HealthChecker;
-import org.sonar.server.platform.monitoring.cluster.AppNodesInfoLoader;
-import org.sonar.server.platform.monitoring.cluster.GlobalInfoLoader;
-import org.sonar.server.platform.monitoring.cluster.NodeInfo;
-import org.sonar.server.platform.monitoring.cluster.SearchNodesInfoLoader;
-import org.sonar.server.telemetry.TelemetryDataLoader;
-import org.sonar.server.user.UserSession;
-
-public class ClusterInfoAction extends BaseInfoWsAction {
-
-  private final GlobalInfoLoader globalInfoLoader;
-  private final AppNodesInfoLoader appNodesInfoLoader;
-  private final SearchNodesInfoLoader searchNodesInfoLoader;
-  private final HealthChecker healthChecker;
-
-  public ClusterInfoAction(UserSession userSession, GlobalInfoLoader globalInfoLoader,
-    AppNodesInfoLoader appNodesInfoLoader, SearchNodesInfoLoader searchNodesInfoLoader,
-    HealthChecker healthChecker, TelemetryDataLoader telemetry) {
-    super(userSession, telemetry);
-    this.globalInfoLoader = globalInfoLoader;
-    this.appNodesInfoLoader = appNodesInfoLoader;
-    this.searchNodesInfoLoader = searchNodesInfoLoader;
-    this.healthChecker = healthChecker;
-  }
-
-  @Override
-  protected void doHandle(Request request, Response response) throws InterruptedException {
-    ClusterHealth clusterHealth = healthChecker.checkCluster();
-    try (JsonWriter json = response.newJsonWriter()) {
-      json.beginObject();
-
-      writeHealth(clusterHealth.getHealth(), json);
-      writeGlobalSections(json);
-      writeApplicationNodes(json, clusterHealth);
-      writeSearchNodes(json, clusterHealth);
-      writeTelemetry(json);
-
-      json.endObject();
-    }
-  }
-
-  private void writeGlobalSections(JsonWriter json) {
-    writeSections(globalInfoLoader.load(), json);
-  }
-
-  private void writeApplicationNodes(JsonWriter json, ClusterHealth clusterHealth)
-    throws InterruptedException {
-    json.name("Application Nodes").beginArray();
-
-    Collection<NodeInfo> appNodes = appNodesInfoLoader.load();
-    for (NodeInfo applicationNode : appNodes) {
-      writeNodeInfo(applicationNode, clusterHealth, json);
-    }
-    json.endArray();
-  }
-
-  private void writeSearchNodes(JsonWriter json, ClusterHealth clusterHealth) {
-    json.name("Search Nodes").beginArray();
-
-    Collection<NodeInfo> searchNodes = searchNodesInfoLoader.load();
-    searchNodes.forEach(node -> writeNodeInfo(node, clusterHealth, json));
-    json.endArray();
-  }
-
-  private void writeNodeInfo(NodeInfo nodeInfo, ClusterHealth clusterHealth, JsonWriter json) {
-    json.beginObject();
-    json.prop("Name", nodeInfo.getName());
-    json.prop("Error", nodeInfo.getErrorMessage().orElse(null));
-    json.prop("Host", nodeInfo.getHost().orElse(null));
-    json.prop("Started At", nodeInfo.getStartedAt().orElse(null));
-
-    clusterHealth.getNodeHealth(nodeInfo.getName()).ifPresent(h -> {
-      json.prop("Health", h.getStatus().name());
-      json.name("Health Causes").beginArray().values(h.getCauses()).endArray();
-    });
-
-    writeSections(nodeInfo.getSections(), json);
-    json.endObject();
-  }
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/ws/ClusterSystemInfoWriter.java b/server/sonar-server/src/main/java/org/sonar/server/platform/ws/ClusterSystemInfoWriter.java
new file mode 100644 (file)
index 0000000..cae9721
--- /dev/null
@@ -0,0 +1,95 @@
+/*
+ * 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.platform.ws;
+
+import java.util.Collection;
+import org.sonar.api.utils.text.JsonWriter;
+import org.sonar.server.health.ClusterHealth;
+import org.sonar.server.health.HealthChecker;
+import org.sonar.server.platform.monitoring.cluster.AppNodesInfoLoader;
+import org.sonar.server.platform.monitoring.cluster.GlobalInfoLoader;
+import org.sonar.server.platform.monitoring.cluster.NodeInfo;
+import org.sonar.server.platform.monitoring.cluster.SearchNodesInfoLoader;
+import org.sonar.server.telemetry.TelemetryDataLoader;
+
+public class ClusterSystemInfoWriter extends SystemInfoWriter {
+  private final GlobalInfoLoader globalInfoLoader;
+  private final AppNodesInfoLoader appNodesInfoLoader;
+  private final SearchNodesInfoLoader searchNodesInfoLoader;
+  private final HealthChecker healthChecker;
+
+  public ClusterSystemInfoWriter(GlobalInfoLoader globalInfoLoader, AppNodesInfoLoader appNodesInfoLoader, SearchNodesInfoLoader searchNodesInfoLoader,
+    HealthChecker healthChecker, TelemetryDataLoader telemetry) {
+    super(telemetry);
+    this.globalInfoLoader = globalInfoLoader;
+    this.appNodesInfoLoader = appNodesInfoLoader;
+    this.searchNodesInfoLoader = searchNodesInfoLoader;
+    this.healthChecker = healthChecker;
+  }
+
+  @Override
+  public void write(JsonWriter json) throws InterruptedException {
+    ClusterHealth clusterHealth = healthChecker.checkCluster();
+    writeHealth(clusterHealth.getHealth(), json);
+    writeGlobalSections(json);
+    writeApplicationNodes(json, clusterHealth);
+    writeSearchNodes(json, clusterHealth);
+    writeTelemetry(json);
+  }
+
+  private void writeGlobalSections(JsonWriter json) {
+    writeSections(globalInfoLoader.load(), json);
+  }
+
+  private void writeApplicationNodes(JsonWriter json, ClusterHealth clusterHealth) throws InterruptedException {
+    json.name("Application Nodes").beginArray();
+
+    Collection<NodeInfo> appNodes = appNodesInfoLoader.load();
+    for (NodeInfo applicationNode : appNodes) {
+      writeNodeInfo(applicationNode, clusterHealth, json);
+    }
+    json.endArray();
+  }
+
+  private void writeSearchNodes(JsonWriter json, ClusterHealth clusterHealth) {
+    json.name("Search Nodes").beginArray();
+
+    Collection<NodeInfo> searchNodes = searchNodesInfoLoader.load();
+    searchNodes.forEach(node -> writeNodeInfo(node, clusterHealth, json));
+    json.endArray();
+  }
+
+  private void writeNodeInfo(NodeInfo nodeInfo, ClusterHealth clusterHealth, JsonWriter json) {
+    json.beginObject();
+    json.prop("Name", nodeInfo.getName());
+    json.prop("Error", nodeInfo.getErrorMessage().orElse(null));
+    json.prop("Host", nodeInfo.getHost().orElse(null));
+    json.prop("Started At", nodeInfo.getStartedAt().orElse(null));
+
+    clusterHealth.getNodeHealth(nodeInfo.getName()).ifPresent(h -> {
+      json.prop("Health", h.getStatus().name());
+      json.name("Health Causes").beginArray().values(h.getCauses()).endArray();
+    });
+
+    writeSections(nodeInfo.getSections(), json);
+    json.endObject();
+  }
+
+}
index 7bcc238d7408e58200f9512dc42ef1305b67468f..177d26965c121a9ab7c9a7dbae4c0140c5c0a2f1 100644 (file)
  */
 package org.sonar.server.platform.ws;
 
-import java.util.List;
 import org.sonar.api.server.ws.Request;
 import org.sonar.api.server.ws.Response;
+import org.sonar.api.server.ws.WebService;
 import org.sonar.api.utils.text.JsonWriter;
-import org.sonar.ce.http.CeHttpClient;
-import org.sonar.core.util.stream.MoreCollectors;
-import org.sonar.process.systeminfo.SystemInfoSection;
-import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
-import org.sonar.server.health.Health;
-import org.sonar.server.health.HealthChecker;
-import org.sonar.server.telemetry.TelemetryDataLoader;
 import org.sonar.server.user.UserSession;
 
-import static java.util.Arrays.stream;
-
 /**
  * Implementation of the {@code info} action for the System WebService.
  */
-public class InfoAction extends BaseInfoWsAction {
+public class InfoAction implements SystemWsAction {
+  private final SystemInfoWriter systemInfoWriter;
+  private final UserSession userSession;
+
+  public InfoAction(UserSession userSession, SystemInfoWriter systemInfoWriter) {
+    this.userSession = userSession;
+    this.systemInfoWriter = systemInfoWriter;
+  }
 
-  private final CeHttpClient ceHttpClient;
-  private final SystemInfoSection[] systemInfoSections;
-  private final HealthChecker healthChecker;
+  @Override
+  public void define(WebService.NewController controller) {
+    controller.createAction("info")
+      .setDescription("Get detailed information about system configuration.<br/>" +
+        "Requires 'Administer' permissions.<br/>" +
+        "Since 5.5, this web service becomes internal in order to more easily update result.")
+      .setSince("5.1")
+      .setInternal(true)
+      .setResponseExample(getClass().getResource("/org/sonar/server/platform/ws/info-example.json"))
+      .setHandler(this);
 
-  public InfoAction(UserSession userSession, TelemetryDataLoader telemetry, CeHttpClient ceHttpClient, HealthChecker healthChecker,
-    SystemInfoSection... systemInfoSections) {
-    super(userSession, telemetry);
-    this.ceHttpClient = ceHttpClient;
-    this.healthChecker = healthChecker;
-    this.systemInfoSections = systemInfoSections;
   }
 
   @Override
-  protected void doHandle(Request request, Response response) {
+  public void handle(Request request, Response response) throws Exception {
+    userSession.checkIsSystemAdministrator();
     try (JsonWriter json = response.newJsonWriter()) {
-      writeJson(json);
+      json.beginObject();
+      systemInfoWriter.write(json);
+      json.endObject();
     }
   }
 
-  private void writeJson(JsonWriter json) {
-    json.beginObject();
-
-    writeHealth(json);
-
-    List<ProtobufSystemInfo.Section> sections = stream(systemInfoSections)
-      .map(SystemInfoSection::toProtobuf)
-      .collect(MoreCollectors.toArrayList());
-    ceHttpClient.retrieveSystemInfo()
-      .ifPresent(ce -> sections.addAll(ce.getSectionsList()));
-
-    writeSections(sections, json);
-    writeTelemetry(json);
-
-    json.endObject();
-  }
-
-  private void writeHealth(JsonWriter json) {
-    Health health = healthChecker.checkNode();
-    writeHealth(health, json);
-  }
 }
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/ws/StandaloneSystemInfoWriter.java b/server/sonar-server/src/main/java/org/sonar/server/platform/ws/StandaloneSystemInfoWriter.java
new file mode 100644 (file)
index 0000000..5eb0636
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * 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.platform.ws;
+
+import java.util.List;
+import org.sonar.api.utils.text.JsonWriter;
+import org.sonar.ce.http.CeHttpClient;
+import org.sonar.core.util.stream.MoreCollectors;
+import org.sonar.process.systeminfo.SystemInfoSection;
+import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
+import org.sonar.server.health.Health;
+import org.sonar.server.health.HealthChecker;
+import org.sonar.server.telemetry.TelemetryDataLoader;
+
+import static java.util.Arrays.stream;
+
+public class StandaloneSystemInfoWriter extends SystemInfoWriter {
+  private final CeHttpClient ceHttpClient;
+  private final HealthChecker healthChecker;
+  private final SystemInfoSection[] systemInfoSections;
+
+  public StandaloneSystemInfoWriter(TelemetryDataLoader telemetry, CeHttpClient ceHttpClient, HealthChecker healthChecker, SystemInfoSection... systemInfoSections) {
+    super(telemetry);
+    this.ceHttpClient = ceHttpClient;
+    this.healthChecker = healthChecker;
+    this.systemInfoSections = systemInfoSections;
+  }
+
+  @Override
+  public void write(JsonWriter json) {
+    writeHealth(json);
+
+    List<ProtobufSystemInfo.Section> sections = stream(systemInfoSections)
+      .map(SystemInfoSection::toProtobuf)
+      .collect(MoreCollectors.toArrayList());
+    ceHttpClient.retrieveSystemInfo()
+      .ifPresent(ce -> sections.addAll(ce.getSectionsList()));
+
+    writeSections(sections, json);
+    writeTelemetry(json);
+  }
+
+  private void writeHealth(JsonWriter json) {
+    Health health = healthChecker.checkNode();
+    writeHealth(health, json);
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/ws/SystemInfoWriter.java b/server/sonar-server/src/main/java/org/sonar/server/platform/ws/SystemInfoWriter.java
new file mode 100644 (file)
index 0000000..caad58b
--- /dev/null
@@ -0,0 +1,95 @@
+/*
+ * 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.platform.ws;
+
+import java.util.Collection;
+import org.sonar.api.utils.text.JsonWriter;
+import org.sonar.process.systeminfo.SystemInfoUtils;
+import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
+import org.sonar.server.health.Health;
+import org.sonar.server.telemetry.TelemetryDataLoader;
+
+import static org.sonar.server.telemetry.TelemetryDataJsonWriter.writeTelemetryData;
+
+public abstract class SystemInfoWriter {
+  private static final String[] ORDERED_SECTION_NAMES = {
+    // standalone
+    "System", "Database", "Plugins",
+
+    // cluster
+    "Web JVM State", "Web Database Connection", "Web Logging", "Web JVM Properties",
+    "Compute Engine Tasks", "Compute Engine JVM State", "Compute Engine Database Connection", "Compute Engine Logging", "Compute Engine JVM Properties",
+    "Search State", "Search Indexes"};
+
+  private final TelemetryDataLoader telemetry;
+
+  SystemInfoWriter(TelemetryDataLoader telemetry) {
+    this.telemetry = telemetry;
+  }
+
+  public abstract void write(JsonWriter json) throws Exception;
+
+  protected void writeSections(Collection<ProtobufSystemInfo.Section> sections, JsonWriter json) {
+    SystemInfoUtils
+      .order(sections, ORDERED_SECTION_NAMES)
+      .forEach(section -> writeSection(section, json));
+  }
+
+  private void writeSection(ProtobufSystemInfo.Section section, JsonWriter json) {
+    json.name(section.getName());
+    json.beginObject();
+    for (ProtobufSystemInfo.Attribute attribute : section.getAttributesList()) {
+      writeAttribute(attribute, json);
+    }
+    json.endObject();
+  }
+
+  private void writeAttribute(ProtobufSystemInfo.Attribute attribute, JsonWriter json) {
+    switch (attribute.getValueCase()) {
+      case BOOLEAN_VALUE:
+        json.prop(attribute.getKey(), attribute.getBooleanValue());
+        break;
+      case LONG_VALUE:
+        json.prop(attribute.getKey(), attribute.getLongValue());
+        break;
+      case DOUBLE_VALUE:
+        json.prop(attribute.getKey(), attribute.getDoubleValue());
+        break;
+      case STRING_VALUE:
+        json.prop(attribute.getKey(), attribute.getStringValue());
+        break;
+      case VALUE_NOT_SET:
+        json.name(attribute.getKey()).beginArray().values(attribute.getStringValuesList()).endArray();
+        break;
+      default:
+        throw new IllegalArgumentException("Unsupported type: " + attribute.getValueCase());
+    }
+  }
+
+  protected void writeHealth(Health health, JsonWriter json) {
+    json.prop("Health", health.getStatus().name());
+    json.name("Health Causes").beginArray().values(health.getCauses()).endArray();
+  }
+
+  protected void writeTelemetry(JsonWriter json) {
+    json.name("Statistics");
+    writeTelemetryData(json, telemetry.load());
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/ws/ClusterSystemInfoWriterTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/ws/ClusterSystemInfoWriterTest.java
new file mode 100644 (file)
index 0000000..c8eafa4
--- /dev/null
@@ -0,0 +1,87 @@
+/*
+ * 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.platform.ws;
+
+import java.io.StringWriter;
+import java.util.Collections;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.sonar.api.utils.text.JsonWriter;
+import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo.Attribute;
+import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo.Section;
+import org.sonar.server.health.ClusterHealth;
+import org.sonar.server.health.Health;
+import org.sonar.server.health.HealthChecker;
+import org.sonar.server.platform.monitoring.cluster.AppNodesInfoLoader;
+import org.sonar.server.platform.monitoring.cluster.GlobalInfoLoader;
+import org.sonar.server.platform.monitoring.cluster.NodeInfo;
+import org.sonar.server.platform.monitoring.cluster.SearchNodesInfoLoader;
+import org.sonar.server.telemetry.TelemetryDataLoader;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class ClusterSystemInfoWriterTest {
+  private GlobalInfoLoader globalInfoLoader = mock(GlobalInfoLoader.class);
+  private AppNodesInfoLoader appNodesInfoLoader = mock(AppNodesInfoLoader.class);
+  private SearchNodesInfoLoader searchNodesInfoLoader = mock(SearchNodesInfoLoader.class);
+  private HealthChecker healthChecker = mock(HealthChecker.class);
+  private TelemetryDataLoader telemetry = mock(TelemetryDataLoader.class, Mockito.RETURNS_MOCKS);
+  private ClusterSystemInfoWriter underTest = new ClusterSystemInfoWriter(globalInfoLoader, appNodesInfoLoader, searchNodesInfoLoader, healthChecker, telemetry);
+
+  @Before
+  public void before() throws InterruptedException {
+    when(globalInfoLoader.load()).thenReturn(Collections.singletonList(createSection("globalInfo")));
+    when(appNodesInfoLoader.load()).thenReturn(Collections.singletonList(createNodeInfo("appNodes")));
+    when(searchNodesInfoLoader.load()).thenReturn(Collections.singletonList(createNodeInfo("searchNodes")));
+    Health health = Health.newHealthCheckBuilder().setStatus(Health.Status.GREEN).build();
+    when(healthChecker.checkCluster()).thenReturn(new ClusterHealth(health, Collections.emptySet()));
+  }
+
+  @Test
+  public void writeInfo() throws InterruptedException {
+    StringWriter writer = new StringWriter();
+    JsonWriter jsonWriter = JsonWriter.of(writer);
+    jsonWriter.beginObject();
+    underTest.write(jsonWriter);
+    jsonWriter.endObject();
+
+    assertThat(writer.toString()).isEqualTo("{\"Health\":\"GREEN\","
+      + "\"Health Causes\":[],\"\":{\"name\":\"globalInfo\"},"
+      + "\"Application Nodes\":[{\"Name\":\"appNodes\",\"\":{\"name\":\"appNodes\"}}],"
+      + "\"Search Nodes\":[{\"Name\":\"searchNodes\",\"\":{\"name\":\"searchNodes\"}}],"
+      + "\"Statistics\":{\"id\":\"\",\"version\":\"\",\"database\":{\"name\":\"\",\"version\":\"\"},\"plugins\":[],"
+      + "\"userCount\":0,\"projectCount\":0,\"usingBranches\":false,\"lines\":0,\"ncloc\":0,\"projectCountByLanguage\":[],\"nclocByLanguage\":[]}}");
+  }
+
+  private static NodeInfo createNodeInfo(String name) {
+    NodeInfo info = new NodeInfo(name);
+    info.addSection(createSection(name));
+    return info;
+  }
+
+  private static Section createSection(String name) {
+    return Section.newBuilder()
+      .addAttributes(Attribute.newBuilder().setKey("name").setStringValue(name).build())
+      .build();
+  }
+}
index 389a013d44a3af8291e737a77ca01b9d416df2a0..9d3b0c4d00d4d20bfda40a5adb3d1976efc39a33 100644 (file)
  */
 package org.sonar.server.platform.ws;
 
-import java.util.Optional;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
-import org.mockito.Mockito;
-import org.sonar.ce.http.CeHttpClient;
-import org.sonar.ce.http.CeHttpClientImpl;
-import org.sonar.process.systeminfo.SystemInfoSection;
-import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
+import org.sonar.api.utils.text.JsonWriter;
 import org.sonar.server.exceptions.ForbiddenException;
-import org.sonar.server.health.TestStandaloneHealthChecker;
-import org.sonar.server.telemetry.TelemetryDataLoader;
 import org.sonar.server.tester.UserSessionRule;
 import org.sonar.server.ws.TestResponse;
 import org.sonar.server.ws.WsActionTester;
 
 import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-import static org.sonar.process.systeminfo.SystemInfoUtils.setAttribute;
 
 public class InfoActionTest {
   @Rule
@@ -48,13 +38,13 @@ public class InfoActionTest {
   @Rule
   public ExpectedException expectedException = ExpectedException.none();
 
-  private SystemInfoSection section1 = mock(SystemInfoSection.class);
-  private SystemInfoSection section2 = mock(SystemInfoSection.class);
-  private CeHttpClient ceHttpClient = mock(CeHttpClientImpl.class, Mockito.RETURNS_MOCKS);
-  private TestStandaloneHealthChecker healthChecker = new TestStandaloneHealthChecker();
-  private TelemetryDataLoader telemetry = mock(TelemetryDataLoader.class, Mockito.RETURNS_MOCKS);
-
-  private InfoAction underTest = new InfoAction(userSessionRule, telemetry, ceHttpClient, healthChecker, section1, section2);
+  private SystemInfoWriter jsonWriter = new SystemInfoWriter(null) {
+    @Override
+    public void write(JsonWriter json) throws Exception {
+      json.prop("key", "value");
+    }
+  };
+  private InfoAction underTest = new InfoAction(userSessionRule, jsonWriter);
   private WsActionTester ws = new WsActionTester(underTest);
 
   @Test
@@ -85,23 +75,8 @@ public class InfoActionTest {
   public void write_json() {
     logInAsSystemAdministrator();
 
-    ProtobufSystemInfo.Section.Builder attributes1 = ProtobufSystemInfo.Section.newBuilder()
-      .setName("Section One");
-    setAttribute(attributes1, "foo", "bar");
-    when(section1.toProtobuf()).thenReturn(attributes1.build());
-
-    ProtobufSystemInfo.Section.Builder attributes2 = ProtobufSystemInfo.Section.newBuilder()
-      .setName("Section Two");
-    setAttribute(attributes2, "one", 1);
-    setAttribute(attributes2, "two", 2);
-    when(section2.toProtobuf()).thenReturn(attributes2.build());
-    when(ceHttpClient.retrieveSystemInfo()).thenReturn(Optional.empty());
-
     TestResponse response = ws.newRequest().execute();
-    // response does not contain empty "Section Three"
-    assertThat(response.getInput()).isEqualTo("{\"Health\":\"GREEN\",\"Health Causes\":[],\"Section One\":{\"foo\":\"bar\"},\"Section Two\":{\"one\":1,\"two\":2}," +
-      "\"Statistics\":{\"id\":\"\",\"version\":\"\",\"database\":{\"name\":\"\",\"version\":\"\"},\"plugins\":[],\"userCount\":0,\"projectCount\":0,\"usingBranches\":false," +
-      "\"lines\":0,\"ncloc\":0,\"projectCountByLanguage\":[],\"nclocByLanguage\":[]}}");
+    assertThat(response.getInput()).isEqualTo("{\"key\":\"value\"}");
   }
 
   private void logInAsSystemAdministrator() {
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/ws/StandaloneSystemInfoWriterTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/ws/StandaloneSystemInfoWriterTest.java
new file mode 100644 (file)
index 0000000..095a86d
--- /dev/null
@@ -0,0 +1,88 @@
+/*
+ * 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.platform.ws;
+
+import java.io.StringWriter;
+import java.util.Optional;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.mockito.Mockito;
+import org.sonar.api.utils.text.JsonWriter;
+import org.sonar.ce.http.CeHttpClient;
+import org.sonar.ce.http.CeHttpClientImpl;
+import org.sonar.process.systeminfo.SystemInfoSection;
+import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
+import org.sonar.server.health.TestStandaloneHealthChecker;
+import org.sonar.server.telemetry.TelemetryDataLoader;
+import org.sonar.server.tester.UserSessionRule;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.sonar.process.systeminfo.SystemInfoUtils.setAttribute;
+
+public class StandaloneSystemInfoWriterTest {
+  @Rule
+  public UserSessionRule userSessionRule = UserSessionRule.standalone()
+    .logIn("login")
+    .setName("name");
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  private SystemInfoSection section1 = mock(SystemInfoSection.class);
+  private SystemInfoSection section2 = mock(SystemInfoSection.class);
+  private CeHttpClient ceHttpClient = mock(CeHttpClientImpl.class, Mockito.RETURNS_MOCKS);
+  private TestStandaloneHealthChecker healthChecker = new TestStandaloneHealthChecker();
+  private TelemetryDataLoader telemetry = mock(TelemetryDataLoader.class, Mockito.RETURNS_MOCKS);
+
+  private StandaloneSystemInfoWriter underTest = new StandaloneSystemInfoWriter(telemetry, ceHttpClient, healthChecker, section1, section2);
+
+  @Test
+  public void write_json() {
+    logInAsSystemAdministrator();
+
+    ProtobufSystemInfo.Section.Builder attributes1 = ProtobufSystemInfo.Section.newBuilder()
+      .setName("Section One");
+    setAttribute(attributes1, "foo", "bar");
+    when(section1.toProtobuf()).thenReturn(attributes1.build());
+
+    ProtobufSystemInfo.Section.Builder attributes2 = ProtobufSystemInfo.Section.newBuilder()
+      .setName("Section Two");
+    setAttribute(attributes2, "one", 1);
+    setAttribute(attributes2, "two", 2);
+    when(section2.toProtobuf()).thenReturn(attributes2.build());
+    when(ceHttpClient.retrieveSystemInfo()).thenReturn(Optional.empty());
+
+    StringWriter writer = new StringWriter();
+    JsonWriter jsonWriter = JsonWriter.of(writer);
+    jsonWriter.beginObject();
+    underTest.write(jsonWriter);
+    jsonWriter.endObject();
+    // response does not contain empty "Section Three"
+    assertThat(writer.toString()).isEqualTo("{\"Health\":\"GREEN\",\"Health Causes\":[],\"Section One\":{\"foo\":\"bar\"},\"Section Two\":{\"one\":1,\"two\":2}," +
+      "\"Statistics\":{\"id\":\"\",\"version\":\"\",\"database\":{\"name\":\"\",\"version\":\"\"},\"plugins\":[],\"userCount\":0,\"projectCount\":0,\"usingBranches\":false," +
+      "\"lines\":0,\"ncloc\":0,\"projectCountByLanguage\":[],\"nclocByLanguage\":[]}}");
+  }
+
+  private void logInAsSystemAdministrator() {
+    userSessionRule.logIn().setSystemAdministrator();
+  }
+}
index 441c239822f8461cbf6cdad26279b9ad3c5376f4..14181fa0940cd758476cfbf674a06d52abe2f44c 100644 (file)
@@ -21,14 +21,9 @@ package org.sonar.server.platform.ws;
 
 import org.junit.Test;
 import org.sonar.api.server.ws.WebService;
-import org.sonar.ce.http.CeHttpClient;
-import org.sonar.ce.http.CeHttpClientImpl;
 import org.sonar.server.app.ProcessCommandWrapper;
 import org.sonar.server.app.RestartFlagHolder;
-import org.sonar.server.health.HealthChecker;
-import org.sonar.server.health.TestStandaloneHealthChecker;
 import org.sonar.server.platform.WebServer;
-import org.sonar.server.telemetry.TelemetryDataLoader;
 import org.sonar.server.tester.AnonymousMockUserSession;
 import org.sonar.server.user.UserSession;
 
@@ -37,14 +32,11 @@ import static org.mockito.Mockito.mock;
 
 public class SystemWsTest {
 
-  private CeHttpClient ceHttpClient = mock(CeHttpClientImpl.class);
-  private HealthChecker healthChecker = new TestStandaloneHealthChecker();
-
   @Test
   public void define() {
     RestartAction action1 = new RestartAction(mock(UserSession.class), mock(ProcessCommandWrapper.class),
       mock(RestartFlagHolder.class), mock(WebServer.class));
-    InfoAction action2 = new InfoAction(new AnonymousMockUserSession(), mock(TelemetryDataLoader.class), ceHttpClient, healthChecker);
+    InfoAction action2 = new InfoAction(new AnonymousMockUserSession(), mock(SystemInfoWriter.class));
     SystemWs ws = new SystemWs(action1, action2);
     WebService.Context context = new WebService.Context();