Bläddra i källkod

SONAR-9802 complete system info page

tags/6.6-RC1
Simon Brandhof 6 år sedan
förälder
incheckning
50a29c569f
57 ändrade filer med 968 tillägg och 858 borttagningar
  1. 5
    1
      server/sonar-ce/src/main/java/org/sonar/ce/CeQueueModule.java
  2. 11
    3
      server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java
  3. 1
    1
      server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java
  4. 2
    0
      server/sonar-main/pom.xml
  5. 1
    0
      server/sonar-process/pom.xml
  6. 55
    0
      server/sonar-process/src/test/java/org/sonar/process/cluster/hz/DistributedAnswerTest.java
  7. 4
    5
      server/sonar-server/src/main/java/org/sonar/server/cluster/StartableHazelcastMember.java
  8. 7
    0
      server/sonar-server/src/main/java/org/sonar/server/health/ClusterHealth.java
  9. 28
    18
      server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/EsStateSection.java
  10. 1
    8
      server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/StandaloneSystemSection.java
  11. 6
    2
      server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/WebSystemInfoModule.java
  12. 3
    6
      server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/cluster/AppNodesInfoLoaderImpl.java
  13. 1
    9
      server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/cluster/GlobalSystemSection.java
  14. 55
    0
      server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/cluster/LoggingSection.java
  15. 31
    8
      server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/cluster/NodeInfo.java
  16. 2
    14
      server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/cluster/NodeSystemSection.java
  17. 7
    18
      server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/cluster/SearchNodesInfoLoader.java
  18. 66
    0
      server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/cluster/SearchNodesInfoLoaderImpl.java
  19. 0
    2
      server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java
  20. 101
    0
      server/sonar-server/src/main/java/org/sonar/server/platform/ws/BaseInfoWsAction.java
  21. 42
    196
      server/sonar-server/src/main/java/org/sonar/server/platform/ws/ClusterInfoAction.java
  22. 19
    59
      server/sonar-server/src/main/java/org/sonar/server/platform/ws/InfoAction.java
  23. 0
    168
      server/sonar-server/src/main/java/org/sonar/server/platform/ws/StandaloneInfoAction.java
  24. 106
    0
      server/sonar-server/src/test/java/org/sonar/server/cluster/StartableHazelcastMemberTest.java
  25. 26
    0
      server/sonar-server/src/test/java/org/sonar/server/health/ClusterHealthTest.java
  26. 1
    18
      server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/StandaloneSystemSectionTest.java
  27. 4
    2
      server/sonar-server/src/test/java/org/sonar/server/platform/ws/InfoActionTest.java
  28. 5
    2
      server/sonar-server/src/test/java/org/sonar/server/platform/ws/SystemWsTest.java
  29. 14
    9
      server/sonar-web/src/main/js/api/system.ts
  30. 16
    5
      server/sonar-web/src/main/js/apps/system/__tests__/utils-test.ts
  31. 3
    11
      server/sonar-web/src/main/js/apps/system/components/App.tsx
  32. 2
    3
      server/sonar-web/src/main/js/apps/system/components/ChangeLogLevelForm.tsx
  33. 1
    1
      server/sonar-web/src/main/js/apps/system/components/ClusterSysInfos.tsx
  34. 3
    3
      server/sonar-web/src/main/js/apps/system/components/StandaloneSysInfos.tsx
  35. 42
    11
      server/sonar-web/src/main/js/apps/system/components/__tests__/ClusterSysInfos-test.tsx
  36. 8
    8
      server/sonar-web/src/main/js/apps/system/components/__tests__/StandaloneSysInfos-test.tsx
  37. 2
    2
      server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/ChangeLogLevelForm-test.tsx.snap
  38. 15
    8
      server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/ClusterSysInfos-test.tsx.snap
  39. 6
    12
      server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/StandaloneSysInfos-test.tsx.snap
  40. 2
    2
      server/sonar-web/src/main/js/apps/system/components/info-items/HealthCard.tsx
  41. 3
    3
      server/sonar-web/src/main/js/apps/system/components/info-items/HealthCauseItem.tsx
  42. 2
    2
      server/sonar-web/src/main/js/apps/system/components/info-items/HealthItem.tsx
  43. 1
    1
      server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/HealthCard-test.tsx
  44. 2
    4
      server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/HealthCauseItem-test.tsx
  45. 3
    10
      server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/HealthItem-test.tsx
  46. 3
    9
      server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/HealthCard-test.tsx.snap
  47. 3
    15
      server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/HealthItem-test.tsx.snap
  48. 71
    69
      server/sonar-web/src/main/js/apps/system/utils.ts
  49. 1
    1
      server/sonar-web/src/main/js/components/common/StatusIndicator.css
  50. 4
    0
      tests/src/test/java/org/sonarqube/pageobjects/Navigation.java
  51. 44
    0
      tests/src/test/java/org/sonarqube/pageobjects/SystemInfoPage.java
  52. 87
    0
      tests/src/test/java/org/sonarqube/pageobjects/SystemInfoPageItem.java
  53. 1
    1
      tests/src/test/java/org/sonarqube/tests/cluster/ClusterTest.java
  54. 32
    3
      tests/src/test/java/org/sonarqube/tests/serverSystem/SystemInfoTest.java
  55. 7
    1
      tests/src/test/java/org/sonarqube/tests/user/RealmAuthenticationTest.java
  56. 0
    80
      tests/src/test/resources/serverSystem/ServerSystemTest/system_info.html
  57. 0
    44
      tests/src/test/resources/user/ExternalAuthenticationTest/system-info.html

