mirror of
https://gitee.com/mirrors/Spring-Cloud-Alibaba.git
synced 2021-06-26 13:25:11 +08:00
Merge pull request #671 from cdfive/SentinelHealthIndicator
Add HealthIndicator in Sentinel Starter
This commit is contained in:
commit
24c8ddee8b
@ -128,7 +128,7 @@ Spring Boot 应用支持通过 Endpoint 来暴露相关信息,OSS Starter 也
|
||||
|
||||
Spring Boot1.x 可以通过访问 http://127.0.0.1:18084/oss 来查看 OSS Endpoint 的信息。
|
||||
|
||||
Spring Boot2.x 可以通过访问 http://127.0.0.1:18084/acutator/oss 来访问。
|
||||
Spring Boot2.x 可以通过访问 http://127.0.0.1:18084/actuator/oss 来访问。
|
||||
|
||||
Endpoint 内部会显示所有的 OSSClient 配置信息,以及该 OSSClient 对应的 Bucket 列表。
|
||||
|
||||
|
@ -114,7 +114,7 @@ You can verify results on the OSS console when you finish uploading or downloadi
|
||||
|
||||
## Endpoint
|
||||
|
||||
OSS starter also supports the implmentation of Spring Boot acutator endpoints.
|
||||
OSS starter also supports the implementation of Spring Boot actuator endpoints.
|
||||
|
||||
**Prerequisite:**
|
||||
|
||||
@ -127,7 +127,7 @@ To view the endpoint information, visit the following URLs:
|
||||
|
||||
Spring Boot1.x: OSS Endpoint URL is http://127.0.0.1:18084/oss.
|
||||
|
||||
Spring Boot2.x: OSS Endpoint URL is http://127.0.0.1:18084/acutator/oss.
|
||||
Spring Boot2.x: OSS Endpoint URL is http://127.0.0.1:18084/actuator/oss.
|
||||
|
||||
Endpoint will show the configurations and the list of buckets of all OSSClients.
|
||||
|
||||
|
@ -184,7 +184,7 @@ Spring Boot 应用支持通过 Endpoint 来暴露相关信息,Sentinel Starter
|
||||
* Spring Boot 1.x 中添加配置 `management.security.enabled=false`
|
||||
* Spring Boot 2.x 中添加配置 `management.endpoints.web.exposure.include=*`
|
||||
|
||||
Spring Boot 1.x 可以通过访问 http://127.0.0.1:18083/sentinel 来查看 Sentinel Endpoint 的信息。Spring Boot 2.x 可以通过访问 http://127.0.0.1:18083/acutator/sentinel 来访问。
|
||||
Spring Boot 1.x 可以通过访问 http://127.0.0.1:18083/sentinel 来查看 Sentinel Endpoint 的信息。Spring Boot 2.x 可以通过访问 http://127.0.0.1:18083/actuator/sentinel 来访问。
|
||||
|
||||
<p align="center"><img src="https://cdn.yuque.com/lark/0/2018/png/54319/1532084199224-1a41591d-7a06-4680-be8a-5de319ac635d.png" width="480" heigh='360' ></p>
|
||||
|
||||
|
@ -1,6 +1,12 @@
|
||||
spring.application.name=sentinel-example
|
||||
server.port=18083
|
||||
management.endpoints.web.exposure.include=*
|
||||
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.eager=true
|
||||
|
||||
@ -19,4 +25,4 @@ spring.cloud.sentinel.datasource.ds4.file.file=classpath: system.json
|
||||
spring.cloud.sentinel.datasource.ds4.file.rule-type=system
|
||||
|
||||
spring.cloud.sentinel.datasource.ds5.file.file=classpath: param-flow.json
|
||||
spring.cloud.sentinel.datasource.ds5.file.rule-type=param-flow
|
||||
spring.cloud.sentinel.datasource.ds5.file.rule-type=param_flow
|
||||
|
@ -306,7 +306,7 @@ Spring Boot 应用支持通过 Endpoint 来暴露相关信息,SMS Starter 也
|
||||
|
||||
Spring Boot1.x 可以通过访问 http://127.0.0.1:18084/sms-info 来查看 SMS Endpoint 的信息。
|
||||
|
||||
Spring Boot2.x 可以通过访问 http://127.0.0.1:18084/acutator/sms-info 来访问。
|
||||
Spring Boot2.x 可以通过访问 http://127.0.0.1:18084/actuator/sms-info 来访问。
|
||||
|
||||
Endpoint 内部会显示最近 20 条单个短信发送的记录和批量短信发送的记录,以及当前短信消息的配置信息(包括是**SmsReport** 还是 **SmsUp**,**队列名称**,以及对应的 **MessageListener** )。
|
||||
|
||||
|
@ -16,7 +16,9 @@
|
||||
|
||||
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.health.ConditionalOnEnabledHealthIndicator;
|
||||
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
@ -38,4 +40,10 @@ public class SentinelEndpointAutoConfiguration {
|
||||
return new SentinelEndpoint(sentinelProperties);
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnEnabledHealthIndicator("sentinel")
|
||||
public SentinelHealthIndicator sentinelHealthIndicator(DefaultListableBeanFactory beanFactory, SentinelProperties sentinelProperties) {
|
||||
return new SentinelHealthIndicator(beanFactory, sentinelProperties);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,132 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import com.alibaba.csp.sentinel.datasource.AbstractDataSource;
|
||||
import com.alibaba.csp.sentinel.heartbeat.HeartbeatSenderProvider;
|
||||
import com.alibaba.csp.sentinel.transport.HeartbeatSender;
|
||||
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.Health;
|
||||
import org.springframework.boot.actuate.health.HealthIndicator;
|
||||
import org.springframework.boot.actuate.health.Status;
|
||||
import org.springframework.cloud.alibaba.sentinel.SentinelProperties;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A {@link HealthIndicator} for Sentinel, which checks the status of
|
||||
* Sentinel Dashboard and DataSource.
|
||||
*
|
||||
* <p>
|
||||
* Check the status of Sentinel Dashboard by sending a heartbeat message to it.
|
||||
* If return true, it's OK.
|
||||
*
|
||||
* Check the status of Sentinel DataSource by calling loadConfig method of {@link AbstractDataSource}.
|
||||
* If no Exception thrown, it's OK.
|
||||
*
|
||||
* If Dashboard and DataSource are both OK, the health status is UP.
|
||||
*</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>
|
||||
*
|
||||
* @author cdfive
|
||||
*/
|
||||
public class SentinelHealthIndicator extends AbstractHealthIndicator {
|
||||
|
||||
private DefaultListableBeanFactory beanFactory;
|
||||
|
||||
private SentinelProperties sentinelProperties;
|
||||
|
||||
public SentinelHealthIndicator(DefaultListableBeanFactory beanFactory, SentinelProperties sentinelProperties) {
|
||||
this.beanFactory = beanFactory;
|
||||
this.sentinelProperties = sentinelProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doHealthCheck(Health.Builder builder) throws Exception {
|
||||
Map<String, Object> detailMap = new HashMap<>();
|
||||
|
||||
// If sentinel isn't enabled, set the status up and set the enabled to false in detail
|
||||
if (!sentinelProperties.isEnabled()) {
|
||||
detailMap.put("enabled", false);
|
||||
builder.up().withDetails(detailMap);
|
||||
return;
|
||||
}
|
||||
|
||||
detailMap.put("enabled", true);
|
||||
|
||||
// Check health of Dashboard
|
||||
boolean dashboardUp = true;
|
||||
String consoleServer = TransportConfig.getConsoleServer();
|
||||
if (StringUtils.isEmpty(consoleServer)) {
|
||||
// If Dashboard isn't configured, it's OK and mark the status of Dashboard with UNKNOWN.
|
||||
detailMap.put("dashboard", new Status(Status.UNKNOWN.getCode(), "dashboard isn't configured"));
|
||||
} else {
|
||||
// If Dashboard is configured, send a heartbeat message to it and check the result
|
||||
HeartbeatSender heartbeatSender = HeartbeatSenderProvider.getHeartbeatSender();
|
||||
boolean result = heartbeatSender.sendHeartbeat();
|
||||
if (result) {
|
||||
detailMap.put("dashboard", Status.UP);
|
||||
} 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"));
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
// If no Exception thrown, it's OK
|
||||
// Note:
|
||||
// Even if the dynamic config center is down, the loadConfig() might return successfully
|
||||
// e.g. for Nacos client, it might retrieve from the local cache)
|
||||
// But in most circumstances it's okay
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
@ -88,6 +88,12 @@
|
||||
"type": "java.lang.String",
|
||||
"defaultValue": "3",
|
||||
"description": "sentinel the cold factor."
|
||||
},
|
||||
{
|
||||
"name": "management.health.sentinel.enabled",
|
||||
"type": "java.lang.Boolean",
|
||||
"description": "Whether to enable sentinel health check.",
|
||||
"defaultValue": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -0,0 +1,162 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
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.transport.HeartbeatSender;
|
||||
import com.alibaba.csp.sentinel.transport.config.TransportConfig;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
|
||||
import org.springframework.boot.actuate.health.Health;
|
||||
import org.springframework.boot.actuate.health.Status;
|
||||
import org.springframework.cloud.alibaba.sentinel.SentinelProperties;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* Test cases for {@link SentinelHealthIndicator}.
|
||||
*
|
||||
* @author cdfive
|
||||
*/
|
||||
public class SentinelHealthIndicatorTests {
|
||||
|
||||
private SentinelHealthIndicator sentinelHealthIndicator;
|
||||
|
||||
private DefaultListableBeanFactory beanFactory;
|
||||
|
||||
private SentinelProperties sentinelProperties;
|
||||
|
||||
private HeartbeatSender heartbeatSender;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
beanFactory = mock(DefaultListableBeanFactory.class);
|
||||
sentinelProperties = mock(SentinelProperties.class);
|
||||
sentinelHealthIndicator = new SentinelHealthIndicator(beanFactory, sentinelProperties);
|
||||
|
||||
SentinelConfig.setConfig(TransportConfig.CONSOLE_SERVER, "");
|
||||
|
||||
heartbeatSender = mock(HeartbeatSender.class);
|
||||
Field heartbeatSenderField = ReflectionUtils.findField(HeartbeatSenderProvider.class, "heartbeatSender");
|
||||
heartbeatSenderField.setAccessible(true);
|
||||
ReflectionUtils.setField(heartbeatSenderField, null, heartbeatSender);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSentinelNotEnabled() {
|
||||
when(sentinelProperties.isEnabled()).thenReturn(false);
|
||||
|
||||
Health health = sentinelHealthIndicator.health();
|
||||
|
||||
assertThat(health.getStatus()).isEqualTo(Status.UP);
|
||||
assertThat(health.getDetails().get("enabled")).isEqualTo(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSentinelDashboardNotConfigured() {
|
||||
when(sentinelProperties.isEnabled()).thenReturn(true);
|
||||
|
||||
Health health = sentinelHealthIndicator.health();
|
||||
|
||||
assertThat(health.getStatus()).isEqualTo(Status.UP);
|
||||
assertThat(health.getDetails().get("dashboard")).isEqualTo(Status.UNKNOWN);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSentinelDashboardConfiguredSuccess() throws Exception {
|
||||
when(sentinelProperties.isEnabled()).thenReturn(true);
|
||||
SentinelConfig.setConfig(TransportConfig.CONSOLE_SERVER, "localhost:8080");
|
||||
when(heartbeatSender.sendHeartbeat()).thenReturn(true);
|
||||
|
||||
Health health = sentinelHealthIndicator.health();
|
||||
|
||||
assertThat(health.getStatus()).isEqualTo(Status.UP);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSentinelDashboardConfiguredFailed() throws Exception {
|
||||
when(sentinelProperties.isEnabled()).thenReturn(true);
|
||||
SentinelConfig.setConfig(TransportConfig.CONSOLE_SERVER, "localhost:8080");
|
||||
when(heartbeatSender.sendHeartbeat()).thenReturn(false);
|
||||
|
||||
|
||||
Health health = sentinelHealthIndicator.health();
|
||||
|
||||
assertThat(health.getStatus()).isEqualTo(Status.DOWN);
|
||||
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…
x
Reference in New Issue
Block a user