]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-9802 complete system info page
authorSimon Brandhof <simon.brandhof@sonarsource.com>
Tue, 19 Sep 2017 20:12:00 +0000 (22:12 +0200)
committerSimon Brandhof <simon.brandhof@sonarsource.com>
Tue, 26 Sep 2017 21:49:38 +0000 (23:49 +0200)
58 files changed:
server/sonar-ce/src/main/java/org/sonar/ce/CeQueueModule.java
server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java
server/sonar-ce/src/main/java/org/sonar/ce/monitoring/CeSystemInfoModule.java [deleted file]
server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java
server/sonar-main/pom.xml
server/sonar-process/pom.xml
server/sonar-process/src/test/java/org/sonar/process/cluster/hz/DistributedAnswerTest.java
server/sonar-server/src/main/java/org/sonar/server/cluster/StartableHazelcastMember.java
server/sonar-server/src/main/java/org/sonar/server/health/ClusterHealth.java
server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/EsStateSection.java
server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/StandaloneSystemSection.java
server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/WebSystemInfoModule.java
server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/cluster/AppNodesInfoLoaderImpl.java
server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/cluster/GlobalSystemSection.java
server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/cluster/LoggingSection.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/cluster/NodeInfo.java
server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/cluster/NodeSystemSection.java
server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/cluster/SearchNodesInfoLoader.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/cluster/SearchNodesInfoLoaderImpl.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java
server/sonar-server/src/main/java/org/sonar/server/platform/ws/BaseInfoWsAction.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/platform/ws/ClusterInfoAction.java
server/sonar-server/src/main/java/org/sonar/server/platform/ws/InfoAction.java
server/sonar-server/src/main/java/org/sonar/server/platform/ws/StandaloneInfoAction.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/cluster/StartableHazelcastMemberTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/health/ClusterHealthTest.java
server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/StandaloneSystemSectionTest.java
server/sonar-server/src/test/java/org/sonar/server/platform/ws/InfoActionTest.java
server/sonar-server/src/test/java/org/sonar/server/platform/ws/SystemWsTest.java
server/sonar-web/src/main/js/api/system.ts
server/sonar-web/src/main/js/apps/system/__tests__/utils-test.ts
server/sonar-web/src/main/js/apps/system/components/App.tsx
server/sonar-web/src/main/js/apps/system/components/ChangeLogLevelForm.tsx
server/sonar-web/src/main/js/apps/system/components/ClusterSysInfos.tsx
server/sonar-web/src/main/js/apps/system/components/StandaloneSysInfos.tsx
server/sonar-web/src/main/js/apps/system/components/__tests__/ClusterSysInfos-test.tsx
server/sonar-web/src/main/js/apps/system/components/__tests__/StandaloneSysInfos-test.tsx
server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/ChangeLogLevelForm-test.tsx.snap
server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/ClusterSysInfos-test.tsx.snap
server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/StandaloneSysInfos-test.tsx.snap
server/sonar-web/src/main/js/apps/system/components/info-items/HealthCard.tsx
server/sonar-web/src/main/js/apps/system/components/info-items/HealthCauseItem.tsx
server/sonar-web/src/main/js/apps/system/components/info-items/HealthItem.tsx
server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/HealthCard-test.tsx
server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/HealthCauseItem-test.tsx
server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/HealthItem-test.tsx
server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/HealthCard-test.tsx.snap
server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/HealthItem-test.tsx.snap
server/sonar-web/src/main/js/apps/system/utils.ts
server/sonar-web/src/main/js/components/common/StatusIndicator.css
tests/src/test/java/org/sonarqube/pageobjects/Navigation.java
tests/src/test/java/org/sonarqube/pageobjects/SystemInfoPage.java [new file with mode: 0644]
tests/src/test/java/org/sonarqube/pageobjects/SystemInfoPageItem.java [new file with mode: 0644]
tests/src/test/java/org/sonarqube/tests/cluster/ClusterTest.java
tests/src/test/java/org/sonarqube/tests/serverSystem/SystemInfoTest.java
tests/src/test/java/org/sonarqube/tests/user/RealmAuthenticationTest.java
tests/src/test/resources/serverSystem/ServerSystemTest/system_info.html [deleted file]
tests/src/test/resources/user/ExternalAuthenticationTest/system-info.html [deleted file]

index d8f37cd5e1126303814bb73f29bc74c8768ce9e9..2811aa16c44e5f0b5d89deff39b2c39de8b27382 100644 (file)
@@ -24,6 +24,8 @@ import org.sonar.ce.monitoring.CeTasksMBeanImpl;
 import org.sonar.ce.queue.CeQueueInitializer;
 import org.sonar.ce.queue.InternalCeQueueImpl;
 import org.sonar.core.platform.Module;
+import org.sonar.process.systeminfo.JvmPropertiesSection;
+import org.sonar.process.systeminfo.JvmStateSection;
 
 public class CeQueueModule extends Module {
   @Override
@@ -35,7 +37,9 @@ public class CeQueueModule extends Module {
       // queue monitoring
       CEQueueStatusImpl.class,
       CeTasksMBeanImpl.class,
-      
+      new JvmStateSection("Compute Engine JVM State"),
+      new JvmPropertiesSection("Compute Engine JVM Properties"),
+
       // init queue state and queue processing
       CeQueueInitializer.class);
   }
index 79f042caf3d5a40b0cb9bc20f21bd9125890f812..b62dca5b43002d443cab8bb7892c25d3e01ffc09 100644 (file)
@@ -49,7 +49,6 @@ import org.sonar.ce.StandaloneCeDistributedInformation;
 import org.sonar.ce.cleaning.CeCleaningModule;
 import org.sonar.ce.db.ReadOnlyPropertiesDao;
 import org.sonar.ce.log.CeProcessLogging;
-import org.sonar.ce.monitoring.CeSystemInfoModule;
 import org.sonar.ce.platform.ComputeEngineExtensionInstaller;
 import org.sonar.ce.queue.CeQueueCleaner;
 import org.sonar.ce.queue.PurgeCeActivities;
@@ -127,6 +126,9 @@ import org.sonar.server.platform.UrlSettings;
 import org.sonar.server.platform.WebServerImpl;
 import org.sonar.server.platform.db.migration.MigrationConfigurationModule;
 import org.sonar.server.platform.db.migration.version.DatabaseVersion;
+import org.sonar.server.platform.monitoring.DatabaseSection;
+import org.sonar.server.platform.monitoring.cluster.ProcessInfoProvider;
+import org.sonar.server.platform.monitoring.cluster.LoggingSection;
 import org.sonar.server.plugins.InstalledPluginReferentialFactory;
 import org.sonar.server.plugins.ServerExtensionInstaller;
 import org.sonar.server.plugins.privileged.PrivilegedPluginsBootstraper;
@@ -423,8 +425,14 @@ public class ComputeEngineContainerImpl implements ComputeEngineContainer {
     if (props.valueAsBoolean(ProcessProperties.CLUSTER_ENABLED)) {
       container.add(
         StartableHazelcastMember.class,
-        CeDistributedInformationImpl.class);
-      container.add(CeSystemInfoModule.forClusterMode());
+
+        // system health
+        CeDistributedInformationImpl.class,
+
+        // system info
+        DatabaseSection.class,
+        ProcessInfoProvider.class,
+        LoggingSection.class);
     } else {
       container.add(StandaloneCeDistributedInformation.class);
     }