+ 5
- 1
server/sonar-ce/src/main/java/org/sonar/ce/CeQueueModule.java Visa fil

@@ -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);
}

+ 11
- 3
server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java Visa fil

@@ -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);
}

+ 1
- 1
server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java Visa fil

@@ -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

+ 2
- 0
server/sonar-main/pom.xml Visa fil

@@ -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>
<!--

+ 1
- 0
server/sonar-process/pom.xml Visa fil

@@ -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>

+ 55
- 0
server/sonar-process/src/test/java/org/sonar/process/cluster/hz/DistributedAnswerTest.java Visa fil

@@ -21,7 +21,9 @@ package org.sonar.process.cluster.hz;

import com.hazelcast.core.Member;
import java.io.IOException;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
@@ -30,9 +32,21 @@ import static org.mockito.Mockito.when;

public class DistributedAnswerTest {

@Rule
public ExpectedException expectedException = ExpectedException.none();

private Member member = newMember("member1");
private DistributedAnswer underTest = new DistributedAnswer();

@Test
public void getMembers_return_all_members() {
underTest.setAnswer(member, "foo");
underTest.setTimedOut(newMember("bar"));
underTest.setFailed(newMember("baz"), new IOException("BOOM"));

assertThat(underTest.getMembers()).hasSize(3);
}

@Test
public void test_call_with_unknown_member() {
assertThat(underTest.getAnswer(member)).isEmpty();
@@ -46,6 +60,7 @@ public class DistributedAnswerTest {

assertThat(underTest.getAnswer(member)).hasValue("foo");
assertThat(underTest.hasTimedOut(member)).isFalse();
assertThat(underTest.getFailed(member)).isEmpty();
}

@Test
@@ -54,6 +69,7 @@ public class DistributedAnswerTest {

assertThat(underTest.getAnswer(member)).isEmpty();
assertThat(underTest.hasTimedOut(member)).isTrue();
assertThat(underTest.getFailed(member)).isEmpty();
}

@Test
@@ -61,6 +77,8 @@ public class DistributedAnswerTest {
IOException e = new IOException();
underTest.setFailed(member, e);

assertThat(underTest.getAnswer(member)).isEmpty();
assertThat(underTest.hasTimedOut(member)).isFalse();
assertThat(underTest.getFailed(member)).hasValue(e);
}

@@ -76,9 +94,46 @@ public class DistributedAnswerTest {
assertThat(underTest.getFailed(member)).hasValue(exception);
}

@Test
public void propagateExceptions_does_nothing_if_no_members() {
// no errors
underTest.propagateExceptions();
}

@Test
public void propagateExceptions_does_nothing_if_no_errors() {
underTest.setAnswer(newMember("foo"), "bar");

// no errors
underTest.propagateExceptions();
}

@Test
public void propagateExceptions_throws_ISE_if_at_least_one_timeout() {
underTest.setAnswer(newMember("bar"), "baz");
underTest.setTimedOut(newMember("foo"));

expectedException.expect(IllegalStateException.class);
expectedException.expectMessage("Distributed cluster action timed out in cluster nodes foo");

underTest.propagateExceptions();
}

@Test
public void propagateExceptions_throws_ISE_if_at_least_one_failure() {
underTest.setAnswer(newMember("bar"), "baz");
underTest.setFailed(newMember("foo"), new IOException("BOOM"));

expectedException.expect(IllegalStateException.class);
expectedException.expectMessage("Distributed cluster action in cluster nodes foo (other nodes may have timed out)");

underTest.propagateExceptions();
}

private static Member newMember(String uuid) {
Member member = mock(Member.class);
when(member.getUuid()).thenReturn(uuid);
when(member.getStringAttribute(HazelcastMember.Attribute.NODE_NAME)).thenReturn(uuid);
return member;
}
}

+ 4
- 5
server/sonar-server/src/main/java/org/sonar/server/cluster/StartableHazelcastMember.java Visa fil

@@ -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);
}

