diff options
author | Simon Brandhof <simon.brandhof@sonarsource.com> | 2017-09-19 22:12:00 +0200 |
---|---|---|
committer | Simon Brandhof <simon.brandhof@sonarsource.com> | 2017-09-26 23:49:38 +0200 |
commit | 50a29c569f8448a939877de1918ab3ff937366b8 (patch) | |
tree | 793a173461e17df5f3da0e01b1d54546972175c6 /server | |
parent | e66b868ad008967f65428a4e5400225600e9413e (diff) | |
download | sonarqube-50a29c569f8448a939877de1918ab3ff937366b8.tar.gz sonarqube-50a29c569f8448a939877de1918ab3ff937366b8.zip |
SONAR-9802 complete system info page
Diffstat (limited to 'server')
49 files changed, 793 insertions, 729 deletions
diff --git a/server/sonar-ce/src/main/java/org/sonar/ce/CeQueueModule.java b/server/sonar-ce/src/main/java/org/sonar/ce/CeQueueModule.java index d8f37cd5e11..2811aa16c44 100644 --- a/server/sonar-ce/src/main/java/org/sonar/ce/CeQueueModule.java +++ b/server/sonar-ce/src/main/java/org/sonar/ce/CeQueueModule.java @@ -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); } diff --git a/server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java b/server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java index 79f042caf3d..b62dca5b430 100644 --- a/server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java +++ b/server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java @@ -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/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java b/server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java index 640b8e76c43..4cf1cac4434 100644 --- a/server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java +++ b/server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java @@ -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 diff --git a/server/sonar-main/pom.xml b/server/sonar-main/pom.xml index 27e46c81cfb..eba72bb7f62 100644 --- a/server/sonar-main/pom.xml +++ b/server/sonar-main/pom.xml @@ -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> <!-- diff --git a/server/sonar-process/pom.xml b/server/sonar-process/pom.xml index 58adf12de38..00811e45e56 100644 --- a/server/sonar-process/pom.xml +++ b/server/sonar-process/pom.xml @@ -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> diff --git a/server/sonar-process/src/test/java/org/sonar/process/cluster/hz/DistributedAnswerTest.java b/server/sonar-process/src/test/java/org/sonar/process/cluster/hz/DistributedAnswerTest.java index 6c9d8270a19..2cdf3d5dd75 100644 --- a/server/sonar-process/src/test/java/org/sonar/process/cluster/hz/DistributedAnswerTest.java +++ b/server/sonar-process/src/test/java/org/sonar/process/cluster/hz/DistributedAnswerTest.java @@ -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,10 +32,22 @@ 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(); assertThat(underTest.hasTimedOut(member)).isFalse(); @@ -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; } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/cluster/StartableHazelcastMember.java b/server/sonar-server/src/main/java/org/sonar/server/cluster/StartableHazelcastMember.java index e746c21435d..1e430e17d29 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/cluster/StartableHazelcastMember.java +++ b/server/sonar-server/src/main/java/org/sonar/server/cluster/StartableHazelcastMember.java @@ -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); } diff --git a/server/sonar-server/src/main/java/org/sonar/server/health/ClusterHealth.java b/server/sonar-server/src/main/java/org/sonar/server/health/ClusterHealth.java index 26a54ce0bcf..53d75a4dede 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/health/ClusterHealth.java +++ b/server/sonar-server/src/main/java/org/sonar/server/health/ClusterHealth.java @@ -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) { diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/EsStateSection.java b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/EsStateSection.java index 900cb59ca14..40d8803dbf9 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/EsStateSection.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/EsStateSection.java @@ -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(); } diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/StandaloneSystemSection.java b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/StandaloneSystemSection.java index acdffa2efb5..799fcbb4023 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/StandaloneSystemSection.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/StandaloneSystemSection.java @@ -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()); diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/WebSystemInfoModule.java b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/WebSystemInfoModule.java index b5f495f2c64..01dec926e3a 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/WebSystemInfoModule.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/WebSystemInfoModule.java @@ -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 }; } diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/cluster/AppNodesInfoLoaderImpl.java b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/cluster/AppNodesInfoLoaderImpl.java index 03eaaa09dfe..67c943028f0 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/cluster/AppNodesInfoLoaderImpl.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/cluster/AppNodesInfoLoaderImpl.java @@ -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); } diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/cluster/GlobalSystemSection.java b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/cluster/GlobalSystemSection.java index 90875c5d7b6..e2999dcb56a 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/cluster/GlobalSystemSection.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/cluster/GlobalSystemSection.java @@ -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 index 00000000000..1af901bb417 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/cluster/LoggingSection.java @@ -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(); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/cluster/NodeInfo.java b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/cluster/NodeInfo.java index 3a53e1b829c..c9a368e4264 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/cluster/NodeInfo.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/cluster/NodeInfo.java @@ -20,15 +20,23 @@ 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) { diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/cluster/NodeSystemSection.java b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/cluster/NodeSystemSection.java index 8ab8a07f1e2..e2def825669 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/cluster/NodeSystemSection.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/cluster/NodeSystemSection.java @@ -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-ce/src/main/java/org/sonar/ce/monitoring/CeSystemInfoModule.java b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/cluster/SearchNodesInfoLoader.java index be53383ebee..3b91ffbf9ba 100644 --- a/server/sonar-ce/src/main/java/org/sonar/ce/monitoring/CeSystemInfoModule.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/cluster/SearchNodesInfoLoader.java @@ -17,25 +17,14 @@ * 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; +package org.sonar.server.platform.monitoring.cluster; -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 { +import java.util.Collection; - 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 - }; - } +/** + * 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 index 00000000000..08703a608b3 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/cluster/SearchNodesInfoLoaderImpl.java @@ -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; + } + +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java index b69534bc605..5d015b81036 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java @@ -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 index 00000000000..d6f821f2d17 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/ws/BaseInfoWsAction.java @@ -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()); + } + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/ws/ClusterInfoAction.java b/server/sonar-server/src/main/java/org/sonar/server/platform/ws/ClusterInfoAction.java index 87264373367..6f5e7d9bde4 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/ws/ClusterInfoAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/ws/ClusterInfoAction.java @@ -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(); } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/ws/InfoAction.java b/server/sonar-server/src/main/java/org/sonar/server/platform/ws/InfoAction.java index e19cea3974f..0011675154c 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/ws/InfoAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/ws/InfoAction.java @@ -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 index adb92772f98..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/ws/StandaloneInfoAction.java +++ /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 index 00000000000..475079444ec --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/cluster/StartableHazelcastMemberTest.java @@ -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) { + } + } + +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/health/ClusterHealthTest.java b/server/sonar-server/src/test/java/org/sonar/server/health/ClusterHealthTest.java index 8a8d918c90d..99ed8271692 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/health/ClusterHealthTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/health/ClusterHealthTest.java @@ -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(); + } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/StandaloneSystemSectionTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/StandaloneSystemSectionTest.java index ea5f52d1054..33fdb132a4c 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/StandaloneSystemSectionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/StandaloneSystemSectionTest.java @@ -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 { @@ -193,19 +189,6 @@ public class StandaloneSystemSectionTest { } @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(); assertThat(attribute(protobuf, "Processors").getLongValue()).isGreaterThan(0); diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/ws/InfoActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/ws/InfoActionTest.java index 9c02608fddd..9e92b6205c7 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/platform/ws/InfoActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/platform/ws/InfoActionTest.java @@ -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\":{}}}"); } diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/ws/SystemWsTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/ws/SystemWsTest.java index 593619663da..179c09bd863 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/platform/ws/SystemWsTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/platform/ws/SystemWsTest.java @@ -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(); diff --git a/server/sonar-web/src/main/js/api/system.ts b/server/sonar-web/src/main/js/api/system.ts index df9da694e40..18851e79091 100644 --- a/server/sonar-web/src/main/js/api/system.ts +++ b/server/sonar-web/src/main/js/api/system.ts @@ -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> { diff --git a/server/sonar-web/src/main/js/apps/system/__tests__/utils-test.ts b/server/sonar-web/src/main/js/apps/system/__tests__/utils-test.ts index 0c550fd943f..b1e915f0370 100644 --- a/server/sonar-web/src/main/js/apps/system/__tests__/utils-test.ts +++ b/server/sonar-web/src/main/js/apps/system/__tests__/utils-test.ts @@ -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'); }); }); diff --git a/server/sonar-web/src/main/js/apps/system/components/App.tsx b/server/sonar-web/src/main/js/apps/system/components/App.tsx index 1cd3752de88..38679882597 100644 --- a/server/sonar-web/src/main/js/apps/system/components/App.tsx +++ b/server/sonar-web/src/main/js/apps/system/components/App.tsx @@ -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} /> ); diff --git a/server/sonar-web/src/main/js/apps/system/components/ChangeLogLevelForm.tsx b/server/sonar-web/src/main/js/apps/system/components/ChangeLogLevelForm.tsx index 6118fe2d956..7de208bd4f6 100644 --- a/server/sonar-web/src/main/js/apps/system/components/ChangeLogLevelForm.tsx +++ b/server/sonar-web/src/main/js/apps/system/components/ChangeLogLevelForm.tsx @@ -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}> diff --git a/server/sonar-web/src/main/js/apps/system/components/ClusterSysInfos.tsx b/server/sonar-web/src/main/js/apps/system/components/ClusterSysInfos.tsx index cb3000dea32..54c70b1999c 100644 --- a/server/sonar-web/src/main/js/apps/system/components/ClusterSysInfos.tsx +++ b/server/sonar-web/src/main/js/apps/system/components/ClusterSysInfos.tsx @@ -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, diff --git a/server/sonar-web/src/main/js/apps/system/components/StandaloneSysInfos.tsx b/server/sonar-web/src/main/js/apps/system/components/StandaloneSysInfos.tsx index 4ed444cc66d..69ddc3cbfab 100644 --- a/server/sonar-web/src/main/js/apps/system/components/StandaloneSysInfos.tsx +++ b/server/sonar-web/src/main/js/apps/system/components/StandaloneSysInfos.tsx @@ -20,18 +20,18 @@ 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; } diff --git a/server/sonar-web/src/main/js/apps/system/components/__tests__/ClusterSysInfos-test.tsx b/server/sonar-web/src/main/js/apps/system/components/__tests__/ClusterSysInfos-test.tsx index 21c60cbd6fc..c2e892abb5d 100644 --- a/server/sonar-web/src/main/js/apps/system/components/__tests__/ClusterSysInfos-test.tsx +++ b/server/sonar-web/src/main/js/apps/system/components/__tests__/ClusterSysInfos-test.tsx @@ -20,20 +20,33 @@ 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') diff --git a/server/sonar-web/src/main/js/apps/system/components/__tests__/StandaloneSysInfos-test.tsx b/server/sonar-web/src/main/js/apps/system/components/__tests__/StandaloneSysInfos-test.tsx index c70202e3219..220cdb85ef1 100644 --- a/server/sonar-web/src/main/js/apps/system/components/__tests__/StandaloneSysInfos-test.tsx +++ b/server/sonar-web/src/main/js/apps/system/components/__tests__/StandaloneSysInfos-test.tsx @@ -20,18 +20,18 @@ 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', () => { diff --git a/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/ChangeLogLevelForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/ChangeLogLevelForm-test.tsx.snap index eeaa775462d..4759423740e 100644 --- a/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/ChangeLogLevelForm-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/ChangeLogLevelForm-test.tsx.snap @@ -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 diff --git a/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/ClusterSysInfos-test.tsx.snap b/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/ClusterSysInfos-test.tsx.snap index abd7d3a63ac..6db92bdf46b 100644 --- a/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/ClusterSysInfos-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/ClusterSysInfos-test.tsx.snap @@ -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", + }, } } /> diff --git a/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/StandaloneSysInfos-test.tsx.snap b/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/StandaloneSysInfos-test.tsx.snap index f4961aeac1e..b7a2d4285c1 100644 --- a/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/StandaloneSysInfos-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/StandaloneSysInfos-test.tsx.snap @@ -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, } } /> diff --git a/server/sonar-web/src/main/js/apps/system/components/info-items/HealthCard.tsx b/server/sonar-web/src/main/js/apps/system/components/info-items/HealthCard.tsx index 459f26ee721..ff49fc75a83 100644 --- a/server/sonar-web/src/main/js/apps/system/components/info-items/HealthCard.tsx +++ b/server/sonar-web/src/main/js/apps/system/components/info-items/HealthCard.tsx @@ -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; diff --git a/server/sonar-web/src/main/js/apps/system/components/info-items/HealthCauseItem.tsx b/server/sonar-web/src/main/js/apps/system/components/info-items/HealthCauseItem.tsx index 49b22b717bb..7d34624a709 100644 --- a/server/sonar-web/src/main/js/apps/system/components/info-items/HealthCauseItem.tsx +++ b/server/sonar-web/src/main/js/apps/system/components/info-items/HealthCauseItem.tsx @@ -19,12 +19,12 @@ */ 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> ); } diff --git a/server/sonar-web/src/main/js/apps/system/components/info-items/HealthItem.tsx b/server/sonar-web/src/main/js/apps/system/components/info-items/HealthItem.tsx index 86618c34c8a..2edc10805e0 100644 --- a/server/sonar-web/src/main/js/apps/system/components/info-items/HealthItem.tsx +++ b/server/sonar-web/src/main/js/apps/system/components/info-items/HealthItem.tsx @@ -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) { diff --git a/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/HealthCard-test.tsx b/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/HealthCard-test.tsx index 98b33bd31ee..933510b929f 100644 --- a/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/HealthCard-test.tsx +++ b/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/HealthCard-test.tsx @@ -59,7 +59,7 @@ function getShallowWrapper(props = {}) { <HealthCard biggerHealth={false} health={HealthType.RED} - healthCauses={[{ message: 'foo' }]} + healthCauses={['foo']} name="Foobar" onClick={() => {}} open={false} diff --git a/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/HealthCauseItem-test.tsx b/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/HealthCauseItem-test.tsx index 24504f09a91..319d069cae5 100644 --- a/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/HealthCauseItem-test.tsx +++ b/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/HealthCauseItem-test.tsx @@ -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(); }); diff --git a/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/HealthItem-test.tsx b/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/HealthItem-test.tsx index 9f1bd198352..7f21d999a4e 100644 --- a/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/HealthItem-test.tsx +++ b/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/HealthItem-test.tsx @@ -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(); }); diff --git a/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/HealthCard-test.tsx.snap b/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/HealthCard-test.tsx.snap index 7390ef8020e..6c054721b70 100644 --- a/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/HealthCard-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/HealthCard-test.tsx.snap @@ -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", ] } /> diff --git a/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/HealthItem-test.tsx.snap b/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/HealthItem-test.tsx.snap index f962437404c..eafb250bdd1 100644 --- a/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/HealthItem-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/HealthItem-test.tsx.snap @@ -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" diff --git a/server/sonar-web/src/main/js/apps/system/utils.ts b/server/sonar-web/src/main/js/apps/system/utils.ts index ae8818edf9d..5d98c9e74f8 100644 --- a/server/sonar-web/src/main/js/apps/system/utils.ts +++ b/server/sonar-web/src/main/js/apps/system/utils.ts @@ -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) - }); -}); + }) +); diff --git a/server/sonar-web/src/main/js/components/common/StatusIndicator.css b/server/sonar-web/src/main/js/components/common/StatusIndicator.css index 26008838cd2..7e7aab81429 100644 --- a/server/sonar-web/src/main/js/components/common/StatusIndicator.css +++ b/server/sonar-web/src/main/js/components/common/StatusIndicator.css @@ -25,7 +25,7 @@ background-color: #d4333f; } -.sstatus-indicator.yellow { +.status-indicator.yellow { background-color: #eabe06; } |