Check the health status of Sentinel DataSource using AbstractDataSource#loadConfig.

pull/671/head
cdfive 6 years ago
parent 3e2fa1b903
commit 584d5f4107

@ -3,6 +3,10 @@ server.port=18083
management.endpoints.web.exposure.include=* management.endpoints.web.exposure.include=*
management.endpoint.health.show-details=always management.endpoint.health.show-details=always
# we can disable health check, default is enable
management.health.diskspace.enabled=false
# management.health.sentinel.enabled=false
spring.cloud.sentinel.transport.dashboard=localhost:8080 spring.cloud.sentinel.transport.dashboard=localhost:8080
spring.cloud.sentinel.eager=true spring.cloud.sentinel.eager=true

@ -16,6 +16,7 @@
package org.springframework.cloud.alibaba.sentinel.endpoint; package org.springframework.cloud.alibaba.sentinel.endpoint;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnEnabledEndpoint; import org.springframework.boot.actuate.autoconfigure.endpoint.condition.ConditionalOnEnabledEndpoint;
import org.springframework.boot.actuate.autoconfigure.health.ConditionalOnEnabledHealthIndicator; import org.springframework.boot.actuate.autoconfigure.health.ConditionalOnEnabledHealthIndicator;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint; import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
@ -42,7 +43,7 @@ public class SentinelEndpointAutoConfiguration {
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
@ConditionalOnEnabledHealthIndicator("sentinel") @ConditionalOnEnabledHealthIndicator("sentinel")
public SentinelHealthIndicator sentinelHealthIndicator(SentinelProperties sentinelProperties) { public SentinelHealthIndicator sentinelHealthIndicator(DefaultListableBeanFactory beanFactory, SentinelProperties sentinelProperties) {
return new SentinelHealthIndicator(sentinelProperties); return new SentinelHealthIndicator(beanFactory, sentinelProperties);
} }
} }