+ 7
- 0
server/sonar-server/src/main/java/org/sonar/server/health/ClusterHealth.java Visa fil

@@ -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) {

+ 28
- 18
server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/EsStateSection.java Visa fil

@@ -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();
}

+ 1
- 8
server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/StandaloneSystemSection.java Visa fil

@@ -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());

+ 6
- 2
server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/WebSystemInfoModule.java Visa fil

@@ -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
};
}

+ 3
- 6
server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/cluster/AppNodesInfoLoaderImpl.java Visa fil

@@ -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);
}

+ 1
- 9
server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/cluster/GlobalSystemSection.java Visa fil

@@ -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());

+ 55
- 0
server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/cluster/LoggingSection.java Visa fil

@@ -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();
}
}

+ 31
- 8
server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/cluster/NodeInfo.java Visa fil

@@ -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) {

+ 2
- 14
server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/cluster/NodeSystemSection.java Visa fil

@@ -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();
}


server/sonar-ce/src/main/java/org/sonar/ce/monitoring/CeSystemInfoModule.java → server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/cluster/SearchNodesInfoLoader.java Visa fil

@@ -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();
}

+ 66
- 0
server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/cluster/SearchNodesInfoLoaderImpl.java Visa fil

@@ -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;
}

}

+ 0
- 2
server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java Visa fil

@@ -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);

+ 101
- 0
server/sonar-server/src/main/java/org/sonar/server/platform/ws/BaseInfoWsAction.java Visa fil

@@ -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());
}
}
}

+ 42
- 196
server/sonar-server/src/main/java/org/sonar/server/platform/ws/ClusterInfoAction.java Visa fil

@@ -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();
}
}

+ 19
- 59
server/sonar-server/src/main/java/org/sonar/server/platform/ws/InfoAction.java Visa fil

@@ -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());
}
}
}

+ 0
- 168
server/sonar-server/src/main/java/org/sonar/server/platform/ws/StandaloneInfoAction.java Visa fil

@@ -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();
}
}
}

+ 106
- 0
server/sonar-server/src/test/java/org/sonar/server/cluster/StartableHazelcastMemberTest.java Visa fil

@@ -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) {
}
}

}

+ 26
- 0
server/sonar-server/src/test/java/org/sonar/server/health/ClusterHealthTest.java Visa fil

@@ -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();
}
}

+ 1
- 18
server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/StandaloneSystemSectionTest.java Visa fil

@@ -30,14 +30,11 @@ import org.sonar.api.utils.log.LoggerLevel;
import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
import org.sonar.server.authentication.IdentityProviderRepositoryRule;
import org.sonar.server.authentication.TestIdentityProvider;
import org.sonar.server.health.Health;
import org.sonar.server.health.TestStandaloneHealthChecker;
import org.sonar.server.platform.ServerId;
import org.sonar.server.platform.ServerIdLoader;
import org.sonar.server.platform.ServerLogging;
import org.sonar.server.user.SecurityRealmFactory;