diff --git a/server/sonar-ce/src/main/java/org/sonar/ce/monitoring/CeSystemInfoModule.java b/server/sonar-ce/src/main/java/org/sonar/ce/monitoring/CeSystemInfoModule.java
deleted file mode 100644 (file)
index be53383..0000000
+++ /dev/null
@@ -1,41 +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.ce.monitoring;
-
-import org.sonar.process.systeminfo.JvmPropertiesSection;
-import org.sonar.process.systeminfo.JvmStateSection;
-import org.sonar.server.platform.monitoring.DatabaseSection;
-import org.sonar.server.platform.monitoring.cluster.ProcessInfoProvider;
-
-public class CeSystemInfoModule {
-
-  private CeSystemInfoModule() {
-    // do not instantiate
-  }
-
-  public static Object[] forClusterMode() {
-    return new Object[] {
-      new JvmPropertiesSection("Compute Engine JVM Properties"),
-      new JvmStateSection("Compute Engine JVM State"),
-      DatabaseSection.class,
-      ProcessInfoProvider.class
-    };
-  }
-}
index 640b8e76c4349bdfcaaf2887e6250144ddda64e1..4cf1cac4434993295b9652abb4df6a7fd3d31243 100644 (file)
@@ -131,7 +131,7 @@ public class ComputeEngineContainerImplTest {
         CONTAINER_ITSELF
           + 73 // level 4
           + 4 // content of CeConfigurationModule
-          + 4 // content of CeQueueModule
+          + 6 // content of CeQueueModule
           + 4 // content of CeHttpModule
           + 3 // content of CeTaskCommonsModule
           + 4 // content of ProjectAnalysisTaskModule
index 27e46c81cfbce1318663f11ec0ec4bb42fadf8e2..eba72bb7f6291a7b288686957e2a6231702dc38c 100644 (file)
@@ -12,6 +12,8 @@
 
   <artifactId>sonar-main</artifactId>
   <name>SonarQube :: Main Process</name>
+  <description>Server process used to bootstrap Elasticsearch, Web Server and
+  Compute Engine processes. Could be merged with sonar-application.</description>
 
   <properties>
     <!--
index 58adf12de382700eda8d7322ff270254e742bc41..00811e45e566293fb49d09a6a9998e26bbb89c55 100644 (file)
@@ -10,6 +10,7 @@
 
   <artifactId>sonar-process</artifactId>
   <name>SonarQube :: Process</name>
+  <description>Library shared by all kinds of server processes: main, web and compute engine</description>
 
   <properties>
     <sonar.exclusions>target/generated-sources/**/*</sonar.exclusions>
index 6c9d8270a19cede68166821a0e45ed99d01c9ac2..2cdf3d5dd752d4eb4ec298e4a2f522fba99b16f6 100644 (file)
@@ -21,7 +21,9 @@ package org.sonar.process.cluster.hz;
 
 import com.hazelcast.core.Member;
 import java.io.IOException;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.ExpectedException;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.Mockito.mock;
@@ -30,9 +32,21 @@ import static org.mockito.Mockito.when;
 
 public class DistributedAnswerTest {
 
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
   private Member member = newMember("member1");
   private DistributedAnswer underTest = new DistributedAnswer();
 
+  @Test
+  public void getMembers_return_all_members() {
+    underTest.setAnswer(member, "foo");
+    underTest.setTimedOut(newMember("bar"));
+    underTest.setFailed(newMember("baz"), new IOException("BOOM"));
+
+    assertThat(underTest.getMembers()).hasSize(3);
+  }
+
   @Test
   public void test_call_with_unknown_member() {
     assertThat(underTest.getAnswer(member)).isEmpty();
@@ -46,6 +60,7 @@ public class DistributedAnswerTest {
 
     assertThat(underTest.getAnswer(member)).hasValue("foo");
     assertThat(underTest.hasTimedOut(member)).isFalse();
+    assertThat(underTest.getFailed(member)).isEmpty();
   }
 
   @Test
@@ -54,6 +69,7 @@ public class DistributedAnswerTest {
 
     assertThat(underTest.getAnswer(member)).isEmpty();
     assertThat(underTest.hasTimedOut(member)).isTrue();
+    assertThat(underTest.getFailed(member)).isEmpty();
   }
 
   @Test
@@ -61,6 +77,8 @@ public class DistributedAnswerTest {
     IOException e = new IOException();
     underTest.setFailed(member, e);
 
+    assertThat(underTest.getAnswer(member)).isEmpty();
+    assertThat(underTest.hasTimedOut(member)).isFalse();
     assertThat(underTest.getFailed(member)).hasValue(e);
   }
 
@@ -76,9 +94,46 @@ public class DistributedAnswerTest {
     assertThat(underTest.getFailed(member)).hasValue(exception);
   }
 
+  @Test
+  public void propagateExceptions_does_nothing_if_no_members() {
+    // no errors
+    underTest.propagateExceptions();
+  }
+
+  @Test
+  public void propagateExceptions_does_nothing_if_no_errors() {
+    underTest.setAnswer(newMember("foo"), "bar");
+
+    // no errors
+    underTest.propagateExceptions();
+  }
+
+  @Test
+  public void propagateExceptions_throws_ISE_if_at_least_one_timeout() {
+    underTest.setAnswer(newMember("bar"), "baz");
+    underTest.setTimedOut(newMember("foo"));
+
+    expectedException.expect(IllegalStateException.class);
+    expectedException.expectMessage("Distributed cluster action timed out in cluster nodes foo");
+
+    underTest.propagateExceptions();
+  }
+
+  @Test
+  public void propagateExceptions_throws_ISE_if_at_least_one_failure() {
+    underTest.setAnswer(newMember("bar"), "baz");
+    underTest.setFailed(newMember("foo"), new IOException("BOOM"));
+
+    expectedException.expect(IllegalStateException.class);
+    expectedException.expectMessage("Distributed cluster action in cluster nodes foo (other nodes may have timed out)");
+
+    underTest.propagateExceptions();
+  }
+
   private static Member newMember(String uuid) {
     Member member = mock(Member.class);
     when(member.getUuid()).thenReturn(uuid);
+    when(member.getStringAttribute(HazelcastMember.Attribute.NODE_NAME)).thenReturn(uuid);
     return member;
   }
 }
index e746c21435dc26b765ce0cac2b6d1ba60988c01a..1e430e17d293c56999dbc6922d0f617c1556fcf4 100644 (file)
@@ -22,7 +22,6 @@ package org.sonar.server.cluster;
 import com.hazelcast.core.Cluster;
 import com.hazelcast.core.IAtomicReference;
 import com.hazelcast.core.MemberSelector;
-import java.net.InetAddress;
 import java.net.UnknownHostException;
 import java.util.List;
 import java.util.Map;
@@ -55,12 +54,12 @@ import static org.sonar.process.ProcessProperties.CLUSTER_NODE_TYPE;
 public class StartableHazelcastMember implements HazelcastMember, Startable {
 
   private final Configuration config;
-  private final NetworkUtils networkUtils;
+  private final NetworkUtils network;
   private HazelcastMember member = null;
 
-  public StartableHazelcastMember(Configuration config, NetworkUtils networkUtils) {
+  public StartableHazelcastMember(Configuration config, NetworkUtils network) {
     this.config = config;
-    this.networkUtils = networkUtils;
+    this.network = network;
   }
 
   @Override
@@ -136,7 +135,7 @@ public class StartableHazelcastMember implements HazelcastMember, Startable {
     String networkAddress = config.get(CLUSTER_NODE_HOST).orElseThrow(() -> new IllegalStateException("Missing node host"));
     int freePort;
     try {
-      freePort = networkUtils.getNextAvailablePort(InetAddress.getByName(networkAddress));
+      freePort = network.getNextAvailablePort(network.toInetAddress(networkAddress));
     } catch (UnknownHostException e) {
       throw new IllegalStateException(format("Can not resolve address %s", networkAddress), e);
     }
index 26a54ce0bcf42cb692651004660349be06678838..53d75a4dede959a60e77ab60f3494a0d109790d3 100644 (file)
@@ -20,6 +20,7 @@
 package org.sonar.server.health;
 
 import java.util.Objects;
+import java.util.Optional;
 import java.util.Set;
 import org.sonar.process.cluster.health.NodeHealth;
 
@@ -43,6 +44,12 @@ public class ClusterHealth {
     return nodes;
   }
 
+  public Optional<NodeHealth> getNodeHealth(String nodeName) {
+    return nodes.stream()
+      .filter(node -> nodeName.equals(node.getDetails().getName()))
+      .findFirst();
+  }
+
   @Override
   public boolean equals(Object o) {
     if (this == o) {
index 900cb59ca1476e9e68ac558959ff6562de3c3bb3..40d8803dbf9c169bc0dcdb533ad9171f2e1dad7a 100644 (file)
@@ -60,29 +60,39 @@ public class EsStateSection implements SystemInfoSection {
   }
 
   private void completeNodeAttributes(ProtobufSystemInfo.Section.Builder protobuf) {
-    NodesStatsResponse nodesStats = esClient.prepareNodesStats().all().get();
+    NodesStatsResponse nodesStats = esClient.prepareNodesStats()
+      .setFs(true)
+      .setProcess(true)
+      .setJvm(true)
+      .setIndices(true)
+      .setBreaker(true)
+      .get();
     if (!nodesStats.getNodes().isEmpty()) {
       NodeStats stats = nodesStats.getNodes().get(0);
-      setAttribute(protobuf, "Disk Available", byteCountToDisplaySize(stats.getFs().getTotal().getAvailable().getBytes()));
-      setAttribute(protobuf, "Store Size", byteCountToDisplaySize(stats.getIndices().getStore().getSizeInBytes()));
-      setAttribute(protobuf, "Open File Descriptors", stats.getProcess().getOpenFileDescriptors());
-      setAttribute(protobuf, "Max File Descriptors", stats.getProcess().getMaxFileDescriptors());
-      setAttribute(protobuf, "Spinning", stats.getFs().getTotal().getSpins());
-      setAttribute(protobuf, "JVM Heap Usage", formatPercent(stats.getJvm().getMem().getHeapUsedPercent()));
-      setAttribute(protobuf, "JVM Heap Used", byteCountToDisplaySize(stats.getJvm().getMem().getHeapUsed().getBytes()));
-      setAttribute(protobuf, "JVM Heap Max", byteCountToDisplaySize(stats.getJvm().getMem().getHeapMax().getBytes()));
-      setAttribute(protobuf, "JVM Non Heap Used", byteCountToDisplaySize(stats.getJvm().getMem().getNonHeapUsed().getBytes()));
-      setAttribute(protobuf, "JVM Threads", stats.getJvm().getThreads().getCount());
-      setAttribute(protobuf, "Field Data Memory", byteCountToDisplaySize(stats.getIndices().getFieldData().getMemorySizeInBytes()));
-      setAttribute(protobuf, "Field Data Circuit Breaker Limit", byteCountToDisplaySize(stats.getBreaker().getStats(CircuitBreaker.FIELDDATA).getLimit()));
-      setAttribute(protobuf, "Field Data Circuit Breaker Estimation", byteCountToDisplaySize(stats.getBreaker().getStats(CircuitBreaker.FIELDDATA).getEstimated()));
-      setAttribute(protobuf, "Request Circuit Breaker Limit", byteCountToDisplaySize(stats.getBreaker().getStats(CircuitBreaker.REQUEST).getLimit()));
-      setAttribute(protobuf, "Request Circuit Breaker Estimation", byteCountToDisplaySize(stats.getBreaker().getStats(CircuitBreaker.REQUEST).getEstimated()));
-      setAttribute(protobuf, "Query Cache Memory", byteCountToDisplaySize(stats.getIndices().getQueryCache().getMemorySizeInBytes()));
-      setAttribute(protobuf, "Request Cache Memory", byteCountToDisplaySize(stats.getIndices().getRequestCache().getMemorySizeInBytes()));
+      toProtobuf(stats, protobuf);
     }
   }
 
+  public static void toProtobuf(NodeStats stats, ProtobufSystemInfo.Section.Builder protobuf) {
+    setAttribute(protobuf, "Disk Available", byteCountToDisplaySize(stats.getFs().getTotal().getAvailable().getBytes()));
+    setAttribute(protobuf, "Store Size", byteCountToDisplaySize(stats.getIndices().getStore().getSizeInBytes()));
+    setAttribute(protobuf, "Open File Descriptors", stats.getProcess().getOpenFileDescriptors());
+    setAttribute(protobuf, "Max File Descriptors", stats.getProcess().getMaxFileDescriptors());
+    setAttribute(protobuf, "Spinning", stats.getFs().getTotal().getSpins());
+    setAttribute(protobuf, "JVM Heap Usage", formatPercent(stats.getJvm().getMem().getHeapUsedPercent()));
+    setAttribute(protobuf, "JVM Heap Used", byteCountToDisplaySize(stats.getJvm().getMem().getHeapUsed().getBytes()));
+    setAttribute(protobuf, "JVM Heap Max", byteCountToDisplaySize(stats.getJvm().getMem().getHeapMax().getBytes()));
+    setAttribute(protobuf, "JVM Non Heap Used", byteCountToDisplaySize(stats.getJvm().getMem().getNonHeapUsed().getBytes()));
+    setAttribute(protobuf, "JVM Threads", stats.getJvm().getThreads().getCount());
+    setAttribute(protobuf, "Field Data Memory", byteCountToDisplaySize(stats.getIndices().getFieldData().getMemorySizeInBytes()));
+    setAttribute(protobuf, "Field Data Circuit Breaker Limit", byteCountToDisplaySize(stats.getBreaker().getStats(CircuitBreaker.FIELDDATA).getLimit()));
+    setAttribute(protobuf, "Field Data Circuit Breaker Estimation", byteCountToDisplaySize(stats.getBreaker().getStats(CircuitBreaker.FIELDDATA).getEstimated()));
+    setAttribute(protobuf, "Request Circuit Breaker Limit", byteCountToDisplaySize(stats.getBreaker().getStats(CircuitBreaker.REQUEST).getLimit()));
+    setAttribute(protobuf, "Request Circuit Breaker Estimation", byteCountToDisplaySize(stats.getBreaker().getStats(CircuitBreaker.REQUEST).getEstimated()));
+    setAttribute(protobuf, "Query Cache Memory", byteCountToDisplaySize(stats.getIndices().getQueryCache().getMemorySizeInBytes()));
+    setAttribute(protobuf, "Request Cache Memory", byteCountToDisplaySize(stats.getIndices().getRequestCache().getMemorySizeInBytes()));
+  }
+
   private ClusterStatsResponse clusterStats() {
     return esClient.prepareClusterStats().get();
   }
index acdffa2efb5a873421121f19b52ec9f26e342ab7..799fcbb4023ba7d29691827ce1d6ebdf6023dff3 100644 (file)
@@ -32,8 +32,6 @@ import org.sonar.core.util.stream.MoreCollectors;
 import org.sonar.process.ProcessProperties;
 import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
 import org.sonar.server.authentication.IdentityProviderRepository;
-import org.sonar.server.health.Health;
-import org.sonar.server.health.HealthChecker;
 import org.sonar.server.platform.ServerIdLoader;
 import org.sonar.server.platform.ServerLogging;
 import org.sonar.server.user.SecurityRealmFactory;
@@ -51,11 +49,10 @@ public class StandaloneSystemSection extends BaseSectionMBean implements SystemS
   private final ServerLogging serverLogging;
   private final ServerIdLoader serverIdLoader;
   private final OfficialDistribution officialDistribution;
-  private final HealthChecker healthChecker;
 
   public StandaloneSystemSection(Configuration config, SecurityRealmFactory securityRealmFactory,
     IdentityProviderRepository identityProviderRepository, Server server, ServerLogging serverLogging,
-    ServerIdLoader serverIdLoader, OfficialDistribution officialDistribution, HealthChecker healthChecker) {
+    ServerIdLoader serverIdLoader, OfficialDistribution officialDistribution) {
     this.config = config;
     this.securityRealmFactory = securityRealmFactory;
     this.identityProviderRepository = identityProviderRepository;
@@ -63,7 +60,6 @@ public class StandaloneSystemSection extends BaseSectionMBean implements SystemS
     this.serverLogging = serverLogging;
     this.serverIdLoader = serverIdLoader;
     this.officialDistribution = officialDistribution;
-    this.healthChecker = healthChecker;
   }
 
   @Override
@@ -123,9 +119,6 @@ public class StandaloneSystemSection extends BaseSectionMBean implements SystemS
       setAttribute(protobuf, "Server ID", serverId.getId());
       setAttribute(protobuf, "Server ID validated", serverId.isValid());
     });
-    Health health = healthChecker.checkNode();
-    setAttribute(protobuf, "Health", health.getStatus().name());
-    setAttribute(protobuf, "Health Causes", health.getCauses());
     setAttribute(protobuf, "Version", getVersion());
     setAttribute(protobuf, "External User Authentication", getExternalUserAuthentication());
     addIfNotEmpty(protobuf, "Accepted external identity providers", getEnabledIdentityProviders());
index b5f495f2c64ca94e7733a0e1a9b93d06d7d99288..01dec926e3ab7ed278a6c73526207339408f365f 100644 (file)
@@ -25,9 +25,11 @@ import org.sonar.server.platform.monitoring.cluster.AppNodesInfoLoaderImpl;
 import org.sonar.server.platform.monitoring.cluster.GlobalInfoLoader;
 import org.sonar.server.platform.monitoring.cluster.GlobalSystemSection;
 import org.sonar.server.platform.monitoring.cluster.ProcessInfoProvider;
+import org.sonar.server.platform.monitoring.cluster.LoggingSection;
 import org.sonar.server.platform.monitoring.cluster.NodeSystemSection;
+import org.sonar.server.platform.monitoring.cluster.SearchNodesInfoLoaderImpl;
 import org.sonar.server.platform.ws.ClusterInfoAction;
-import org.sonar.server.platform.ws.StandaloneInfoAction;
+import org.sonar.server.platform.ws.InfoAction;
 
 public class WebSystemInfoModule {
 
@@ -48,7 +50,7 @@ public class WebSystemInfoModule {
 
       OfficialDistribution.class,
 
-      StandaloneInfoAction.class
+      InfoAction.class
     };
   }
 
@@ -59,6 +61,7 @@ public class WebSystemInfoModule {
       DatabaseSection.class,
       EsStatisticsSection.class,
       GlobalSystemSection.class,
+      LoggingSection.class,
       NodeSystemSection.class,
       PluginsSection.class,
       SettingsSection.class,
@@ -68,6 +71,7 @@ public class WebSystemInfoModule {
       ProcessInfoProvider.class,
       GlobalInfoLoader.class,
       AppNodesInfoLoaderImpl.class,
+      SearchNodesInfoLoaderImpl.class,
       ClusterInfoAction.class
     };
   }
index 03eaaa09dfef866b8917a044aca46490a609a9e5..67c943028f01b1eb960b6ca687f68ed4f3a3d843 100644 (file)
@@ -50,9 +50,6 @@ public class AppNodesInfoLoaderImpl implements AppNodesInfoLoader {
         if (nodeInfo == null) {
           nodeInfo = new NodeInfo(nodeName);
           nodesByName.put(nodeName, nodeInfo);
-
-          String hostname = member.getStringAttribute(HazelcastMember.Attribute.HOSTNAME);
-          nodeInfo.setAttribute("Hostname", hostname);
         }
         completeNodeInfo(distributedAnswer, member, nodeInfo);
       }
@@ -64,13 +61,13 @@ public class AppNodesInfoLoaderImpl implements AppNodesInfoLoader {
     }
   }
 
-  private void completeNodeInfo(DistributedAnswer<ProtobufSystemInfo.SystemInfo> distributedAnswer, Member member, NodeInfo nodeInfo) {
+  private static void completeNodeInfo(DistributedAnswer<ProtobufSystemInfo.SystemInfo> distributedAnswer, Member member, NodeInfo nodeInfo) {
     Optional<ProtobufSystemInfo.SystemInfo> nodeAnswer = distributedAnswer.getAnswer(member);
     Optional<Exception> failure = distributedAnswer.getFailed(member);
     if (distributedAnswer.hasTimedOut(member)) {
-      nodeInfo.setAttribute("Error", "Failed to retrieve information on time");
+      nodeInfo.setErrorMessage("Failed to retrieve information on time");
     } else if (failure.isPresent()) {
-      nodeInfo.setAttribute("Error", "Failed to retrieve information: " + failure.get().getMessage());
+      nodeInfo.setErrorMessage("Failed to retrieve information: " + failure.get().getMessage());
     } else if (nodeAnswer.isPresent()) {
       nodeAnswer.get().getSectionsList().forEach(nodeInfo::addSection);
     }
index 90875c5d7b6d73d85452db90e8dbade1191dedd1..e2999dcb56ab687a1701580f9379050e6f7f57da 100644 (file)
@@ -33,8 +33,6 @@ import org.sonar.process.systeminfo.Global;
 import org.sonar.process.systeminfo.SystemInfoSection;
 import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
 import org.sonar.server.authentication.IdentityProviderRepository;
-import org.sonar.server.health.ClusterHealth;
-import org.sonar.server.health.HealthChecker;
 import org.sonar.server.platform.ServerIdLoader;
 import org.sonar.server.user.SecurityRealmFactory;
 
@@ -48,15 +46,13 @@ public class GlobalSystemSection implements SystemInfoSection, Global {
   private final ServerIdLoader serverIdLoader;
   private final SecurityRealmFactory securityRealmFactory;
   private final IdentityProviderRepository identityProviderRepository;
-  private final HealthChecker healthChecker;
 
   public GlobalSystemSection(Configuration config, ServerIdLoader serverIdLoader, SecurityRealmFactory securityRealmFactory,
-    IdentityProviderRepository identityProviderRepository, HealthChecker healthChecker) {
+    IdentityProviderRepository identityProviderRepository) {
     this.config = config;
     this.serverIdLoader = serverIdLoader;
     this.securityRealmFactory = securityRealmFactory;
     this.identityProviderRepository = identityProviderRepository;
-    this.healthChecker = healthChecker;
   }
 
   @Override
@@ -64,10 +60,6 @@ public class GlobalSystemSection implements SystemInfoSection, Global {
     ProtobufSystemInfo.Section.Builder protobuf = ProtobufSystemInfo.Section.newBuilder();
     protobuf.setName("System");
 
-    ClusterHealth health = healthChecker.checkCluster();
-
-    setAttribute(protobuf, "Health", health.getHealth().getStatus().name());
-    setAttribute(protobuf, "Health Causes", health.getHealth().getCauses());
     serverIdLoader.get().ifPresent(serverId -> {
       setAttribute(protobuf, "Server ID", serverId.getId());
       setAttribute(protobuf, "Server ID validated", serverId.isValid());
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/cluster/LoggingSection.java b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/cluster/LoggingSection.java
new file mode 100644 (file)
index 0000000..1af901b
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * 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.monitoring.cluster;
+
+import org.sonar.api.SonarQubeSide;
+import org.sonar.api.SonarRuntime;
+import org.sonar.api.ce.ComputeEngineSide;
+import org.sonar.api.server.ServerSide;
+import org.sonar.process.systeminfo.SystemInfoSection;
+import org.sonar.process.systeminfo.SystemInfoUtils;
+import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
+import org.sonar.server.platform.ServerLogging;
+
+@ComputeEngineSide
+@ServerSide
+public class LoggingSection implements SystemInfoSection {
+
+  private final SonarRuntime runtime;
+  private final ServerLogging logging;
+
+  public LoggingSection(SonarRuntime runtime, ServerLogging logging) {
+    this.runtime = runtime;
+    this.logging = logging;
+  }
+
+  @Override
+  public ProtobufSystemInfo.Section toProtobuf() {
+    ProtobufSystemInfo.Section.Builder protobuf = ProtobufSystemInfo.Section.newBuilder();
+    if (runtime.getSonarQubeSide() == SonarQubeSide.COMPUTE_ENGINE) {
+      protobuf.setName("Compute Engine Logging");
+    } else {
+      protobuf.setName("Web Logging");
+    }
+    SystemInfoUtils.setAttribute(protobuf, "Logs Level", logging.getRootLoggerLevel().name());
+    SystemInfoUtils.setAttribute(protobuf, "Logs Dir", logging.getLogsDir().getAbsolutePath());
+    return protobuf.build();
+  }
+}
index 3a53e1b829cfc11f76ef7c2befadf36a91cc5f1d..c9a368e4264925b844f32ca16c5d3d2599ca35e6 100644 (file)
 package org.sonar.server.platform.monitoring.cluster;
 
 import java.util.ArrayList;
-import java.util.LinkedHashMap;
 import java.util.List;
-import java.util.Map;
+import java.util.Optional;
+import javax.annotation.Nullable;
 import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
 
+/**
+ * Represents the system information of a cluster node. In the case of
+ * application node, it merges information from Web Server and Compute
+ * Engine processes.
+ *
+ */
 public class NodeInfo {
 
   private final String name;
-  private final Map<String, String> attributes = new LinkedHashMap<>();
+  private String host = null;
+  private Long startedAt = null;
+  private String errorMessage = null;
   private final List<ProtobufSystemInfo.Section> sections = new ArrayList<>();
 
   public NodeInfo(String name) {
@@ -39,13 +47,28 @@ public class NodeInfo {
     return name;
   }
 
-  public NodeInfo setAttribute(String key, String value) {
-    this.attributes.put(key, value);
-    return this;
+  public Optional<String> getHost() {
+    return Optional.ofNullable(host);
+  }
+
+  public void setHost(@Nullable String s) {
+    this.host = s;
+  }
+
+  public Optional<Long> getStartedAt() {
+    return Optional.ofNullable(startedAt);
+  }
+
+  public void setStartedAt(@Nullable Long l) {
+    this.startedAt = l;
+  }
+
+  public Optional<String> getErrorMessage() {
+    return Optional.ofNullable(errorMessage);
   }
 
-  public Map<String, String> getAttributes() {
-    return attributes;
+  public void setErrorMessage(@Nullable String s) {
+    this.errorMessage = s;
   }
 
   public NodeInfo addSection(ProtobufSystemInfo.Section section) {
index 8ab8a07f1e267927639bdb9f6e55f40c6dbbf2bb..e2def8256692ed42ab60c8a3854a63417ce16163 100644 (file)
@@ -25,9 +25,6 @@ import org.sonar.api.server.ServerSide;
 import org.sonar.process.ProcessProperties;
 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.platform.ServerLogging;
 import org.sonar.server.platform.monitoring.OfficialDistribution;
 
 import static org.sonar.process.systeminfo.SystemInfoUtils.setAttribute;
@@ -37,17 +34,12 @@ public class NodeSystemSection implements SystemInfoSection {
 
   private final Configuration config;
   private final Server server;
-  private final ServerLogging serverLogging;
   private final OfficialDistribution officialDistribution;
-  private final HealthChecker healthChecker;
 
-  public NodeSystemSection(Configuration config, Server server, ServerLogging serverLogging,
-    OfficialDistribution officialDistribution, HealthChecker healthChecker) {
+  public NodeSystemSection(Configuration config, Server server, OfficialDistribution officialDistribution) {
     this.config = config;
     this.server = server;
-    this.serverLogging = serverLogging;
     this.officialDistribution = officialDistribution;
-    this.healthChecker = healthChecker;
   }
 
   @Override
@@ -55,16 +47,12 @@ public class NodeSystemSection implements SystemInfoSection {
     ProtobufSystemInfo.Section.Builder protobuf = ProtobufSystemInfo.Section.newBuilder();
     protobuf.setName("System");
 
-    Health health = healthChecker.checkNode();
-    setAttribute(protobuf, "Health", health.getStatus().name());
-    setAttribute(protobuf, "Health Causes", health.getCauses());
     setAttribute(protobuf, "Version", server.getVersion());
     setAttribute(protobuf, "Official Distribution", officialDistribution.check());
     setAttribute(protobuf, "Home Dir", config.get(ProcessProperties.PATH_HOME).orElse(null));
     setAttribute(protobuf, "Data Dir", config.get(ProcessProperties.PATH_DATA).orElse(null));
     setAttribute(protobuf, "Temp Dir", config.get(ProcessProperties.PATH_TEMP).orElse(null));
-    setAttribute(protobuf, "Logs Dir", config.get(ProcessProperties.PATH_LOGS).orElse(null));
-    setAttribute(protobuf, "Logs Level", serverLogging.getRootLoggerLevel().name());
+    setAttribute(protobuf, "Processors", Runtime.getRuntime().availableProcessors());
     return protobuf.build();
   }
 
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/cluster/SearchNodesInfoLoader.java b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/cluster/SearchNodesInfoLoader.java
new file mode 100644 (file)
index 0000000..3b91ffb
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * 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.monitoring.cluster;
+
+
+import java.util.Collection;
+
+/**
+ * Loads "system information" of all Elasticsearch nodes.
+ */
+public interface SearchNodesInfoLoader {
+  Collection<NodeInfo> load();
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/cluster/SearchNodesInfoLoaderImpl.java b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/cluster/SearchNodesInfoLoaderImpl.java
new file mode 100644 (file)
index 0000000..08703a6
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * 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.monitoring.cluster;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import org.elasticsearch.action.admin.cluster.node.stats.NodeStats;
+import org.elasticsearch.action.admin.cluster.node.stats.NodesStatsResponse;
+import org.sonar.api.server.ServerSide;
+import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
+import org.sonar.server.es.EsClient;
+import org.sonar.server.platform.monitoring.EsStateSection;
+
+@ServerSide
+public class SearchNodesInfoLoaderImpl implements SearchNodesInfoLoader {
+
+  private final EsClient esClient;
+
+  public SearchNodesInfoLoaderImpl(EsClient esClient) {
+    this.esClient = esClient;
+  }
+
+  public Collection<NodeInfo> load() {
+    NodesStatsResponse nodesStats = esClient.prepareNodesStats()
+      .setFs(true)
+      .setProcess(true)
+      .setJvm(true)
+      .setIndices(true)
+      .setBreaker(true)
+      .get();
+    List<NodeInfo> result = new ArrayList<>();
+    nodesStats.getNodes().forEach(nodeStat -> result.add(toNodeInfo(nodeStat)));
+    return result;
+  }
+
+  private static NodeInfo toNodeInfo(NodeStats stat) {
+    String nodeName = stat.getNode().getName();
+    NodeInfo info = new NodeInfo(nodeName);
+
+    ProtobufSystemInfo.Section.Builder section = ProtobufSystemInfo.Section.newBuilder();
+    section.setName("Search State");
+    EsStateSection.toProtobuf(stat, section);
+    info.addSection(section.build());
+    
+    return info;
+  }
+
+}
index b69534bc6058bb9d7ee940018ccce5e4a32f0729..5d015b81036d3160aea82d7db74ff31d5347c917 100644 (file)
@@ -114,7 +114,6 @@ import org.sonar.server.platform.web.requestid.HttpRequestIdModule;
 import org.sonar.server.platform.ws.ChangeLogLevelAction;
 import org.sonar.server.platform.ws.DbMigrationStatusAction;
 import org.sonar.server.platform.ws.HealthActionModule;
-import org.sonar.server.platform.ws.InfoAction;
 import org.sonar.server.platform.ws.L10nWs;
 import org.sonar.server.platform.ws.LogsAction;
 import org.sonar.server.platform.ws.MigrateDbAction;
@@ -542,7 +541,6 @@ public class PlatformLevel4 extends PlatformLevel {
     addIfStartupLeader(TelemetryDaemon.class, TelemetryClient.class);
 
     // system info
-    add(InfoAction.class);
     addIfCluster(WebSystemInfoModule.forClusterMode()).otherwiseAdd(WebSystemInfoModule.forStandaloneMode());
 
     addAll(level4AddedComponents);
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
new file mode 100644 (file)
index 0000000..d6f821f
--- /dev/null
@@ -0,0 +1,101 @@
+/*
+ * 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.user.UserSession;
+
+public abstract class BaseInfoWsAction implements SystemWsAction {
+
+  private static final String[] ORDERED_SECTION_NAMES = {
+    "System", "Database", "Plugins",
+    "Web JVM State", "Web Logging", "Web JVM Properties",
+    "Search State", "Search Statistics",
+    "Compute Engine Database Connection", "Compute Engine JVM State", "Compute Engine Logging", "Compute Engine Tasks", "Compute Engine JVM Properties"};
+
+  private final UserSession userSession;
+
+  public BaseInfoWsAction(UserSession userSession) {
+    this.userSession = userSession;
+  }
+
+  @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) {
+    userSession.checkIsSystemAdministrator();
+    doHandle(request, response);
+  }
+
+  protected abstract void doHandle(Request request, Response response);
+
+  protected void writeSectionsToJson(Collection<ProtobufSystemInfo.Section> sections, JsonWriter json) {
+    SystemInfoUtils
+      .order(sections, ORDERED_SECTION_NAMES)
+      .forEach(section -> writeSectionToJson(section, json));
+  }
+
+  protected void writeSectionToJson(ProtobufSystemInfo.Section section, JsonWriter json) {
+    json.name(section.getName());
+    json.beginObject();
+    for (ProtobufSystemInfo.Attribute attribute : section.getAttributesList()) {
+      writeAttributeToJson(attribute, json);
+    }
+    json.endObject();
+  }
+
+  protected void writeAttributeToJson(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());
+    }
+  }
+}
index 87264373367605e4d5ecf9b442d58c6e706f317f..6f5e7d9bde433f04c2c80c3b6927c1d4a4af3dbc 100644 (file)
@@ -22,236 +22,82 @@ 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.protobuf.ProtobufSystemInfo;
+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.user.UserSession;
 
-public class ClusterInfoAction implements SystemWsAction {
+public class ClusterInfoAction extends BaseInfoWsAction {
 
-  private final UserSession userSession;
   private final GlobalInfoLoader globalInfoLoader;
   private final AppNodesInfoLoader appNodesInfoLoader;
+  private final SearchNodesInfoLoader searchNodesInfoLoader;
+  private final HealthChecker healthChecker;
 
-  public ClusterInfoAction(UserSession userSession, GlobalInfoLoader globalInfoLoader, AppNodesInfoLoader appNodesInfoLoader) {
-    this.userSession = userSession;
+  public ClusterInfoAction(UserSession userSession, GlobalInfoLoader globalInfoLoader,
+    AppNodesInfoLoader appNodesInfoLoader, SearchNodesInfoLoader searchNodesInfoLoader, HealthChecker healthChecker) {
+    super(userSession);
     this.globalInfoLoader = globalInfoLoader;
     this.appNodesInfoLoader = appNodesInfoLoader;
+    this.searchNodesInfoLoader = searchNodesInfoLoader;
+    this.healthChecker = healthChecker;
   }
 
   @Override
-  public void define(WebService.NewController controller) {
-    controller.createAction("cluster_info")
-      .setDescription("WIP")
-      .setSince("6.6")
-      .setInternal(true)
-      .setResponseExample(getClass().getResource("/org/sonar/server/platform/ws/info-example.json"))
-      .setHandler(this);
-  }
-
-  @Override
-  public void handle(Request request, Response response) {
-    userSession.checkIsSystemAdministrator();
-
+  protected void doHandle(Request request, Response response) {
+    ClusterHealth clusterHealth = healthChecker.checkCluster();
     try (JsonWriter json = response.newJsonWriter()) {
       json.beginObject();
-      writeGlobal(json);
-      writeApplicationNodes(json);
+
+      json.prop("Health", clusterHealth.getHealth().getStatus().name());
+      json.name("Health Causes").beginArray().values(clusterHealth.getHealth().getCauses()).endArray();
+
+      writeGlobalSections(json);
+      writeApplicationNodes(json, clusterHealth);
+      writeSearchNodes(json, clusterHealth);
       json.endObject();
     }
-
-//    try (JsonWriter json = response.newJsonWriter()) {
-//      json.beginObject();
-//
-//      // global section
-//      json.prop("Cluster", true);
-//      json.prop("Cluster Name", "foo");
-//      json.prop("Server Id", "ABC123");
-//      json.prop("Health", "RED");
-//      json
-//        .name("Health Causes")
-//        .beginArray().beginObject().prop("message", "Requires at least two search nodes").endObject().endArray();
-//
-//      json.name("Settings");
-//      json.beginObject();
-//      json.prop("sonar.forceAuthentication", true);
-//      json.prop("sonar.externalIdentityProviders", "GitHub, BitBucket");
-//      json.endObject();
-//
-//      json.name("Database");
-//      json
-//        .beginObject()
-//        .prop("Name", "PostgreSQL")
-//        .prop("Version", "9.6.3")
-//        .endObject();
-//
-//      json.name("Compute Engine");
-//      json
-//        .beginObject()
-//        .prop("Pending", 5)
-//        .prop("In Progress", 4)
-//        .prop("workers", 8)
-//        .prop("workersPerNode", 4)
-//        .endObject();
-//
-//      json.name("Elasticsearch");
-//      json
-//        .beginObject()
-//        .prop("Health", "GREEN")
-//        .prop("Number of Nodes", 4)
-//        .prop("Index Components - Docs", 152_515_155)
-//        .prop("Index Components - Shards", 20)
-//        .prop("Index Components - Size", "25GB")
-//        .prop("Index Issues - Docs", 5)
-//        .prop("Index Issues - Shards", 5)
-//        .prop("Index Issues - Size", "52MB")
-//        .prop("Index Tests - Docs", 56605)
-//        .prop("Index Tests - Shards", 2)
-//        .prop("Index Tests - Size", "520MB")
-//        .endObject();
-//
-//      json.name("Application Nodes");
-//      json
-//        .beginArray()
-//        .beginObject()
-//        .prop("Name", "Mont Blanc")
-//        .prop("Host", "10.158.92.16")
-//        .prop("Health", "YELLOW")
-//        .name("healthCauses").beginArray().beginObject().prop("message", "Db connectivity error").endObject().endArray()
-//        .prop("Start Time", "2017-05-30T10:23:45")
-//        .prop("Official Distribution", true)
-//        .prop("Processors", 4);
-//      json
-//        .name("Web JVM").beginObject()
-//        .prop("JVM Name", "Java HotSpot(TM) 64-Bit Server VM")
-//        .prop("JVM Vendor", "Oracle Corporation")
-//        .prop("Max Memory", "948MB")
-//        .prop("Free Memory", "38MB")
-//        .endObject()
-//
-//        .name("Web JVM Properties").beginObject()
-//        .prop("catalina.home", "/sonarsource/var/tmp/sonarsource/sssonarqube/tc")
-//        .prop("glowroot.tmp.dir", "/var/tmp/sonarsource/ssglowroot-agent")
-//        .prop("glowroot.adad.dir", "/var/tmp/sonarsource/ssglowroot-agent")
-//        .prop("java.specification.version", "1.8")
-//        .endObject()
-//
-//        .name("Web Database Connectivity").beginObject()
-//        .prop("Driver", "PostgreSQL JDBC Driver")
-//        .prop("Driver Version", "PostgreSQL JDBC Driver")
-//        .prop("Pool Idle Connections", 2)
-//        .prop("Pool Max Connections", 50)
-//        .prop("URL", "jdbc:postgresql://next-rds.cn6pfc2xc6oq.us-east-1.rds.amazonaws.com/dory")
-//        .endObject();
-//
-//      json
-//        .name("Compute Engine JVM").beginObject()
-//        .prop("JVM Name", "Java HotSpot(TM) 64-Bit Server VM")
-//        .prop("JVM Vendor", "Oracle Corporation")
-//        .prop("Max Memory", "25MB")
-//        .prop("Free Memory", "8MB")
-//        .endObject();
-//
-//      json
-//        .name("Compute Engine JVM Properties").beginObject()
-//        .prop("java.ext.dirs", "/opt/sonarsource/jvm/java-1.8.0-sun-x64/jre/lib/ext:/usr/java/packages/lib/ext")
-//        .prop("java.io.tmpdir", "/opt/sonarsource/jvm/java-1.8.0-sun-x64/jre/lib/ext:/usr/java/packages/lib/ext")
-//        .prop("java.library.path", "/opt/sonarsource/jvm/java-1.8.0-sun-x64/jre/lib/ext:/usr/java/packages/lib/ext")
-//        .prop("java.net.preferIPv4Stack", true)
-//        .prop("java.rmi.server.randomIDs", true)
-//        .prop("java.specification.version", "1.8")
-//        .endObject();
-//
-//      json.endObject().endArray();
-//
-//      json.name("Search Nodes");
-//      json
-//        .beginArray()
-//        .beginObject()
-//        .prop("Name", "Parmelan")
-//        .prop("Host", "10.158.92.19")
-//        .prop("Health", "GREEN")
-//        .name("Health Causes").beginArray().endArray()
-//        .prop("Start Time", "2017-05-30T10:23:45")
-//        .prop("Processors", 2)
-//        .prop("Disk Available", "25GB")
-//        .prop("JVM Threads", 52)
-//
-//        .name("JVM Properties").beginObject()
-//        .prop("java.ext.dirs", "/opt/sonarsource/jvm/java-1.8.0-sun-x64/jre/lib/ext:/usr/java/packages/lib/ext")
-//        .prop("java.io.tmpdir", "/opt/sonarsource/jvm/java-1.8.0-sun-x64/jre/lib/ext:/usr/java/packages/lib/ext")
-//        .prop("java.library.path", "/opt/sonarsource/jvm/java-1.8.0-sun-x64/jre/lib/ext:/usr/java/packages/lib/ext")
-//        .prop("java.net.preferIPv4Stack", true)
-//        .prop("java.rmi.server.randomIDs", true)
-//        .endObject()
-//
-//        .name("JVM").beginObject()
-//        .prop("java.ext.dirs", "/opt/sonarsource/jvm/java-1.8.0-sun-x64/jre/lib/ext:/usr/java/packages/lib/ext")
-//        .prop("java.io.tmpdir", "/opt/sonarsource/jvm/java-1.8.0-sun-x64/jre/lib/ext:/usr/java/packages/lib/ext")
-//        .prop("java.library.path", "/opt/sonarsource/jvm/java-1.8.0-sun-x64/jre/lib/ext:/usr/java/packages/lib/ext")
-//        .prop("java.net.preferIPv4Stack", true)
-//        .prop("java.rmi.server.randomIDs", true)
-//        .endObject()
-//
-//        .endObject()
-//        .endArray();
-//
-//      json.endObject();
-//    }
   }
 
-  private void writeGlobal(JsonWriter json) {
-    globalInfoLoader.load().forEach(section -> sectionToJson(section, json));
+  private void writeGlobalSections(JsonWriter json) {
+    globalInfoLoader.load().forEach(section -> writeSectionToJson(section, json));
   }
 
-  private void writeApplicationNodes(JsonWriter json) {
+  private void writeApplicationNodes(JsonWriter json, ClusterHealth clusterHealth) {
     json.name("Application Nodes").beginArray();
 
     Collection<NodeInfo> appNodes = appNodesInfoLoader.load();
     for (NodeInfo applicationNode : appNodes) {
-      writeApplicationNode(json, applicationNode);
+      writeNodeInfoToJson(applicationNode, clusterHealth, json);
     }
     json.endArray();
   }
 
-  private void writeApplicationNode(JsonWriter json, NodeInfo applicationNode) {
-    json.beginObject();
-    json.prop("Name", applicationNode.getName());
-    applicationNode.getSections().forEach(section -> sectionToJson(section, json));
-    json.endObject();
+  private void writeSearchNodes(JsonWriter json, ClusterHealth clusterHealth) {
+    json.name("Search Nodes").beginArray();
+
+    Collection<NodeInfo> searchNodes = searchNodesInfoLoader.load();
+    searchNodes.forEach(node -> writeNodeInfoToJson(node, clusterHealth, json));
+    json.endArray();
   }
 
-  private static void sectionToJson(ProtobufSystemInfo.Section section, JsonWriter json) {
-    json.name(section.getName());
+  private void writeNodeInfoToJson(NodeInfo nodeInfo, ClusterHealth clusterHealth, JsonWriter json) {
     json.beginObject();
-    for (ProtobufSystemInfo.Attribute attribute : section.getAttributesList()) {
-      attributeToJson(json, attribute);
-    }
-    json.endObject();
-  }
+    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));
 
-  private static void attributeToJson(JsonWriter json, ProtobufSystemInfo.Attribute attribute) {
-    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());
-    }
+    clusterHealth.getNodeHealth(nodeInfo.getName()).ifPresent(h -> {
+      json.prop("Health", h.getStatus().name());
+      json.name("Health Causes").beginArray().values(h.getCauses()).endArray();
+    });
+
+    writeSectionsToJson(nodeInfo.getSections(), json);
+    json.endObject();
   }
 }
index e19cea3974f066105812a94a8d7c8f85365e821f..0011675154ce638e3490b162ff7189015554a4f4 100644 (file)
@@ -22,13 +22,13 @@ 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.SystemInfoUtils;
 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;
 
@@ -38,39 +38,24 @@ import static org.sonar.server.telemetry.TelemetryDataJsonWriter.writeTelemetryD
 /**
  * Implementation of the {@code info} action for the System WebService.
  */
-public class InfoAction implements SystemWsAction {
+public class InfoAction extends BaseInfoWsAction {
 
-  private static final String[] ORDERED_SECTION_NAMES = {
-    "System", "Database", "Web JVM Properties", "Web JVM State", "Search State", "Search Statistics",
-    "Compute Engine Database Connection", "Compute Engine JVM State", "Compute Engine Tasks"};
-  private final UserSession userSession;
   private final CeHttpClient ceHttpClient;
   private final SystemInfoSection[] systemInfoSections;
+  private final HealthChecker healthChecker;
   private final TelemetryDataLoader statistics;
 
-  public InfoAction(UserSession userSession, CeHttpClient ceHttpClient, TelemetryDataLoader statistics, SystemInfoSection... systemInfoSections) {
-    this.userSession = userSession;
+  public InfoAction(UserSession userSession, CeHttpClient ceHttpClient, HealthChecker healthChecker, TelemetryDataLoader statistics,
+    SystemInfoSection... systemInfoSections) {
+    super(userSession);
     this.ceHttpClient = ceHttpClient;
+    this.healthChecker = healthChecker;
     this.statistics = statistics;
     this.systemInfoSections = systemInfoSections;
   }
 
   @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) {
-    userSession.checkIsSystemAdministrator();
-
+  protected void doHandle(Request request, Response response) {
     try (JsonWriter json = response.newJsonWriter()) {
       writeJson(json);
     }
@@ -79,53 +64,28 @@ public class InfoAction implements SystemWsAction {
   private void writeJson(JsonWriter json) {
     json.beginObject();
 
+    writeHealthToJson(json);
     List<ProtobufSystemInfo.Section> sections = stream(systemInfoSections)
       .map(SystemInfoSection::toProtobuf)
       .collect(MoreCollectors.toArrayList());
     ceHttpClient.retrieveSystemInfo()
       .ifPresent(ce -> sections.addAll(ce.getSectionsList()));
-    SystemInfoUtils
-      .order(sections, ORDERED_SECTION_NAMES)
-      .forEach(section -> sectionToJson(section, json));
 
-    writeStatistics(json);
+    writeSectionsToJson(sections, json);
+    writeStatisticsToJson(json);
 
     json.endObject();
   }
 
-  private void writeStatistics(JsonWriter json) {
-    json.name("Statistics");
-    writeTelemetryData(json, statistics.load());
+  private void writeHealthToJson(JsonWriter json) {
+    Health health = healthChecker.checkNode();
+    json.prop("Health", health.getStatus().name());
+    json.name("Health Causes").beginArray().values(health.getCauses()).endArray();
   }
 
-  private static void sectionToJson(ProtobufSystemInfo.Section section, JsonWriter json) {
-    json.name(section.getName());
-    json.beginObject();
-    for (ProtobufSystemInfo.Attribute attribute : section.getAttributesList()) {
-      attributeToJson(json, attribute);
-    }
-    json.endObject();
+  private void writeStatisticsToJson(JsonWriter json) {
+    json.name("Statistics");
+    writeTelemetryData(json, statistics.load());
   }
 
-  private static void attributeToJson(JsonWriter json, ProtobufSystemInfo.Attribute attribute) {
-    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());
-    }
-  }
 }
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/ws/StandaloneInfoAction.java b/server/sonar-server/src/main/java/org/sonar/server/platform/ws/StandaloneInfoAction.java
deleted file mode 100644 (file)
index adb9277..0000000
+++ /dev/null
@@ -1,168 +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 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.server.user.UserSession;
-
-public class StandaloneInfoAction implements SystemWsAction {
-
-  private final UserSession userSession;
-
-  public StandaloneInfoAction(UserSession userSession) {
-    this.userSession = userSession;
-  }
-
-  @Override
-  public void define(WebService.NewController controller) {
-    controller.createAction("standalone_info")
-      .setDescription("WIP")
-      .setSince("6.6")
-      .setInternal(true)
-      .setResponseExample(getClass().getResource("/org/sonar/server/platform/ws/info-example.json"))
-      .setHandler(this);
-  }
-
-  @Override
-  public void handle(Request request, Response response) {
-    userSession.checkIsSystemAdministrator();
-
-    try (JsonWriter json = response.newJsonWriter()) {
-      json.beginObject();
-
-      // global section
-      json
-        .prop("Server Id", "ABC123")
-        .prop("Health", "RED")
-        .prop("Host", "10.158.92.16")
-        .prop("Start Time", "2017-05-30T10:23:45")
-        .prop("Official Distribution", true)
-        .prop("Processors", 4)
-        .prop("Disk Available", "25GB")
-        .prop("JVM Threads", 52);
-      json
-        .name("Health Causes")
-        .beginArray().beginObject().prop("message", "Db connectivity error").endObject().endArray();
-
-      json.name("Settings");
-      json.beginObject();
-      json.prop("sonar.forceAuthentication", true);
-      json.prop("sonar.externalIdentityProviders", "GitHub, BitBucket");
-      json.endObject();
-
-
-
-      json.name("Database");
-      json
-        .beginObject()
-        .prop("Name", "PostgreSQL")
-        .prop("Version", "9.6.3")
-        .endObject();
-
-      json.name("Compute Engine");
-      json
-        .beginObject()
-        .prop("Pending", 5)
-        .prop("In Progress", 4)
-        .prop("workers", 8)
-        .prop("workersPerNode", 4)
-        .endObject();
-
-      json.name("Elasticsearch");
-      json
-        .beginObject()
-        .prop("Health", "GREEN")
-        .prop("Number of Nodes", 4)
-        .prop("Index Components - Docs", 152_515_155)
-        .prop("Index Components - Shards", 20)
-        .prop("Index Components - Size", "25GB")
-        .prop("Index Issues - Docs", 5)
-        .prop("Index Issues - Shards", 5)
-        .prop("Index Issues - Size", "52MB")
-        .prop("Index Tests - Docs", 56605)
-        .prop("Index Tests - Shards", 2)
-        .prop("Index Tests - Size", "520MB")
-        .endObject();
-
-      json
-        .name("Web JVM").beginObject()
-        .prop("JVM Name", "Java HotSpot(TM) 64-Bit Server VM")
-        .prop("JVM Vendor", "Oracle Corporation")
-        .prop("Max Memory", "948MB")
-        .prop("Free Memory", "38MB")
-        .endObject()
-
-        .name("Web JVM Properties").beginObject()
-        .prop("catalina.home", "/sonarsource/var/tmp/sonarsource/sssonarqube/tc")
-        .prop("glowroot.tmp.dir", "/var/tmp/sonarsource/ssglowroot-agent")
-        .prop("glowroot.adad.dir", "/var/tmp/sonarsource/ssglowroot-agent")
-        .prop("java.specification.version", "1.8")
-        .endObject()
-
-        .name("Web Database Connectivity").beginObject()
-        .prop("Driver", "PostgreSQL JDBC Driver")
-        .prop("Driver Version", "PostgreSQL JDBC Driver")
-        .prop("Pool Idle Connections", 2)
-        .prop("Pool Max Connections", 50)
-        .prop("URL", "jdbc:postgresql://next-rds.cn6pfc2xc6oq.us-east-1.rds.amazonaws.com/dory")
-        .endObject();
-
-      json
-        .name("Compute Engine JVM").beginObject()
-        .prop("JVM Name", "Java HotSpot(TM) 64-Bit Server VM")
-        .prop("JVM Vendor", "Oracle Corporation")
-        .prop("Max Memory", "25MB")
-        .prop("Free Memory", "8MB")
-        .endObject();
-
-      json
-        .name("Compute Engine JVM Properties").beginObject()
-        .prop("java.ext.dirs", "/opt/sonarsource/jvm/java-1.8.0-sun-x64/jre/lib/ext:/usr/java/packages/lib/ext")
-        .prop("java.io.tmpdir", "/opt/sonarsource/jvm/java-1.8.0-sun-x64/jre/lib/ext:/usr/java/packages/lib/ext")
-        .prop("java.library.path", "/opt/sonarsource/jvm/java-1.8.0-sun-x64/jre/lib/ext:/usr/java/packages/lib/ext")
-        .prop("java.net.preferIPv4Stack", true)
-        .prop("java.rmi.server.randomIDs", true)
-        .prop("java.specification.version", "1.8")
-        .endObject();
-
-      json
-        .name("Search JVM Properties").beginObject()
-        .prop("java.ext.dirs", "/opt/sonarsource/jvm/java-1.8.0-sun-x64/jre/lib/ext:/usr/java/packages/lib/ext")
-        .prop("java.io.tmpdir", "/opt/sonarsource/jvm/java-1.8.0-sun-x64/jre/lib/ext:/usr/java/packages/lib/ext")
-        .prop("java.library.path", "/opt/sonarsource/jvm/java-1.8.0-sun-x64/jre/lib/ext:/usr/java/packages/lib/ext")
-        .prop("java.net.preferIPv4Stack", true)
-        .prop("java.rmi.server.randomIDs", true)
-        .endObject();
-
-      json.name("Search JVM").beginObject()
-        .prop("java.ext.dirs", "/opt/sonarsource/jvm/java-1.8.0-sun-x64/jre/lib/ext:/usr/java/packages/lib/ext")
-        .prop("java.io.tmpdir", "/opt/sonarsource/jvm/java-1.8.0-sun-x64/jre/lib/ext:/usr/java/packages/lib/ext")
-        .prop("java.library.path", "/opt/sonarsource/jvm/java-1.8.0-sun-x64/jre/lib/ext:/usr/java/packages/lib/ext")
-        .prop("java.net.preferIPv4Stack", true)
-        .prop("java.rmi.server.randomIDs", true)
-        .endObject();
-
-      json.endObject();
-    }
-  }
-}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/cluster/StartableHazelcastMemberTest.java b/server/sonar-server/src/test/java/org/sonar/server/cluster/StartableHazelcastMemberTest.java
new file mode 100644 (file)
index 0000000..4750794
--- /dev/null
@@ -0,0 +1,106 @@
+/*
+ * 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.cluster;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.function.Supplier;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.config.internal.MapSettings;
+import org.sonar.process.NetworkUtils;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+
+public class StartableHazelcastMemberTest {
+
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  private MapSettings settings = new MapSettings();
+  private String loopback = InetAddress.getLoopbackAddress().getHostAddress();
+
+  @Test
+  public void start_initializes_hazelcast() {
+    completeValidSettings();
+    StartableHazelcastMember underTest = new StartableHazelcastMember(settings.asConfig(), NetworkUtils.INSTANCE);
+    verifyStopped(underTest);
+
+    underTest.start();
+
+    assertThat(underTest.getUuid()).isNotEmpty();
+    assertThat(underTest.getCluster().getMembers()).hasSize(1);
+    assertThat(underTest.getMemberUuids()).containsExactly(underTest.getUuid());
+    assertThat(underTest.getSet("foo")).isNotNull();
+    assertThat(underTest.getReplicatedMap("foo")).isNotNull();
+    assertThat(underTest.getAtomicReference("foo")).isNotNull();
+    assertThat(underTest.getList("foo")).isNotNull();
+    assertThat(underTest.getMap("foo")).isNotNull();
+    assertThat(underTest.getLock("foo")).isNotNull();
+    assertThat(underTest.getClusterTime()).isGreaterThan(0);
+
+    underTest.stop();
+
+    verifyStopped(underTest);
+  }
+
+  @Test
+  public void throw_ISE_if_host_for_random_port_cant_be_resolved() throws Exception{
+    NetworkUtils network = mock(NetworkUtils.class);
+    doThrow(new UnknownHostException("BOOM")).when(network).toInetAddress(anyString());
+    completeValidSettings();
+    StartableHazelcastMember underTest = new StartableHazelcastMember(settings.asConfig(), network);
+
+    expectedException.expect(IllegalStateException.class);
+    expectedException.expectMessage("Can not resolve address ");
+
+    underTest.start();
+
+    verifyStopped(underTest);
+  }
+
+  private void completeValidSettings() {
+    settings.setProperty("sonar.cluster.name", "foo");
+    settings.setProperty("sonar.cluster.node.host", loopback);
+    settings.setProperty("sonar.cluster.node.name", "bar");
+    settings.setProperty("sonar.cluster.node.type", "application");
+    settings.setProperty("process.key", "ce");
+  }
+
+  private static void verifyStopped(StartableHazelcastMember member) {
+    expectNpe(member::getMemberUuids);
+    expectNpe(member::getCluster);
+    expectNpe(member::getUuid);
+  }
+
+  private static void expectNpe(Supplier supplier) {
+    try {
+      supplier.get();
+      fail();
+    } catch (NullPointerException e) {
+    }
+  }
+
+}
index 8a8d918c90d9c2abcab801e90efed5fe32326c06..99ed82716924921f74a900cb8cc330704de60be3 100644 (file)
@@ -19,7 +19,9 @@
  */
 package org.sonar.server.health;
 
+import java.util.Arrays;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.Random;
 import java.util.Set;
 import java.util.stream.Collectors;
@@ -100,6 +102,17 @@ public class ClusterHealthTest {
     assertThat(underTest.toString()).isEqualTo("ClusterHealth{health=" + health + ", nodes=" + nodeHealths + "}");
   }
 
+  @Test
+  public void test_getNodeHealth() {
+    Health health = randomHealth();
+    Set<NodeHealth> nodeHealths = new HashSet<>(Arrays.asList(newNodeHealth("foo"), newNodeHealth("bar")));
+
+    ClusterHealth underTest = new ClusterHealth(health, nodeHealths);
+
+    assertThat(underTest.getNodeHealth("does_not_exist")).isEmpty();
+    assertThat(underTest.getNodeHealth("bar")).isPresent();
+  }
+
   private Health randomHealth() {
     Health.Builder healthBuilder = Health.newHealthCheckBuilder();
     healthBuilder.setStatus(Health.Status.values()[random.nextInt(Health.Status.values().length)]);
@@ -120,4 +133,17 @@ public class ClusterHealthTest {
           .build())
       .build()).collect(Collectors.toSet());
   }
+
+  private static NodeHealth newNodeHealth(String nodeName) {
+    return NodeHealth.newNodeHealthBuilder()
+      .setStatus(NodeHealth.Status.YELLOW)
+      .setDetails(NodeDetails.newNodeDetailsBuilder()
+        .setType(NodeDetails.Type.APPLICATION)
+        .setName(nodeName)
+        .setHost(randomAlphanumeric(4))
+        .setPort(3000)
+        .setStartedAt(1_000L)
+        .build())
+      .build();
+  }
 }
index ea5f52d10548c40abe70e6e7c34cef056ff83d13..33fdb132a4c1da9af39e900fa4979b0625a981f1 100644 (file)
@@ -30,14 +30,11 @@ import org.sonar.api.utils.log.LoggerLevel;
 import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
 import org.sonar.server.authentication.IdentityProviderRepositoryRule;
 import org.sonar.server.authentication.TestIdentityProvider;
-import org.sonar.server.health.Health;
-import org.sonar.server.health.TestStandaloneHealthChecker;
 import org.sonar.server.platform.ServerId;
 import org.sonar.server.platform.ServerIdLoader;
 import org.sonar.server.platform.ServerLogging;
 import org.sonar.server.user.SecurityRealmFactory;
 
-import static java.util.Arrays.asList;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
@@ -57,11 +54,10 @@ public class StandaloneSystemSectionTest {
   private ServerIdLoader serverIdLoader = mock(ServerIdLoader.class);
   private ServerLogging serverLogging = mock(ServerLogging.class);
   private SecurityRealmFactory securityRealmFactory = mock(SecurityRealmFactory.class);
-  private TestStandaloneHealthChecker healthChecker = new TestStandaloneHealthChecker();
   private OfficialDistribution officialDistribution = mock(OfficialDistribution.class);
 
   private StandaloneSystemSection underTest = new StandaloneSystemSection(settings.asConfig(), securityRealmFactory, identityProviderRepository, server,
-    serverLogging, serverIdLoader, officialDistribution, healthChecker);
+    serverLogging, serverIdLoader, officialDistribution);
 
   @Before
   public void setUp() throws Exception {
@@ -192,19 +188,6 @@ public class StandaloneSystemSectionTest {
     assertThatAttributeIs(protobuf, "External identity providers whose users are allowed to sign themselves up", "GitHub");
   }
 
-  @Test
-  public void return_health() {
-    healthChecker.setHealth(Health.newHealthCheckBuilder()
-      .setStatus(Health.Status.YELLOW)
-      .addCause("foo")
-      .addCause("bar")
-      .build());
-
-    ProtobufSystemInfo.Section protobuf = underTest.toProtobuf();
-    assertThatAttributeIs(protobuf, "Health", "YELLOW");
-    SystemInfoTesting.assertThatAttributeHasOnlyValues(protobuf, "Health Causes", asList("foo", "bar"));
-  }
-
   @Test
   public void return_nb_of_processors() {
     ProtobufSystemInfo.Section protobuf = underTest.toProtobuf();
index 9c02608fddd2ec1b121772fbae1aa2e09b2b013b..9e92b6205c739532c97f424523ac227c6a4f7d7c 100644 (file)
@@ -29,6 +29,7 @@ import org.sonar.ce.http.CeHttpClientImpl;
 import org.sonar.process.systeminfo.SystemInfoSection;
 import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
 import org.sonar.server.exceptions.ForbiddenException;
+import org.sonar.server.health.TestStandaloneHealthChecker;
 import org.sonar.server.telemetry.TelemetryData;
 import org.sonar.server.telemetry.TelemetryDataLoader;
 import org.sonar.server.tester.UserSessionRule;
@@ -51,9 +52,10 @@ public class InfoActionTest {
   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 statistics = mock(TelemetryDataLoader.class);
 
-  private InfoAction underTest = new InfoAction(userSessionRule, ceHttpClient, statistics, section1, section2);
+  private InfoAction underTest = new InfoAction(userSessionRule, ceHttpClient, healthChecker, statistics, section1, section2);
   private WsActionTester ws = new WsActionTester(underTest);
 
   @Test
@@ -100,7 +102,7 @@ public class InfoActionTest {
     TestResponse response = ws.newRequest().execute();
     // response does not contain empty "Section Three"
     verify(statistics).load();
-    assertThat(response.getInput()).isEqualTo("{\"Section One\":{\"foo\":\"bar\"},\"Section Two\":{\"one\":1,\"two\":2}," +
+    assertThat(response.getInput()).isEqualTo("{\"Health\":\"GREEN\",\"Health Causes\":[],\"Section One\":{\"foo\":\"bar\"},\"Section Two\":{\"one\":1,\"two\":2}," +
       "\"Statistics\":{\"plugins\":{},\"userCount\":0,\"projectCount\":0,\"lines\":0,\"ncloc\":0,\"projectCountByLanguage\":{},\"nclocByLanguage\":{}}}");
   }
 
index 593619663da896f07a5c0c45208342bb90832180..179c09bd863a31a881e8c360645a06346e9ae21c 100644 (file)
@@ -26,6 +26,8 @@ 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.Platform;
 import org.sonar.server.platform.WebServer;
 import org.sonar.server.telemetry.TelemetryDataLoader;
@@ -37,13 +39,14 @@ import static org.mockito.Mockito.mock;
 
 public class SystemWsTest {
 
-  CeHttpClient ceHttpClient = mock(CeHttpClientImpl.class);
+  private CeHttpClient ceHttpClient = mock(CeHttpClientImpl.class);
+  private HealthChecker healthChecker = new TestStandaloneHealthChecker();
 
   @Test
   public void define() {
     RestartAction action1 = new RestartAction(mock(UserSession.class), mock(Configuration.class), mock(Platform.class), mock(ProcessCommandWrapper.class),
       mock(RestartFlagHolder.class), mock(WebServer.class));
-    InfoAction action2 = new InfoAction(new AnonymousMockUserSession(), ceHttpClient, mock(TelemetryDataLoader.class));
+    InfoAction action2 = new InfoAction(new AnonymousMockUserSession(), ceHttpClient, healthChecker, mock(TelemetryDataLoader.class));
     SystemWs ws = new SystemWs(action1, action2);
     WebService.Context context = new WebService.Context();
 
index df9da694e4087672d863245fc5ce737bf91801a5..18851e79091950bdd24deaa80a8c894fe8bea043 100644 (file)
@@ -36,21 +36,26 @@ export enum HealthType {
   GREEN = 'GREEN'
 }
 
-export interface HealthCause extends SysValueObject {
-  message: string;
-}
-
 export interface NodeInfo extends SysValueObject {
-  Name: string;
+  'Compute Engine Logging': { 'Logs Level': string };
   Health: HealthType;
-  'Health Causes': HealthCause[];
-  'Logs Level': string;
+  'Health Causes': string[];
+  Name: string;
+  'Web Logging': { 'Logs Level': string };
 }
 
 export interface SysInfo extends SysValueObject {
-  Cluster: boolean;
   Health: HealthType;
-  'Health Causes': HealthCause[];
+  'Health Causes': string[];
+  System: {
+    'High Availability': boolean;
+    'Logs Level': string;
+  };
+}
+
+export interface ClusterSysInfo extends SysInfo {
+  'Application Nodes': NodeInfo[];
+  'Search Nodes': NodeInfo[];
 }
 
 export function setLogLevel(level: string): Promise<void | Response> {
index 0c550fd943fa4f3e435d458b9aa5c94c2e610fc9..b1e915f037051be46e85ac0976f978220d51da37 100644 (file)
@@ -18,6 +18,7 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import * as u from '../utils';
+import { ClusterSysInfo, SysInfo } from '../../../api/system';
 
 describe('parseQuery', () => {
   it('should correctly parse the expand array', () => {
@@ -44,16 +45,26 @@ describe('groupSections', () => {
 
 describe('getSystemLogsLevel', () => {
   it('should correctly return log level for standalone mode', () => {
-    expect(u.getSystemLogsLevel({ 'Logs Level': 'FOO' } as u.StandaloneSysInfo)).toBe('FOO');
-    expect(u.getSystemLogsLevel({} as u.StandaloneSysInfo)).toBe('INFO');
+    expect(u.getSystemLogsLevel({ System: { 'Logs Level': 'FOO' } } as SysInfo)).toBe('FOO');
+    expect(u.getSystemLogsLevel({} as SysInfo)).toBe('INFO');
     expect(u.getSystemLogsLevel()).toBe('INFO');
   });
+
   it('should return the worst log level for cluster mode', () => {
     expect(
       u.getSystemLogsLevel({
-        Cluster: true,
-        'Application Nodes': [{ 'Logs Level': 'INFO' }, { 'Logs Level': 'DEBUG' }]
-      } as u.ClusterSysInfo)
+        System: { 'High Availability': true },
+        'Application Nodes': [
+          {
+            'Compute Engine Logging': { 'Logs Level': 'DEBUG' },
+            'Web Logging': { 'Logs Level': 'INFO' }
+          },
+          {
+            'Compute Engine Logging': { 'Logs Level': 'INFO' },
+            'Web Logging': { 'Logs Level': 'INFO' }
+          }
+        ]
+      } as ClusterSysInfo)
     ).toBe('DEBUG');
   });
 });
index 1cd3752de880a56357b81b36a49a17603b1190ef..3867988259786d86571903dc379e55ea8f32adb5 100644 (file)
@@ -24,16 +24,8 @@ import ClusterSysInfos from './ClusterSysInfos';
 import PageHeader from './PageHeader';
 import StandaloneSysInfos from './StandaloneSysInfos';
 import { translate } from '../../../helpers/l10n';
-import { getSystemInfo, SysInfo } from '../../../api/system';
-import {
-  ClusterSysInfo,
-  getSystemLogsLevel,
-  isCluster,
-  parseQuery,
-  Query,
-  serializeQuery,
-  StandaloneSysInfo
-} from '../utils';
+import { ClusterSysInfo, getSystemInfo, SysInfo } from '../../../api/system';
+import { getSystemLogsLevel, isCluster, parseQuery, Query, serializeQuery } from '../utils';
 import { RawQuery } from '../../../helpers/query';
 import '../styles.css';
 
@@ -114,7 +106,7 @@ export default class App extends React.PureComponent<Props, State> {
     return (
       <StandaloneSysInfos
         expandedCards={query.expandedCards}
-        sysInfoData={sysInfoData as StandaloneSysInfo}
+        sysInfoData={sysInfoData}
         toggleCard={this.toggleSysInfoCards}
       />
     );
index 6118fe2d9568915b79b95df64db216ef0cbdd5f7..7de208bd4f67cecfb4e5f3cf98944e2c8033c412 100644 (file)
@@ -49,7 +49,7 @@ export default class ChangeLogLevelForm extends React.PureComponent<Props, State
   handleFormSubmit = (event: React.SyntheticEvent<HTMLFormElement>) => {
     event.preventDefault();
     const { newLevel } = this.state;
-    if (!this.state.updating && newLevel !== this.props.logLevel) {
+    if (!this.state.updating) {
       this.setState({ updating: true });
       setLogLevel(newLevel).then(
         () => this.props.onChange(newLevel),
@@ -64,7 +64,6 @@ export default class ChangeLogLevelForm extends React.PureComponent<Props, State
   render() {
     const { updating, newLevel } = this.state;
     const header = translate('system.set_log_level');
-    const disableSubmit = updating || newLevel === this.props.logLevel;
     return (
       <Modal
         isOpen={true}
@@ -100,7 +99,7 @@ export default class ChangeLogLevelForm extends React.PureComponent<Props, State
           </div>
           <div className="modal-foot">
             {updating && <i className="spinner spacer-right" />}
-            <button disabled={disableSubmit} id="set-log-level-submit">
+            <button disabled={updating} id="set-log-level-submit">
               {translate('save')}
             </button>
             <a href="#" id="set-log-level-cancel" onClick={this.handleCancelClick}>
index cb3000dea3254d8a380bf4f2393eea1f0dfba37e..54c70b1999cbe0019a264493ecfcf5cedd3b8973 100644 (file)
@@ -21,8 +21,8 @@ import * as React from 'react';
 import { sortBy } from 'lodash';
 import HealthCard from './info-items/HealthCard';
 import { translate } from '../../../helpers/l10n';
+import { ClusterSysInfo } from '../../../api/system';
 import {
-  ClusterSysInfo,
   getAppNodes,
   getHealth,
   getHealthCauses,
index 4ed444cc66d06145c6317303d788d445b1965b54..69ddc3cbfab3a1877ef51016c23aa2a5706c4a83 100644 (file)
 import * as React from 'react';
 import { map } from 'lodash';
 import HealthCard from './info-items/HealthCard';
+import { SysInfo } from '../../../api/system';
 import {
   getHealth,
   getHealthCauses,
   getStandaloneMainSections,
   getStandaloneSecondarySections,
-  ignoreInfoFields,
-  StandaloneSysInfo
+  ignoreInfoFields
 } from '../utils';
 
 interface Props {
   expandedCards: string[];
-  sysInfoData: StandaloneSysInfo;
+  sysInfoData: SysInfo;
   toggleCard: (toggledCard: string) => void;
 }
 
index 21c60cbd6fc7e224b3a73d55bdb88d83ce23563b..c2e892abb5dc0e8ca4729c3f34bfaf45cc6d98c2 100644 (file)
 import * as React from 'react';
 import { shallow } from 'enzyme';
 import ClusterSysInfos from '../ClusterSysInfos';
-import { HealthType } from '../../../../api/system';
-import { ClusterSysInfo } from '../../utils';
+import { ClusterSysInfo, HealthType } from '../../../../api/system';
 
 const sysInfoData: ClusterSysInfo = {
-  Cluster: true,
   Health: HealthType.RED,
-  Name: 'Foo',
-  'Health Causes': [{ message: 'Database down' }],
+  'Health Causes': ['Database down'],
   'Application Nodes': [
-    { Name: 'Bar', Health: HealthType.GREEN, 'Health Causes': [], 'Logs Level': 'INFO' }
+    {
+      Name: 'Bar',
+      Health: HealthType.GREEN,
+      'Health Causes': [],
+      'Compute Engine Logging': { 'Logs Level': 'INFO' },
+      'Web Logging': { 'Logs Level': 'INFO' }
+    }
   ],
   'Search Nodes': [
-    { Name: 'Baz', Health: HealthType.YELLOW, 'Health Causes': [], 'Logs Level': 'INFO' }
-  ]
+    {
+      Name: 'Baz',
+      Health: HealthType.YELLOW,
+      'Health Causes': [],
+      'Compute Engine Logging': { 'Logs Level': 'INFO' },
+      'Web Logging': { 'Logs Level': 'INFO' }
+    }
+  ],
+  System: {
+    'High Availability': true,
+    'Logs Level': 'INFO'
+  }
 };
 
 it('should render correctly', () => {
@@ -42,9 +55,27 @@ it('should render correctly', () => {
       sysInfoData: {
         ...sysInfoData,
         'Application Nodes': [
-          { Name: 'Foo', Health: HealthType.GREEN, 'Health Causes': [], 'Logs Level': 'INFO' },
-          { Name: 'Bar', Health: HealthType.RED, 'Health Causes': [], 'Logs Level': 'DEBUG' },
-          { Name: 'Baz', Health: HealthType.YELLOW, 'Health Causes': [], 'Logs Level': 'TRACE' }
+          {
+            Name: 'Foo',
+            Health: HealthType.GREEN,
+            'Health Causes': [],
+            'Compute Engine Logging': { 'Logs Level': 'INFO' },
+            'Web Logging': { 'Logs Level': 'INFO' }
+          },
+          {
+            Name: 'Bar',
+            Health: HealthType.RED,
+            'Health Causes': [],
+            'Compute Engine Logging': { 'Logs Level': 'INFO' },
+            'Web Logging': { 'Logs Level': 'DEBUG' }
+          },
+          {
+            Name: 'Baz',
+            Health: HealthType.YELLOW,
+            'Health Causes': [],
+            'Compute Engine Logging': { 'Logs Level': 'TRACE' },
+            'Web Logging': { 'Logs Level': 'DEBUG' }
+          }
         ]
       }
     }).find('HealthCard')
index c70202e3219c58b91d7731ca4c85f8eb3915aeb8..220cdb85ef1393e632df1cf5a798fce360bc2c6f 100644 (file)
 import * as React from 'react';
 import { shallow } from 'enzyme';
 import StandaloneSysInfos from '../StandaloneSysInfos';
-import { HealthType } from '../../../../api/system';
-import { StandaloneSysInfo } from '../../utils';
+import { HealthType, SysInfo } from '../../../../api/system';
 
-const sysInfoData: StandaloneSysInfo = {
-  Cluster: true,
+const sysInfoData: SysInfo = {
   Health: HealthType.RED,
-  'Logs Level': 'DEBUG',
-  Name: 'Foo',
-  'Health Causes': [{ message: 'Database down' }],
+  'Health Causes': ['Database down'],
   'Web JVM': { 'Max Memory': '2Gb' },
   'Compute Engine': { Pending: 4 },
-  Elasticsearch: { 'Number of Nodes': 1 }
+  Search: { 'Number of Nodes': 1 },
+  System: {
+    'High Availability': true,
+    'Logs Level': 'DEBUG'
+  }
 };
 
 it('should render correctly', () => {
index eeaa775462de635b075b9c713bda33ac06fe1844..4759423740e43accfa6f2b7231f532294fced251 100644 (file)
@@ -97,7 +97,7 @@ exports[`should display some warning messages for non INFO levels 1`] = `
       className="modal-foot"
     >
       <button
-        disabled={true}
+        disabled={false}
         id="set-log-level-submit"
       >
         save
@@ -206,7 +206,7 @@ exports[`should render correctly 1`] = `
       className="modal-foot"
     >
       <button
-        disabled={true}
+        disabled={false}
         id="set-log-level-submit"
       >
         save
index abd7d3a63ace6321ffa6ca3588a59f9a6d0ac6ee..6db92bdf46b7d36b84af49e3e9070e2bc59cabc5 100644 (file)
@@ -7,9 +7,7 @@ exports[`should support more than two nodes 1`] = `
     health="RED"
     healthCauses={
       Array [
-        Object {
-          "message": "Database down",
-        },
+        "Database down",
       ]
     }
     name="System"
@@ -17,7 +15,8 @@ exports[`should support more than two nodes 1`] = `
     open={true}
     sysInfoData={
       Object {
-        "Name": "Foo",
+        "High Availability": true,
+        "Logs Level": "INFO",
       }
     }
   />
@@ -34,8 +33,12 @@ exports[`should support more than two nodes 1`] = `
     open={false}
     sysInfoData={
       Object {
-        "Logs Level": "INFO",
-        "Name": "Bar",
+        "Compute Engine Logging": Object {
+          "Logs Level": "INFO",
+        },
+        "Web Logging": Object {
+          "Logs Level": "INFO",
+        },
       }
     }
   />
@@ -52,8 +55,12 @@ exports[`should support more than two nodes 1`] = `
     open={false}
     sysInfoData={
       Object {
-        "Logs Level": "INFO",
-        "Name": "Baz",
+        "Compute Engine Logging": Object {
+          "Logs Level": "INFO",
+        },
+        "Web Logging": Object {
+          "Logs Level": "INFO",
+        },
       }
     }
   />
index f4961aeac1e135e974785ff158d0530f39836d9f..b7a2d4285c16e7a2c9597908e559bffc9f94c821 100644 (file)
@@ -7,9 +7,7 @@ exports[`should render correctly 1`] = `
     health="RED"
     healthCauses={
       Array [
-        Object {
-          "message": "Database down",
-        },
+        "Database down",
       ]
     }
     name="System"
@@ -17,8 +15,8 @@ exports[`should render correctly 1`] = `
     open={false}
     sysInfoData={
       Object {
+        "High Availability": true,
         "Logs Level": "DEBUG",
-        "Name": "Foo",
       }
     }
   />
@@ -28,11 +26,9 @@ exports[`should render correctly 1`] = `
     open={false}
     sysInfoData={
       Object {
-        "Web Database Connectivity": undefined,
         "Web JVM": Object {
           "Max Memory": "2Gb",
         },
-        "Web JVM Properties": undefined,
       }
     }
   />
@@ -42,9 +38,9 @@ exports[`should render correctly 1`] = `
     open={true}
     sysInfoData={
       Object {
-        "Compute Engine JVM": undefined,
-        "Compute Engine JVM Properties": undefined,
-        "Pending": 4,
+        "Compute Engine": Object {
+          "Pending": 4,
+        },
       }
     }
   />
@@ -54,11 +50,9 @@ exports[`should render correctly 1`] = `
     open={false}
     sysInfoData={
       Object {
-        "Elasticsearch": Object {
+        "Search": Object {
           "Number of Nodes": 1,
         },
-        "Search JVM": undefined,
-        "Search JVM Properties": undefined,
       }
     }
   />
index 459f26ee7216c7c79a3affa940cf44067591a1cf..ff49fc75a833bfca2d81e618da81a8e527d3da89 100644 (file)
@@ -23,14 +23,14 @@ import { map } from 'lodash';
 import HealthItem from './HealthItem';
 import OpenCloseIcon from '../../../../components/icons-components/OpenCloseIcon';
 import Section from './Section';
-import { HealthType, HealthCause, SysValueObject } from '../../../../api/system';
+import { HealthType, SysValueObject } from '../../../../api/system';
 import { LOGS_LEVELS, groupSections, getLogsLevel } from '../../utils';
 import { translate } from '../../../../helpers/l10n';
 
 interface Props {
   biggerHealth?: boolean;
   health?: HealthType;
-  healthCauses?: HealthCause[];
+  healthCauses?: string[];
   onClick: (toggledCard: string) => void;
   open: boolean;
   name: string;
index 49b22b717bb8a4f8766882a42f42f3bdd331f81b..7d34624a7093801d8770204825c1860c80b07f27 100644 (file)
  */
 import * as React from 'react';
 import * as classNames from 'classnames';
-import { HealthCause, HealthType } from '../../../../api/system';
+import { HealthType } from '../../../../api/system';
 
 interface Props {
   className?: string;
   health: HealthType;
-  healthCause: HealthCause;
+  healthCause: string;
 }
 
 export default function HealthCauseItem({ className, health, healthCause }: Props) {
@@ -35,7 +35,7 @@ export default function HealthCauseItem({ className, health, healthCause }: Prop
         health === HealthType.RED ? 'alert-danger' : 'alert-warning',
         className
       )}>
-      {healthCause.message}
+      {healthCause}
     </span>
   );
 }
index 86618c34c8a19ce9a5b9d7775789af4bd5614d39..2edc10805e00e5ba89a45fdfc9437e93d2c1196f 100644 (file)
@@ -21,13 +21,13 @@ import * as React from 'react';
 import * as classNames from 'classnames';
 import HealthCauseItem from './HealthCauseItem';
 import StatusIndicator from '../../../../components/common/StatusIndicator';
-import { HealthType, HealthCause } from '../../../../api/system';
+import { HealthType } from '../../../../api/system';
 
 interface Props {
   biggerHealth?: boolean;
   className?: string;
   health: HealthType;
-  healthCauses?: HealthCause[];
+  healthCauses?: string[];
 }
 
 export default function HealthItem({ biggerHealth, className, health, healthCauses }: Props) {
index 98b33bd31eebaa2f41521384c61904f1c80c0003..933510b929f0d5fddd7dbf4296422d03f6e0dd6f 100644 (file)
@@ -59,7 +59,7 @@ function getShallowWrapper(props = {}) {
     <HealthCard
       biggerHealth={false}
       health={HealthType.RED}
-      healthCauses={[{ message: 'foo' }]}
+      healthCauses={['foo']}
       name="Foobar"
       onClick={() => {}}
       open={false}
index 24504f09a913537729412d64cf684d18e482be35..319d069cae5aaf0574695548ccd82d82b7424eb5 100644 (file)
@@ -23,10 +23,8 @@ import HealthCauseItem from '../HealthCauseItem';
 import { HealthType } from '../../../../../api/system';
 
 it('should render correctly', () => {
+  expect(shallow(<HealthCauseItem health={HealthType.RED} healthCause="foo" />)).toMatchSnapshot();
   expect(
-    shallow(<HealthCauseItem health={HealthType.RED} healthCause={{ message: 'foo' }} />)
-  ).toMatchSnapshot();
-  expect(
-    shallow(<HealthCauseItem health={HealthType.YELLOW} healthCause={{ message: 'foo' }} />)
+    shallow(<HealthCauseItem health={HealthType.YELLOW} healthCause="foo" />)
   ).toMatchSnapshot();
 });
index 9f1bd19835268a2867462f875b757e4c953dab01..7f21d999a4e93fc3e01b05eb4c0414881b54adb3 100644 (file)
@@ -24,26 +24,19 @@ import { HealthType } from '../../../../../api/system';
 
 it('should render correctly', () => {
   expect(
-    shallow(
-      <HealthItem biggerHealth={true} health={HealthType.RED} healthCauses={[{ message: 'foo' }]} />
-    )
+    shallow(<HealthItem biggerHealth={true} health={HealthType.RED} healthCauses={['foo']} />)
   ).toMatchSnapshot();
 });
 
 it('should not render health causes', () => {
   expect(
-    shallow(<HealthItem health={HealthType.GREEN} healthCauses={[{ message: 'foo' }]} />)
+    shallow(<HealthItem health={HealthType.GREEN} healthCauses={['foo']} />)
   ).toMatchSnapshot();
   expect(shallow(<HealthItem health={HealthType.YELLOW} healthCauses={[]} />)).toMatchSnapshot();
 });
 
 it('should render multiple health causes', () => {
   expect(
-    shallow(
-      <HealthItem
-        health={HealthType.YELLOW}
-        healthCauses={[{ message: 'foo' }, { message: 'bar' }]}
-      />
-    )
+    shallow(<HealthItem health={HealthType.YELLOW} healthCauses={['foo', 'bar']} />)
   ).toMatchSnapshot();
 });
index 7390ef8020e73ae840bd77734751ba4084252675..6c054721b70be7eca0441c2387ea47986cb129f3 100644 (file)
@@ -31,9 +31,7 @@ exports[`should display the sysinfo detail 1`] = `
       health="RED"
       healthCauses={
         Array [
-          Object {
-            "message": "foo",
-          },
+          "foo",
         ]
       }
     />
@@ -69,9 +67,7 @@ exports[`should render correctly 1`] = `
       health="RED"
       healthCauses={
         Array [
-          Object {
-            "message": "foo",
-          },
+          "foo",
         ]
       }
     />
@@ -102,9 +98,7 @@ exports[`should show a main section and multiple sub sections 1`] = `
       health="RED"
       healthCauses={
         Array [
-          Object {
-            "message": "foo",
-          },
+          "foo",
         ]
       }
     />
index f962437404c374c88db67e012386c912528f5c29..eafb250bdd15b5fb2252871a109323bf92af59a5 100644 (file)
@@ -27,11 +27,7 @@ exports[`should render correctly 1`] = `
   <HealthCauseItem
     className="spacer-right"
     health="RED"
-    healthCause={
-      Object {
-        "message": "foo",
-      }
-    }
+    healthCause="foo"
   />
   <StatusIndicator
     color="red"
@@ -47,20 +43,12 @@ exports[`should render multiple health causes 1`] = `
   <HealthCauseItem
     className="spacer-right"
     health="YELLOW"
-    healthCause={
-      Object {
-        "message": "foo",
-      }
-    }
+    healthCause="foo"
   />
   <HealthCauseItem
     className="spacer-right"
     health="YELLOW"
-    healthCause={
-      Object {
-        "message": "bar",
-      }
-    }
+    healthCause="bar"
   />
   <StatusIndicator
     color="yellow"
index ae8818edf9d99622ec86c76d7a2512816e01bd2a..5d98c9e74f83baebde48df064bb04d1cee963bc9 100644 (file)
@@ -17,7 +17,7 @@
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
-import { each, omit, memoize, sortBy } from 'lodash';
+import { each, memoize, omit, omitBy, pickBy, sortBy } from 'lodash';
 import {
   cleanQuery,
   parseAsArray,
@@ -26,7 +26,7 @@ import {
   serializeStringArray
 } from '../../helpers/query';
 import {
-  HealthCause,
+  ClusterSysInfo,
   HealthType,
   NodeInfo,
   SysInfo,
@@ -38,45 +38,64 @@ export interface Query {
   expandedCards: string[];
 }
 
-export interface ClusterSysInfo extends SysInfo {
-  'Application Nodes': NodeInfo[];
-  'Search Nodes': NodeInfo[];
-}
-
-export interface StandaloneSysInfo extends SysInfo {
-  'Logs Level': string;
-}
-
 export const LOGS_LEVELS = ['INFO', 'DEBUG', 'TRACE'];
+export const HA_FIELD = 'High Availability';
 export const HEALTH_FIELD = 'Health';
 export const HEALTHCAUSES_FIELD = 'Health Causes';
 
 export function ignoreInfoFields(sysInfoObject: SysValueObject): SysValueObject {
-  return omit(sysInfoObject, ['Cluster', HEALTH_FIELD, HEALTHCAUSES_FIELD]);
+  return omit(sysInfoObject, [HEALTH_FIELD, HEALTHCAUSES_FIELD, 'Name', 'Settings']);
 }
 
 export function getHealth(sysInfoObject: SysValueObject): HealthType {
   return sysInfoObject[HEALTH_FIELD] as HealthType;
 }
 
-export function getHealthCauses(sysInfoObject: SysValueObject): HealthCause[] {
-  return sysInfoObject[HEALTHCAUSES_FIELD] as HealthCause[];
+export function getHealthCauses(sysInfoObject: SysValueObject): string[] {
+  return sysInfoObject[HEALTHCAUSES_FIELD] as string[];
 }
 
 export function getLogsLevel(sysInfoObject: SysValueObject): string {
+  if (sysInfoObject['Web Logging']) {
+    return sortBy(
+      [
+        (sysInfoObject as NodeInfo)['Compute Engine Logging']['Logs Level'],
+        (sysInfoObject as NodeInfo)['Web Logging']['Logs Level']
+      ],
+      logLevel => LOGS_LEVELS.indexOf(logLevel)
+    )[1];
+  }
+  if (sysInfoObject['System']) {
+    return (sysInfoObject as SysInfo)['System']['Logs Level'];
+  }
   return (sysInfoObject['Logs Level'] || LOGS_LEVELS[0]) as string;
 }
 
+export function getAppNodes(sysInfoData: ClusterSysInfo): NodeInfo[] {
+  return sysInfoData['Application Nodes'];
+}
+
+export function getSearchNodes(sysInfoData: ClusterSysInfo): NodeInfo[] {
+  return sysInfoData['Search Nodes'];
+}
+
+export function isCluster(sysInfoData?: SysInfo): boolean {
+  return (
+    sysInfoData != undefined && sysInfoData['System'] && sysInfoData['System'][HA_FIELD] === true
+  );
+}
+
 export function getSystemLogsLevel(sysInfoData?: SysInfo): string {
   const defaultLevel = LOGS_LEVELS[0];
   if (!sysInfoData) {
     return defaultLevel;
   }
   if (isCluster(sysInfoData)) {
-    const nodes = sortBy(getAppNodes(sysInfoData as ClusterSysInfo), node =>
-      LOGS_LEVELS.indexOf(getLogsLevel(node))
+    const logLevels = sortBy(
+      getAppNodes(sysInfoData as ClusterSysInfo).map(getLogsLevel),
+      logLevel => LOGS_LEVELS.indexOf(logLevel)
     );
-    return nodes.length > 0 ? getLogsLevel(nodes[nodes.length - 1]) : defaultLevel;
+    return logLevels.length > 0 ? logLevels[logLevels.length - 1] : defaultLevel;
   } else {
     return getLogsLevel(sysInfoData);
   }
@@ -87,51 +106,40 @@ export function getNodeName(nodeInfo: NodeInfo): string {
 }
 
 export function getClusterMainCardSection(sysInfoData: ClusterSysInfo): SysValueObject {
-  return omit(sysInfoData, ['Application Nodes', 'Search Nodes', 'Settings', 'Statistics']);
-}
-
-export function getStandaloneMainSections(sysInfoData: StandaloneSysInfo): SysValueObject {
-  return omit(sysInfoData, [
-    'Settings',
-    'Statistics',
-    'Compute Engine',
-    'Compute Engine JVM',
-    'Compute Engine JVM Properties',
-    'Elasticsearch',
-    'Search JVM',
-    'Search JVM Properties',
-    'Web Database Connectivity',
-    'Web JVM',
-    'Web JVM Properties'
-  ]);
-}
-
-export function getStandaloneSecondarySections(sysInfoData: StandaloneSysInfo): SysInfoSection {
   return {
-    Web: {
-      'Web Database Connectivity': sysInfoData['Web Database Connectivity'],
-      'Web JVM': sysInfoData['Web JVM'],
-      'Web JVM Properties': sysInfoData['Web JVM Properties']
-    },
-    'Compute Engine': {
-      ...sysInfoData['Compute Engine'] as SysValueObject,
-      'Compute Engine JVM': sysInfoData['Compute Engine JVM'],
-      'Compute Engine JVM Properties': sysInfoData['Compute Engine JVM Properties']
-    },
-    Search: {
-      Elasticsearch: sysInfoData['Elasticsearch'] as SysValueObject,
-      'Search JVM': sysInfoData['Search JVM'],
-      'Search JVM Properties': sysInfoData['Search JVM Properties']
-    }
+    ...sysInfoData['System'],
+    ...omit(sysInfoData, [
+      'Application Nodes',
+      'Plugins',
+      'Search Nodes',
+      'Settings',
+      'Statistics',
+      'System'
+    ])
   };
 }
 
-export function getAppNodes(sysInfoData: ClusterSysInfo): NodeInfo[] {
-  return sysInfoData['Application Nodes'];
+export function getStandaloneMainSections(sysInfoData: SysInfo): SysValueObject {
+  return {
+    ...sysInfoData['System'],
+    ...omitBy(
+      sysInfoData,
+      (value, key) =>
+        value == null ||
+        ['Plugins', 'Settings', 'Statistics', 'System'].includes(key) ||
+        key.startsWith('Compute Engine') ||
+        key.startsWith('Search') ||
+        key.startsWith('Web')
+    )
+  };
 }
 
-export function getSearchNodes(sysInfoData: ClusterSysInfo): NodeInfo[] {
-  return sysInfoData['Search Nodes'];
+export function getStandaloneSecondarySections(sysInfoData: SysInfo): SysInfoSection {
+  return {
+    Web: pickBy(sysInfoData, (_, key) => key.startsWith('Web')),
+    'Compute Engine': pickBy(sysInfoData, (_, key) => key.startsWith('Compute Engine')),
+    Search: pickBy(sysInfoData, (_, key) => key.startsWith('Search'))
+  };
 }
 
 export function groupSections(sysInfoData: SysValueObject) {
@@ -147,18 +155,12 @@ export function groupSections(sysInfoData: SysValueObject) {
   return { mainSection, sections };
 }
 
-export function isCluster(sysInfoData?: SysInfo): boolean {
-  return sysInfoData != undefined && sysInfoData['Cluster'] === true;
-}
+export const parseQuery = memoize((urlQuery: RawQuery): Query => ({
+  expandedCards: parseAsArray(urlQuery.expand, parseAsString)
+}));
 
-export const parseQuery = memoize((urlQuery: RawQuery): Query => {
-  return {
-    expandedCards: parseAsArray(urlQuery.expand, parseAsString)
-  };
-});
-
-export const serializeQuery = memoize((query: Query): RawQuery => {
-  return cleanQuery({
+export const serializeQuery = memoize((query: Query): RawQuery =>
+  cleanQuery({
     expand: serializeStringArray(query.expandedCards)
-  });
-});
+  })
+);
index 26008838cd243ab095167a411fd8ee71dc13fab2..7e7aab814293ae783cbbf64bdc02bfb1e425bfa0 100644 (file)
@@ -25,7 +25,7 @@
   background-color: #d4333f;
 }
 
-.sstatus-indicator.yellow {
+.status-indicator.yellow {
   background-color: #eabe06;
 }
 
index a586cb31c47b072311632612ec5cd069ffd7ddb2..96bac547e32bd828a60bdc94ecca0c6a33cde8c0 100644 (file)
@@ -174,6 +174,10 @@ public class Navigation {
     return open("/settings/server_id", ServerIdPage.class);
   }
 
+  public SystemInfoPage openSystemInfo() {
+    return open("/admin/system", SystemInfoPage.class);
+  }
+
   public NotificationsPage openNotifications() {
     return open("/account/notifications", NotificationsPage.class);
   }
diff --git a/tests/src/test/java/org/sonarqube/pageobjects/SystemInfoPage.java b/tests/src/test/java/org/sonarqube/pageobjects/SystemInfoPage.java
new file mode 100644 (file)
index 0000000..84ba01b
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * 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.sonarqube.pageobjects;
+
+import com.codeborne.selenide.CollectionCondition;
+import com.codeborne.selenide.SelenideElement;
+
+import static com.codeborne.selenide.Condition.exist;
+import static com.codeborne.selenide.Condition.text;
+import static com.codeborne.selenide.Selenide.$;
+import static com.codeborne.selenide.Selenide.$$;
+
+public class SystemInfoPage {
+  public SystemInfoPage() {
+    $(".page-title").should(exist).shouldHave(text("System Info"));
+  }
+
+  public SystemInfoPage shouldHaveCards(String... titles) {
+    $$(".system-info-health-card").shouldHave(CollectionCondition.texts(titles));
+    return this;
+  }
+
+  public SystemInfoPageItem getCardItem(String card) {
+    SelenideElement cardTitle = $$(".system-info-health-card-title").find(text(card)).should(exist);
+    return new SystemInfoPageItem(cardTitle.parent().parent());
+  }
+}
\ No newline at end of file
diff --git a/tests/src/test/java/org/sonarqube/pageobjects/SystemInfoPageItem.java b/tests/src/test/java/org/sonarqube/pageobjects/SystemInfoPageItem.java
new file mode 100644 (file)
index 0000000..19a023e
--- /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.sonarqube.pageobjects;
+
+import com.codeborne.selenide.SelenideElement;
+
+import static com.codeborne.selenide.Condition.exist;
+import static com.codeborne.selenide.Condition.text;
+
+public class SystemInfoPageItem {
+  private final SelenideElement elt;
+
+  public SystemInfoPageItem(SelenideElement elt) {
+      this.elt = elt;
+    }
+
+  public SystemInfoPageItem shouldHaveHealth() {
+    elt.$(".system-info-health-info .status-indicator").should(exist);
+    return this;
+  }
+
+  public SystemInfoPageItem shouldHaveSection(String section) {
+    ensureOpen();
+    elt.$$("h4").findBy(text(section)).should(exist);
+    return this;
+  }
+
+  public SystemInfoPageItem shouldNotHaveSection(String section) {
+    ensureOpen();
+    elt.$$("h4").findBy(text(section)).shouldNot(exist);
+    return this;
+  }
+
+  public SystemInfoPageItem shouldHaveMainSection() {
+    ensureOpen();
+    elt.$$(".system-info-section").get(0).find("h4").shouldNot(exist);
+    return this;
+  }
+
+  public SystemInfoPageItem shouldHaveField(String field) {
+    ensureOpen();
+    elt.$$(".system-info-section-item-name").findBy(text(field)).should(exist);
+    return this;
+  }
+
+  public SystemInfoPageItem shouldNotHaveField(String field) {
+    ensureOpen();
+    elt.$$(".system-info-section-item-name").findBy(text(field)).shouldNot(exist);
+    return this;
+  }
+
+  public SystemInfoPageItem shouldHaveFieldWithValue(String field, String value) {
+    ensureOpen();
+    SelenideElement fieldElem = elt.$$(".system-info-section-item-name").findBy(text(field)).should(exist);
+    fieldElem.parent().parent().$$("td").shouldHaveSize(2).get(1).shouldHave(text(value));
+    return this;
+  }
+
+  public SystemInfoPageItem ensureOpen() {
+    if(!isOpen()) {
+      elt.click();
+      elt.$(".boxed-group-inner").should(exist);
+    }
+    return this;
+  }
+
+  private boolean isOpen() {
+    return elt.$(".boxed-group-inner").exists();
+  }
+}
index 615c077fd957284cd1d78c07a29a42cd9b4d10a9..2fab9bedb0b17697555ab86182a95b3ae75becce 100644 (file)
@@ -82,7 +82,7 @@ public class ClusterTest {
       app.waitForHealthGreen();
 
       System.out.println("-----------------------------------------------------------------------");
-      String json = app.wsClient().wsConnector().call(new GetRequest("api/system/cluster_info")).content();
+      String json = app.wsClient().wsConnector().call(new GetRequest("api/system/info")).content();
       System.out.println(json);
       System.out.println("-----------------------------------------------------------------------");
     }
index 62098db37497da84ee1323ea0a129dbaca4484e0..605b058df2687534e6ab0eb8610f141c4fc36b47 100644 (file)
@@ -27,6 +27,7 @@ import org.apache.commons.io.FileUtils;
 import org.junit.ClassRule;
 import org.junit.Rule;
 import org.junit.Test;
+import org.sonarqube.pageobjects.SystemInfoPage;
 import org.sonarqube.tests.Category4Suite;
 import org.sonarqube.tests.Tester;
 import org.sonarqube.ws.client.GetRequest;
@@ -48,7 +49,35 @@ public class SystemInfoTest {
   @Test
   public void test_system_info_page() {
     tester.users().generateAdministrator(u -> u.setLogin(ADMIN_USER_LOGIN).setPassword(ADMIN_USER_LOGIN));
-    tester.runHtmlTests("/serverSystem/ServerSystemTest/system_info.html");
+    SystemInfoPage page = tester.openBrowser().logIn().submitCredentials(ADMIN_USER_LOGIN).openSystemInfo();
+    page.shouldHaveCards("System", "Web", "Compute Engine", "Search");
+
+    page.getCardItem("System")
+      .shouldHaveHealth()
+      .shouldHaveMainSection()
+      .shouldHaveSection("Database")
+      .shouldNotHaveSection("Settings")
+      .shouldNotHaveSection("Plugins")
+      .shouldNotHaveSection("Statistics")
+      .shouldHaveField("Official Distribution")
+      .shouldHaveField("Version")
+      .shouldHaveField("Logs Level")
+      .shouldHaveField("High Availability")
+      .shouldNotHaveField("Health")
+      .shouldNotHaveField("Health Causes");
+
+    page.getCardItem("Web")
+      .shouldHaveSection("Web JVM Properties")
+      .shouldHaveSection("Web JVM State");
+
+    page.getCardItem("Compute Engine")
+      .shouldHaveSection("Compute Engine Database Connection")
+      .shouldHaveSection("Compute Engine JVM State")
+      .shouldHaveSection("Compute Engine Tasks");
+
+    page.getCardItem("Search")
+      .shouldHaveSection("Search State")
+      .shouldHaveSection("Search Statistics");
   }
 
   @Test
@@ -63,9 +92,9 @@ public class SystemInfoTest {
 
     // SONAR-7436 monitor ES and CE
     assertThat((Map)json.get("Compute Engine Database Connection")).isNotEmpty();
-    assertThat((Map)json.get("Compute Engine State")).isNotEmpty();
+    assertThat((Map)json.get("Compute Engine JVM State")).isNotEmpty();
     assertThat((Map)json.get("Compute Engine Tasks")).isNotEmpty();
-    Map<String,Object> esJson = (Map) json.get("Elasticsearch");
+    Map<String,Object> esJson = (Map) json.get("Search State");
     assertThat(esJson.get("State")).isEqualTo("GREEN");
 
     // SONAR-7271 get settings
index 5e4170fceb139a2dec357fcfbfd20616dc54a4e3..12de1b740af2679f996a4697e8b7a1b30e8914d6 100644 (file)
@@ -36,6 +36,8 @@ import org.sonar.wsclient.base.HttpException;
 import org.sonar.wsclient.connectors.HttpClient4Connector;
 import org.sonar.wsclient.services.AuthenticationQuery;
 import org.sonar.wsclient.user.UserParameters;
+import org.sonarqube.pageobjects.SystemInfoPage;
+import org.sonarqube.tests.Tester;
 import org.sonarqube.ws.client.GetRequest;
 import org.sonarqube.ws.client.WsResponse;
 import org.sonarqube.ws.client.user.CreateRequest;
@@ -81,6 +83,9 @@ public class RealmAuthenticationTest {
   @Rule
   public UserRule userRule = UserRule.from(orchestrator);
 
+  @Rule
+  public Tester tester = new Tester(orchestrator).disableOrganizations();
+
   @Before
   @After
   public void resetData() throws Exception {
@@ -121,7 +126,8 @@ public class RealmAuthenticationTest {
     runSelenese(orchestrator, "/user/ExternalAuthenticationTest/external-user-details.html");
 
     // SONAR-4462
-    runSelenese(orchestrator, "/user/ExternalAuthenticationTest/system-info.html");
+    SystemInfoPage page = tester.openBrowser().logIn().submitCredentials(ADMIN_USER_LOGIN).openSystemInfo();
+    page.getCardItem("System").shouldHaveFieldWithValue("External User Authentication", "FakeRealm");
   }
 
   /**
diff --git a/tests/src/test/resources/serverSystem/ServerSystemTest/system_info.html b/tests/src/test/resources/serverSystem/ServerSystemTest/system_info.html
deleted file mode 100644 (file)
index feee888..0000000
+++ /dev/null
@@ -1,80 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
-<head profile="http://selenium-ide.openqa.org/profiles/test-case">
-  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
-  <title>system_info</title>
-</head>
-<body>
-<table cellpadding="1" cellspacing="1" border="1">
-  <thead>
-  <tr>
-    <td rowspan="1" colspan="3">system_info</td>
-  </tr>
-  </thead>
-  <tbody>
-
-  <tr>
-    <td>open</td>
-    <td>/sessions/new</td>
-    <td></td>
-  </tr>
-  <tr>
-    <td>type</td>
-    <td>login</td>
-    <td>admin-user</td>
-  </tr>
-  <tr>
-    <td>type</td>
-    <td>password</td>
-    <td>admin-user</td>
-  </tr>
-  <tr>
-    <td>clickAndWait</td>
-    <td>commit</td>
-    <td></td>
-  </tr>
-  <tr>
-    <td>waitForElementPresent</td>
-    <td>css=.js-user-authenticated</td>
-    <td></td>
-  </tr>
-  <tr>
-    <td>open</td>
-    <td>/system/index</td>
-    <td></td>
-  </tr>
-  <tr>
-    <td>waitForText</td>
-    <td>id=content</td>
-    <td>*Official Distribution*</td>
-  </tr>
-  <tr>
-    <td>waitForText</td>
-    <td>id=content</td>
-    <td>*Database Version*</td>
-  </tr>
-  <tr>
-    <td>waitForText</td>
-    <td>id=content</td>
-    <td>*Pool Active Connections*</td>
-  </tr>
-  <tr>
-    <td>waitForText</td>
-    <td>id=content</td>
-    <td>*Start Time*</td>
-  </tr>
-  <tr>
-    <td>waitForText</td>
-    <td>id=content</td>
-    <td>*Processors*</td>
-  </tr>
-  <tr>
-    <td>waitForText</td>
-    <td>id=content</td>
-    <td>*java.class.path*java.specification.version*</td>
-  </tr>
-  </tbody>
-</table>
-</body>
-</html>
diff --git a/tests/src/test/resources/user/ExternalAuthenticationTest/system-info.html b/tests/src/test/resources/user/ExternalAuthenticationTest/system-info.html
deleted file mode 100644 (file)
index 1daf46d..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE html PUBLIC "-/W3C/DTD XHTML 1.0 Strict/EN" "http:/www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http:/www.w3.org/1999/xhtml" xml:lang="en" lang="en">
-<head profile="http:/selenium-ide.openqa.org/profiles/test-case">
-  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
-  <title>external_user_details</title>
-</head>
-<body>
-<table cellpadding="1" cellspacing="1" border="1">
-  <thead>
-  <tr>
-    <td rowspan="1" colspan="3">external_user_details</td>
-  </tr>
-  </thead>
-  <tbody>
-  <tr>
-    <td>open</td>
-    <td>/system</td>
-    <td></td>
-  </tr>
-  <tr>
-    <td>type</td>
-    <td>login</td>
-    <td>admin</td>
-  </tr>
-  <tr>
-    <td>type</td>
-    <td>password</td>
-    <td>admin</td>
-  </tr>
-  <tr>
-    <td>clickAndWait</td>
-    <td>commit</td>
-    <td></td>
-  </tr>
-  <tr>
-    <td>waitForText</td>
-    <td>id=content</td>
-    <td>*External User Authentication*FakeRealm*</td>
-  </tr>
-  </tbody>
-</table>
-</body>
-</html>