@ -1,8 +1,26 @@
/*
* Copyright (C) 2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.sentinel.endpoint; package org.springframework.cloud.alibaba.sentinel.endpoint;
import com.alibaba.csp.sentinel.datasource.AbstractDataSource;
import com.alibaba.csp.sentinel.heartbeat.HeartbeatSenderProvider; import com.alibaba.csp.sentinel.heartbeat.HeartbeatSenderProvider;
import com.alibaba.csp.sentinel.transport.HeartbeatSender; import com.alibaba.csp.sentinel.transport.HeartbeatSender;
import com.alibaba.csp.sentinel.transport.config.TransportConfig; import com.alibaba.csp.sentinel.transport.config.TransportConfig;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.boot.actuate.health.AbstractHealthIndicator; import org.springframework.boot.actuate.health.AbstractHealthIndicator;
import org.springframework.boot.actuate.health.Health; import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.HealthIndicator; import org.springframework.boot.actuate.health.HealthIndicator;
@ -14,21 +32,36 @@ import java.util.HashMap;
import java.util.Map; import java.util.Map;
/** /**
* {@link HealthIndicator} for Sentinel. * A {@link HealthIndicator} for Sentinel, which checks the status of
* Sentinel Dashboard and DataSource.
*
* <p> * <p>
* Check the status of Sentinel Dashboard by sending a heartbeat message to it. * Check the status of Sentinel Dashboard by sending a heartbeat message to it.
* If no Exception thrown, it's OK.
*
* Check the status of Sentinel DataSource by calling loadConfig method of {@link AbstractDataSource}.
* If return true, it's OK.
* *
* Note: if sentinel isn't enabled or sentinel-dashboard isn't configured, * If Dashboard and DataSource are both OK, the health status is UP.
* the health status is up and more infos are provided in detail. *</p>
*
* <p>
* Note:
* If Sentinel isn't enabled, the health status is up.
* If Sentinel Dashboard isn't configured, it's OK and mark the status of Dashboard with UNKNOWN.
* More informations are provided in details.
* </p> * </p>
* *
* @author cdfive * @author cdfive
*/ */
public class SentinelHealthIndicator extends AbstractHealthIndicator { public class SentinelHealthIndicator extends AbstractHealthIndicator {
private DefaultListableBeanFactory beanFactory;
private SentinelProperties sentinelProperties; private SentinelProperties sentinelProperties;
public SentinelHealthIndicator(SentinelProperties sentinelProperties) { public SentinelHealthIndicator(DefaultListableBeanFactory beanFactory, SentinelProperties sentinelProperties) {
this.beanFactory = beanFactory;
this.sentinelProperties = sentinelProperties; this.sentinelProperties = sentinelProperties;
} }
@ -45,22 +78,49 @@ public class SentinelHealthIndicator extends AbstractHealthIndicator {
detailMap.put("enabled", true); detailMap.put("enabled", true);
// Check health of Dashboard
boolean dashboardUp = true;
String consoleServer = TransportConfig.getConsoleServer(); String consoleServer = TransportConfig.getConsoleServer();
// If dashboard isn't configured, set the status to UNKNOWN
if (StringUtils.isEmpty(consoleServer)) { if (StringUtils.isEmpty(consoleServer)) {
// If Dashboard isn't configured, mark the status of Dashboard with UNKNOWN and the dashboardUp is still true
detailMap.put("dashboard", new Status(Status.UNKNOWN.getCode(), "dashboard isn't configured")); detailMap.put("dashboard", new Status(Status.UNKNOWN.getCode(), "dashboard isn't configured"));
builder.up().withDetails(detailMap); } else {
return; // If Dashboard is configured, send a heartbeat message to it and check the result
}
// If dashboard is configured, send a heartbeat message to it and check the result
HeartbeatSender heartbeatSender = HeartbeatSenderProvider.getHeartbeatSender(); HeartbeatSender heartbeatSender = HeartbeatSenderProvider.getHeartbeatSender();
boolean result = heartbeatSender.sendHeartbeat(); boolean result = heartbeatSender.sendHeartbeat();
if (result) { if (result) {
detailMap.put("dashboard", Status.UP); detailMap.put("dashboard", Status.UP);
builder.up().withDetails(detailMap);
} else { } else {
// If failed to send heartbeat message, means that the Dashboard is DOWN
dashboardUp = false;
detailMap.put("dashboard", new Status(Status.DOWN.getCode(), consoleServer + " can't be connected")); detailMap.put("dashboard", new Status(Status.DOWN.getCode(), consoleServer + " can't be connected"));
}
}
// Check health of DataSource
boolean dataSourceUp = true;
Map<String, Object> dataSourceDetailMap = new HashMap<>();
detailMap.put("dataSource", dataSourceDetailMap);
// Get all DataSources and each call loadConfig to check if it's OK
Map<String, AbstractDataSource> dataSourceMap = beanFactory.getBeansOfType(AbstractDataSource.class);
for (Map.Entry<String, AbstractDataSource> dataSourceMapEntry : dataSourceMap.entrySet()) {
String dataSourceBeanName = dataSourceMapEntry.getKey();
AbstractDataSource dataSource = dataSourceMapEntry.getValue();
try {
dataSource.loadConfig();
dataSourceDetailMap.put(dataSourceBeanName, Status.UP);
} catch (Exception e) {
// If one DataSource failed to loadConfig, means that the DataSource is DOWN
dataSourceUp = false;
dataSourceDetailMap.put(dataSourceBeanName, new Status(Status.DOWN.getCode(), e.getMessage()));
}
}
// If Dashboard and DataSource are both OK, the health status is UP
if (dashboardUp && dataSourceUp) {
builder.up().withDetails(detailMap);
} else {
builder.down().withDetails(detailMap); builder.down().withDetails(detailMap);
} }
} }

@ -88,6 +88,12 @@
"type": "java.lang.String", "type": "java.lang.String",
"defaultValue": "3", "defaultValue": "3",
"description": "sentinel the cold factor." "description": "sentinel the cold factor."
},
{
"name": "management.health.sentinel.enabled",
"type": "java.lang.Boolean",
"description": "Whether to enable sentinel health check.",
"defaultValue": true
} }
] ]
} }