import static java.util.Arrays.asList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -57,11 +54,10 @@ public class StandaloneSystemSectionTest {
private ServerIdLoader serverIdLoader = mock(ServerIdLoader.class);
private ServerLogging serverLogging = mock(ServerLogging.class);
private SecurityRealmFactory securityRealmFactory = mock(SecurityRealmFactory.class);
private TestStandaloneHealthChecker healthChecker = new TestStandaloneHealthChecker();
private OfficialDistribution officialDistribution = mock(OfficialDistribution.class);

private StandaloneSystemSection underTest = new StandaloneSystemSection(settings.asConfig(), securityRealmFactory, identityProviderRepository, server,
serverLogging, serverIdLoader, officialDistribution, healthChecker);
serverLogging, serverIdLoader, officialDistribution);

@Before
public void setUp() throws Exception {
@@ -192,19 +188,6 @@ public class StandaloneSystemSectionTest {
assertThatAttributeIs(protobuf, "External identity providers whose users are allowed to sign themselves up", "GitHub");
}

@Test
public void return_health() {
healthChecker.setHealth(Health.newHealthCheckBuilder()
.setStatus(Health.Status.YELLOW)
.addCause("foo")
.addCause("bar")
.build());

ProtobufSystemInfo.Section protobuf = underTest.toProtobuf();
assertThatAttributeIs(protobuf, "Health", "YELLOW");
SystemInfoTesting.assertThatAttributeHasOnlyValues(protobuf, "Health Causes", asList("foo", "bar"));
}

@Test
public void return_nb_of_processors() {
ProtobufSystemInfo.Section protobuf = underTest.toProtobuf();

+ 4
- 2
server/sonar-server/src/test/java/org/sonar/server/platform/ws/InfoActionTest.java Visa fil

@@ -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\":{}}}");
}


+ 5
- 2
server/sonar-server/src/test/java/org/sonar/server/platform/ws/SystemWsTest.java Visa fil

@@ -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();


+ 14
- 9
server/sonar-web/src/main/js/api/system.ts Visa fil

@@ -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> {

+ 16
- 5
server/sonar-web/src/main/js/apps/system/__tests__/utils-test.ts Visa fil

@@ -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');
});
});

+ 3
- 11
server/sonar-web/src/main/js/apps/system/components/App.tsx Visa fil

@@ -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}
/>
);

+ 2
- 3
server/sonar-web/src/main/js/apps/system/components/ChangeLogLevelForm.tsx Visa fil

@@ -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}>

+ 1
- 1
server/sonar-web/src/main/js/apps/system/components/ClusterSysInfos.tsx Visa fil

@@ -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,

+ 3
- 3
server/sonar-web/src/main/js/apps/system/components/StandaloneSysInfos.tsx Visa fil

@@ -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;
}


+ 42
- 11
server/sonar-web/src/main/js/apps/system/components/__tests__/ClusterSysInfos-test.tsx Visa fil

@@ -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')

+ 8
- 8
server/sonar-web/src/main/js/apps/system/components/__tests__/StandaloneSysInfos-test.tsx Visa fil

@@ -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', () => {

+ 2
- 2
server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/ChangeLogLevelForm-test.tsx.snap Visa fil

@@ -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

+ 15
- 8
server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/ClusterSysInfos-test.tsx.snap Visa fil

@@ -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",
},
}
}
/>

+ 6
- 12
server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/StandaloneSysInfos-test.tsx.snap Visa fil

@@ -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,
}
}
/>

+ 2
- 2
server/sonar-web/src/main/js/apps/system/components/info-items/HealthCard.tsx Visa fil

@@ -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;

+ 3
- 3
server/sonar-web/src/main/js/apps/system/components/info-items/HealthCauseItem.tsx Visa fil

@@ -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>
);
}

+ 2
- 2
server/sonar-web/src/main/js/apps/system/components/info-items/HealthItem.tsx Visa fil

