From 74c8a8ac5e83144fab7291b9ad57c14742fe8bbf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Aubert?= Date: Mon, 11 Sep 2017 17:01:34 +0200 Subject: [PATCH] SONAR-9802 Add nodes to system info in cluster mode --- .../org/sonar/ce/CeConfigurationModule.java | 4 +- .../systeminfo/SystemInfoHttpActionTest.java | 6 +- .../process/cluster/hz/DistributedAnswer.java | 6 +- .../systeminfo/JvmPropertiesSection.java} | 31 ++- ...teSystemInfo.java => JvmStateSection.java} | 38 ++-- .../process/systeminfo/SystemInfoUtils.java | 64 ++++++ .../systeminfo/JvmPropertiesSectionTest.java} | 25 ++- ...InfoTest.java => JvmStateSectionTest.java} | 6 +- ...onitorMBean.java => BaseSectionMBean.java} | 10 +- ...abaseMonitor.java => DatabaseSection.java} | 66 +++--- ...orMBean.java => DatabaseSectionMBean.java} | 2 +- .../server/platform/monitoring/EsMonitor.java | 140 ------------- .../server/platform/monitoring/EsSection.java | 119 +++++++++++ ...sMonitorMBean.java => EsSectionMBean.java} | 5 +- ...luginsMonitor.java => PluginsSection.java} | 32 ++- ...tingsMonitor.java => SettingsSection.java} | 22 +- ...QubeMonitor.java => SonarQubeSection.java} | 57 +++-- ...rMBean.java => SonarQubeSectionMBean.java} | 2 +- .../platform/monitoring/SystemMonitor.java | 104 ---------- .../platform/monitoring/SystemSection.java | 103 ++++++++++ .../monitoring/WebSystemInfoModule.java | 59 ++++++ .../platformlevel/PlatformLevel4.java | 33 ++- .../sonar/server/platform/ws/InfoAction.java | 56 +++-- ...eanTest.java => BaseSectionMBeanTest.java} | 4 +- ...itorTest.java => DatabaseSectionTest.java} | 22 +- ...{EsMonitorTest.java => EsSectionTest.java} | 55 ++--- .../{FakeMonitor.java => FakeSection.java} | 9 +- ...onitorMBean.java => FakeSectionMBean.java} | 2 +- ...nitorTest.java => PluginsSectionTest.java} | 30 ++- ...itorTest.java => SettingsSectionTest.java} | 28 +-- ...torTest.java => SonarQubeSectionTest.java} | 68 +++--- .../monitoring/SystemInfoTesting.java | 44 ++++ ...onitorTest.java => SystemSectionTest.java} | 18 +- ...Test.java => WebSystemInfoModuleTest.java} | 19 +- .../server/platform/ws/InfoActionTest.java | 35 ++-- .../server/telemetry/TelemetryModuleTest.java | 37 ---- server/sonar-web/src/main/js/api/system.ts | 47 ++++- .../js/apps/system/__tests__/utils-test.ts | 43 ++++ .../main/js/apps/system/components/App.tsx | 125 +++++++++++ .../system/components/ChangeLogLevelForm.tsx | 114 ++++++++++ .../system/components/ClusterSysInfos.tsx | 82 ++++++++ .../js/apps/system/components/PageActions.tsx | 163 +++++++++++++++ .../js/apps/system/components/PageHeader.tsx} | 47 +++-- .../system/components/StandAloneSysInfos.tsx | 30 +++ .../__tests__/ChangeLogLevelForm-test.tsx | 38 ++++ .../__tests__/ClusterSysInfos-test.tsx | 47 +++++ .../components/__tests__/PageActions-test.tsx | 55 +++++ .../components/__tests__/PageHeader-test.tsx | 34 +++ .../ChangeLogLevelForm-test.tsx.snap | 194 ++++++++++++++++++ .../ClusterSysInfos-test.tsx.snap | 59 ++++++ .../__snapshots__/PageActions-test.tsx.snap | 126 ++++++++++++ .../__snapshots__/PageHeader-test.tsx.snap | 38 ++++ .../components/info-items/HealthCard.tsx | 84 ++++++++ .../components/info-items/HealthCauseItem.tsx | 41 ++++ .../components/info-items/HealthItem.tsx | 42 ++++ .../system/components/info-items/Section.tsx | 52 +++++ .../components/info-items/SysInfoItem.tsx | 71 +++++++ .../info-items/__tests__/HealthCard-test.tsx | 64 ++++++ .../__tests__/HealthCauseItem-test.tsx | 32 +++ .../info-items/__tests__/HealthItem-test.tsx | 47 +++++ .../info-items/__tests__/Section-test.tsx | 32 +++ .../info-items/__tests__/SysInfoItem-test.tsx | 60 ++++++ .../__snapshots__/HealthCard-test.tsx.snap | 132 ++++++++++++ .../HealthCauseItem-test.tsx.snap | 17 ++ .../__snapshots__/HealthItem-test.tsx.snap | 68 ++++++ .../__snapshots__/Section-test.tsx.snap | 125 +++++++++++ .../__snapshots__/SysInfoItem-test.tsx.snap | 180 ++++++++++++++++ .../sonar-web/src/main/js/apps/system/main.js | 12 +- .../src/main/js/apps/system/routes.ts | 10 +- .../src/main/js/apps/system/styles.css | 81 ++++++++ .../src/main/js/apps/system/utils.ts | 99 +++++++++ .../main/js/components/common/RestartForm.tsx | 93 +++++++++ .../main/js/components/facet/FacetHeader.js | 28 +-- .../__snapshots__/FacetHeader-test.js.snap | 110 ++-------- .../icons-components/OpenCloseIcon.tsx | 44 ++++ .../js/components/icons-components/icons.js | 2 + server/sonar-web/src/main/js/helpers/urls.ts | 22 +- .../resources/org/sonar/l10n/core.properties | 13 +- 78 files changed, 3375 insertions(+), 789 deletions(-) rename server/{sonar-server/src/main/java/org/sonar/server/platform/monitoring/JvmPropsMonitor.java => sonar-process/src/main/java/org/sonar/process/systeminfo/JvmPropertiesSection.java} (57%) rename server/sonar-process/src/main/java/org/sonar/process/systeminfo/{ProcessStateSystemInfo.java => JvmStateSection.java} (60%) create mode 100644 server/sonar-process/src/main/java/org/sonar/process/systeminfo/SystemInfoUtils.java rename server/{sonar-server/src/test/java/org/sonar/server/platform/ws/InfoActionModuleTest.java => sonar-process/src/test/java/org/sonar/process/systeminfo/JvmPropertiesSectionTest.java} (55%) rename server/sonar-process/src/test/java/org/sonar/process/systeminfo/{ProcessStateSystemInfoTest.java => JvmStateSectionTest.java} (90%) rename server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/{BaseMonitorMBean.java => BaseSectionMBean.java} (82%) rename server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/{DatabaseMonitor.java => DatabaseSection.java} (56%) rename server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/{DatabaseMonitorMBean.java => DatabaseSectionMBean.java} (98%) delete mode 100644 server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/EsMonitor.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/EsSection.java rename server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/{EsMonitorMBean.java => EsSectionMBean.java} (86%) rename server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/{PluginsMonitor.java => PluginsSection.java} (65%) rename server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/{SettingsMonitor.java => SettingsSection.java} (73%) rename server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/{SonarQubeMonitor.java => SonarQubeSection.java} (69%) rename server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/{SonarQubeMonitorMBean.java => SonarQubeSectionMBean.java} (96%) delete mode 100644 server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SystemMonitor.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SystemSection.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/WebSystemInfoModule.java rename server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/{BaseMonitorMBeanTest.java => BaseSectionMBeanTest.java} (95%) rename server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/{DatabaseMonitorTest.java => DatabaseSectionTest.java} (67%) rename server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/{EsMonitorTest.java => EsSectionTest.java} (59%) rename server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/{FakeMonitor.java => FakeSection.java} (80%) rename server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/{FakeMonitorMBean.java => FakeSectionMBean.java} (96%) rename server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/{PluginsMonitorTest.java => PluginsSectionTest.java} (66%) rename server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/{SettingsMonitorTest.java => SettingsSectionTest.java} (61%) rename server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/{SonarQubeMonitorTest.java => SonarQubeSectionTest.java} (66%) create mode 100644 server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SystemInfoTesting.java rename server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/{JvmPropsMonitorTest.java => SystemSectionTest.java} (63%) rename server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/{SystemMonitorTest.java => WebSystemInfoModuleTest.java} (74%) delete mode 100644 server/sonar-server/src/test/java/org/sonar/server/telemetry/TelemetryModuleTest.java create mode 100644 server/sonar-web/src/main/js/apps/system/__tests__/utils-test.ts create mode 100644 server/sonar-web/src/main/js/apps/system/components/App.tsx create mode 100644 server/sonar-web/src/main/js/apps/system/components/ChangeLogLevelForm.tsx create mode 100644 server/sonar-web/src/main/js/apps/system/components/ClusterSysInfos.tsx create mode 100644 server/sonar-web/src/main/js/apps/system/components/PageActions.tsx rename server/{sonar-server/src/main/java/org/sonar/server/platform/monitoring/Monitor.java => sonar-web/src/main/js/apps/system/components/PageHeader.tsx} (52%) create mode 100644 server/sonar-web/src/main/js/apps/system/components/StandAloneSysInfos.tsx create mode 100644 server/sonar-web/src/main/js/apps/system/components/__tests__/ChangeLogLevelForm-test.tsx create mode 100644 server/sonar-web/src/main/js/apps/system/components/__tests__/ClusterSysInfos-test.tsx create mode 100644 server/sonar-web/src/main/js/apps/system/components/__tests__/PageActions-test.tsx create mode 100644 server/sonar-web/src/main/js/apps/system/components/__tests__/PageHeader-test.tsx create mode 100644 server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/ChangeLogLevelForm-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/ClusterSysInfos-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/PageActions-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/PageHeader-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/apps/system/components/info-items/HealthCard.tsx create mode 100644 server/sonar-web/src/main/js/apps/system/components/info-items/HealthCauseItem.tsx create mode 100644 server/sonar-web/src/main/js/apps/system/components/info-items/HealthItem.tsx create mode 100644 server/sonar-web/src/main/js/apps/system/components/info-items/Section.tsx create mode 100644 server/sonar-web/src/main/js/apps/system/components/info-items/SysInfoItem.tsx create mode 100644 server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/HealthCard-test.tsx create mode 100644 server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/HealthCauseItem-test.tsx create mode 100644 server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/HealthItem-test.tsx create mode 100644 server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/Section-test.tsx create mode 100644 server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/SysInfoItem-test.tsx create mode 100644 server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/HealthCard-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/HealthCauseItem-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/HealthItem-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/Section-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/SysInfoItem-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/apps/system/styles.css create mode 100644 server/sonar-web/src/main/js/apps/system/utils.ts create mode 100644 server/sonar-web/src/main/js/components/common/RestartForm.tsx create mode 100644 server/sonar-web/src/main/js/components/icons-components/OpenCloseIcon.tsx diff --git a/server/sonar-ce/src/main/java/org/sonar/ce/CeConfigurationModule.java b/server/sonar-ce/src/main/java/org/sonar/ce/CeConfigurationModule.java index cb61583ff067..e239b685e160 100644 --- a/server/sonar-ce/src/main/java/org/sonar/ce/CeConfigurationModule.java +++ b/server/sonar-ce/src/main/java/org/sonar/ce/CeConfigurationModule.java @@ -22,7 +22,7 @@ import org.sonar.ce.configuration.CeConfigurationImpl; import org.sonar.ce.log.CeLogging; import org.sonar.core.platform.Module; -import org.sonar.process.systeminfo.ProcessStateSystemInfo; +import org.sonar.process.systeminfo.JvmStateSection; import org.sonar.ce.monitoring.CeDatabaseMBeanImpl; public class CeConfigurationModule extends Module { @@ -32,6 +32,6 @@ protected void configureModule() { CeConfigurationImpl.class, CeLogging.class, CeDatabaseMBeanImpl.class, - new ProcessStateSystemInfo("Compute Engine State")); + new JvmStateSection("Compute Engine JVM State")); } } diff --git a/server/sonar-ce/src/test/java/org/sonar/ce/systeminfo/SystemInfoHttpActionTest.java b/server/sonar-ce/src/test/java/org/sonar/ce/systeminfo/SystemInfoHttpActionTest.java index 086d087ec0d1..9452c4da1cd4 100644 --- a/server/sonar-ce/src/test/java/org/sonar/ce/systeminfo/SystemInfoHttpActionTest.java +++ b/server/sonar-ce/src/test/java/org/sonar/ce/systeminfo/SystemInfoHttpActionTest.java @@ -26,7 +26,7 @@ import org.junit.Test; import org.junit.rules.ExpectedException; import org.sonar.ce.httpd.HttpAction; -import org.sonar.process.systeminfo.ProcessStateSystemInfo; +import org.sonar.process.systeminfo.JvmStateSection; import org.sonar.process.systeminfo.SystemInfoSection; import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo; @@ -44,8 +44,8 @@ public class SystemInfoHttpActionTest { @Rule public ExpectedException expectedException = ExpectedException.none(); - private SystemInfoSection stateProvider1 = new ProcessStateSystemInfo("state1"); - private SystemInfoSection stateProvider2 = new ProcessStateSystemInfo("state2"); + private SystemInfoSection stateProvider1 = new JvmStateSection("state1"); + private SystemInfoSection stateProvider2 = new JvmStateSection("state2"); private SystemInfoHttpAction underTest; @Before diff --git a/server/sonar-process/src/main/java/org/sonar/process/cluster/hz/DistributedAnswer.java b/server/sonar-process/src/main/java/org/sonar/process/cluster/hz/DistributedAnswer.java index 25c60a3c712a..be6105cc5118 100644 --- a/server/sonar-process/src/main/java/org/sonar/process/cluster/hz/DistributedAnswer.java +++ b/server/sonar-process/src/main/java/org/sonar/process/cluster/hz/DistributedAnswer.java @@ -59,15 +59,15 @@ public Collection getMembers() { return members; } - void setAnswer(Member member, T answer) { + public void setAnswer(Member member, T answer) { this.answers.put(member, answer); } - void setTimedOut(Member member) { + public void setTimedOut(Member member) { this.timedOutMembers.add(member); } - void setFailed(Member member, Exception e) { + public void setFailed(Member member, Exception e) { failedMembers.put(member, e); } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/JvmPropsMonitor.java b/server/sonar-process/src/main/java/org/sonar/process/systeminfo/JvmPropertiesSection.java similarity index 57% rename from server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/JvmPropsMonitor.java rename to server/sonar-process/src/main/java/org/sonar/process/systeminfo/JvmPropertiesSection.java index c1790530c9ae..8b0a31b0065b 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/JvmPropsMonitor.java +++ b/server/sonar-process/src/main/java/org/sonar/process/systeminfo/JvmPropertiesSection.java @@ -17,24 +17,35 @@ * 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; +package org.sonar.process.systeminfo; import java.util.Map; import java.util.Objects; -import java.util.TreeMap; +import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo; -public class JvmPropsMonitor implements Monitor { - @Override - public String name() { - return "JvmProperties"; +import static org.sonar.process.systeminfo.SystemInfoUtils.setAttribute; + +/** + * Dumps {@link System#getProperties()} + */ +public class JvmPropertiesSection implements SystemInfoSection { + + private final String name; + + public JvmPropertiesSection(String name) { + this.name = name; } @Override - public Map attributes() { - Map sortedProps = new TreeMap<>(); + public ProtobufSystemInfo.Section toProtobuf() { + ProtobufSystemInfo.Section.Builder protobuf = ProtobufSystemInfo.Section.newBuilder(); + protobuf.setName(name); + for (Map.Entry systemProp : System.getProperties().entrySet()) { - sortedProps.put(Objects.toString(systemProp.getKey()), Objects.toString(systemProp.getValue())); + if (systemProp.getValue() != null) { + setAttribute(protobuf, Objects.toString(systemProp.getKey()), Objects.toString(systemProp.getValue())); + } } - return sortedProps; + return protobuf.build(); } } diff --git a/server/sonar-process/src/main/java/org/sonar/process/systeminfo/ProcessStateSystemInfo.java b/server/sonar-process/src/main/java/org/sonar/process/systeminfo/JvmStateSection.java similarity index 60% rename from server/sonar-process/src/main/java/org/sonar/process/systeminfo/ProcessStateSystemInfo.java rename to server/sonar-process/src/main/java/org/sonar/process/systeminfo/JvmStateSection.java index 7ad80c576e16..575083280b10 100644 --- a/server/sonar-process/src/main/java/org/sonar/process/systeminfo/ProcessStateSystemInfo.java +++ b/server/sonar-process/src/main/java/org/sonar/process/systeminfo/JvmStateSection.java @@ -25,11 +25,16 @@ import java.lang.management.ThreadMXBean; import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo; -public class ProcessStateSystemInfo implements SystemInfoSection { +import static org.sonar.process.systeminfo.SystemInfoUtils.setAttribute; + +/** + * Dumps state of JVM (memory, threads) + */ +public class JvmStateSection implements SystemInfoSection { private static final long MEGABYTE = 1024L * 1024L; private final String name; - public ProcessStateSystemInfo(String name) { + public JvmStateSection(String name) { this.name = name; } @@ -40,26 +45,27 @@ public ProtobufSystemInfo.Section toProtobuf() { // Visible for testing ProtobufSystemInfo.Section toProtobuf(MemoryMXBean memoryBean) { - ProtobufSystemInfo.Section.Builder builder = ProtobufSystemInfo.Section.newBuilder(); - builder.setName(name); + ProtobufSystemInfo.Section.Builder protobuf = ProtobufSystemInfo.Section.newBuilder(); + protobuf.setName(name); MemoryUsage heap = memoryBean.getHeapMemoryUsage(); - addAttributeInMb(builder, "Heap Committed (MB)", heap.getCommitted()); - addAttributeInMb(builder, "Heap Init (MB)", heap.getInit()); - addAttributeInMb(builder, "Heap Max (MB)", heap.getMax()); - addAttributeInMb(builder, "Heap Used (MB)", heap.getUsed()); + addAttributeInMb(protobuf, "Heap Committed (MB)", heap.getCommitted()); + addAttributeInMb(protobuf, "Heap Init (MB)", heap.getInit()); + addAttributeInMb(protobuf, "Heap Max (MB)", heap.getMax()); + addAttributeInMb(protobuf, "Heap Used (MB)", heap.getUsed()); MemoryUsage nonHeap = memoryBean.getNonHeapMemoryUsage(); - addAttributeInMb(builder, "Non Heap Committed (MB)", nonHeap.getCommitted()); - addAttributeInMb(builder, "Non Heap Init (MB)", nonHeap.getInit()); - addAttributeInMb(builder, "Non Heap Max (MB)", nonHeap.getMax()); - addAttributeInMb(builder, "Non Heap Used (MB)", nonHeap.getUsed()); + addAttributeInMb(protobuf, "Non Heap Committed (MB)", nonHeap.getCommitted()); + addAttributeInMb(protobuf, "Non Heap Init (MB)", nonHeap.getInit()); + addAttributeInMb(protobuf, "Non Heap Max (MB)", nonHeap.getMax()); + addAttributeInMb(protobuf, "Non Heap Used (MB)", nonHeap.getUsed()); ThreadMXBean thread = ManagementFactory.getThreadMXBean(); - builder.addAttributesBuilder().setKey("Thread Count").setLongValue(thread.getThreadCount()).build(); - return builder.build(); + setAttribute(protobuf, "Thread Count", thread.getThreadCount()); + + return protobuf.build(); } - private static void addAttributeInMb(ProtobufSystemInfo.Section.Builder builder, String key, long valueInBytes) { + private static void addAttributeInMb(ProtobufSystemInfo.Section.Builder protobuf, String key, long valueInBytes) { if (valueInBytes >= 0L) { - builder.addAttributesBuilder().setKey(key).setLongValue(valueInBytes / MEGABYTE).build(); + setAttribute(protobuf, key, valueInBytes / MEGABYTE); } } } diff --git a/server/sonar-process/src/main/java/org/sonar/process/systeminfo/SystemInfoUtils.java b/server/sonar-process/src/main/java/org/sonar/process/systeminfo/SystemInfoUtils.java new file mode 100644 index 000000000000..738329480ff3 --- /dev/null +++ b/server/sonar-process/src/main/java/org/sonar/process/systeminfo/SystemInfoUtils.java @@ -0,0 +1,64 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.process.systeminfo; + +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; +import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo; + +public class SystemInfoUtils { + + private SystemInfoUtils() { + // prevent instantiation + } + + public static void setAttribute(ProtobufSystemInfo.Section.Builder section, String key, @Nullable String value) { + if (value != null) { + section.addAttributesBuilder() + .setKey(key) + .setStringValue(value) + .build(); + } + } + + public static void setAttribute(ProtobufSystemInfo.Section.Builder section, String key, boolean value) { + section.addAttributesBuilder() + .setKey(key) + .setBooleanValue(value) + .build(); + } + + public static void setAttribute(ProtobufSystemInfo.Section.Builder section, String key, long value) { + section.addAttributesBuilder() + .setKey(key) + .setLongValue(value) + .build(); + } + + @CheckForNull + public static ProtobufSystemInfo.Attribute attribute(ProtobufSystemInfo.Section section, String key) { + for (ProtobufSystemInfo.Attribute attribute : section.getAttributesList()) { + if (attribute.getKey().equals(key)) { + return attribute; + } + } + return null; + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/ws/InfoActionModuleTest.java b/server/sonar-process/src/test/java/org/sonar/process/systeminfo/JvmPropertiesSectionTest.java similarity index 55% rename from server/sonar-server/src/test/java/org/sonar/server/platform/ws/InfoActionModuleTest.java rename to server/sonar-process/src/test/java/org/sonar/process/systeminfo/JvmPropertiesSectionTest.java index d06d5dbdf208..dcf4cb9893c2 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/platform/ws/InfoActionModuleTest.java +++ b/server/sonar-process/src/test/java/org/sonar/process/systeminfo/JvmPropertiesSectionTest.java @@ -17,22 +17,29 @@ * 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; +package org.sonar.process.systeminfo; +import org.assertj.core.api.Assertions; import org.junit.Test; -import org.sonar.core.platform.ComponentContainer; +import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo; import static org.assertj.core.api.Assertions.assertThat; -import static org.sonar.core.platform.ComponentContainer.COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER; +import static org.sonar.process.systeminfo.SystemInfoUtils.attribute; -public class InfoActionModuleTest { - @Test - public void verify_count_of_added_components() { - ComponentContainer container = new ComponentContainer(); +public class JvmPropertiesSectionTest { - new InfoActionModule().configure(container); + private JvmPropertiesSection underTest = new JvmPropertiesSection("Web JVM Properties"); - assertThat(container.size()).isEqualTo(4 + COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER); + @Test + public void name_is_not_empty() { + assertThat(underTest.toProtobuf().getName()).isEqualTo("Web JVM Properties"); } + @Test + public void test_toProtobuf() { + ProtobufSystemInfo.Section section = underTest.toProtobuf(); + + Assertions.assertThat(attribute(section, "java.vm.vendor").getStringValue()).isNotEmpty(); + Assertions.assertThat(attribute(section, "os.name").getStringValue()).isNotEmpty(); + } } diff --git a/server/sonar-process/src/test/java/org/sonar/process/systeminfo/ProcessStateSystemInfoTest.java b/server/sonar-process/src/test/java/org/sonar/process/systeminfo/JvmStateSectionTest.java similarity index 90% rename from server/sonar-process/src/test/java/org/sonar/process/systeminfo/ProcessStateSystemInfoTest.java rename to server/sonar-process/src/test/java/org/sonar/process/systeminfo/JvmStateSectionTest.java index 94e9ad1fd51e..5276a5243536 100644 --- a/server/sonar-process/src/test/java/org/sonar/process/systeminfo/ProcessStateSystemInfoTest.java +++ b/server/sonar-process/src/test/java/org/sonar/process/systeminfo/JvmStateSectionTest.java @@ -28,13 +28,13 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -public class ProcessStateSystemInfoTest { +public class JvmStateSectionTest { public static final String PROCESS_NAME = "the process name"; @Test public void toSystemInfoSection() { - ProcessStateSystemInfo underTest = new ProcessStateSystemInfo(PROCESS_NAME); + JvmStateSection underTest = new JvmStateSection(PROCESS_NAME); ProtobufSystemInfo.Section section = underTest.toProtobuf(); assertThat(section.getName()).isEqualTo(PROCESS_NAME); @@ -47,7 +47,7 @@ public void should_hide_attributes_without_values() { MemoryMXBean memoryBean = mock(MemoryMXBean.class, Mockito.RETURNS_DEEP_STUBS); when(memoryBean.getHeapMemoryUsage().getCommitted()).thenReturn(-1L); - ProcessStateSystemInfo underTest = new ProcessStateSystemInfo(PROCESS_NAME); + JvmStateSection underTest = new JvmStateSection(PROCESS_NAME); ProtobufSystemInfo.Section section = underTest.toProtobuf(memoryBean); assertThat(section.getAttributesList()).extracting("key").doesNotContain("Heap Committed (MB)"); diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/BaseMonitorMBean.java b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/BaseSectionMBean.java similarity index 82% rename from server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/BaseMonitorMBean.java rename to server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/BaseSectionMBean.java index 4531b2ab7a19..eba68231c9e4 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/BaseMonitorMBean.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/BaseSectionMBean.java @@ -21,12 +21,13 @@ import org.picocontainer.Startable; import org.sonar.process.Jmx; +import org.sonar.process.systeminfo.SystemInfoSection; /** - * Base implementation of a {@link org.sonar.server.platform.monitoring.Monitor} + * Base implementation of a {@link SystemInfoSection} * that is exported as a JMX bean */ -public abstract class BaseMonitorMBean implements Monitor, Startable { +public abstract class BaseSectionMBean implements SystemInfoSection, Startable { /** * Auto-registers to MBean server @@ -47,4 +48,9 @@ public void stop() { String objectName() { return "SonarQube:name=" + name(); } + + /** + * Name of section in System Info page + */ + abstract String name(); } diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/DatabaseMonitor.java b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/DatabaseSection.java similarity index 56% rename from server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/DatabaseMonitor.java rename to server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/DatabaseSection.java index 2ec74c63f2d6..a64e8d25bb1f 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/DatabaseMonitor.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/DatabaseSection.java @@ -19,25 +19,25 @@ */ package org.sonar.server.platform.monitoring; -import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.SQLException; -import java.util.LinkedHashMap; -import java.util.Map; import org.apache.commons.dbcp.BasicDataSource; import org.sonar.db.DbClient; import org.sonar.db.DbSession; +import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo.Section; import org.sonar.server.platform.db.migration.version.DatabaseVersion; +import static org.sonar.process.systeminfo.SystemInfoUtils.setAttribute; + /** * Information about database and connection pool */ -public class DatabaseMonitor extends BaseMonitorMBean implements DatabaseMonitorMBean { +public class DatabaseSection extends BaseSectionMBean implements DatabaseSectionMBean { private final DatabaseVersion dbVersion; private final DbClient dbClient; - public DatabaseMonitor(DatabaseVersion dbVersion, DbClient dbClient) { + public DatabaseSection(DatabaseVersion dbVersion, DbClient dbClient) { this.dbVersion = dbVersion; this.dbClient = dbClient; } @@ -98,40 +98,40 @@ public int getPoolRemoveAbandonedTimeoutSeconds() { } @Override - public Map attributes() { - Map attributes = new LinkedHashMap<>(); - completeDbAttributes(attributes); - completePoolAttributes(attributes); - return attributes; - } - - private void completePoolAttributes(Map attributes) { - attributes.put("Pool Active Connections", getPoolActiveConnections()); - attributes.put("Pool Max Connections", getPoolMaxActiveConnections()); - attributes.put("Pool Initial Size", getPoolInitialSize()); - attributes.put("Pool Idle Connections", getPoolIdleConnections()); - attributes.put("Pool Min Idle Connections", getPoolMinIdleConnections()); - attributes.put("Pool Max Idle Connections", getPoolMaxIdleConnections()); - attributes.put("Pool Max Wait (ms)", getPoolMaxWaitMillis()); - attributes.put("Pool Remove Abandoned", getPoolRemoveAbandoned()); - attributes.put("Pool Remove Abandoned Timeout (seconds)", getPoolRemoveAbandonedTimeoutSeconds()); + public Section toProtobuf() { + Section.Builder protobuf = Section.newBuilder(); + protobuf.setName(name()); + completeDbAttributes(protobuf); + completePoolAttributes(protobuf); + return protobuf.build(); + } + + private void completePoolAttributes(Section.Builder protobuf) { + setAttribute(protobuf, "Pool Active Connections", getPoolActiveConnections()); + setAttribute(protobuf, "Pool Max Connections", getPoolMaxActiveConnections()); + setAttribute(protobuf, "Pool Initial Size", getPoolInitialSize()); + setAttribute(protobuf, "Pool Idle Connections", getPoolIdleConnections()); + setAttribute(protobuf, "Pool Min Idle Connections", getPoolMinIdleConnections()); + setAttribute(protobuf, "Pool Max Idle Connections", getPoolMaxIdleConnections()); + setAttribute(protobuf, "Pool Max Wait (ms)", getPoolMaxWaitMillis()); + setAttribute(protobuf, "Pool Remove Abandoned", getPoolRemoveAbandoned()); + setAttribute(protobuf, "Pool Remove Abandoned Timeout (seconds)", getPoolRemoveAbandonedTimeoutSeconds()); } private BasicDataSource commonsDbcp() { return (BasicDataSource) dbClient.getDatabase().getDataSource(); } - private void completeDbAttributes(Map attributes) { - try (DbSession dbSession = dbClient.openSession(false); - Connection connection = dbSession.getConnection()) { - DatabaseMetaData metadata = connection.getMetaData(); - attributes.put("Database", metadata.getDatabaseProductName()); - attributes.put("Database Version", metadata.getDatabaseProductVersion()); - attributes.put("Username", metadata.getUserName()); - attributes.put("URL", metadata.getURL()); - attributes.put("Driver", metadata.getDriverName()); - attributes.put("Driver Version", metadata.getDriverVersion()); - attributes.put("Version Status", getMigrationStatus()); + private void completeDbAttributes(Section.Builder protobuf) { + try (DbSession dbSession = dbClient.openSession(false)) { + DatabaseMetaData metadata = dbSession.getConnection().getMetaData(); + setAttribute(protobuf, "Database", metadata.getDatabaseProductName()); + setAttribute(protobuf, "Database Version", metadata.getDatabaseProductVersion()); + setAttribute(protobuf, "Username", metadata.getUserName()); + setAttribute(protobuf, "URL", metadata.getURL()); + setAttribute(protobuf, "Driver", metadata.getDriverName()); + setAttribute(protobuf, "Driver Version", metadata.getDriverVersion()); + setAttribute(protobuf, "Version Status", getMigrationStatus()); } catch (SQLException e) { throw new IllegalStateException("Fail to get DB metadata", e); } diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/DatabaseMonitorMBean.java b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/DatabaseSectionMBean.java similarity index 98% rename from server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/DatabaseMonitorMBean.java rename to server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/DatabaseSectionMBean.java index b47f6f2848db..0ddd97fff68f 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/DatabaseMonitorMBean.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/DatabaseSectionMBean.java @@ -19,7 +19,7 @@ */ package org.sonar.server.platform.monitoring; -public interface DatabaseMonitorMBean { +public interface DatabaseSectionMBean { /** * Is database schema up-to-date or should it be upgraded ? diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/EsMonitor.java b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/EsMonitor.java deleted file mode 100644 index fcc3ccb32e25..000000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/EsMonitor.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2017 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.server.platform.monitoring; - -import java.util.LinkedHashMap; -import java.util.Map; -import org.elasticsearch.ElasticsearchException; -import org.elasticsearch.action.admin.cluster.node.stats.NodeStats; -import org.elasticsearch.action.admin.cluster.node.stats.NodesStatsResponse; -import org.elasticsearch.action.admin.cluster.stats.ClusterStatsResponse; -import org.elasticsearch.action.admin.indices.stats.IndexStats; -import org.elasticsearch.action.admin.indices.stats.IndicesStatsResponse; -import org.elasticsearch.cluster.health.ClusterHealthStatus; -import org.elasticsearch.cluster.node.DiscoveryNode; -import org.elasticsearch.common.breaker.CircuitBreaker; -import org.sonar.api.utils.log.Loggers; -import org.sonar.server.es.EsClient; - -import static org.apache.commons.io.FileUtils.byteCountToDisplaySize; - -public class EsMonitor extends BaseMonitorMBean implements EsMonitorMBean { - - private final EsClient esClient; - - public EsMonitor(EsClient esClient) { - this.esClient = esClient; - } - - @Override - public String name() { - return "Elasticsearch"; - } - - /** - * MXBean does not allow to return enum {@link ClusterHealthStatus}, so - * returning String. - */ - @Override - public String getState() { - return getStateAsEnum().name(); - } - - private ClusterHealthStatus getStateAsEnum() { - return clusterStats().getStatus(); - } - - @Override - public int getNumberOfNodes() { - return clusterStats().getNodesStats().getCounts().getTotal(); - } - - @Override - public Map attributes() { - try { - Map attributes = new LinkedHashMap<>(); - attributes.put("State", getStateAsEnum()); - attributes.put("Indices", indexAttributes()); - attributes.put("Number of Nodes", getNumberOfNodes()); - attributes.put("Nodes", nodeAttributes()); - return attributes; - } catch (Exception es) { - Loggers.get(EsMonitor.class).warn("Failed to retrieve ES attributes. There will be only a single \"state\" attribute.", es); - Map attributes = new LinkedHashMap<>(); - attributes.put("State", es.getCause() instanceof ElasticsearchException ? es.getCause().getMessage() : es.getMessage()); - return attributes; - } - } - - private LinkedHashMap> indexAttributes() { - LinkedHashMap> indices = new LinkedHashMap<>(); - IndicesStatsResponse indicesStats = esClient.prepareStats().all().get(); - - for (Map.Entry indexStats : indicesStats.getIndices().entrySet()) { - LinkedHashMap attributes = new LinkedHashMap<>(); - indices.put(indexStats.getKey(), attributes); - attributes.put("Docs", indexStats.getValue().getPrimaries().getDocs().getCount()); - attributes.put("Shards", indexStats.getValue().getShards().length); - attributes.put("Store Size", byteCountToDisplaySize(indexStats.getValue().getPrimaries().getStore().getSizeInBytes())); - } - return indices; - } - - /** - * map of {node name -> node attributes} - */ - private LinkedHashMap> nodeAttributes() { - LinkedHashMap> nodes = new LinkedHashMap<>(); - NodesStatsResponse nodesStats = esClient.prepareNodesStats().all().get(); - for (Map.Entry entry : nodesStats.getNodesMap().entrySet()) { - - LinkedHashMap nodeAttributes = new LinkedHashMap<>(); - NodeStats stats = entry.getValue(); - DiscoveryNode node = stats.getNode(); - nodes.put(node.getName(), nodeAttributes); - nodeAttributes.put("Address", node.getAddress().toString()); - nodeAttributes.put("Type", node.isMasterNode() ? "Master" : "Slave"); - nodeAttributes.put("Disk Available", byteCountToDisplaySize(stats.getFs().getTotal().getAvailable().getBytes())); - nodeAttributes.put("Store Size", byteCountToDisplaySize(stats.getIndices().getStore().getSizeInBytes())); - nodeAttributes.put("Open Files", stats.getProcess().getOpenFileDescriptors()); - nodeAttributes.put("JVM Heap Usage", formatPercent(stats.getJvm().getMem().getHeapUsedPercent())); - nodeAttributes.put("JVM Heap Used", byteCountToDisplaySize(stats.getJvm().getMem().getHeapUsed().getBytes())); - nodeAttributes.put("JVM Heap Max", byteCountToDisplaySize(stats.getJvm().getMem().getHeapMax().getBytes())); - nodeAttributes.put("JVM Non Heap Used", byteCountToDisplaySize(stats.getJvm().getMem().getNonHeapUsed().getBytes())); - nodeAttributes.put("JVM Threads", stats.getJvm().getThreads().getCount()); - nodeAttributes.put("Field Data Memory", byteCountToDisplaySize(stats.getIndices().getFieldData().getMemorySizeInBytes())); - nodeAttributes.put("Field Data Circuit Breaker Limit", byteCountToDisplaySize(stats.getBreaker().getStats(CircuitBreaker.FIELDDATA).getLimit())); - nodeAttributes.put("Field Data Circuit Breaker Estimation", byteCountToDisplaySize(stats.getBreaker().getStats(CircuitBreaker.FIELDDATA).getEstimated())); - nodeAttributes.put("Request Circuit Breaker Limit", byteCountToDisplaySize(stats.getBreaker().getStats(CircuitBreaker.REQUEST).getLimit())); - nodeAttributes.put("Request Circuit Breaker Estimation", byteCountToDisplaySize(stats.getBreaker().getStats(CircuitBreaker.REQUEST).getEstimated())); - nodeAttributes.put("Query Cache Memory", byteCountToDisplaySize(stats.getIndices().getQueryCache().getMemorySizeInBytes())); - nodeAttributes.put("Request Cache Memory", byteCountToDisplaySize(stats.getIndices().getRequestCache().getMemorySizeInBytes())); - } - return nodes; - } - - private ClusterStatsResponse clusterStats() { - return esClient.prepareClusterStats().get(); - } - - private static String formatPercent(long amount) { - return String.format("%.1f%%", 100 * amount * 1.0D / 100L); - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/EsSection.java b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/EsSection.java new file mode 100644 index 000000000000..2e86e6ef854e --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/EsSection.java @@ -0,0 +1,119 @@ +/* + * 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; + +import java.util.Map; +import org.elasticsearch.ElasticsearchException; +import org.elasticsearch.action.admin.cluster.node.stats.NodeStats; +import org.elasticsearch.action.admin.cluster.node.stats.NodesStatsResponse; +import org.elasticsearch.action.admin.cluster.stats.ClusterStatsResponse; +import org.elasticsearch.action.admin.indices.stats.IndexStats; +import org.elasticsearch.action.admin.indices.stats.IndicesStatsResponse; +import org.elasticsearch.cluster.health.ClusterHealthStatus; +import org.elasticsearch.common.breaker.CircuitBreaker; +import org.sonar.api.utils.log.Loggers; +import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo; +import org.sonar.server.es.EsClient; + +import static org.apache.commons.io.FileUtils.byteCountToDisplaySize; +import static org.sonar.process.systeminfo.SystemInfoUtils.setAttribute; + +public class EsSection extends BaseSectionMBean implements EsSectionMBean { + + private final EsClient esClient; + + public EsSection(EsClient esClient) { + this.esClient = esClient; + } + + @Override + public String name() { + return "Elasticsearch"; + } + + /** + * MXBean does not allow to return enum {@link ClusterHealthStatus}, so + * returning String. + */ + @Override + public String getState() { + return getStateAsEnum().name(); + } + + private ClusterHealthStatus getStateAsEnum() { + return clusterStats().getStatus(); + } + + @Override + public ProtobufSystemInfo.Section toProtobuf() { + ProtobufSystemInfo.Section.Builder protobuf = ProtobufSystemInfo.Section.newBuilder(); + protobuf.setName(name()); + try { + setAttribute(protobuf, "State", getStateAsEnum().name()); + completeNodeAttributes(protobuf); + completeIndexAttributes(protobuf); + + } catch (Exception es) { + Loggers.get(EsSection.class).warn("Failed to retrieve ES attributes. There will be only a single \"state\" attribute.", es); + setAttribute(protobuf, "State", es.getCause() instanceof ElasticsearchException ? es.getCause().getMessage() : es.getMessage()); + } + return protobuf.build(); + } + + private void completeIndexAttributes(ProtobufSystemInfo.Section.Builder protobuf) { + IndicesStatsResponse indicesStats = esClient.prepareStats().all().get(); + for (Map.Entry indexStats : indicesStats.getIndices().entrySet()) { + String prefix = "Index " + indexStats.getKey() + " - "; + setAttribute(protobuf, prefix + "Docs", indexStats.getValue().getPrimaries().getDocs().getCount()); + setAttribute(protobuf, prefix + "Shards", indexStats.getValue().getShards().length); + setAttribute(protobuf, prefix + "Store Size", byteCountToDisplaySize(indexStats.getValue().getPrimaries().getStore().getSizeInBytes())); + } + } + + private void completeNodeAttributes(ProtobufSystemInfo.Section.Builder protobuf) { + NodesStatsResponse nodesStats = esClient.prepareNodesStats().all().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 Files", stats.getProcess().getOpenFileDescriptors()); + 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(); + } + + private static String formatPercent(long amount) { + return String.format("%.1f%%", 100 * amount * 1.0D / 100L); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/EsMonitorMBean.java b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/EsSectionMBean.java similarity index 86% rename from server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/EsMonitorMBean.java rename to server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/EsSectionMBean.java index 647f6687dcec..6358f68593f8 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/EsMonitorMBean.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/EsSectionMBean.java @@ -20,10 +20,9 @@ package org.sonar.server.platform.monitoring; /** - * The public attributes of {@link org.sonar.server.platform.monitoring.EsMonitor} + * The public attributes of {@link EsSection} * to be exported in JMX bean. */ -public interface EsMonitorMBean { +public interface EsSectionMBean { String getState(); - int getNumberOfNodes(); } diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/PluginsMonitor.java b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/PluginsSection.java similarity index 65% rename from server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/PluginsMonitor.java rename to server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/PluginsSection.java index 9e3c9517e398..491a8bf1265a 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/PluginsMonitor.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/PluginsSection.java @@ -19,39 +19,33 @@ */ package org.sonar.server.platform.monitoring; -import java.util.LinkedHashMap; -import java.util.Map; import org.sonar.core.platform.PluginInfo; import org.sonar.core.platform.PluginRepository; +import org.sonar.process.systeminfo.SystemInfoSection; +import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo; import org.sonar.updatecenter.common.Version; -/** - * Installed plugins (excluding core plugins) - */ -public class PluginsMonitor implements Monitor { +import static org.sonar.process.systeminfo.SystemInfoUtils.setAttribute; + +public class PluginsSection implements SystemInfoSection { private final PluginRepository repository; - public PluginsMonitor(PluginRepository repository) { + public PluginsSection(PluginRepository repository) { this.repository = repository; } @Override - public String name() { - return "Plugins"; - } - - @Override - public Map attributes() { - Map attributes = new LinkedHashMap<>(); + public ProtobufSystemInfo.Section toProtobuf() { + ProtobufSystemInfo.Section.Builder protobuf = ProtobufSystemInfo.Section.newBuilder(); + protobuf.setName("Plugins"); for (PluginInfo plugin : repository.getPluginInfos()) { - LinkedHashMap pluginAttributes = new LinkedHashMap<>(); - pluginAttributes.put("Name", plugin.getName()); + String label = "[" + plugin.getName() + "]"; Version version = plugin.getVersion(); if (version != null) { - pluginAttributes.put("Version", version.getName()); + label = version.getName() + " " + label; } - attributes.put(plugin.getKey(), pluginAttributes); + setAttribute(protobuf, plugin.getKey(), label); } - return attributes; + return protobuf.build(); } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SettingsMonitor.java b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SettingsSection.java similarity index 73% rename from server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SettingsMonitor.java rename to server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SettingsSection.java index 1b45ef205fc2..12f1d688623d 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SettingsMonitor.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SettingsSection.java @@ -19,41 +19,39 @@ */ package org.sonar.server.platform.monitoring; -import com.google.common.collect.ImmutableSortedMap; import java.util.Map; -import java.util.SortedMap; import org.sonar.api.PropertyType; import org.sonar.api.config.PropertyDefinition; import org.sonar.api.config.PropertyDefinitions; import org.sonar.api.config.Settings; +import org.sonar.process.systeminfo.SystemInfoSection; +import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo; import static org.apache.commons.lang.StringUtils.abbreviate; +import static org.sonar.process.systeminfo.SystemInfoUtils.setAttribute; -public class SettingsMonitor implements Monitor { +public class SettingsSection implements SystemInfoSection { static final int MAX_VALUE_LENGTH = 500; private final Settings settings; - public SettingsMonitor(Settings settings) { + public SettingsSection(Settings settings) { this.settings = settings; } @Override - public String name() { - return "Settings"; - } + public ProtobufSystemInfo.Section toProtobuf() { + ProtobufSystemInfo.Section.Builder protobuf = ProtobufSystemInfo.Section.newBuilder(); + protobuf.setName("Settings"); - @Override - public SortedMap attributes() { PropertyDefinitions definitions = settings.getDefinitions(); - ImmutableSortedMap.Builder builder = ImmutableSortedMap.naturalOrder(); for (Map.Entry prop : settings.getProperties().entrySet()) { String key = prop.getKey(); PropertyDefinition def = definitions.get(key); if (def == null || def.type() != PropertyType.PASSWORD) { - builder.put(key, abbreviate(prop.getValue(), MAX_VALUE_LENGTH)); + setAttribute(protobuf, key, abbreviate(prop.getValue(), MAX_VALUE_LENGTH)); } } - return builder.build(); + return protobuf.build(); } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SonarQubeMonitor.java b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SonarQubeSection.java similarity index 69% rename from server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SonarQubeMonitor.java rename to server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SonarQubeSection.java index 823876d9911b..66eefa134211 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SonarQubeMonitor.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SonarQubeSection.java @@ -21,9 +21,7 @@ import com.google.common.base.Joiner; import java.io.File; -import java.util.LinkedHashMap; import java.util.List; -import java.util.Map; import javax.annotation.CheckForNull; import javax.annotation.Nullable; import org.sonar.api.CoreProperties; @@ -33,12 +31,15 @@ import org.sonar.api.server.authentication.IdentityProvider; import org.sonar.core.util.stream.MoreCollectors; import org.sonar.process.ProcessProperties; +import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo; import org.sonar.server.authentication.IdentityProviderRepository; import org.sonar.server.platform.ServerIdLoader; import org.sonar.server.platform.ServerLogging; import org.sonar.server.user.SecurityRealmFactory; -public class SonarQubeMonitor extends BaseMonitorMBean implements SonarQubeMonitorMBean { +import static org.sonar.process.systeminfo.SystemInfoUtils.setAttribute; + +public class SonarQubeSection extends BaseSectionMBean implements SonarQubeSectionMBean { private static final Joiner COMMA_JOINER = Joiner.on(", "); @@ -51,7 +52,7 @@ public class SonarQubeMonitor extends BaseMonitorMBean implements SonarQubeMonit private final ServerLogging serverLogging; private final ServerIdLoader serverIdLoader; - public SonarQubeMonitor(Configuration config, SecurityRealmFactory securityRealmFactory, + public SonarQubeSection(Configuration config, SecurityRealmFactory securityRealmFactory, IdentityProviderRepository identityProviderRepository, Server server, ServerLogging serverLogging, ServerIdLoader serverIdLoader) { this.config = config; @@ -118,39 +119,31 @@ public String name() { } @Override - public Map attributes() { - Map attributes = new LinkedHashMap<>(); - completeWithServerIdAttributes(attributes); - attributes.put("Version", getVersion()); - addIfNotNull("External User Authentication", getExternalUserAuthentication(), attributes); - addIfNotEmpty("Accepted external identity providers", getEnabledIdentityProviders(), attributes); - addIfNotEmpty("External identity providers whose users are allowed to sign themselves up", getAllowsToSignUpEnabledIdentityProviders(), attributes); - attributes.put("Force authentication", getForceAuthentication()); - attributes.put("Official Distribution", isOfficialDistribution()); - attributes.put("Home Dir", config.get(ProcessProperties.PATH_HOME).orElse(null)); - attributes.put("Data Dir", config.get(ProcessProperties.PATH_DATA).orElse(null)); - attributes.put("Temp Dir", config.get(ProcessProperties.PATH_TEMP).orElse(null)); - attributes.put("Logs Dir", config.get(ProcessProperties.PATH_LOGS).orElse(null)); - attributes.put("Logs Level", getLogLevel()); - return attributes; - } + public ProtobufSystemInfo.Section toProtobuf() { + ProtobufSystemInfo.Section.Builder protobuf = ProtobufSystemInfo.Section.newBuilder(); + protobuf.setName(name()); - private void completeWithServerIdAttributes(Map attributes) { serverIdLoader.get().ifPresent(serverId -> { - attributes.put("Server ID", serverId.getId()); - attributes.put("Server ID validated", serverId.isValid()); + setAttribute(protobuf, "Server ID", serverId.getId()); + setAttribute(protobuf, "Server ID validated", serverId.isValid()); }); + setAttribute(protobuf, "Version", getVersion()); + setAttribute(protobuf, "External User Authentication", getExternalUserAuthentication()); + addIfNotEmpty(protobuf, "Accepted external identity providers", getEnabledIdentityProviders()); + addIfNotEmpty(protobuf, "External identity providers whose users are allowed to sign themselves up", getAllowsToSignUpEnabledIdentityProviders()); + setAttribute(protobuf, "Force authentication", getForceAuthentication()); + setAttribute(protobuf, "Official Distribution", isOfficialDistribution()); + 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", getLogLevel()); + return protobuf.build(); } - private static void addIfNotNull(String key, @Nullable String value, Map attributes) { - if (value != null) { - attributes.put(key, value); - } - } - - private static void addIfNotEmpty(String key, List values, Map attributes) { - if (!values.isEmpty()) { - attributes.put(key, COMMA_JOINER.join(values)); + private static void addIfNotEmpty(ProtobufSystemInfo.Section.Builder protobuf, String key, @Nullable List values) { + if (values != null && !values.isEmpty()) { + setAttribute(protobuf, key, COMMA_JOINER.join(values)); } } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SonarQubeMonitorMBean.java b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SonarQubeSectionMBean.java similarity index 96% rename from server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SonarQubeMonitorMBean.java rename to server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SonarQubeSectionMBean.java index 9a74127dd9a7..1a562a6bc30b 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SonarQubeMonitorMBean.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SonarQubeSectionMBean.java @@ -21,7 +21,7 @@ import javax.annotation.CheckForNull; -public interface SonarQubeMonitorMBean { +public interface SonarQubeSectionMBean { @CheckForNull String getServerId(); diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SystemMonitor.java b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SystemMonitor.java deleted file mode 100644 index 94e02989a6b0..000000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SystemMonitor.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2017 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.server.platform.monitoring; - -import java.lang.management.ClassLoadingMXBean; -import java.lang.management.ManagementFactory; -import java.lang.management.MemoryMXBean; -import java.lang.management.RuntimeMXBean; -import java.lang.management.ThreadMXBean; -import java.util.Date; -import java.util.LinkedHashMap; -import java.util.Map; -import org.sonar.api.utils.System2; - -import static org.sonar.api.utils.DateUtils.formatDateTime; - -/** - * JVM runtime information. Not exported as a MXBean because these informations - * are natively provided. - */ -public class SystemMonitor implements Monitor { - private final System2 system; - - public SystemMonitor() { - this(System2.INSTANCE); - } - - SystemMonitor(System2 system) { - this.system = system; - } - - @Override - public String name() { - return "System"; - } - - @Override - public Map attributes() { - Map attributes = new LinkedHashMap<>(); - attributes.put("System Date", formatDateTime(new Date(system.now()))); - attributes.put("Start Time", formatDateTime(new Date(runtimeMXBean().getStartTime()))); - attributes.put("JVM Vendor", runtimeMXBean().getVmVendor()); - attributes.put("JVM Name", runtimeMXBean().getVmName()); - attributes.put("JVM Version", runtimeMXBean().getVmVersion()); - attributes.put("Processors", runtime().availableProcessors()); - attributes.put("System Classpath", runtimeMXBean().getClassPath()); - attributes.put("BootClassPath", runtimeMXBean().getBootClassPath()); - attributes.put("Library Path", runtimeMXBean().getLibraryPath()); - attributes.put("Total Memory", formatMemory(runtime().totalMemory())); - attributes.put("Free Memory", formatMemory(runtime().freeMemory())); - attributes.put("Max Memory", formatMemory(runtime().maxMemory())); - attributes.put("Heap", memoryMXBean().getHeapMemoryUsage().toString()); - attributes.put("Non Heap", memoryMXBean().getNonHeapMemoryUsage().toString()); - attributes.put("System Load Average", String.format("%.1f%% (last minute)", ManagementFactory.getOperatingSystemMXBean().getSystemLoadAverage() * 100.0)); - attributes.put("Loaded Classes", classLoadingMXBean().getLoadedClassCount()); - attributes.put("Total Loaded Classes", classLoadingMXBean().getTotalLoadedClassCount()); - attributes.put("Unloaded Classes", classLoadingMXBean().getUnloadedClassCount()); - attributes.put("Threads", threadMXBean().getThreadCount()); - attributes.put("Threads Peak", threadMXBean().getPeakThreadCount()); - attributes.put("Daemon Thread", threadMXBean().getDaemonThreadCount()); - return attributes; - } - - private static RuntimeMXBean runtimeMXBean() { - return ManagementFactory.getRuntimeMXBean(); - } - - private static Runtime runtime() { - return Runtime.getRuntime(); - } - - private static MemoryMXBean memoryMXBean() { - return ManagementFactory.getMemoryMXBean(); - } - - private static ClassLoadingMXBean classLoadingMXBean() { - return ManagementFactory.getClassLoadingMXBean(); - } - - private static ThreadMXBean threadMXBean() { - return ManagementFactory.getThreadMXBean(); - } - - private static String formatMemory(long memoryInBytes) { - return String.format("%d MB", memoryInBytes / 1_000_000); - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SystemSection.java b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SystemSection.java new file mode 100644 index 000000000000..bbd623f52b92 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/SystemSection.java @@ -0,0 +1,103 @@ +/* + * 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; + +import java.lang.management.ClassLoadingMXBean; +import java.lang.management.ManagementFactory; +import java.lang.management.MemoryMXBean; +import java.lang.management.RuntimeMXBean; +import java.lang.management.ThreadMXBean; +import java.util.Date; +import org.sonar.api.utils.System2; +import org.sonar.process.systeminfo.SystemInfoSection; +import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo; + +import static java.lang.String.format; +import static org.sonar.api.utils.DateUtils.formatDateTime; +import static org.sonar.process.systeminfo.SystemInfoUtils.setAttribute; + +/** + * JVM runtime information. Not exported as a MXBean because these information + * are natively provided. + */ +public class SystemSection implements SystemInfoSection { + private final System2 system; + + public SystemSection() { + this(System2.INSTANCE); + } + + SystemSection(System2 system) { + this.system = system; + } + + @Override + public ProtobufSystemInfo.Section toProtobuf() { + ProtobufSystemInfo.Section.Builder protobuf = ProtobufSystemInfo.Section.newBuilder(); + protobuf.setName("System"); + + setAttribute(protobuf, "System Date", formatDateTime(new Date(system.now()))); + setAttribute(protobuf, "Start Time", formatDateTime(new Date(runtimeMXBean().getStartTime()))); + setAttribute(protobuf, "JVM Vendor", runtimeMXBean().getVmVendor()); + setAttribute(protobuf, "JVM Name", runtimeMXBean().getVmName()); + setAttribute(protobuf, "JVM Version", runtimeMXBean().getVmVersion()); + setAttribute(protobuf, "Processors", runtime().availableProcessors()); + setAttribute(protobuf, "System Classpath", runtimeMXBean().getClassPath()); + setAttribute(protobuf, "BootClassPath", runtimeMXBean().getBootClassPath()); + setAttribute(protobuf, "Library Path", runtimeMXBean().getLibraryPath()); + setAttribute(protobuf, "Total Memory", formatMemory(runtime().totalMemory())); + setAttribute(protobuf, "Free Memory", formatMemory(runtime().freeMemory())); + setAttribute(protobuf, "Max Memory", formatMemory(runtime().maxMemory())); + setAttribute(protobuf, "Heap", memoryMXBean().getHeapMemoryUsage().toString()); + setAttribute(protobuf, "Non Heap", memoryMXBean().getNonHeapMemoryUsage().toString()); + setAttribute(protobuf, "System Load Average", format("%.1f%% (last minute)", ManagementFactory.getOperatingSystemMXBean().getSystemLoadAverage() * 100.0)); + setAttribute(protobuf, "Loaded Classes", classLoadingMXBean().getLoadedClassCount()); + setAttribute(protobuf, "Total Loaded Classes", classLoadingMXBean().getTotalLoadedClassCount()); + setAttribute(protobuf, "Unloaded Classes", classLoadingMXBean().getUnloadedClassCount()); + setAttribute(protobuf, "Threads", threadMXBean().getThreadCount()); + setAttribute(protobuf, "Threads Peak", threadMXBean().getPeakThreadCount()); + setAttribute(protobuf, "Daemon Thread", threadMXBean().getDaemonThreadCount()); + return protobuf.build(); + } + + private static RuntimeMXBean runtimeMXBean() { + return ManagementFactory.getRuntimeMXBean(); + } + + private static Runtime runtime() { + return Runtime.getRuntime(); + } + + private static MemoryMXBean memoryMXBean() { + return ManagementFactory.getMemoryMXBean(); + } + + private static ClassLoadingMXBean classLoadingMXBean() { + return ManagementFactory.getClassLoadingMXBean(); + } + + private static ThreadMXBean threadMXBean() { + return ManagementFactory.getThreadMXBean(); + } + + private static String formatMemory(long memoryInBytes) { + return format("%d MB", memoryInBytes / 1_000_000); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/WebSystemInfoModule.java b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/WebSystemInfoModule.java new file mode 100644 index 000000000000..c565219240f1 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/WebSystemInfoModule.java @@ -0,0 +1,59 @@ +/* + * 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; + +import org.sonar.process.systeminfo.JvmPropertiesSection; +import org.sonar.process.systeminfo.JvmStateSection; +import org.sonar.server.platform.ws.ClusterInfoAction; +import org.sonar.server.platform.ws.StandaloneInfoAction; + +public class WebSystemInfoModule { + + private WebSystemInfoModule() { + // do not instantiate + } + + public static Object[] forStandaloneMode() { + return new Object[] { + new JvmPropertiesSection("Web JVM Properties"), + new JvmStateSection("Web JVM State"), + DatabaseSection.class, + EsSection.class, + PluginsSection.class, + SettingsSection.class, + SonarQubeSection.class, + SystemSection.class, + + StandaloneInfoAction.class + }; + } + + public static Object[] forClusterMode() { + return new Object[] { + new JvmPropertiesSection("Web JVM Properties"), + new JvmStateSection("Web JVM State"), + DatabaseSection.class, + EsSection.class, + PluginsSection.class, + + ClusterInfoAction.class + }; + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java index 46d62bdf8127..b69534bc6058 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java @@ -108,19 +108,13 @@ import org.sonar.server.platform.PersistentSettings; import org.sonar.server.platform.ServerLogging; import org.sonar.server.platform.SettingsChangeNotifier; -import org.sonar.server.platform.monitoring.DatabaseMonitor; -import org.sonar.server.platform.monitoring.EsMonitor; -import org.sonar.server.platform.monitoring.JvmPropsMonitor; -import org.sonar.server.platform.monitoring.PluginsMonitor; -import org.sonar.server.platform.monitoring.SettingsMonitor; -import org.sonar.server.platform.monitoring.SonarQubeMonitor; -import org.sonar.server.platform.monitoring.SystemMonitor; +import org.sonar.server.platform.monitoring.WebSystemInfoModule; import org.sonar.server.platform.web.WebPagesFilter; import org.sonar.server.platform.web.requestid.HttpRequestIdModule; import org.sonar.server.platform.ws.ChangeLogLevelAction; import org.sonar.server.platform.ws.DbMigrationStatusAction; import org.sonar.server.platform.ws.HealthActionModule; -import org.sonar.server.platform.ws.InfoActionModule; +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; @@ -192,7 +186,9 @@ import org.sonar.server.source.ws.RawAction; import org.sonar.server.source.ws.ScmAction; import org.sonar.server.source.ws.SourcesWs; -import org.sonar.server.telemetry.TelemetryModule; +import org.sonar.server.telemetry.TelemetryClient; +import org.sonar.server.telemetry.TelemetryDaemon; +import org.sonar.server.telemetry.TelemetryDataLoader; import org.sonar.server.test.index.TestIndex; import org.sonar.server.test.index.TestIndexDefinition; import org.sonar.server.test.index.TestIndexer; @@ -486,17 +482,9 @@ protected void configureLevel() { // System ServerLogging.class, RestartAction.class, - InfoActionModule.class, PingAction.class, UpgradesAction.class, StatusAction.class, - SystemMonitor.class, - SettingsMonitor.class, - SonarQubeMonitor.class, - EsMonitor.class, - PluginsMonitor.class, - JvmPropsMonitor.class, - DatabaseMonitor.class, MigrateDbAction.class, LogsAction.class, ChangeLogLevelAction.class, @@ -548,9 +536,14 @@ protected void configureLevel() { RecoveryIndexer.class, ProjectIndexersImpl.class); - addIfStartupLeader( - // Telemetry - TelemetryModule.class); + + // telemetry + add(TelemetryDataLoader.class); + addIfStartupLeader(TelemetryDaemon.class, TelemetryClient.class); + + // system info + add(InfoAction.class); + addIfCluster(WebSystemInfoModule.forClusterMode()).otherwiseAdd(WebSystemInfoModule.forStandaloneMode()); addAll(level4AddedComponents); } diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/ws/InfoAction.java b/server/sonar-server/src/main/java/org/sonar/server/platform/ws/InfoAction.java index 3f543c4e8f34..447ec555b282 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/ws/InfoAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/ws/InfoAction.java @@ -19,15 +19,15 @@ */ package org.sonar.server.platform.ws; -import java.util.Map; +import java.util.Arrays; import java.util.Optional; 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.process.systeminfo.SystemInfoSection; import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo; -import org.sonar.server.platform.monitoring.Monitor; import org.sonar.server.telemetry.TelemetryDataLoader; import org.sonar.server.user.UserSession; @@ -40,14 +40,14 @@ public class InfoAction implements SystemWsAction { private final UserSession userSession; private final CeHttpClient ceHttpClient; - private final Monitor[] monitors; + private final SystemInfoSection[] systemInfoSections; private final TelemetryDataLoader statistics; - public InfoAction(UserSession userSession, CeHttpClient ceHttpClient, TelemetryDataLoader statistics, Monitor... monitors) { + public InfoAction(UserSession userSession, CeHttpClient ceHttpClient, TelemetryDataLoader statistics, SystemInfoSection... systemInfoSections) { this.userSession = userSession; this.ceHttpClient = ceHttpClient; this.statistics = statistics; - this.monitors = monitors; + this.systemInfoSections = systemInfoSections; } @Override @@ -66,32 +66,19 @@ public void define(WebService.NewController controller) { public void handle(Request request, Response response) { userSession.checkIsSystemAdministrator(); - JsonWriter json = response.newJsonWriter(); - writeJson(json); - json.close(); + try (JsonWriter json = response.newJsonWriter()) { + writeJson(json); + } } private void writeJson(JsonWriter json) { json.beginObject(); - for (Monitor monitor : monitors) { - Map attributes = monitor.attributes(); - json.name(monitor.name()); - json.beginObject(); - for (Map.Entry attribute : attributes.entrySet()) { - json.name(attribute.getKey()).valueObject(attribute.getValue()); - } - json.endObject(); - } + Arrays.stream(systemInfoSections) + .map(SystemInfoSection::toProtobuf) + .forEach(section -> sectionToJson(section, json)); Optional ceSysInfo = ceHttpClient.retrieveSystemInfo(); if (ceSysInfo.isPresent()) { - for (ProtobufSystemInfo.Section section : ceSysInfo.get().getSectionsList()) { - json.name(section.getName()); - json.beginObject(); - for (ProtobufSystemInfo.Attribute attribute : section.getAttributesList()) { - writeAttribute(json, attribute); - } - json.endObject(); - } + ceSysInfo.get().getSectionsList().forEach(section -> sectionToJson(section, json)); } writeStatistics(json); json.endObject(); @@ -102,19 +89,28 @@ private void writeStatistics(JsonWriter json) { writeTelemetryData(json, statistics.load()); } - private static void writeAttribute(JsonWriter json, ProtobufSystemInfo.Attribute attribute) { + 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 static void attributeToJson(JsonWriter json, ProtobufSystemInfo.Attribute attribute) { switch (attribute.getValueCase()) { case BOOLEAN_VALUE: - json.name(attribute.getKey()).valueObject(attribute.getBooleanValue()); + json.prop(attribute.getKey(), attribute.getBooleanValue()); break; case LONG_VALUE: - json.name(attribute.getKey()).valueObject(attribute.getLongValue()); + json.prop(attribute.getKey(), attribute.getLongValue()); break; case DOUBLE_VALUE: - json.name(attribute.getKey()).valueObject(attribute.getDoubleValue()); + json.prop(attribute.getKey(), attribute.getDoubleValue()); break; case STRING_VALUE: - json.name(attribute.getKey()).valueObject(attribute.getStringValue()); + json.prop(attribute.getKey(), attribute.getStringValue()); break; case VALUE_NOT_SET: break; diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/BaseMonitorMBeanTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/BaseSectionMBeanTest.java similarity index 95% rename from server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/BaseMonitorMBeanTest.java rename to server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/BaseSectionMBeanTest.java index 0218ac4e72c4..31dc349c24f4 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/BaseMonitorMBeanTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/BaseSectionMBeanTest.java @@ -28,9 +28,9 @@ import static org.assertj.core.api.Assertions.assertThat; -public class BaseMonitorMBeanTest { +public class BaseSectionMBeanTest { - FakeMonitor underTest = new FakeMonitor(); + private FakeSection underTest = new FakeSection(); @Test public void test_registration() throws Exception { diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/DatabaseMonitorTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/DatabaseSectionTest.java similarity index 67% rename from server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/DatabaseMonitorTest.java rename to server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/DatabaseSectionTest.java index 5489e46b259e..f4139c81f07e 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/DatabaseMonitorTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/DatabaseSectionTest.java @@ -19,25 +19,27 @@ */ package org.sonar.server.platform.monitoring; -import java.util.Map; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.sonar.api.utils.System2; import org.sonar.db.DbTester; +import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo; import org.sonar.server.platform.db.migration.version.DatabaseVersion; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import static org.sonar.process.systeminfo.SystemInfoUtils.attribute; +import static org.sonar.server.platform.monitoring.SystemInfoTesting.assertThatAttributeIs; -public class DatabaseMonitorTest { +public class DatabaseSectionTest { @Rule public DbTester dbTester = DbTester.create(System2.INSTANCE); private DatabaseVersion databaseVersion = mock(DatabaseVersion.class); - private DatabaseMonitor underTest = new DatabaseMonitor(databaseVersion, dbTester.getDbClient()); + private DatabaseSection underTest = new DatabaseSection(databaseVersion, dbTester.getDbClient()); @Before public void setUp() throws Exception { @@ -51,16 +53,16 @@ public void name_is_not_empty() { @Test public void db_info() { - Map attributes = underTest.attributes(); - assertThat(attributes.get("Database")).isEqualTo("H2"); - assertThat(attributes.get("Database Version").toString()).startsWith("1."); - assertThat(attributes.get("Username")).isEqualTo("SONAR"); - assertThat(attributes.get("Driver Version").toString()).startsWith("1."); + ProtobufSystemInfo.Section section = underTest.toProtobuf(); + assertThatAttributeIs(section, "Database", "H2"); + assertThat(attribute(section, "Database Version").getStringValue()).startsWith("1."); + assertThatAttributeIs(section, "Username", "SONAR"); + assertThat(attribute(section, "Driver Version").getStringValue()).startsWith("1."); } @Test public void pool_info() { - Map attributes = underTest.attributes(); - assertThat((int) attributes.get("Pool Max Connections")).isGreaterThan(0); + ProtobufSystemInfo.Section section = underTest.toProtobuf(); + assertThat(attribute(section, "Pool Max Connections").getLongValue()).isGreaterThan(0L); } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/EsMonitorTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/EsSectionTest.java similarity index 59% rename from server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/EsMonitorTest.java rename to server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/EsSectionTest.java index 345d23793ccc..a55ae819df32 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/EsMonitorTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/EsSectionTest.java @@ -19,12 +19,12 @@ */ package org.sonar.server.platform.monitoring; -import java.util.Map; import org.elasticsearch.ElasticsearchException; import org.elasticsearch.cluster.health.ClusterHealthStatus; import org.junit.Rule; import org.junit.Test; import org.sonar.api.config.internal.MapSettings; +import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo; import org.sonar.server.es.EsClient; import org.sonar.server.es.EsTester; import org.sonar.server.issue.index.IssueIndexDefinition; @@ -32,13 +32,15 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import static org.sonar.process.systeminfo.SystemInfoUtils.attribute; +import static org.sonar.server.platform.monitoring.SystemInfoTesting.assertThatAttributeIs; -public class EsMonitorTest { +public class EsSectionTest { @Rule public EsTester esTester = new EsTester(new IssueIndexDefinition(new MapSettings().asConfig())); - private EsMonitor underTest = new EsMonitor(esTester.client()); + private EsSection underTest = new EsSection(esTester.client()); @Test public void name() { @@ -46,67 +48,54 @@ public void name() { } @Test - public void cluster_attributes() { - Map attributes = underTest.attributes(); + public void es_state() { assertThat(underTest.getState()).isEqualTo(ClusterHealthStatus.GREEN.name()); - assertThat(attributes.get("State")).isEqualTo(ClusterHealthStatus.GREEN); - assertThat(attributes.get("Number of Nodes")).isEqualTo(1); + assertThatAttributeIs(underTest.toProtobuf(), "State", ClusterHealthStatus.GREEN.name()); } @Test public void node_attributes() { - Map attributes = underTest.attributes(); - Map nodesAttributes = (Map) attributes.get("Nodes"); - - // one node - assertThat(nodesAttributes).hasSize(1); - Map nodeAttributes = (Map) nodesAttributes.values().iterator().next(); - assertThat(nodeAttributes.get("Type")).isEqualTo("Master"); - assertThat(nodeAttributes.get("Store Size")).isNotNull(); + ProtobufSystemInfo.Section section = underTest.toProtobuf(); + assertThat(attribute(section, "Store Size")).isNotNull(); } @Test public void index_attributes() { - Map attributes = underTest.attributes(); - Map indicesAttributes = (Map) attributes.get("Indices"); + ProtobufSystemInfo.Section section = underTest.toProtobuf(); // one index "issues" - Map indexAttributes = (Map) indicesAttributes.get(IssueIndexDefinition.INDEX_TYPE_ISSUE.getIndex()); - assertThat(indexAttributes.get("Docs")).isEqualTo(0L); - assertThat((int) indexAttributes.get("Shards")).isGreaterThan(0); - assertThat(indexAttributes.get("Store Size")).isNotNull(); + assertThat(attribute(section, "Index issues - Docs").getLongValue()).isEqualTo(0L); + assertThat(attribute(section, "Index issues - Shards").getLongValue()).isGreaterThan(0); + assertThat(attribute(section, "Index issues - Store Size").getStringValue()).isNotNull(); } @Test public void attributes_displays_exception_message_when_cause_null_when_client_fails() { EsClient esClientMock = mock(EsClient.class); - EsMonitor underTest = new EsMonitor(esClientMock); + EsSection underTest = new EsSection(esClientMock); when(esClientMock.prepareClusterStats()).thenThrow(new RuntimeException("RuntimeException with no cause")); - Map attributes = underTest.attributes(); - assertThat(attributes).hasSize(1); - assertThat(attributes.get("State")).isEqualTo("RuntimeException with no cause"); + ProtobufSystemInfo.Section section = underTest.toProtobuf(); + assertThatAttributeIs(section, "State", "RuntimeException with no cause"); } @Test public void attributes_displays_exception_message_when_cause_is_not_ElasticSearchException_when_client_fails() { EsClient esClientMock = mock(EsClient.class); - EsMonitor underTest = new EsMonitor(esClientMock); + EsSection underTest = new EsSection(esClientMock); when(esClientMock.prepareClusterStats()).thenThrow(new RuntimeException("RuntimeException with cause not ES", new IllegalArgumentException("some cause message"))); - Map attributes = underTest.attributes(); - assertThat(attributes).hasSize(1); - assertThat(attributes.get("State")).isEqualTo("RuntimeException with cause not ES"); + ProtobufSystemInfo.Section section = underTest.toProtobuf(); + assertThatAttributeIs(section, "State", "RuntimeException with cause not ES"); } @Test public void attributes_displays_cause_message_when_cause_is_ElasticSearchException_when_client_fails() { EsClient esClientMock = mock(EsClient.class); - EsMonitor underTest = new EsMonitor(esClientMock); + EsSection underTest = new EsSection(esClientMock); when(esClientMock.prepareClusterStats()).thenThrow(new RuntimeException("RuntimeException with ES cause", new ElasticsearchException("some cause message"))); - Map attributes = underTest.attributes(); - assertThat(attributes).hasSize(1); - assertThat(attributes.get("State")).isEqualTo("some cause message"); + ProtobufSystemInfo.Section section = underTest.toProtobuf(); + assertThatAttributeIs(section, "State", "some cause message"); } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/FakeMonitor.java b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/FakeSection.java similarity index 80% rename from server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/FakeMonitor.java rename to server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/FakeSection.java index 959438b9ccff..7018c1e8aaaa 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/FakeMonitor.java +++ b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/FakeSection.java @@ -19,10 +19,9 @@ */ package org.sonar.server.platform.monitoring; -import java.util.Collections; -import java.util.Map; +import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo; -public class FakeMonitor extends BaseMonitorMBean implements FakeMonitorMBean { +public class FakeSection extends BaseSectionMBean implements FakeSectionMBean { @Override public int getFake() { @@ -35,7 +34,7 @@ public String name() { } @Override - public Map attributes() { - return Collections.emptyMap(); + public ProtobufSystemInfo.Section toProtobuf() { + return ProtobufSystemInfo.Section.newBuilder().build(); } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/FakeMonitorMBean.java b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/FakeSectionMBean.java similarity index 96% rename from server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/FakeMonitorMBean.java rename to server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/FakeSectionMBean.java index d355b62cde38..6421859d540b 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/FakeMonitorMBean.java +++ b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/FakeSectionMBean.java @@ -19,6 +19,6 @@ */ package org.sonar.server.platform.monitoring; -public interface FakeMonitorMBean { +public interface FakeSectionMBean { int getFake(); } diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/PluginsMonitorTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/PluginsSectionTest.java similarity index 66% rename from server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/PluginsMonitorTest.java rename to server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/PluginsSectionTest.java index 278f8691e96b..58783258e7e2 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/PluginsMonitorTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/PluginsSectionTest.java @@ -20,49 +20,43 @@ package org.sonar.server.platform.monitoring; import java.util.Arrays; -import java.util.Map; import org.junit.Test; import org.sonar.core.platform.PluginInfo; import org.sonar.core.platform.PluginRepository; +import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo; import org.sonar.updatecenter.common.Version; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import static org.sonar.server.platform.monitoring.SystemInfoTesting.assertThatAttributeIs; -public class PluginsMonitorTest { +public class PluginsSectionTest { - PluginRepository repo = mock(PluginRepository.class); - PluginsMonitor underTest = new PluginsMonitor(repo); + private PluginRepository repo = mock(PluginRepository.class); + private PluginsSection underTest = new PluginsSection(repo); @Test public void name() { - assertThat(underTest.name()).isEqualTo("Plugins"); + assertThat(underTest.toProtobuf().getName()).isEqualTo("Plugins"); } @Test public void plugin_name_and_version() { when(repo.getPluginInfos()).thenReturn(Arrays.asList( new PluginInfo("key-1") - .setName("plugin-1") + .setName("Plugin 1") .setVersion(Version.create("1.1")), new PluginInfo("key-2") - .setName("plugin-2") + .setName("Plugin 2") .setVersion(Version.create("2.2")), new PluginInfo("no-version") .setName("No Version"))); - Map attributes = underTest.attributes(); + ProtobufSystemInfo.Section section = underTest.toProtobuf(); - assertThat(attributes).containsKeys("key-1", "key-2"); - assertThat((Map) attributes.get("key-1")) - .containsEntry("Name", "plugin-1") - .containsEntry("Version", "1.1"); - assertThat((Map) attributes.get("key-2")) - .containsEntry("Name", "plugin-2") - .containsEntry("Version", "2.2"); - assertThat((Map) attributes.get("no-version")) - .containsEntry("Name", "No Version") - .doesNotContainKey("Version"); + assertThatAttributeIs(section, "key-1", "1.1 [Plugin 1]"); + assertThatAttributeIs(section, "key-2", "2.2 [Plugin 2]"); + assertThatAttributeIs(section, "no-version", "[No Version]"); } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SettingsMonitorTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SettingsSectionTest.java similarity index 61% rename from server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SettingsMonitorTest.java rename to server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SettingsSectionTest.java index 9c1edb795a2e..b5f5c14dadc5 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SettingsMonitorTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SettingsSectionTest.java @@ -19,53 +19,57 @@ */ package org.sonar.server.platform.monitoring; -import java.util.SortedMap; import org.junit.Test; import org.sonar.api.PropertyType; import org.sonar.api.config.PropertyDefinition; import org.sonar.api.config.PropertyDefinitions; import org.sonar.api.config.Settings; import org.sonar.api.config.internal.MapSettings; +import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo; import static org.apache.commons.lang.StringUtils.repeat; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.entry; +import static org.sonar.process.systeminfo.SystemInfoUtils.attribute; +import static org.sonar.server.platform.monitoring.SystemInfoTesting.assertThatAttributeIs; -public class SettingsMonitorTest { +public class SettingsSectionTest { private static final String PASSWORD_PROPERTY = "sonar.password"; - PropertyDefinitions defs = new PropertyDefinitions(PropertyDefinition.builder(PASSWORD_PROPERTY).type(PropertyType.PASSWORD).build()); - Settings settings = new MapSettings(defs); - SettingsMonitor underTest = new SettingsMonitor(settings); + private PropertyDefinitions defs = new PropertyDefinitions(PropertyDefinition.builder(PASSWORD_PROPERTY).type(PropertyType.PASSWORD).build()); + private Settings settings = new MapSettings(defs); + private SettingsSection underTest = new SettingsSection(settings); @Test public void return_properties_and_sort_by_key() { settings.setProperty("foo", "foo value"); settings.setProperty("bar", "bar value"); - SortedMap attributes = underTest.attributes(); - assertThat(attributes).containsExactly(entry("bar", "bar value"), entry("foo", "foo value")); + ProtobufSystemInfo.Section protobuf = underTest.toProtobuf(); + assertThatAttributeIs(protobuf, "bar", "bar value"); + assertThatAttributeIs(protobuf, "foo", "foo value"); } @Test public void truncate_long_property_values() { settings.setProperty("foo", repeat("abcde", 1_000)); - String value = (String) underTest.attributes().get("foo"); - assertThat(value).hasSize(SettingsMonitor.MAX_VALUE_LENGTH).startsWith("abcde"); + ProtobufSystemInfo.Section protobuf = underTest.toProtobuf(); + String value = attribute(protobuf, "foo").getStringValue(); + assertThat(value).hasSize(SettingsSection.MAX_VALUE_LENGTH).startsWith("abcde"); } @Test public void exclude_password_properties() { settings.setProperty(PASSWORD_PROPERTY, "abcde"); - assertThat(underTest.attributes()).isEmpty(); + ProtobufSystemInfo.Section protobuf = underTest.toProtobuf(); + assertThat(attribute(protobuf, PASSWORD_PROPERTY)).isNull(); } @Test public void test_monitor_name() { - assertThat(underTest.name()).isEqualTo("Settings"); + assertThat(underTest.toProtobuf().getName()).isEqualTo("Settings"); } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SonarQubeMonitorTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SonarQubeSectionTest.java similarity index 66% rename from server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SonarQubeMonitorTest.java rename to server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SonarQubeSectionTest.java index e967f10d8de4..9817a2ec1010 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SonarQubeMonitorTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SonarQubeSectionTest.java @@ -20,7 +20,6 @@ package org.sonar.server.platform.monitoring; import java.io.File; -import java.util.Map; import java.util.Optional; import org.apache.commons.io.FileUtils; import org.junit.Before; @@ -31,6 +30,7 @@ import org.sonar.api.platform.Server; import org.sonar.api.security.SecurityRealm; import org.sonar.api.utils.log.LoggerLevel; +import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo; import org.sonar.server.authentication.IdentityProviderRepositoryRule; import org.sonar.server.authentication.TestIdentityProvider; import org.sonar.server.platform.ServerId; @@ -39,11 +39,12 @@ import org.sonar.server.user.SecurityRealmFactory; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.data.MapEntry.entry; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import static org.sonar.process.systeminfo.SystemInfoUtils.attribute; +import static org.sonar.server.platform.monitoring.SystemInfoTesting.assertThatAttributeIs; -public class SonarQubeMonitorTest { +public class SonarQubeSectionTest { private static final String SERVER_ID_PROPERTY = "Server ID"; private static final String SERVER_ID_VALIDATED_PROPERTY = "Server ID validated"; @@ -54,13 +55,13 @@ public class SonarQubeMonitorTest { @Rule public IdentityProviderRepositoryRule identityProviderRepository = new IdentityProviderRepositoryRule(); - MapSettings settings = new MapSettings(); - Server server = mock(Server.class); - ServerIdLoader serverIdLoader = mock(ServerIdLoader.class); - ServerLogging serverLogging = mock(ServerLogging.class); - SecurityRealmFactory securityRealmFactory = mock(SecurityRealmFactory.class); + private MapSettings settings = new MapSettings(); + private Server server = mock(Server.class); + private ServerIdLoader serverIdLoader = mock(ServerIdLoader.class); + private ServerLogging serverLogging = mock(ServerLogging.class); + private SecurityRealmFactory securityRealmFactory = mock(SecurityRealmFactory.class); - SonarQubeMonitor underTest = new SonarQubeMonitor(settings.asConfig(), securityRealmFactory, identityProviderRepository, server, + private SonarQubeSection underTest = new SonarQubeSection(settings.asConfig(), securityRealmFactory, identityProviderRepository, server, serverLogging, serverIdLoader); @Before @@ -80,7 +81,7 @@ public void test_getServerId() { when(serverIdLoader.getRaw()).thenReturn(Optional.of("ABC")); assertThat(underTest.getServerId()).isEqualTo("ABC"); - when(serverIdLoader.getRaw()).thenReturn(Optional.empty()); + when(serverIdLoader.getRaw()).thenReturn(Optional.empty()); assertThat(underTest.getServerId()).isNull(); } @@ -88,35 +89,38 @@ public void test_getServerId() { public void attributes_contain_information_about_valid_server_id() { when(serverIdLoader.get()).thenReturn(Optional.of(new ServerId("ABC", true))); - Map attributes = underTest.attributes(); - assertThat(attributes).contains(entry(SERVER_ID_PROPERTY, "ABC"), entry(SERVER_ID_VALIDATED_PROPERTY, true)); + ProtobufSystemInfo.Section protobuf = underTest.toProtobuf(); + assertThatAttributeIs(protobuf, SERVER_ID_PROPERTY, "ABC"); + assertThatAttributeIs(protobuf, SERVER_ID_VALIDATED_PROPERTY, true); } @Test public void attributes_contain_information_about_non_valid_server_id() { when(serverIdLoader.get()).thenReturn(Optional.of(new ServerId("ABC", false))); - Map attributes = underTest.attributes(); - assertThat(attributes).contains(entry(SERVER_ID_PROPERTY, "ABC"), entry(SERVER_ID_VALIDATED_PROPERTY, false)); + ProtobufSystemInfo.Section protobuf = underTest.toProtobuf(); + assertThatAttributeIs(protobuf, SERVER_ID_PROPERTY, "ABC"); + assertThatAttributeIs(protobuf, SERVER_ID_VALIDATED_PROPERTY, false); } @Test public void attributes_do_not_contain_information_about_server_id_if_absent() { - when(serverIdLoader.get()).thenReturn(Optional.empty()); + when(serverIdLoader.get()).thenReturn(Optional.empty()); - Map attributes = underTest.attributes(); - assertThat(attributes).doesNotContainKeys(SERVER_ID_PROPERTY, SERVER_ID_VALIDATED_PROPERTY); + ProtobufSystemInfo.Section protobuf = underTest.toProtobuf(); + assertThat(attribute(protobuf, SERVER_ID_PROPERTY)).isNull(); + assertThat(attribute(protobuf, SERVER_ID_VALIDATED_PROPERTY)).isNull(); } @Test public void official_distribution() throws Exception { File rootDir = temp.newFolder(); - FileUtils.write(new File(rootDir, SonarQubeMonitor.BRANDING_FILE_PATH), "1.2"); + FileUtils.write(new File(rootDir, SonarQubeSection.BRANDING_FILE_PATH), "1.2"); when(server.getRootDir()).thenReturn(rootDir); - Map attributes = underTest.attributes(); - assertThat(attributes).containsEntry("Official Distribution", Boolean.TRUE); + ProtobufSystemInfo.Section protobuf = underTest.toProtobuf(); + assertThatAttributeIs(protobuf, "Official Distribution", true); } @Test @@ -125,14 +129,14 @@ public void not_an_official_distribution() throws Exception { // branding file is missing when(server.getRootDir()).thenReturn(rootDir); - Map attributes = underTest.attributes(); - assertThat(attributes).containsEntry("Official Distribution", Boolean.FALSE); + ProtobufSystemInfo.Section protobuf = underTest.toProtobuf(); + assertThatAttributeIs(protobuf, "Official Distribution", false); } @Test public void get_log_level() throws Exception { - Map attributes = underTest.attributes(); - assertThat(attributes).containsEntry("Logs Level", "DEBUG"); + ProtobufSystemInfo.Section protobuf = underTest.toProtobuf(); + assertThatAttributeIs(protobuf, "Logs Level", "DEBUG"); } @Test @@ -141,16 +145,16 @@ public void get_realm() throws Exception { when(realm.getName()).thenReturn("LDAP"); when(securityRealmFactory.getRealm()).thenReturn(realm); - Map attributes = underTest.attributes(); - assertThat(attributes).containsEntry("External User Authentication", "LDAP"); + ProtobufSystemInfo.Section protobuf = underTest.toProtobuf(); + assertThatAttributeIs(protobuf, "External User Authentication", "LDAP"); } @Test public void no_realm() throws Exception { when(securityRealmFactory.getRealm()).thenReturn(null); - Map attributes = underTest.attributes(); - assertThat(attributes).doesNotContainKey("External User Authentication"); + ProtobufSystemInfo.Section protobuf = underTest.toProtobuf(); + assertThat(attribute(protobuf, "External User Authentication")).isNull(); } @Test @@ -168,8 +172,8 @@ public void get_enabled_identity_providers() throws Exception { .setName("Disabled") .setEnabled(false)); - Map attributes = underTest.attributes(); - assertThat(attributes).containsEntry("Accepted external identity providers", "Bitbucket, GitHub"); + ProtobufSystemInfo.Section protobuf = underTest.toProtobuf(); + assertThatAttributeIs(protobuf, "Accepted external identity providers", "Bitbucket, GitHub"); } @Test @@ -190,7 +194,7 @@ public void get_enabled_identity_providers_allowing_users_to_signup() throws Exc .setEnabled(false) .setAllowsUsersToSignUp(true)); - Map attributes = underTest.attributes(); - assertThat(attributes).containsEntry("External identity providers whose users are allowed to sign themselves up", "GitHub"); + ProtobufSystemInfo.Section protobuf = underTest.toProtobuf(); + assertThatAttributeIs(protobuf, "External identity providers whose users are allowed to sign themselves up", "GitHub"); } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SystemInfoTesting.java b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SystemInfoTesting.java new file mode 100644 index 000000000000..bd02658896f5 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SystemInfoTesting.java @@ -0,0 +1,44 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.platform.monitoring; + +import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.sonar.process.systeminfo.SystemInfoUtils.attribute; + +public class SystemInfoTesting { + + private SystemInfoTesting() { + // do not instantiate + } + + public static void assertThatAttributeIs(ProtobufSystemInfo.Section section, String key, String expectedValue) { + ProtobufSystemInfo.Attribute value = attribute(section, key); + assertThat(value).as(key).isNotNull(); + assertThat(value.getStringValue()).isEqualTo(expectedValue); + } + + public static void assertThatAttributeIs(ProtobufSystemInfo.Section section, String key, boolean expectedValue) { + ProtobufSystemInfo.Attribute value = attribute(section, key); + assertThat(value).as(key).isNotNull(); + assertThat(value.getBooleanValue()).isEqualTo(expectedValue); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/JvmPropsMonitorTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SystemSectionTest.java similarity index 63% rename from server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/JvmPropsMonitorTest.java rename to server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SystemSectionTest.java index b48649679acb..94378667a592 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/JvmPropsMonitorTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SystemSectionTest.java @@ -19,24 +19,26 @@ */ package org.sonar.server.platform.monitoring; -import java.util.Map; import org.junit.Test; +import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo; import static org.assertj.core.api.Assertions.assertThat; +import static org.sonar.process.systeminfo.SystemInfoUtils.attribute; -public class JvmPropsMonitorTest { +public class SystemSectionTest { - JvmPropsMonitor underTest = new JvmPropsMonitor(); + private SystemSection underTest = new SystemSection(); @Test - public void name_is_not_empty() { - assertThat(underTest.name()).isNotEmpty(); + public void name() { + assertThat(underTest.toProtobuf().getName()).isEqualTo("System"); } @Test - public void attributes() { - Map attributes = underTest.attributes(); + public void system_properties() { + ProtobufSystemInfo.Section section = underTest.toProtobuf(); - assertThat(attributes).containsKeys("java.vm.vendor", "os.name"); + assertThat(attribute(section, "System Date").getStringValue()).isNotEmpty(); + assertThat(attribute(section, "Processors").getLongValue()).isGreaterThan(0); } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SystemMonitorTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/WebSystemInfoModuleTest.java similarity index 74% rename from server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SystemMonitorTest.java rename to server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/WebSystemInfoModuleTest.java index 6c7b568cba11..a1bb583a67fe 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/SystemMonitorTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/WebSystemInfoModuleTest.java @@ -19,24 +19,23 @@ */ package org.sonar.server.platform.monitoring; -import java.util.Map; import org.junit.Test; import static org.assertj.core.api.Assertions.assertThat; -public class SystemMonitorTest { - - SystemMonitor underTest = new SystemMonitor(); +public class WebSystemInfoModuleTest { @Test - public void name() { - assertThat(underTest.name()).isEqualTo("System"); + public void test_forStandaloneMode() { + assertThat(WebSystemInfoModule.forStandaloneMode()) + .isNotEmpty() + .doesNotContainNull(); } @Test - public void system_properties() { - Map attributes = underTest.attributes(); - - assertThat(attributes).containsKeys("System Date", "Processors"); + public void test_forClusterMode() { + assertThat(WebSystemInfoModule.forClusterMode()) + .isNotEmpty() + .doesNotContainNull(); } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/ws/InfoActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/ws/InfoActionTest.java index b90037f812f9..9c02608fddd2 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/platform/ws/InfoActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/platform/ws/InfoActionTest.java @@ -19,8 +19,6 @@ */ package org.sonar.server.platform.ws; -import java.util.LinkedHashMap; -import java.util.Map; import java.util.Optional; import org.junit.Rule; import org.junit.Test; @@ -28,8 +26,9 @@ import org.mockito.Mockito; import org.sonar.ce.http.CeHttpClient; import org.sonar.ce.http.CeHttpClientImpl; +import org.sonar.process.systeminfo.SystemInfoSection; +import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo; import org.sonar.server.exceptions.ForbiddenException; -import org.sonar.server.platform.monitoring.Monitor; import org.sonar.server.telemetry.TelemetryData; import org.sonar.server.telemetry.TelemetryDataLoader; import org.sonar.server.tester.UserSessionRule; @@ -40,6 +39,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static org.sonar.process.systeminfo.SystemInfoUtils.setAttribute; public class InfoActionTest { @Rule @@ -48,12 +48,12 @@ public class InfoActionTest { @Rule public ExpectedException expectedException = ExpectedException.none(); - private Monitor monitor1 = mock(Monitor.class); - private Monitor monitor2 = mock(Monitor.class); + private SystemInfoSection section1 = mock(SystemInfoSection.class); + private SystemInfoSection section2 = mock(SystemInfoSection.class); private CeHttpClient ceHttpClient = mock(CeHttpClientImpl.class, Mockito.RETURNS_MOCKS); private TelemetryDataLoader statistics = mock(TelemetryDataLoader.class); - private InfoAction underTest = new InfoAction(userSessionRule, ceHttpClient, statistics, monitor1, monitor2); + private InfoAction underTest = new InfoAction(userSessionRule, ceHttpClient, statistics, section1, section2); private WsActionTester ws = new WsActionTester(underTest); @Test @@ -84,22 +84,23 @@ public void request_fails_with_ForbiddenException_when_user_is_not_system_admini public void write_json() { logInAsSystemAdministrator(); - Map attributes1 = new LinkedHashMap<>(); - attributes1.put("foo", "bar"); - Map attributes2 = new LinkedHashMap<>(); - attributes2.put("one", 1); - attributes2.put("two", 2); - when(monitor1.name()).thenReturn("Monitor One"); - when(monitor1.attributes()).thenReturn(attributes1); - when(monitor2.name()).thenReturn("Monitor Two"); - when(monitor2.attributes()).thenReturn(attributes2); + ProtobufSystemInfo.Section.Builder attributes1 = ProtobufSystemInfo.Section.newBuilder() + .setName("Section One"); + setAttribute(attributes1, "foo", "bar"); + when(section1.toProtobuf()).thenReturn(attributes1.build()); + + ProtobufSystemInfo.Section.Builder attributes2 = ProtobufSystemInfo.Section.newBuilder() + .setName("Section Two"); + setAttribute(attributes2, "one", 1); + setAttribute(attributes2, "two", 2); + when(section2.toProtobuf()).thenReturn(attributes2.build()); when(ceHttpClient.retrieveSystemInfo()).thenReturn(Optional.empty()); when(statistics.load()).thenReturn(mock(TelemetryData.class)); TestResponse response = ws.newRequest().execute(); - // response does not contain empty "Monitor Three" + // response does not contain empty "Section Three" verify(statistics).load(); - assertThat(response.getInput()).isEqualTo("{\"Monitor One\":{\"foo\":\"bar\"},\"Monitor Two\":{\"one\":1,\"two\":2}," + + assertThat(response.getInput()).isEqualTo("{\"Section One\":{\"foo\":\"bar\"},\"Section Two\":{\"one\":1,\"two\":2}," + "\"Statistics\":{\"plugins\":{},\"userCount\":0,\"projectCount\":0,\"lines\":0,\"ncloc\":0,\"projectCountByLanguage\":{},\"nclocByLanguage\":{}}}"); } diff --git a/server/sonar-server/src/test/java/org/sonar/server/telemetry/TelemetryModuleTest.java b/server/sonar-server/src/test/java/org/sonar/server/telemetry/TelemetryModuleTest.java deleted file mode 100644 index 3edc6524aa80..000000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/telemetry/TelemetryModuleTest.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2017 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.server.telemetry; - -import org.junit.Test; -import org.sonar.core.platform.ComponentContainer; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.sonar.core.platform.ComponentContainer.COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER; - -public class TelemetryModuleTest { - @Test - public void verify_count_of_added_components() { - ComponentContainer container = new ComponentContainer(); - - new TelemetryModule().configure(container); - - assertThat(container.size()).isEqualTo(2 + COMPONENTS_IN_EMPTY_COMPONENT_CONTAINER); - } -} diff --git a/server/sonar-web/src/main/js/api/system.ts b/server/sonar-web/src/main/js/api/system.ts index 93eeaaf5b777..1b4fd125a972 100644 --- a/server/sonar-web/src/main/js/api/system.ts +++ b/server/sonar-web/src/main/js/api/system.ts @@ -18,21 +18,56 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import { getJSON, post } from '../helpers/request'; +import throwGlobalError from '../app/utils/throwGlobalError'; -export function setLogLevel(level: string): Promise { - return post('/api/system/change_log_level', { level }); +export type SysValue = boolean | string | number | HealthType | SysValueObject | SysValueArray; +export interface SysValueObject { + [key: string]: SysValue; } +export interface SysValueArray extends Array {} -export function getSystemInfo(): Promise { - return getJSON('/api/system/info'); +export interface SysInfoSection { + [sectionName: string]: SysValueObject; +} + +export enum HealthType { + RED = 'RED', + YELLOW = 'YELLOW', + GREEN = 'GREEN' +} + +export interface HealthCause extends SysValueObject { + message: string; +} + +export interface NodeInfo extends SysValueObject { + Name: string; + Health: HealthType; + 'Health Causes': HealthCause[]; +} + +export interface SysInfo extends SysValueObject { + Cluster: boolean; + Health: HealthType; + 'Health Causes': HealthCause[]; + 'Application Nodes': NodeInfo[]; + 'Search Nodes': NodeInfo[]; +} + +export function setLogLevel(level: string): Promise { + return post('/api/system/change_log_level', { level }).catch(throwGlobalError); +} + +export function getSystemInfo(): Promise { + return getJSON('/api/system/info').catch(throwGlobalError); } export function getSystemStatus(): Promise { return getJSON('/api/system/status'); } -export function restart(): Promise { - return post('/api/system/restart'); +export function restart(): Promise { + return post('/api/system/restart').catch(throwGlobalError); } const POLLING_INTERVAL = 2000; diff --git a/server/sonar-web/src/main/js/apps/system/__tests__/utils-test.ts b/server/sonar-web/src/main/js/apps/system/__tests__/utils-test.ts new file mode 100644 index 000000000000..cd10c605418f --- /dev/null +++ b/server/sonar-web/src/main/js/apps/system/__tests__/utils-test.ts @@ -0,0 +1,43 @@ +/* + * 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. + */ +import * as utils from '../utils'; + +describe('parseQuery', () => { + it('should correctly parse the expand array', () => { + expect(utils.parseQuery({})).toEqual({ expandedCards: [] }); + expect(utils.parseQuery({ expand: 'foo,bar' })).toEqual({ expandedCards: ['foo', 'bar'] }); + }); +}); + +describe('serializeQuery', () => { + it('should correctly serialize the expand array', () => { + expect(utils.serializeQuery({ expandedCards: [] })).toEqual({}); + expect(utils.serializeQuery({ expandedCards: ['foo', 'bar'] })).toEqual({ expand: 'foo,bar' }); + }); +}); + +describe('groupSections', () => { + it('should correctly group the root field into a main section', () => { + expect(utils.groupSections({ foo: 'Foo', bar: 3, baz: { a: 'a' } })).toEqual({ + mainSection: { foo: 'Foo', bar: 3 }, + sections: { baz: { a: 'a' } } + }); + }); +}); diff --git a/server/sonar-web/src/main/js/apps/system/components/App.tsx b/server/sonar-web/src/main/js/apps/system/components/App.tsx new file mode 100644 index 000000000000..440b117929a9 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/system/components/App.tsx @@ -0,0 +1,125 @@ +/* + * 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. + */ +import * as React from 'react'; +import * as PropTypes from 'prop-types'; +import Helmet from 'react-helmet'; +import ClusterSysInfos from './ClusterSysInfos'; +import PageHeader from './PageHeader'; +import StandAloneSysInfos from './StandAloneSysInfos'; +import { translate } from '../../../helpers/l10n'; +import { getSystemInfo, SysInfo } from '../../../api/system'; +import { isCluster, parseQuery, Query, serializeQuery } from '../utils'; +import { RawQuery } from '../../../helpers/query'; +import '../styles.css'; + +interface Props { + location: { pathname: string; query: RawQuery }; +} + +interface State { + loading: boolean; + sysInfoData?: SysInfo; +} + +export default class App extends React.PureComponent { + mounted: boolean; + state: State = { loading: true }; + + static contextTypes = { + router: PropTypes.object + }; + + componentDidMount() { + this.mounted = true; + this.fetchSysInfo(); + } + + componentWillUnmount() { + this.mounted = false; + } + + fetchSysInfo = () => { + this.setState({ loading: true }); + getSystemInfo().then( + (sysInfoData: SysInfo) => { + if (this.mounted) { + this.setState({ loading: false, sysInfoData }); + } + }, + () => { + if (this.mounted) { + this.setState({ loading: false }); + } + } + ); + }; + + toggleSysInfoCards = (toggledCard: string) => { + const query = parseQuery(this.props.location.query); + let expandedCards; + if (query.expandedCards.includes(toggledCard)) { + expandedCards = query.expandedCards.filter(card => card !== toggledCard); + } else { + expandedCards = query.expandedCards.concat(toggledCard); + } + this.updateQuery({ expandedCards }); + }; + + updateQuery = (newQuery: Query) => { + const query = serializeQuery({ ...parseQuery(this.props.location.query), ...newQuery }); + this.context.router.push({ pathname: this.props.location.pathname, query }); + }; + + renderSysInfo() { + const { sysInfoData } = this.state; + if (!sysInfoData) { + return null; + } + + const query = parseQuery(this.props.location.query); + if (isCluster(sysInfoData)) { + return ( + + ); + } + return ; + } + + render() { + const { loading, sysInfoData } = this.state; + // TODO Correctly get logLevel, we are not sure yet how we want to do it for cluster mode + return ( +
+ + + {this.renderSysInfo()} +
+ ); + } +} diff --git a/server/sonar-web/src/main/js/apps/system/components/ChangeLogLevelForm.tsx b/server/sonar-web/src/main/js/apps/system/components/ChangeLogLevelForm.tsx new file mode 100644 index 000000000000..3065a0be66db --- /dev/null +++ b/server/sonar-web/src/main/js/apps/system/components/ChangeLogLevelForm.tsx @@ -0,0 +1,114 @@ +/* + * 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. + */ +import * as React from 'react'; +import Modal from 'react-modal'; +import { setLogLevel } from '../../../api/system'; +import { translate } from '../../../helpers/l10n'; + +interface Props { + infoMsg: string; + logLevel: string; + onChange: (level: string) => void; + onClose: () => void; +} + +interface State { + newLevel: string; + updating: boolean; +} + +const LOG_LEVELS = ['INFO', 'DEBUG', 'TRACE']; + +export default class ChangeLogLevelForm extends React.PureComponent { + constructor(props: Props) { + super(props); + this.state = { newLevel: props.logLevel, updating: false }; + } + + handleCancelClick = (event: React.SyntheticEvent) => { + event.preventDefault(); + this.props.onClose(); + }; + + handleFormSubmit = (event: React.SyntheticEvent) => { + event.preventDefault(); + const { newLevel } = this.state; + if (!this.state.updating && newLevel !== this.props.logLevel) { + this.setState({ updating: true }); + setLogLevel(newLevel).then( + () => this.props.onChange(newLevel), + () => this.setState({ updating: false }) + ); + } + }; + + handleLevelChange = (event: React.ChangeEvent) => + this.setState({ newLevel: event.currentTarget.value }); + + render() { + const { updating, newLevel } = this.state; + const header = translate('system.set_log_level'); + const disableSubmit = updating || newLevel === this.props.logLevel; + return ( + +
+
+

{header}

+
+
+ {LOG_LEVELS.map(level => ( +

+ + {level} +

+ ))} +
{this.props.infoMsg}
+ {newLevel !== 'INFO' && ( +
+ {translate('system.log_level.warning')} +
+ )} +
+
+ {updating && } + + + {translate('cancel')} + +
+ +
+ ); + } +} diff --git a/server/sonar-web/src/main/js/apps/system/components/ClusterSysInfos.tsx b/server/sonar-web/src/main/js/apps/system/components/ClusterSysInfos.tsx new file mode 100644 index 000000000000..7f68dc6771a9 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/system/components/ClusterSysInfos.tsx @@ -0,0 +1,82 @@ +/* + * 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. + */ +import * as React from 'react'; +import { sortBy } from 'lodash'; +import HealthCard from './info-items/HealthCard'; +import { translate } from '../../../helpers/l10n'; +import { + getAppNodes, + getHealth, + getHealthCauses, + getMainCardSection, + getNodeName, + getSearchNodes, + ignoreInfoFields +} from '../utils'; +import { SysInfo } from '../../../api/system'; + +interface Props { + expandedCards: string[]; + sysInfoData: SysInfo; + toggleCard: (toggledCard: string) => void; +} + +export default function ClusterSysInfos({ expandedCards, sysInfoData, toggleCard }: Props) { + const mainCardName = 'System'; + return ( +
    + +
  • + {translate('system.application_nodes_title')} +
  • + {sortBy(getAppNodes(sysInfoData), 'name').map(node => ( + + ))} +
  • {translate('system.search_nodes_title')}
  • + {sortBy(getSearchNodes(sysInfoData), 'name').map(node => ( + + ))} +
+ ); +} diff --git a/server/sonar-web/src/main/js/apps/system/components/PageActions.tsx b/server/sonar-web/src/main/js/apps/system/components/PageActions.tsx new file mode 100644 index 000000000000..1b2ef92d34ad --- /dev/null +++ b/server/sonar-web/src/main/js/apps/system/components/PageActions.tsx @@ -0,0 +1,163 @@ +/* + * 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. + */ +import * as React from 'react'; +import ChangeLogLevelForm from './ChangeLogLevelForm'; +import RestartForm from '../../../components/common/RestartForm'; +import { getBaseUrl } from '../../../helpers/urls'; +import { translate } from '../../../helpers/l10n'; + +interface Props { + canDownloadLogs: boolean; + canRestart: boolean; + cluster: boolean; + logLevel: string; +} + +interface State { + logLevel: string; + openLogsLevelForm: boolean; + openRestartForm: boolean; +} + +export default class PageActions extends React.PureComponent { + constructor(props: Props) { + super(props); + this.state = { + openLogsLevelForm: false, + openRestartForm: false, + logLevel: props.logLevel + }; + } + + componentWillReceiveProps(nextProps: Props) { + if (nextProps.logLevel !== this.state.logLevel) { + this.setState({ logLevel: nextProps.logLevel }); + } + } + + handleLogsLevelOpen = (event: React.SyntheticEvent) => { + event.preventDefault(); + this.setState({ openLogsLevelForm: true }); + }; + + handleLogsLevelChange = (logLevel: string) => { + this.setState({ logLevel }); + this.handleLogsLevelClose(); + }; + + handleLogsLevelClose = () => this.setState({ openLogsLevelForm: false }); + + handleServerRestartOpen = () => this.setState({ openRestartForm: true }); + handleServerRestartClose = () => this.setState({ openRestartForm: false }); + + render() { + const infoUrl = getBaseUrl() + '/api/system/info'; + const logsUrl = getBaseUrl() + '/api/system/logs'; + return ( +
+ + {translate('system.logs_level')} + {':'} + {this.state.logLevel} + + + {this.props.canDownloadLogs && ( + + )} + + {translate('system.download_system_info')} + + {this.props.canRestart && ( + + )} + {this.props.canRestart && + this.state.openRestartForm && } + {this.state.openLogsLevelForm && ( + + )} +
+ ); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/Monitor.java b/server/sonar-web/src/main/js/apps/system/components/PageHeader.tsx similarity index 52% rename from server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/Monitor.java rename to server/sonar-web/src/main/js/apps/system/components/PageHeader.tsx index 48ae159208b2..c9d84569e302 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/Monitor.java +++ b/server/sonar-web/src/main/js/apps/system/components/PageHeader.tsx @@ -17,25 +17,34 @@ * 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; +import * as React from 'react'; +import PageActions from './PageActions'; +import { translate } from '../../../helpers/l10n'; -import java.util.Map; -import org.sonar.api.server.ServerSide; -import org.sonar.server.platform.ws.InfoAction; - -/** - * Any component that is involved in the information returned by the web service api/system/info - */ -@ServerSide -public interface Monitor { - /** - * Name of section in System Info page - */ - String name(); +interface Props { + isCluster: boolean; + loading: boolean; + logLevel: string; + showActions: boolean; +} - /** - * Type of attribute values must be supported by {@link org.sonar.api.utils.text.JsonWriter#valueObject(Object)} - * because of JSON export by {@link InfoAction}. - */ - Map attributes(); +export default function PageHeader({ isCluster, loading, logLevel, showActions }: Props) { + return ( +
+

{translate('system_info.page')}

+ {showActions && ( + + )} + {loading && ( +
+ +
+ )} +
+ ); } diff --git a/server/sonar-web/src/main/js/apps/system/components/StandAloneSysInfos.tsx b/server/sonar-web/src/main/js/apps/system/components/StandAloneSysInfos.tsx new file mode 100644 index 000000000000..2a142c58a0ef --- /dev/null +++ b/server/sonar-web/src/main/js/apps/system/components/StandAloneSysInfos.tsx @@ -0,0 +1,30 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; + +interface Props { + sysInfoData: object; +} + +export default class StandAloneSysInfos extends React.PureComponent { + render() { + return
StandAloneSysInfos
; + } +} diff --git a/server/sonar-web/src/main/js/apps/system/components/__tests__/ChangeLogLevelForm-test.tsx b/server/sonar-web/src/main/js/apps/system/components/__tests__/ChangeLogLevelForm-test.tsx new file mode 100644 index 000000000000..08c177e418b3 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/system/components/__tests__/ChangeLogLevelForm-test.tsx @@ -0,0 +1,38 @@ +/* + * 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. + */ +import * as React from 'react'; +import { shallow } from 'enzyme'; +import ChangeLogLevelForm from '../ChangeLogLevelForm'; + +it('should render correctly', () => { + expect( + shallow( + {}} onClose={() => {}} /> + ) + ).toMatchSnapshot(); +}); + +it('should display some warning messages for non INFO levels', () => { + expect( + shallow( + {}} onClose={() => {}} /> + ) + ).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/system/components/__tests__/ClusterSysInfos-test.tsx b/server/sonar-web/src/main/js/apps/system/components/__tests__/ClusterSysInfos-test.tsx new file mode 100644 index 000000000000..5b703a1a214d --- /dev/null +++ b/server/sonar-web/src/main/js/apps/system/components/__tests__/ClusterSysInfos-test.tsx @@ -0,0 +1,47 @@ +/* + * 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. + */ +import * as React from 'react'; +import { shallow } from 'enzyme'; +import ClusterSysInfos from '../ClusterSysInfos'; +import { HealthType, SysInfo } from '../../../../api/system'; + +const sysInfoData: SysInfo = { + Cluster: true, + Health: HealthType.RED, + Name: 'Foo', + 'Health Causes': [{ message: 'Database down' }], + 'Application Nodes': [{ Name: 'Bar', Health: HealthType.GREEN, 'Health Causes': [] }], + 'Search Nodes': [{ Name: 'Baz', Health: HealthType.YELLOW, 'Health Causes': [] }] +}; + +it('should render correctly', () => { + expect(getWrapper()).toMatchSnapshot(); +}); + +function getWrapper(props = {}) { + return shallow( + {}} + {...props} + /> + ); +} diff --git a/server/sonar-web/src/main/js/apps/system/components/__tests__/PageActions-test.tsx b/server/sonar-web/src/main/js/apps/system/components/__tests__/PageActions-test.tsx new file mode 100644 index 000000000000..098e897ec921 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/system/components/__tests__/PageActions-test.tsx @@ -0,0 +1,55 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import { shallow } from 'enzyme'; +import PageActions from '../PageActions'; +import { click } from '../../../../helpers/testUtils'; + +it('should render correctly', () => { + expect( + shallow( + + ) + ).toMatchSnapshot(); +}); + +it('should render without restart and log download', () => { + expect( + shallow( + + ) + ).toMatchSnapshot(); +}); + +it('should open restart modal', () => { + const wrapper = shallow( + + ); + click(wrapper.find('#restart-server-button')); + expect(wrapper.find('RestartForm')).toHaveLength(1); +}); + +it('should open change log level modal', () => { + const wrapper = shallow( + + ); + click(wrapper.find('#edit-logs-level-button')); + expect(wrapper.find('ChangeLogLevelForm')).toHaveLength(1); +}); diff --git a/server/sonar-web/src/main/js/apps/system/components/__tests__/PageHeader-test.tsx b/server/sonar-web/src/main/js/apps/system/components/__tests__/PageHeader-test.tsx new file mode 100644 index 000000000000..f5255c7228ed --- /dev/null +++ b/server/sonar-web/src/main/js/apps/system/components/__tests__/PageHeader-test.tsx @@ -0,0 +1,34 @@ +/* + * 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. + */ +import * as React from 'react'; +import { shallow } from 'enzyme'; +import PageHeader from '../PageHeader'; + +it('should render correctly', () => { + expect( + shallow() + ).toMatchSnapshot(); +}); + +it('should show a loading spinner and no actions', () => { + expect( + shallow() + ).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/ChangeLogLevelForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/ChangeLogLevelForm-test.tsx.snap new file mode 100644 index 000000000000..04cfc11d49b7 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/ChangeLogLevelForm-test.tsx.snap @@ -0,0 +1,194 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should display some warning messages for non INFO levels 1`] = ` + +
+
+

+ system.set_log_level +

+
+
+

+ + INFO +

+

+ + DEBUG +

+

+ + TRACE +

+
+ Foo +
+
+ system.log_level.warning +
+
+
+ + + cancel + +
+
+
+`; + +exports[`should render correctly 1`] = ` + +
+
+

+ system.set_log_level +

+
+
+

+ + INFO +

+

+ + DEBUG +

+

+ + TRACE +

+
+ Foo +
+
+
+ + + cancel + +
+
+
+`; diff --git a/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/ClusterSysInfos-test.tsx.snap b/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/ClusterSysInfos-test.tsx.snap new file mode 100644 index 000000000000..20ed8d541f6e --- /dev/null +++ b/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/ClusterSysInfos-test.tsx.snap @@ -0,0 +1,59 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly 1`] = ` +
    + +
  • + system.application_nodes_title +
  • + +
  • + system.search_nodes_title +
  • + +
+`; diff --git a/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/PageActions-test.tsx.snap b/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/PageActions-test.tsx.snap new file mode 100644 index 000000000000..9d84700539f7 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/PageActions-test.tsx.snap @@ -0,0 +1,126 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly 1`] = ` + +`; + +exports[`should render without restart and log download 1`] = ` +
+ + system.logs_level + : + + INFO + + + + + system.download_system_info + +
+`; diff --git a/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/PageHeader-test.tsx.snap b/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/PageHeader-test.tsx.snap new file mode 100644 index 000000000000..2945b6df9327 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/system/components/__tests__/__snapshots__/PageHeader-test.tsx.snap @@ -0,0 +1,38 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly 1`] = ` +
+

+ system_info.page +

+ +
+`; + +exports[`should show a loading spinner and no actions 1`] = ` +
+

+ system_info.page +

+
+ +
+
+`; diff --git a/server/sonar-web/src/main/js/apps/system/components/info-items/HealthCard.tsx b/server/sonar-web/src/main/js/apps/system/components/info-items/HealthCard.tsx new file mode 100644 index 000000000000..b45205dccbab --- /dev/null +++ b/server/sonar-web/src/main/js/apps/system/components/info-items/HealthCard.tsx @@ -0,0 +1,84 @@ +/* + * 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. + */ +import * as React from 'react'; +import * as classNames from 'classnames'; +import { map } from 'lodash'; +import HealthItem from './HealthItem'; +import OpenCloseIcon from '../../../../components/icons-components/OpenCloseIcon'; +import Section from './Section'; +import { HealthType, HealthCause, SysValueObject } from '../../../../api/system'; +import { groupSections } from '../../utils'; + +interface Props { + biggerHealth?: boolean; + health: HealthType; + healthCauses: HealthCause[]; + onClick: (toggledCard: string) => void; + open: boolean; + name: string; + sysInfoData: SysValueObject; +} + +interface State { + hoveringDetail: boolean; +} + +export default class HealthCard extends React.PureComponent { + state: State = { hoveringDetail: false }; + + handleClick = () => this.props.onClick(this.props.name); + onDetailEnter = () => this.setState({ hoveringDetail: true }); + onDetailLeave = () => this.setState({ hoveringDetail: false }); + + render() { + const { open, sysInfoData } = this.props; + const { mainSection, sections } = groupSections(sysInfoData); + const showFields = open && mainSection && Object.keys(mainSection).length > 0; + const showSections = open && sections; + return ( +
  • +
    + + + {this.props.name} + + +
    + {open && ( +
    + {showFields &&
    } + {showSections && + map(sections, (section, name) =>
    )} +
    + )} +
  • + ); + } +} diff --git a/server/sonar-web/src/main/js/apps/system/components/info-items/HealthCauseItem.tsx b/server/sonar-web/src/main/js/apps/system/components/info-items/HealthCauseItem.tsx new file mode 100644 index 000000000000..49b22b717bb8 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/system/components/info-items/HealthCauseItem.tsx @@ -0,0 +1,41 @@ +/* + * 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. + */ +import * as React from 'react'; +import * as classNames from 'classnames'; +import { HealthCause, HealthType } from '../../../../api/system'; + +interface Props { + className?: string; + health: HealthType; + healthCause: HealthCause; +} + +export default function HealthCauseItem({ className, health, healthCause }: Props) { + return ( + + {healthCause.message} + + ); +} diff --git a/server/sonar-web/src/main/js/apps/system/components/info-items/HealthItem.tsx b/server/sonar-web/src/main/js/apps/system/components/info-items/HealthItem.tsx new file mode 100644 index 000000000000..a0793a818311 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/system/components/info-items/HealthItem.tsx @@ -0,0 +1,42 @@ +/* + * 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. + */ +import * as React from 'react'; +import * as classNames from 'classnames'; +import HealthCauseItem from './HealthCauseItem'; +import { HealthType, HealthCause } from '../../../../api/system'; + +interface Props { + className?: string; + health: HealthType; + healthCauses?: HealthCause[]; +} + +export default function HealthItem({ className, health, healthCauses }: Props) { + const hasHealthCauses = healthCauses && healthCauses.length > 0 && health !== HealthType.GREEN; + return ( +
    + {hasHealthCauses && + healthCauses!.map((cause, idx) => ( + + ))} + +
    + ); +} diff --git a/server/sonar-web/src/main/js/apps/system/components/info-items/Section.tsx b/server/sonar-web/src/main/js/apps/system/components/info-items/Section.tsx new file mode 100644 index 000000000000..3da339cbdf2e --- /dev/null +++ b/server/sonar-web/src/main/js/apps/system/components/info-items/Section.tsx @@ -0,0 +1,52 @@ +/* + * 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. + */ +import * as React from 'react'; +import { map } from 'lodash'; +import SysInfoItem from './SysInfoItem'; +import { SysValueObject } from '../../../../api/system'; + +interface Props { + name?: string; + items: SysValueObject; +} + +export default function Section({ name, items }: Props) { + return ( +
    + {name &&

    {name}

    } + + + {map(items, (value, name) => { + return ( + + + + + ); + })} + +
    +
    {name}
    +
    + +
    +
    + ); +} diff --git a/server/sonar-web/src/main/js/apps/system/components/info-items/SysInfoItem.tsx b/server/sonar-web/src/main/js/apps/system/components/info-items/SysInfoItem.tsx new file mode 100644 index 000000000000..12876d75940d --- /dev/null +++ b/server/sonar-web/src/main/js/apps/system/components/info-items/SysInfoItem.tsx @@ -0,0 +1,71 @@ +/* + * 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. + */ +import * as React from 'react'; +import { map } from 'lodash'; +import HealthItem from './HealthItem'; +import { HealthType, SysValue, SysValueObject } from '../../../../api/system'; +import { HEALTH_FIELD } from '../../utils'; + +interface Props { + name: string; + value: SysValue; +} + +export default function SysInfoItem({ name, value }: Props): JSX.Element { + if (name === HEALTH_FIELD) { + return ; + } + if (value instanceof Array) { + return {JSON.stringify(value)}; + } + switch (typeof value) { + case 'boolean': + return ; + case 'object': + return ; + default: + return {value}; + } +} + +function BooleanItem({ value }: { value: boolean }) { + if (value) { + return ; + } else { + return ; + } +} + +function ObjectItem({ value }: { value: SysValueObject }) { + return ( + + + {map(value, (value, name) => ( + + + + + ))} + +
    {name} + +
    + ); +} diff --git a/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/HealthCard-test.tsx b/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/HealthCard-test.tsx new file mode 100644 index 000000000000..c080f444decf --- /dev/null +++ b/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/HealthCard-test.tsx @@ -0,0 +1,64 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import { shallow } from 'enzyme'; +import HealthCard from '../HealthCard'; +import { click } from '../../../../../helpers/testUtils'; +import { HealthType } from '../../../../../api/system'; + +it('should render correctly', () => { + expect(getShallowWrapper()).toMatchSnapshot(); +}); + +it('should display the sysinfo detail', () => { + expect(getShallowWrapper({ biggerHealth: true, open: true })).toMatchSnapshot(); +}); + +it('should show the sysinfo detail when the card is clicked', () => { + const onClick = jest.fn(); + click(getShallowWrapper({ onClick }).find('.boxed-group-header')); + expect(onClick).toBeCalled(); + expect(onClick).toBeCalledWith('Foobar'); +}); + +it('should show a main section and multiple sub sections', () => { + const sysInfoData = { + Name: 'foo', + bar: 'Bar', + Database: { db: 'test' }, + Elasticseach: { Elastic: 'search' } + }; + expect(getShallowWrapper({ open: true, sysInfoData })).toMatchSnapshot(); +}); + +function getShallowWrapper(props = {}) { + return shallow( + {}} + open={false} + sysInfoData={{}} + {...props} + /> + ); +} diff --git a/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/HealthCauseItem-test.tsx b/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/HealthCauseItem-test.tsx new file mode 100644 index 000000000000..24504f09a913 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/HealthCauseItem-test.tsx @@ -0,0 +1,32 @@ +/* + * 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. + */ +import * as React from 'react'; +import { shallow } from 'enzyme'; +import HealthCauseItem from '../HealthCauseItem'; +import { HealthType } from '../../../../../api/system'; + +it('should render correctly', () => { + expect( + shallow() + ).toMatchSnapshot(); + expect( + shallow() + ).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/HealthItem-test.tsx b/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/HealthItem-test.tsx new file mode 100644 index 000000000000..f77622b9e92b --- /dev/null +++ b/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/HealthItem-test.tsx @@ -0,0 +1,47 @@ +/* + * 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. + */ +import * as React from 'react'; +import { shallow } from 'enzyme'; +import HealthItem from '../HealthItem'; +import { HealthType } from '../../../../../api/system'; + +it('should render correctly', () => { + expect( + shallow() + ).toMatchSnapshot(); +}); + +it('should not render health causes', () => { + expect( + shallow() + ).toMatchSnapshot(); + expect(shallow()).toMatchSnapshot(); +}); + +it('should render multiple health causes', () => { + expect( + shallow( + + ) + ).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/Section-test.tsx b/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/Section-test.tsx new file mode 100644 index 000000000000..2778dc855b87 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/Section-test.tsx @@ -0,0 +1,32 @@ +/* + * 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. + */ +import * as React from 'react'; +import { shallow } from 'enzyme'; +import Section from '../Section'; + +it('should render correctly', () => { + expect( + shallow(
    ) + ).toMatchSnapshot(); +}); + +it('should not render a title', () => { + expect(shallow(
    )).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/SysInfoItem-test.tsx b/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/SysInfoItem-test.tsx new file mode 100644 index 000000000000..2761a2ddbf98 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/SysInfoItem-test.tsx @@ -0,0 +1,60 @@ +/* + * 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. + */ +import * as React from 'react'; +import { shallow, mount } from 'enzyme'; +import SysInfoItem from '../SysInfoItem'; + +it('should render string', () => { + const wrapper = shallow(); + expect(wrapper.find('code').text()).toBe('/some/path/as/an/example'); +}); + +it('should render object', () => { + const wrapper = shallow(); + expect(wrapper.find('ObjectItem').prop('value')).toEqual({ bar: 'baz' }); +}); + +it('should render boolean', () => { + const wrapper = shallow(); + expect(wrapper.find('BooleanItem').prop('value')).toBe(true); +}); + +it('should render health item', () => { + const wrapper = shallow(); + expect(wrapper.find('HealthItem').prop('health')).toBe('GREEN'); +}); + +it('should render object correctly', () => { + expect( + mount( + + ).find('ObjectItem') + ).toMatchSnapshot(); +}); + +it('should render `true`', () => { + const wrapper = mount(); + expect(wrapper.find('.icon-check')).toHaveLength(1); +}); + +it('should render `false`', () => { + const wrapper = mount(); + expect(wrapper.find('.icon-delete')).toHaveLength(1); +}); diff --git a/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/HealthCard-test.tsx.snap b/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/HealthCard-test.tsx.snap new file mode 100644 index 000000000000..724001990fa9 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/HealthCard-test.tsx.snap @@ -0,0 +1,132 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should display the sysinfo detail 1`] = ` +
  • +
    + + + Foobar + + +
    +
    +
  • +`; + +exports[`should render correctly 1`] = ` +
  • +
    + + + Foobar + + +
    +
  • +`; + +exports[`should show a main section and multiple sub sections 1`] = ` +
  • +
    + + + Foobar + + +
    +
    +
    +
    +
    +
    +
  • +`; diff --git a/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/HealthCauseItem-test.tsx.snap b/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/HealthCauseItem-test.tsx.snap new file mode 100644 index 000000000000..3202387cf1c7 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/HealthCauseItem-test.tsx.snap @@ -0,0 +1,17 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly 1`] = ` + + foo + +`; + +exports[`should render correctly 2`] = ` + + foo + +`; diff --git a/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/HealthItem-test.tsx.snap b/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/HealthItem-test.tsx.snap new file mode 100644 index 000000000000..d9fa53a29564 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/HealthItem-test.tsx.snap @@ -0,0 +1,68 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should not render health causes 1`] = ` +
    + +
    +`; + +exports[`should not render health causes 2`] = ` +
    + +
    +`; + +exports[`should render correctly 1`] = ` +
    + + +
    +`; + +exports[`should render multiple health causes 1`] = ` +
    + + + +
    +`; diff --git a/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/Section-test.tsx.snap b/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/Section-test.tsx.snap new file mode 100644 index 000000000000..20fce92a9b79 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/Section-test.tsx.snap @@ -0,0 +1,125 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should not render a title 1`] = ` +
    + + + + + + + +
    +
    + foo +
    +
    + +
    +
    +`; + +exports[`should render correctly 1`] = ` +
    +

    + foo +

    + + + + + + + + + + + + + + + +
    +
    + foo +
    +
    + +
    +
    + bar +
    +
    + +
    +
    + baz +
    +
    + +
    +
    +`; diff --git a/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/SysInfoItem-test.tsx.snap b/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/SysInfoItem-test.tsx.snap new file mode 100644 index 000000000000..a1ff67c35061 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/system/components/info-items/__tests__/__snapshots__/SysInfoItem-test.tsx.snap @@ -0,0 +1,180 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render object correctly 1`] = ` +Array [ + + + + + + + + + + + + + + + + +
    + foo + + + + Far + + +
    + bar + + + + + + + + + + + + + + +
    + a + + + + 1 + + +
    + b + + + + b + + +
    +
    +
    +
    + baz + + + + + + +
    +
    , + + + + + + + + + + + + +
    + a + + + + 1 + + +
    + b + + + + b + + +
    +
    , +] +`; diff --git a/server/sonar-web/src/main/js/apps/system/main.js b/server/sonar-web/src/main/js/apps/system/main.js index 411170e6f757..150bd042adf7 100644 --- a/server/sonar-web/src/main/js/apps/system/main.js +++ b/server/sonar-web/src/main/js/apps/system/main.js @@ -23,7 +23,7 @@ import { sortBy } from 'lodash'; import { getSystemInfo } from '../../api/system'; import Section from './section'; import { translate } from '../../helpers/l10n'; -import RestartModal from '../../components/RestartModal'; +import RestartForm from '../../components/common/RestartForm'; const SECTIONS_ORDER = [ 'SonarQube', @@ -38,6 +38,8 @@ const SECTIONS_ORDER = [ ]; export default class Main extends React.PureComponent { + state = { openRestartForm: false }; + componentDidMount() { getSystemInfo().then(info => this.setState({ sections: this.parseSections(info) })); } @@ -60,9 +62,8 @@ export default class Main extends React.PureComponent { orderItems = items => sortBy(items, 'name'); - handleServerRestart = () => { - new RestartModal().render(); - }; + handleServerRestartOpen = () => this.setState({ openRestartForm: true }); + handleServerRestartClose = () => this.setState({ openRestartForm: false }); render() { let sections = null; @@ -113,9 +114,10 @@ export default class Main extends React.PureComponent { + {this.state.openRestartForm && } {sections} diff --git a/server/sonar-web/src/main/js/apps/system/routes.ts b/server/sonar-web/src/main/js/apps/system/routes.ts index 9f7f40c4cd2d..1a8d6a5ab38c 100644 --- a/server/sonar-web/src/main/js/apps/system/routes.ts +++ b/server/sonar-web/src/main/js/apps/system/routes.ts @@ -17,12 +17,18 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { RouterState, IndexRouteProps } from 'react-router'; +import { RouterState, RouteComponent, IndexRouteProps } from 'react-router'; const routes = [ { getIndexRoute(_: RouterState, callback: (err: any, route: IndexRouteProps) => any) { - import('./main').then(i => callback(null, { component: (i as any).default })); + import('./components/App').then(i => callback(null, { component: i.default })); + } + }, + { + path: 'old', + getComponent(_: RouterState, callback: (err: any, component: RouteComponent) => any) { + import('./main').then(i => callback(null, (i as any).default)); } } ]; diff --git a/server/sonar-web/src/main/js/apps/system/styles.css b/server/sonar-web/src/main/js/apps/system/styles.css new file mode 100644 index 000000000000..f1156609add2 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/system/styles.css @@ -0,0 +1,81 @@ +.system-info-health-title { + margin-top: 24px; + margin-bottom: 16px; +} + +.system-info-health-card { + margin-bottom: 8px; + transition: border-color 0.3s ease; +} + +.system-info-health-card:not(.no-hover):hover { + border-color: #4b9fd5; +} + +.system-info-health-card:not(.no-hover):hover .system-info-health-card-title { + color: #4b9fd5; +} + +.system-info-health-card .boxed-group-header { + cursor: pointer; + padding-bottom: 15px; +} + +.system-info-health-card .boxed-group-inner { + padding-top: 0; +} + +.system-info-health-card-title { + font-weight: bold; +} + +.system-info-health-info { + margin-top: -4px; +} + +.system-info-health-dot { + display: inline-block; + width: 16px; + height: 16px; + margin: 4px; + border-radius: 16px; + box-sizing: border-box; +} + +.system-info-health-dot.GREEN { + background-color: #00aa00; +} + +.system-info-health-dot.YELLOW { + background-color: #eabe06; +} +.system-info-health-dot.RED { + background-color: #d4333f; +} + +.system-info-health-info .alert { + display: inline-block; + position: relative; + top: -8px; +} + +.system-info-health-info.big-dot .system-info-health-dot { + width: 24px; + height: 24px; + margin: 0; + border-radius: 24px; +} + +.system-info-health-info.no-margin .system-info-health-dot { + margin: 0; +} + +.system-info-section ~ .system-info-section { + margin-top: 16px; +} + +.system-info-section-item-name { + width: 25vw; + overflow: hidden; + text-overflow: ellipsis; +} diff --git a/server/sonar-web/src/main/js/apps/system/utils.ts b/server/sonar-web/src/main/js/apps/system/utils.ts new file mode 100644 index 000000000000..41ad5223544e --- /dev/null +++ b/server/sonar-web/src/main/js/apps/system/utils.ts @@ -0,0 +1,99 @@ +/* + * 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. + */ +import { each, omit, memoize } from 'lodash'; +import { + cleanQuery, + parseAsArray, + parseAsString, + RawQuery, + serializeStringArray +} from '../../helpers/query'; +import { + HealthCause, + HealthType, + NodeInfo, + SysInfo, + SysInfoSection, + SysValueObject +} from '../../api/system'; + +export interface Query { + expandedCards: string[]; +} + +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]); +} + +export function getAppNodes(sysInfoData: SysInfo): NodeInfo[] { + return sysInfoData['Application Nodes']; +} + +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 getMainCardSection(sysInfoData: SysInfo): SysValueObject { + return omit(sysInfoData, ['Application Nodes', 'Search Nodes', 'Settings', 'Statistics']); +} + +export function getNodeName(nodeInfo: NodeInfo): string { + return nodeInfo['Name']; +} + +export function getSearchNodes(sysInfoData: SysInfo): NodeInfo[] { + return sysInfoData['Search Nodes']; +} + +export function groupSections(sysInfoData: SysValueObject) { + let mainSection: SysValueObject = {}; + let sections: SysInfoSection = {}; + each(sysInfoData, (item, key) => { + if (typeof item !== 'object' || item instanceof Array) { + mainSection[key] = item; + } else { + sections[key] = item; + } + }); + return { mainSection, sections }; +} + +export function isCluster(sysInfoData?: SysInfo): boolean { + return sysInfoData != undefined && sysInfoData['Cluster']; +} + +export const parseQuery = memoize((urlQuery: RawQuery): Query => { + return { + expandedCards: parseAsArray(urlQuery.expand, parseAsString) + }; +}); + +export const serializeQuery = memoize((query: Query): RawQuery => { + return cleanQuery({ + expand: serializeStringArray(query.expandedCards) + }); +}); diff --git a/server/sonar-web/src/main/js/components/common/RestartForm.tsx b/server/sonar-web/src/main/js/components/common/RestartForm.tsx new file mode 100644 index 000000000000..43e12ab31f17 --- /dev/null +++ b/server/sonar-web/src/main/js/components/common/RestartForm.tsx @@ -0,0 +1,93 @@ +/* + * 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. + */ +import * as React from 'react'; +import * as classNames from 'classnames'; +import Modal from 'react-modal'; +import { restartAndWait } from '../../api/system'; +import { translate } from '../../helpers/l10n'; + +interface Props { + onClose: () => void; +} + +interface State { + restarting: boolean; +} + +export default class RestartForm extends React.PureComponent { + state: State = { restarting: false }; + + handleCancelClick = (event: React.SyntheticEvent) => { + event.preventDefault(); + this.props.onClose(); + }; + + handleFormSubmit = (event: React.SyntheticEvent) => { + event.preventDefault(); + if (!this.state.restarting) { + this.setState({ restarting: true }); + restartAndWait().then( + () => document.location.reload(), + () => this.setState({ restarting: false }) + ); + } + }; + + render() { + const { restarting } = this.state; + const header = translate('system.restart_server'); + return ( + +
    +
    +

    {header}

    +
    +
    +

    + {translate(restarting ? 'system.is_restarting' : 'system.are_you_sure_to_restart')} +

    + {restarting && ( +

    + +

    + )} +
    + {!restarting && ( +
    + + + {translate('cancel')} + +
    + )} + +
    + ); + } +} diff --git a/server/sonar-web/src/main/js/components/facet/FacetHeader.js b/server/sonar-web/src/main/js/components/facet/FacetHeader.js index 7950444a2e0f..befa67b9223b 100644 --- a/server/sonar-web/src/main/js/components/facet/FacetHeader.js +++ b/server/sonar-web/src/main/js/components/facet/FacetHeader.js @@ -20,8 +20,9 @@ // @flow /* eslint-disable max-len */ import React from 'react'; -import Tooltip from '../controls/Tooltip'; +import OpenCloseIcon from '../icons-components/OpenCloseIcon'; import HelpIcon from '../icons-components/HelpIcon'; +import Tooltip from '../controls/Tooltip'; import { translate } from '../../helpers/l10n'; /*:: @@ -58,29 +59,6 @@ export default class FacetHeader extends React.PureComponent { } }; - renderCheckbox() { - return ( - - {this.props.open ? ( - - ) : ( - - )} - - ); - } - renderHelper() { if (!this.props.helper) { return null; @@ -119,7 +97,7 @@ export default class FacetHeader extends React.PureComponent { {this.props.onClick ? ( - {this.renderCheckbox()} + {this.props.name} {this.renderHelper()} diff --git a/server/sonar-web/src/main/js/components/facet/__tests__/__snapshots__/FacetHeader-test.js.snap b/server/sonar-web/src/main/js/components/facet/__tests__/__snapshots__/FacetHeader-test.js.snap index 7def87dae3ce..dc22f0697605 100644 --- a/server/sonar-web/src/main/js/components/facet/__tests__/__snapshots__/FacetHeader-test.js.snap +++ b/server/sonar-web/src/main/js/components/facet/__tests__/__snapshots__/FacetHeader-test.js.snap @@ -15,26 +15,10 @@ exports[`should clear 1`] = ` href="#" onClick={[Function]} > - - - + open={false} + /> foo - - - + open={false} + /> foo - - - + open={false} + /> foo @@ -130,26 +82,10 @@ exports[`should render open facet with value 1`] = ` href="#" onClick={[Function]} > - - - + open={true} + /> foo @@ -165,26 +101,10 @@ exports[`should render open facet without value 1`] = ` href="#" onClick={[Function]} > - - - + open={true} + /> foo diff --git a/server/sonar-web/src/main/js/components/icons-components/OpenCloseIcon.tsx b/server/sonar-web/src/main/js/components/icons-components/OpenCloseIcon.tsx new file mode 100644 index 000000000000..739bc6027e7c --- /dev/null +++ b/server/sonar-web/src/main/js/components/icons-components/OpenCloseIcon.tsx @@ -0,0 +1,44 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; + +interface Props { + className?: string; + open: boolean; + size?: number; +} + +export default function OpenCloseIcon({ className, open, size = 14 }: Props) { + return ( + + {open ? ( + + ) : ( + + )} + + ); +} diff --git a/server/sonar-web/src/main/js/components/icons-components/icons.js b/server/sonar-web/src/main/js/components/icons-components/icons.js index 890a7c87ebce..c50065921f44 100644 --- a/server/sonar-web/src/main/js/components/icons-components/icons.js +++ b/server/sonar-web/src/main/js/components/icons-components/icons.js @@ -30,6 +30,7 @@ import _HelpIcon from './HelpIcon'; import _HistoryIcon from './HistoryIcon'; import _LinkIcon from './LinkIcon'; import _ListIcon from './ListIcon'; +import _OpenCloseIcon from './OpenCloseIcon'; import _OrganizationIcon from './OrganizationIcon'; import _ProjectEventIcon from './ProjectEventIcon'; import _QualifierIcon from './QualifierIcon'; @@ -51,6 +52,7 @@ export const HelpIcon = _HelpIcon; export const HistoryIcon = _HistoryIcon; export const LinkIcon = _LinkIcon; export const ListIcon = _ListIcon; +export const OpenCloseIcon = _OpenCloseIcon; export const OrganizationIcon = _OrganizationIcon; export const ProjectEventIcon = _ProjectEventIcon; export const QualifierIcon = _QualifierIcon; diff --git a/server/sonar-web/src/main/js/helpers/urls.ts b/server/sonar-web/src/main/js/helpers/urls.ts index 8deba26fcad3..773448fad23b 100644 --- a/server/sonar-web/src/main/js/helpers/urls.ts +++ b/server/sonar-web/src/main/js/helpers/urls.ts @@ -31,21 +31,23 @@ interface Location { query?: Query; } +export function getBaseUrl(): string { + return (window as any).baseUrl; +} + /** * Generate URL for a component's home page */ export function getComponentUrl(componentKey: string, branch?: string): string { const branchQuery = branch ? `&branch=${encodeURIComponent(branch)}` : ''; - return ( - (window as any).baseUrl + '/dashboard?id=' + encodeURIComponent(componentKey) + branchQuery - ); + return getBaseUrl() + '/dashboard?id=' + encodeURIComponent(componentKey) + branchQuery; } export function getProjectUrl(key: string, branch?: string): Location { return { pathname: '/dashboard', query: { id: key, branch } }; } -export function getProjectBranchUrl(key: string, branch: Branch) { +export function getProjectBranchUrl(key: string, branch: Branch): Location { if (isShortLivingBranch(branch)) { return { pathname: '/project/issues', @@ -74,13 +76,17 @@ export function getComponentIssuesUrl(componentKey: string, query?: Query): Loca export function getComponentIssuesUrlAsString(componentKey: string, query?: Query): string { const path = getComponentIssuesUrl(componentKey, query); - return `${(window as any).baseUrl}${path.pathname}?${stringify(path.query)}`; + return `${getBaseUrl()}${path.pathname}?${stringify(path.query)}`; } /** * Generate URL for a component's drilldown page */ -export function getComponentDrilldownUrl(componentKey: string, metric: string, branch?: string) { +export function getComponentDrilldownUrl( + componentKey: string, + metric: string, + branch?: string +): Location { return { pathname: '/component_measures', query: { id: componentKey, metric, branch } }; } @@ -159,9 +165,9 @@ export function getDeprecatedActiveRulesUrl(query = {}, organization?: string | } export function getProjectsUrl(): string { - return (window as any).baseUrl + '/projects'; + return getBaseUrl() + '/projects'; } export function getMarkdownHelpUrl(): string { - return (window as any).baseUrl + '/markdown/help'; + return getBaseUrl() + '/markdown/help'; } diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties index aca17c1f279e..ef96a4360d58 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -146,6 +146,7 @@ remove=Remove rename=Rename reset_verb=Reset resolution=Resolution +restart=Restart restore=Restore result=Result results=Results @@ -2840,8 +2841,18 @@ background_tasks.add_more_with_governance=Add more with Governance # SYSTEM # #------------------------------------------------------------------------------ +system.application_nodes_title=Application Nodes +system.are_you_sure_to_restart=Are you sure you want to restart the server? +system.cluster_log_level.info=Changes apply to all Application nodes but not to Search nodes. +system.download_logs=Download Logs +system.download_system_info=Download System Info +system.is_restarting=Server is restarting. This page will be automatically refreshed. system.log_level.warning=Current level has performance impacts, please make sure to get back to INFO level once your investigation is done. Please note that when the server is restarted, logging will revert to the level configured in sonar.properties. - +system.log_level.info=Changes don't apply to Search. +system.logs_level=Logs level +system.restart_server=Restart Server +system.search_nodes_title=Search Nodes +system.set_log_level=Set logs level #------------------------------------------------------------------------------