@ -1,17 +1,38 @@
/*
* Copyright (C) 2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.alibaba.sentinel.endpoint; package org.springframework.cloud.alibaba.sentinel.endpoint;
import com.alibaba.csp.sentinel.config.SentinelConfig; import com.alibaba.csp.sentinel.config.SentinelConfig;
import com.alibaba.csp.sentinel.datasource.AbstractDataSource;
import com.alibaba.csp.sentinel.datasource.FileRefreshableDataSource;
import com.alibaba.csp.sentinel.heartbeat.HeartbeatSenderProvider; import com.alibaba.csp.sentinel.heartbeat.HeartbeatSenderProvider;
import com.alibaba.csp.sentinel.transport.HeartbeatSender; import com.alibaba.csp.sentinel.transport.HeartbeatSender;
import com.alibaba.csp.sentinel.transport.config.TransportConfig; import com.alibaba.csp.sentinel.transport.config.TransportConfig;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.boot.actuate.health.Health; import org.springframework.boot.actuate.health.Health;
import org.springframework.boot.actuate.health.Status; import org.springframework.boot.actuate.health.Status;
import org.springframework.cloud.alibaba.sentinel.SentinelProperties; import org.springframework.cloud.alibaba.sentinel.SentinelProperties;
import org.springframework.util.ReflectionUtils; import org.springframework.util.ReflectionUtils;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
@ -26,14 +47,17 @@ public class SentinelHealthIndicatorTests {
private SentinelHealthIndicator sentinelHealthIndicator; private SentinelHealthIndicator sentinelHealthIndicator;
private DefaultListableBeanFactory beanFactory;
private SentinelProperties sentinelProperties; private SentinelProperties sentinelProperties;
private HeartbeatSender heartbeatSender; private HeartbeatSender heartbeatSender;
@Before @Before
public void setUp() { public void setUp() {
beanFactory = mock(DefaultListableBeanFactory.class);
sentinelProperties = mock(SentinelProperties.class); sentinelProperties = mock(SentinelProperties.class);
sentinelHealthIndicator = new SentinelHealthIndicator(sentinelProperties); sentinelHealthIndicator = new SentinelHealthIndicator(beanFactory, sentinelProperties);
SentinelConfig.setConfig(TransportConfig.CONSOLE_SERVER, ""); SentinelConfig.setConfig(TransportConfig.CONSOLE_SERVER, "");
@ -64,19 +88,18 @@ public class SentinelHealthIndicatorTests {
} }
@Test @Test
public void testSentinelDashboardConfiguredCheckSuccess() throws Exception { public void testSentinelDashboardConfiguredSuccess() throws Exception {
when(sentinelProperties.isEnabled()).thenReturn(true); when(sentinelProperties.isEnabled()).thenReturn(true);
SentinelConfig.setConfig(TransportConfig.CONSOLE_SERVER, "localhost:8080"); SentinelConfig.setConfig(TransportConfig.CONSOLE_SERVER, "localhost:8080");
when(heartbeatSender.sendHeartbeat()).thenReturn(true); when(heartbeatSender.sendHeartbeat()).thenReturn(true);
Health health = sentinelHealthIndicator.health(); Health health = sentinelHealthIndicator.health();
assertThat(health.getStatus()).isEqualTo(Status.UP); assertThat(health.getStatus()).isEqualTo(Status.UP);
} }
@Test @Test
public void testSentinelDashboardConfiguredCheckFailed() throws Exception { public void testSentinelDashboardConfiguredFailed() throws Exception {
when(sentinelProperties.isEnabled()).thenReturn(true); when(sentinelProperties.isEnabled()).thenReturn(true);
SentinelConfig.setConfig(TransportConfig.CONSOLE_SERVER, "localhost:8080"); SentinelConfig.setConfig(TransportConfig.CONSOLE_SERVER, "localhost:8080");
when(heartbeatSender.sendHeartbeat()).thenReturn(false); when(heartbeatSender.sendHeartbeat()).thenReturn(false);
@ -87,4 +110,53 @@ public class SentinelHealthIndicatorTests {
assertThat(health.getStatus()).isEqualTo(Status.DOWN); assertThat(health.getStatus()).isEqualTo(Status.DOWN);
assertThat(health.getDetails().get("dashboard")).isEqualTo(new Status(Status.DOWN.getCode(), "localhost:8080 can't be connected")); assertThat(health.getDetails().get("dashboard")).isEqualTo(new Status(Status.DOWN.getCode(), "localhost:8080 can't be connected"));
} }
@Test
public void testSentinelDataSourceSuccess() throws Exception {
when(sentinelProperties.isEnabled()).thenReturn(true);
SentinelConfig.setConfig(TransportConfig.CONSOLE_SERVER, "localhost:8080");
when(heartbeatSender.sendHeartbeat()).thenReturn(true);
Map<String, AbstractDataSource> dataSourceMap = new HashMap<>();
FileRefreshableDataSource fileDataSource1 = mock(FileRefreshableDataSource.class);
dataSourceMap.put("ds1-sentinel-file-datasource", fileDataSource1);
FileRefreshableDataSource fileDataSource2 = mock(FileRefreshableDataSource.class);
dataSourceMap.put("ds2-sentinel-file-datasource", fileDataSource2);
when(beanFactory.getBeansOfType(AbstractDataSource.class)).thenReturn(dataSourceMap);
Health health = sentinelHealthIndicator.health();
assertThat(health.getStatus()).isEqualTo(Status.UP);
Map<String, Status> dataSourceDetailMap = (Map<String, Status>) health.getDetails().get("dataSource");
assertThat(dataSourceDetailMap.get("ds1-sentinel-file-datasource")).isEqualTo(Status.UP);
assertThat(dataSourceDetailMap.get("ds2-sentinel-file-datasource")).isEqualTo(Status.UP);
}
@Test
public void testSentinelDataSourceFailed() throws Exception {
when(sentinelProperties.isEnabled()).thenReturn(true);
SentinelConfig.setConfig(TransportConfig.CONSOLE_SERVER, "localhost:8080");
when(heartbeatSender.sendHeartbeat()).thenReturn(true);
Map<String, AbstractDataSource> dataSourceMap = new HashMap<>();
FileRefreshableDataSource fileDataSource1 = mock(FileRefreshableDataSource.class);
dataSourceMap.put("ds1-sentinel-file-datasource", fileDataSource1);
FileRefreshableDataSource fileDataSource2 = mock(FileRefreshableDataSource.class);
when(fileDataSource2.loadConfig()).thenThrow(new RuntimeException("fileDataSource2 error"));
dataSourceMap.put("ds2-sentinel-file-datasource", fileDataSource2);
when(beanFactory.getBeansOfType(AbstractDataSource.class)).thenReturn(dataSourceMap);
Health health = sentinelHealthIndicator.health();
assertThat(health.getStatus()).isEqualTo(Status.DOWN);
Map<String, Status> dataSourceDetailMap = (Map<String, Status>) health.getDetails().get("dataSource");
assertThat(dataSourceDetailMap.get("ds1-sentinel-file-datasource")).isEqualTo(Status.UP);
assertThat(dataSourceDetailMap.get("ds2-sentinel-file-datasource")).isEqualTo(new Status(Status.DOWN.getCode(), "fileDataSource2 error"));
}
} }

Loading…
Cancel
Save