@@ -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) {

+ 1
- 1
server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/HealthCard-test.tsx Visa fil

@@ -59,7 +59,7 @@ function getShallowWrapper(props = {}) {
<HealthCard
biggerHealth={false}
health={HealthType.RED}
healthCauses={[{ message: 'foo' }]}
healthCauses={['foo']}
name="Foobar"
onClick={() => {}}
open={false}

+ 2
- 4
server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/HealthCauseItem-test.tsx Visa fil

@@ -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();
});

+ 3
- 10
server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/HealthItem-test.tsx Visa fil

@@ -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();
});

+ 3
- 9
server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/HealthCard-test.tsx.snap Visa fil

@@ -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",
]
}
/>

+ 3
- 15
server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/HealthItem-test.tsx.snap Visa fil

@@ -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"

+ 71
- 69
server/sonar-web/src/main/js/apps/system/utils.ts Visa fil

@@ -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)
});
});
})
);

+ 1
- 1
server/sonar-web/src/main/js/components/common/StatusIndicator.css Visa fil

@@ -25,7 +25,7 @@
background-color: #d4333f;
}

.sstatus-indicator.yellow {
.status-indicator.yellow {
background-color: #eabe06;
}


+ 4
- 0
tests/src/test/java/org/sonarqube/pageobjects/Navigation.java Visa fil

@@ -174,6 +174,10 @@ public class Navigation {
return open("/settings/server_id", ServerIdPage.class);
}

public SystemInfoPage openSystemInfo() {
return open("/admin/system", SystemInfoPage.class);
}

public NotificationsPage openNotifications() {
return open("/account/notifications", NotificationsPage.class);
}

+ 44
- 0
tests/src/test/java/org/sonarqube/pageobjects/SystemInfoPage.java Visa fil

@@ -0,0 +1,44 @@
/*
* SonarQube
* Copyright (C) 2009-2017 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonarqube.pageobjects;

import com.codeborne.selenide.CollectionCondition;
import com.codeborne.selenide.SelenideElement;

import static com.codeborne.selenide.Condition.exist;
import static com.codeborne.selenide.Condition.text;
import static com.codeborne.selenide.Selenide.$;
import static com.codeborne.selenide.Selenide.$$;

public class SystemInfoPage {
public SystemInfoPage() {
$(".page-title").should(exist).shouldHave(text("System Info"));
}

public SystemInfoPage shouldHaveCards(String... titles) {
$$(".system-info-health-card").shouldHave(CollectionCondition.texts(titles));
return this;
}

public SystemInfoPageItem getCardItem(String card) {
SelenideElement cardTitle = $$(".system-info-health-card-title").find(text(card)).should(exist);
return new SystemInfoPageItem(cardTitle.parent().parent());
}
}

+ 87
- 0
tests/src/test/java/org/sonarqube/pageobjects/SystemInfoPageItem.java Visa fil

@@ -0,0 +1,87 @@
/*
* SonarQube
* Copyright (C) 2009-2017 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonarqube.pageobjects;

import com.codeborne.selenide.SelenideElement;

import static com.codeborne.selenide.Condition.exist;
import static com.codeborne.selenide.Condition.text;

public class SystemInfoPageItem {
private final SelenideElement elt;

public SystemInfoPageItem(SelenideElement elt) {
this.elt = elt;
}

public SystemInfoPageItem shouldHaveHealth() {
elt.$(".system-info-health-info .status-indicator").should(exist);
return this;
}

public SystemInfoPageItem shouldHaveSection(String section) {
ensureOpen();
elt.$$("h4").findBy(text(section)).should(exist);
return this;
}

public SystemInfoPageItem shouldNotHaveSection(String section) {
ensureOpen();
elt.$$("h4").findBy(text(section)).shouldNot(exist);
return this;
}

public SystemInfoPageItem shouldHaveMainSection() {
ensureOpen();
elt.$$(".system-info-section").get(0).find("h4").shouldNot(exist);
return this;
}

public SystemInfoPageItem shouldHaveField(String field) {
ensureOpen();
elt.$$(".system-info-section-item-name").findBy(text(field)).should(exist);
return this;
}

public SystemInfoPageItem shouldNotHaveField(String field) {
ensureOpen();
elt.$$(".system-info-section-item-name").findBy(text(field)).shouldNot(exist);
return this;
}

public SystemInfoPageItem shouldHaveFieldWithValue(String field, String value) {
ensureOpen();
SelenideElement fieldElem = elt.$$(".system-info-section-item-name").findBy(text(field)).should(exist);
fieldElem.parent().parent().$$("td").shouldHaveSize(2).get(1).shouldHave(text(value));
return this;
}

public SystemInfoPageItem ensureOpen() {
if(!isOpen()) {
elt.click();
elt.$(".boxed-group-inner").should(exist);
}
return this;
}

private boolean isOpen() {
return elt.$(".boxed-group-inner").exists();
}
}

+ 1
- 1
tests/src/test/java/org/sonarqube/tests/cluster/ClusterTest.java Visa fil

@@ -82,7 +82,7 @@ public class ClusterTest {
app.waitForHealthGreen();

System.out.println("-----------------------------------------------------------------------");
String json = app.wsClient().wsConnector().call(new GetRequest("api/system/cluster_info")).content();
String json = app.wsClient().wsConnector().call(new GetRequest("api/system/info")).content();
System.out.println(json);
System.out.println("-----------------------------------------------------------------------");
}

+ 32
- 3
tests/src/test/java/org/sonarqube/tests/serverSystem/SystemInfoTest.java Visa fil

@@ -27,6 +27,7 @@ import org.apache.commons.io.FileUtils;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.sonarqube.pageobjects.SystemInfoPage;
import org.sonarqube.tests.Category4Suite;
import org.sonarqube.tests.Tester;
import org.sonarqube.ws.client.GetRequest;
@@ -48,7 +49,35 @@ public class SystemInfoTest {
@Test
public void test_system_info_page() {
tester.users().generateAdministrator(u -> u.setLogin(ADMIN_USER_LOGIN).setPassword(ADMIN_USER_LOGIN));
tester.runHtmlTests("/serverSystem/ServerSystemTest/system_info.html");
SystemInfoPage page = tester.openBrowser().logIn().submitCredentials(ADMIN_USER_LOGIN).openSystemInfo();
page.shouldHaveCards("System", "Web", "Compute Engine", "Search");

page.getCardItem("System")
.shouldHaveHealth()
.shouldHaveMainSection()
.shouldHaveSection("Database")
.shouldNotHaveSection("Settings")
.shouldNotHaveSection("Plugins")
.shouldNotHaveSection("Statistics")
.shouldHaveField("Official Distribution")
.shouldHaveField("Version")
.shouldHaveField("Logs Level")
.shouldHaveField("High Availability")
.shouldNotHaveField("Health")
.shouldNotHaveField("Health Causes");

page.getCardItem("Web")
.shouldHaveSection("Web JVM Properties")
.shouldHaveSection("Web JVM State");

page.getCardItem("Compute Engine")
.shouldHaveSection("Compute Engine Database Connection")
.shouldHaveSection("Compute Engine JVM State")
.shouldHaveSection("Compute Engine Tasks");

page.getCardItem("Search")
.shouldHaveSection("Search State")
.shouldHaveSection("Search Statistics");
}

@Test
@@ -63,9 +92,9 @@ public class SystemInfoTest {

// SONAR-7436 monitor ES and CE
assertThat((Map)json.get("Compute Engine Database Connection")).isNotEmpty();
assertThat((Map)json.get("Compute Engine State")).isNotEmpty();
assertThat((Map)json.get("Compute Engine JVM State")).isNotEmpty();
assertThat((Map)json.get("Compute Engine Tasks")).isNotEmpty();
Map<String,Object> esJson = (Map) json.get("Elasticsearch");
Map<String,Object> esJson = (Map) json.get("Search State");
assertThat(esJson.get("State")).isEqualTo("GREEN");

// SONAR-7271 get settings

+ 7
- 1
tests/src/test/java/org/sonarqube/tests/user/RealmAuthenticationTest.java Visa fil

@@ -36,6 +36,8 @@ import org.sonar.wsclient.base.HttpException;
import org.sonar.wsclient.connectors.HttpClient4Connector;
import org.sonar.wsclient.services.AuthenticationQuery;
import org.sonar.wsclient.user.UserParameters;
import org.sonarqube.pageobjects.SystemInfoPage;
import org.sonarqube.tests.Tester;
import org.sonarqube.ws.client.GetRequest;
import org.sonarqube.ws.client.WsResponse;
import org.sonarqube.ws.client.user.CreateRequest;
@@ -81,6 +83,9 @@ public class RealmAuthenticationTest {
@Rule
public UserRule userRule = UserRule.from(orchestrator);

@Rule
public Tester tester = new Tester(orchestrator).disableOrganizations();

@Before
@After
public void resetData() throws Exception {
@@ -121,7 +126,8 @@ public class RealmAuthenticationTest {
runSelenese(orchestrator, "/user/ExternalAuthenticationTest/external-user-details.html");

// SONAR-4462
runSelenese(orchestrator, "/user/ExternalAuthenticationTest/system-info.html");
SystemInfoPage page = tester.openBrowser().logIn().submitCredentials(ADMIN_USER_LOGIN).openSystemInfo();
page.getCardItem("System").shouldHaveFieldWithValue("External User Authentication", "FakeRealm");
}

/**

+ 0
- 80
tests/src/test/resources/serverSystem/ServerSystemTest/system_info.html Visa fil

@@ -1,80 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head profile="http://selenium-ide.openqa.org/profiles/test-case">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<title>system_info</title>
</head>
<body>
<table cellpadding="1" cellspacing="1" border="1">
<thead>
<tr>
<td rowspan="1" colspan="3">system_info</td>
</tr>
</thead>
<tbody>

<tr>
<td>open</td>
<td>/sessions/new</td>
<td></td>
</tr>
<tr>
<td>type</td>
<td>login</td>
<td>admin-user</td>
</tr>
<tr>
<td>type</td>
<td>password</td>
<td>admin-user</td>
</tr>
<tr>
<td>clickAndWait</td>
<td>commit</td>
<td></td>
</tr>
<tr>
<td>waitForElementPresent</td>
<td>css=.js-user-authenticated</td>
<td></td>
</tr>
<tr>
<td>open</td>
<td>/system/index</td>
<td></td>
</tr>
<tr>
<td>waitForText</td>
<td>id=content</td>
<td>*Official Distribution*</td>
</tr>
<tr>
<td>waitForText</td>
<td>id=content</td>
<td>*Database Version*</td>
</tr>
<tr>
<td>waitForText</td>
<td>id=content</td>
<td>*Pool Active Connections*</td>
</tr>
<tr>
<td>waitForText</td>
<td>id=content</td>
<td>*Start Time*</td>
</tr>
<tr>
<td>waitForText</td>
<td>id=content</td>
<td>*Processors*</td>
</tr>
<tr>
<td>waitForText</td>
<td>id=content</td>
<td>*java.class.path*java.specification.version*</td>
</tr>
</tbody>
</table>
</body>
</html>

+ 0
- 44
tests/src/test/resources/user/ExternalAuthenticationTest/system-info.html Visa fil

@@ -1,44 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-/W3C/DTD XHTML 1.0 Strict/EN" "http:/www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http:/www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head profile="http:/selenium-ide.openqa.org/profiles/test-case">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<title>external_user_details</title>
</head>
<body>
<table cellpadding="1" cellspacing="1" border="1">
<thead>
<tr>
<td rowspan="1" colspan="3">external_user_details</td>
</tr>
</thead>
<tbody>
<tr>
<td>open</td>
<td>/system</td>
<td></td>
</tr>
<tr>
<td>type</td>
<td>login</td>
<td>admin</td>
</tr>
<tr>
<td>type</td>
<td>password</td>
<td>admin</td>
</tr>
<tr>
<td>clickAndWait</td>
<td>commit</td>
<td></td>
</tr>
<tr>
<td>waitForText</td>
<td>id=content</td>
<td>*External User Authentication*FakeRealm*</td>
</tr>
</tbody>
</table>
</body>
</html>

Laddar…
Avbryt
Spara