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
// 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);
}
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;
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;
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);
}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-package org.sonar.ce.monitoring;
-
-import org.sonar.process.systeminfo.JvmPropertiesSection;
-import org.sonar.process.systeminfo.JvmStateSection;
-import org.sonar.server.platform.monitoring.DatabaseSection;
-import org.sonar.server.platform.monitoring.cluster.ProcessInfoProvider;
-
-public class CeSystemInfoModule {
-
- private CeSystemInfoModule() {
- // do not instantiate
- }
-
- public static Object[] forClusterMode() {
- return new Object[] {
- new JvmPropertiesSection("Compute Engine JVM Properties"),
- new JvmStateSection("Compute Engine JVM State"),
- DatabaseSection.class,
- ProcessInfoProvider.class
- };
- }
-}
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
<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>
<!--
<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>
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;
public class DistributedAnswerTest {
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
private Member member = newMember("member1");
private DistributedAnswer underTest = new DistributedAnswer();
+ @Test
+ public void getMembers_return_all_members() {
+ underTest.setAnswer(member, "foo");
+ underTest.setTimedOut(newMember("bar"));
+ underTest.setFailed(newMember("baz"), new IOException("BOOM"));
+
+ assertThat(underTest.getMembers()).hasSize(3);
+ }
+
@Test
public void test_call_with_unknown_member() {
assertThat(underTest.getAnswer(member)).isEmpty();
assertThat(underTest.getAnswer(member)).hasValue("foo");
assertThat(underTest.hasTimedOut(member)).isFalse();
+ assertThat(underTest.getFailed(member)).isEmpty();
}
@Test
assertThat(underTest.getAnswer(member)).isEmpty();
assertThat(underTest.hasTimedOut(member)).isTrue();
+ assertThat(underTest.getFailed(member)).isEmpty();
}
@Test
IOException e = new IOException();
underTest.setFailed(member, e);
+ assertThat(underTest.getAnswer(member)).isEmpty();
+ assertThat(underTest.hasTimedOut(member)).isFalse();
assertThat(underTest.getFailed(member)).hasValue(e);
}
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;
}
}
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;
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
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);
}
package org.sonar.server.health;
import java.util.Objects;
+import java.util.Optional;
import java.util.Set;
import org.sonar.process.cluster.health.NodeHealth;
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) {
}
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();
}
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;
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;
this.serverLogging = serverLogging;
this.serverIdLoader = serverIdLoader;
this.officialDistribution = officialDistribution;
- this.healthChecker = healthChecker;
}
@Override
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());
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 {
OfficialDistribution.class,
- StandaloneInfoAction.class
+ InfoAction.class
};
}
DatabaseSection.class,
EsStatisticsSection.class,
GlobalSystemSection.class,
+ LoggingSection.class,
NodeSystemSection.class,
PluginsSection.class,
SettingsSection.class,
ProcessInfoProvider.class,
GlobalInfoLoader.class,
AppNodesInfoLoaderImpl.class,
+ SearchNodesInfoLoaderImpl.class,
ClusterInfoAction.class
};
}
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);
}
}
}
- 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);
}
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;
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
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());
--- /dev/null
+/*
+ * 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();
+ }
+}
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) {
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) {
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;
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
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();
}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.platform.monitoring.cluster;
+
+
+import java.util.Collection;
+
+/**
+ * Loads "system information" of all Elasticsearch nodes.
+ */
+public interface SearchNodesInfoLoader {
+ Collection<NodeInfo> load();
+}
--- /dev/null
+/*
+ * 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;
+ }
+
+}
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;
addIfStartupLeader(TelemetryDaemon.class, TelemetryClient.class);
// system info
- add(InfoAction.class);
addIfCluster(WebSystemInfoModule.forClusterMode()).otherwiseAdd(WebSystemInfoModule.forStandaloneMode());
addAll(level4AddedComponents);
--- /dev/null
+/*
+ * 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());
+ }
+ }
+}
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();
}
}
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;
/**
* 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);
}
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());
- }
- }
}
+++ /dev/null
-/*
- * 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();
- }
- }
-}
--- /dev/null
+/*
+ * 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) {
+ }
+ }
+
+}
*/
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;
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)]);
.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();
+ }
}
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;
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 {
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();
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;
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
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\":{}}}");
}
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;
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();
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> {
* 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', () => {
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');
});
});
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';
return (
<StandaloneSysInfos
expandedCards={query.expandedCards}
- sysInfoData={sysInfoData as StandaloneSysInfo}
+ sysInfoData={sysInfoData}
toggleCard={this.toggleSysInfoCards}
/>
);
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),
render() {
const { updating, newLevel } = this.state;
const header = translate('system.set_log_level');
- const disableSubmit = updating || newLevel === this.props.logLevel;
return (
<Modal
isOpen={true}
</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}>
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,
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;
}
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', () => {
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')
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', () => {
className="modal-foot"
>
<button
- disabled={true}
+ disabled={false}
id="set-log-level-submit"
>
save
className="modal-foot"
>
<button
- disabled={true}
+ disabled={false}
id="set-log-level-submit"
>
save
health="RED"
healthCauses={
Array [
- Object {
- "message": "Database down",
- },
+ "Database down",
]
}
name="System"
open={true}
sysInfoData={
Object {
- "Name": "Foo",
+ "High Availability": true,
+ "Logs Level": "INFO",
}
}
/>
open={false}
sysInfoData={
Object {
- "Logs Level": "INFO",
- "Name": "Bar",
+ "Compute Engine Logging": Object {
+ "Logs Level": "INFO",
+ },
+ "Web Logging": Object {
+ "Logs Level": "INFO",
+ },
}
}
/>
open={false}
sysInfoData={
Object {
- "Logs Level": "INFO",
- "Name": "Baz",
+ "Compute Engine Logging": Object {
+ "Logs Level": "INFO",
+ },
+ "Web Logging": Object {
+ "Logs Level": "INFO",
+ },
}
}
/>
health="RED"
healthCauses={
Array [
- Object {
- "message": "Database down",
- },
+ "Database down",
]
}
name="System"
open={false}
sysInfoData={
Object {
+ "High Availability": true,
"Logs Level": "DEBUG",
- "Name": "Foo",
}
}
/>
open={false}
sysInfoData={
Object {
- "Web Database Connectivity": undefined,
"Web JVM": Object {
"Max Memory": "2Gb",
},
- "Web JVM Properties": undefined,
}
}
/>
open={true}
sysInfoData={
Object {
- "Compute Engine JVM": undefined,
- "Compute Engine JVM Properties": undefined,
- "Pending": 4,
+ "Compute Engine": Object {
+ "Pending": 4,
+ },
}
}
/>
open={false}
sysInfoData={
Object {
- "Elasticsearch": Object {
+ "Search": Object {
"Number of Nodes": 1,
},
- "Search JVM": undefined,
- "Search JVM Properties": undefined,
}
}
/>
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;
*/
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) {
health === HealthType.RED ? 'alert-danger' : 'alert-warning',
className
)}>
- {healthCause.message}
+ {healthCause}
</span>
);
}
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) {
<HealthCard
biggerHealth={false}
health={HealthType.RED}
- healthCauses={[{ message: 'foo' }]}
+ healthCauses={['foo']}
name="Foobar"
onClick={() => {}}
open={false}
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();
});
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();
});
health="RED"
healthCauses={
Array [
- Object {
- "message": "foo",
- },
+ "foo",
]
}
/>
health="RED"
healthCauses={
Array [
- Object {
- "message": "foo",
- },
+ "foo",
]
}
/>
health="RED"
healthCauses={
Array [
- Object {
- "message": "foo",
- },
+ "foo",
]
}
/>
<HealthCauseItem
className="spacer-right"
health="RED"
- healthCause={
- Object {
- "message": "foo",
- }
- }
+ healthCause="foo"
/>
<StatusIndicator
color="red"
<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"
* 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,
serializeStringArray
} from '../../helpers/query';
import {
- HealthCause,
+ ClusterSysInfo,
HealthType,
NodeInfo,
SysInfo,
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);
}
}
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) {
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)
- });
-});
+ })
+);
background-color: #d4333f;
}
-.sstatus-indicator.yellow {
+.status-indicator.yellow {
background-color: #eabe06;
}
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);
}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonarqube.pageobjects;
+
+import com.codeborne.selenide.CollectionCondition;
+import com.codeborne.selenide.SelenideElement;
+
+import static com.codeborne.selenide.Condition.exist;
+import static com.codeborne.selenide.Condition.text;
+import static com.codeborne.selenide.Selenide.$;
+import static com.codeborne.selenide.Selenide.$$;
+
+public class SystemInfoPage {
+ public SystemInfoPage() {
+ $(".page-title").should(exist).shouldHave(text("System Info"));
+ }
+
+ public SystemInfoPage shouldHaveCards(String... titles) {
+ $$(".system-info-health-card").shouldHave(CollectionCondition.texts(titles));
+ return this;
+ }
+
+ public SystemInfoPageItem getCardItem(String card) {
+ SelenideElement cardTitle = $$(".system-info-health-card-title").find(text(card)).should(exist);
+ return new SystemInfoPageItem(cardTitle.parent().parent());
+ }
+}
\ No newline at end of file
--- /dev/null
+/*
+ * 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();
+ }
+}
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("-----------------------------------------------------------------------");
}
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;
@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
// 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
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;
@Rule
public UserRule userRule = UserRule.from(orchestrator);
+ @Rule
+ public Tester tester = new Tester(orchestrator).disableOrganizations();
+
@Before
@After
public void resetData() throws Exception {
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");
}
/**
+++ /dev/null
-<?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>
+++ /dev/null
-<?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>