element = elementsIterator.next();
+ String name = element.getKey();
+ if (registry.containsKey(name)) {
+ apiPredicateItemClass = registry.get(name);
+ break;
+ }
+ }
+ if (apiPredicateItemClass == null) {
+ return null;
+ }
+ return mapper.readValue(root.toString(), apiPredicateItemClass);
+ }
}
- @Bean("sentinel-json-degrade-converter")
- public JsonConverter jsonDegradeConverter() {
- return new JsonConverter(objectMapper, DegradeRule.class);
+ @Configuration
+ protected static class SentinelJsonConfiguration {
+
+ private ObjectMapper objectMapper = new ObjectMapper();
+
+ public SentinelJsonConfiguration() {
+ objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,
+ false);
+
+ ApiPredicateItemDeserializer deserializer = new ApiPredicateItemDeserializer();
+ deserializer.registerApiPredicateItem("pattern",
+ ApiPathPredicateItem.class);
+ deserializer.registerApiPredicateItem("items",
+ ApiPredicateGroupItem.class);
+ SimpleModule module = new SimpleModule(
+ "PolymorphicApiPredicateItemDeserializerModule",
+ new Version(1, 0, 0, null));
+ module.addDeserializer(ApiPredicateItem.class, deserializer);
+ objectMapper.registerModule(module);
+ }
+
+ @Bean("sentinel-json-flow-converter")
+ public JsonConverter jsonFlowConverter() {
+ return new JsonConverter(objectMapper, FlowRule.class);
+ }
+
+ @Bean("sentinel-json-degrade-converter")
+ public JsonConverter jsonDegradeConverter() {
+ return new JsonConverter(objectMapper, DegradeRule.class);
+ }
+
+ @Bean("sentinel-json-system-converter")
+ public JsonConverter jsonSystemConverter() {
+ return new JsonConverter(objectMapper, SystemRule.class);
+ }
+
+ @Bean("sentinel-json-authority-converter")
+ public JsonConverter jsonAuthorityConverter() {
+ return new JsonConverter(objectMapper, AuthorityRule.class);
+ }
+
+ @Bean("sentinel-json-param-flow-converter")
+ public JsonConverter jsonParamFlowConverter() {
+ return new JsonConverter(objectMapper, ParamFlowRule.class);
+ }
+
+ @Bean("sentinel-json-gw-flow-converter")
+ public JsonConverter jsonGatewayFlowConverter() {
+ return new JsonConverter(objectMapper, GatewayFlowRule.class);
+ }
+
+ @Bean("sentinel-json-gw-api-group-converter")
+ public JsonConverter jsonApiConverter() {
+ return new JsonConverter(objectMapper, ApiDefinition.class);
+ }
}
- @Bean("sentinel-json-system-converter")
- public JsonConverter jsonSystemConverter() {
- return new JsonConverter(objectMapper, SystemRule.class);
+ @ConditionalOnClass(XmlMapper.class)
+ @Configuration
+ protected static class SentinelXmlConfiguration {
+
+ private XmlMapper xmlMapper = new XmlMapper();
+
+ public SentinelXmlConfiguration() {
+ xmlMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES,
+ false);
+ ApiPredicateItemDeserializer deserializer = new ApiPredicateItemDeserializer();
+ deserializer.registerApiPredicateItem("pattern",
+ ApiPathPredicateItem.class);
+ deserializer.registerApiPredicateItem("items",
+ ApiPredicateGroupItem.class);
+ SimpleModule module = new SimpleModule(
+ "PolymorphicGatewayDeserializerModule",
+ new Version(1, 0, 0, null));
+ module.addDeserializer(ApiPredicateItem.class, deserializer);
+ xmlMapper.registerModule(module);
+ }
+
+ @Bean("sentinel-xml-flow-converter")
+ public XmlConverter xmlFlowConverter() {
+ return new XmlConverter(xmlMapper, FlowRule.class);
+ }
+
+ @Bean("sentinel-xml-degrade-converter")
+ public XmlConverter xmlDegradeConverter() {
+ return new XmlConverter(xmlMapper, DegradeRule.class);
+ }
+
+ @Bean("sentinel-xml-system-converter")
+ public XmlConverter xmlSystemConverter() {
+ return new XmlConverter(xmlMapper, SystemRule.class);
+ }
+
+ @Bean("sentinel-xml-authority-converter")
+ public XmlConverter xmlAuthorityConverter() {
+ return new XmlConverter(xmlMapper, AuthorityRule.class);
+ }
+
+ @Bean("sentinel-xml-param-flow-converter")
+ public XmlConverter xmlParamFlowConverter() {
+ return new XmlConverter(xmlMapper, ParamFlowRule.class);
+ }
+
+ @Bean("sentinel-xml-gw-flow-converter")
+ public XmlConverter xmlGatewayFlowConverter() {
+ return new XmlConverter(xmlMapper, GatewayFlowRule.class);
+ }
+
+ @Bean("sentinel-xml-gw-api-group-converter")
+ public XmlConverter xmlApiConverter() {
+ return new XmlConverter(xmlMapper, ApiDefinition.class);
+ }
+
}
-
- @Bean("sentinel-json-authority-converter")
- public JsonConverter jsonAuthorityConverter() {
- return new JsonConverter(objectMapper, AuthorityRule.class);
- }
-
- @Bean("sentinel-json-param-flow-converter")
- public JsonConverter jsonParamFlowConverter() {
- return new JsonConverter(objectMapper, ParamFlowRule.class);
- }
-
- }
-
- @ConditionalOnClass(XmlMapper.class)
- protected static class SentinelXmlConfiguration {
-
- private XmlMapper xmlMapper = new XmlMapper();
-
- @Bean("sentinel-xml-flow-converter")
- public XmlConverter xmlFlowConverter() {
- return new XmlConverter(xmlMapper, FlowRule.class);
- }
-
- @Bean("sentinel-xml-degrade-converter")
- public XmlConverter xmlDegradeConverter() {
- return new XmlConverter(xmlMapper, DegradeRule.class);
- }
-
- @Bean("sentinel-xml-system-converter")
- public XmlConverter xmlSystemConverter() {
- return new XmlConverter(xmlMapper, SystemRule.class);
- }
-
- @Bean("sentinel-xml-authority-converter")
- public XmlConverter xmlAuthorityConverter() {
- return new XmlConverter(xmlMapper, AuthorityRule.class);
- }
-
- @Bean("sentinel-xml-param-flow-converter")
- public XmlConverter xmlParamFlowConverter() {
- return new XmlConverter(xmlMapper, ParamFlowRule.class);
- }
-
}
}
diff --git a/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelDataSourceHandler.java b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelDataSourceHandler.java
index df57f919..f1dd0cfc 100644
--- a/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelDataSourceHandler.java
+++ b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/custom/SentinelDataSourceHandler.java
@@ -1,23 +1,5 @@
package org.springframework.cloud.alibaba.sentinel.custom;
-import com.alibaba.csp.sentinel.datasource.AbstractDataSource;
-import com.alibaba.csp.sentinel.datasource.ReadableDataSource;
-import com.alibaba.csp.sentinel.slots.block.AbstractRule;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.beans.factory.SmartInitializingSingleton;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.support.BeanDefinitionBuilder;
-import org.springframework.beans.factory.support.DefaultListableBeanFactory;
-import org.springframework.cloud.alibaba.sentinel.SentinelProperties;
-import org.springframework.cloud.alibaba.sentinel.datasource.config.AbstractDataSourceProperties;
-import org.springframework.cloud.alibaba.sentinel.datasource.converter.JsonConverter;
-import org.springframework.cloud.alibaba.sentinel.datasource.converter.XmlConverter;
-import org.springframework.util.CollectionUtils;
-import org.springframework.util.ReflectionUtils;
-import org.springframework.util.StringUtils;
-
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.HashMap;
@@ -25,6 +7,22 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.SmartInitializingSingleton;
+import org.springframework.beans.factory.support.BeanDefinitionBuilder;
+import org.springframework.beans.factory.support.DefaultListableBeanFactory;
+import org.springframework.cloud.alibaba.sentinel.SentinelProperties;
+import org.springframework.cloud.alibaba.sentinel.datasource.config.AbstractDataSourceProperties;
+import org.springframework.cloud.alibaba.sentinel.datasource.converter.JsonConverter;
+import org.springframework.cloud.alibaba.sentinel.datasource.converter.XmlConverter;
+import org.springframework.core.env.Environment;
+import org.springframework.util.ReflectionUtils;
+import org.springframework.util.StringUtils;
+
+import com.alibaba.csp.sentinel.datasource.AbstractDataSource;
+import com.alibaba.csp.sentinel.datasource.ReadableDataSource;
+
/**
* Sentinel {@link ReadableDataSource} Handler Handle the configurations of
* 'spring.cloud.sentinel.datasource'
@@ -47,12 +45,16 @@ public class SentinelDataSourceHandler implements SmartInitializingSingleton {
private final DefaultListableBeanFactory beanFactory;
- public SentinelDataSourceHandler(DefaultListableBeanFactory beanFactory) {
- this.beanFactory = beanFactory;
- }
+ private final SentinelProperties sentinelProperties;
- @Autowired
- private SentinelProperties sentinelProperties;
+ private final Environment env;
+
+ public SentinelDataSourceHandler(DefaultListableBeanFactory beanFactory,
+ SentinelProperties sentinelProperties, Environment env) {
+ this.beanFactory = beanFactory;
+ this.sentinelProperties = sentinelProperties;
+ this.env = env;
+ }
@Override
public void afterSingletonsInstantiated() {
@@ -68,6 +70,7 @@ public class SentinelDataSourceHandler implements SmartInitializingSingleton {
}
AbstractDataSourceProperties abstractDataSourceProperties = dataSourceProperties
.getValidDataSourceProperties();
+ abstractDataSourceProperties.setEnv(env);
abstractDataSourceProperties.preCheck(dataSourceName);
registerBean(abstractDataSourceProperties, dataSourceName
+ "-sentinel-" + validFields.get(0) + "-datasource");
@@ -181,56 +184,8 @@ public class SentinelDataSourceHandler implements SmartInitializingSingleton {
AbstractDataSource newDataSource = (AbstractDataSource) this.beanFactory
.getBean(dataSourceName);
- logAndCheckRuleType(newDataSource, dataSourceName,
- dataSourceProperties.getRuleType().getClazz());
-
// register property in RuleManager
dataSourceProperties.postRegister(newDataSource);
}
- private void logAndCheckRuleType(AbstractDataSource dataSource, String dataSourceName,
- Class extends AbstractRule> ruleClass) {
- Object ruleConfig;
- try {
- ruleConfig = dataSource.loadConfig();
- }
- catch (Exception e) {
- log.error("[Sentinel Starter] DataSource " + dataSourceName
- + " loadConfig error: " + e.getMessage(), e);
- return;
- }
- if (ruleConfig instanceof List) {
- List convertedRuleList = (List) ruleConfig;
- if (CollectionUtils.isEmpty(convertedRuleList)) {
- log.warn("[Sentinel Starter] DataSource {} rule list is empty.",
- dataSourceName);
- return;
- }
- if (convertedRuleList.stream()
- .noneMatch(rule -> rule.getClass() == ruleClass)) {
- log.error("[Sentinel Starter] DataSource {} none rules are {} type.",
- dataSourceName, ruleClass.getSimpleName());
- throw new IllegalArgumentException("[Sentinel Starter] DataSource "
- + dataSourceName + " none rules are " + ruleClass.getSimpleName()
- + " type.");
- }
- else if (!convertedRuleList.stream()
- .allMatch(rule -> rule.getClass() == ruleClass)) {
- log.warn("[Sentinel Starter] DataSource {} all rules are not {} type.",
- dataSourceName, ruleClass.getSimpleName());
- }
- else {
- log.info("[Sentinel Starter] DataSource {} load {} {}", dataSourceName,
- convertedRuleList.size(), ruleClass.getSimpleName());
- }
- }
- else {
- log.error("[Sentinel Starter] DataSource " + dataSourceName
- + " rule class is not List<" + ruleClass.getSimpleName()
- + ">. Class: " + ruleConfig.getClass());
- throw new IllegalArgumentException("[Sentinel Starter] DataSource "
- + dataSourceName + " rule class is not List<"
- + ruleClass.getSimpleName() + ">. Class: " + ruleConfig.getClass());
- }
- }
}
diff --git a/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/endpoint/SentinelEndpointAutoConfiguration.java b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/endpoint/SentinelEndpointAutoConfiguration.java
index 7c9b5d1c..9af7be97 100644
--- a/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/endpoint/SentinelEndpointAutoConfiguration.java
+++ b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/endpoint/SentinelEndpointAutoConfiguration.java
@@ -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);
+ }
}
diff --git a/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/endpoint/SentinelHealthIndicator.java b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/endpoint/SentinelHealthIndicator.java
new file mode 100644
index 00000000..8ca863ab
--- /dev/null
+++ b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/endpoint/SentinelHealthIndicator.java
@@ -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.
+ *
+ *
+ * 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.
+ *
+ *
+ *
+ * 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.
+ *
+ *
+ * @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 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 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 dataSourceMap = beanFactory.getBeansOfType(AbstractDataSource.class);
+ for (Map.Entry 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);
+ }
+ }
+}
diff --git a/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/feign/SentinelContractHolder.java b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/feign/SentinelContractHolder.java
index 6056e800..e1a43d2e 100644
--- a/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/feign/SentinelContractHolder.java
+++ b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/feign/SentinelContractHolder.java
@@ -25,7 +25,7 @@ import feign.MethodMetadata;
/**
*
- * Using static field {@link SentinelContractHolder#metadataMap} to hold
+ * Using static field {@link SentinelContractHolder#METADATA_MAP} to hold
* {@link MethodMetadata} data
*
* @author Jim
@@ -38,7 +38,7 @@ public class SentinelContractHolder implements Contract {
* map key is constructed by ClassFullName + configKey. configKey is constructed by
* {@link feign.Feign#configKey}
*/
- public final static Map metadataMap = new HashMap();
+ public final static Map METADATA_MAP = new HashMap<>();
public SentinelContractHolder(Contract delegate) {
this.delegate = delegate;
@@ -47,7 +47,7 @@ public class SentinelContractHolder implements Contract {
@Override
public List parseAndValidatateMetadata(Class> targetType) {
List metadatas = delegate.parseAndValidatateMetadata(targetType);
- metadatas.forEach(metadata -> metadataMap
+ metadatas.forEach(metadata -> METADATA_MAP
.put(targetType.getName() + metadata.configKey(), metadata));
return metadatas;
}
diff --git a/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/feign/SentinelInvocationHandler.java b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/feign/SentinelInvocationHandler.java
index f1f34fd5..7e7614e8 100644
--- a/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/feign/SentinelInvocationHandler.java
+++ b/spring-cloud-alibaba-sentinel/src/main/java/org/springframework/cloud/alibaba/sentinel/feign/SentinelInvocationHandler.java
@@ -90,9 +90,9 @@ public class SentinelInvocationHandler implements InvocationHandler {
// only handle by HardCodedTarget
if (target instanceof Target.HardCodedTarget) {
Target.HardCodedTarget hardCodedTarget = (Target.HardCodedTarget) target;
- MethodMetadata methodMetadata = SentinelContractHolder.metadataMap
- .get(method.getDeclaringClass().getName()
- + Feign.configKey(method.getDeclaringClass(), method));
+ MethodMetadata methodMetadata = SentinelContractHolder.METADATA_MAP
+ .get(hardCodedTarget.type().getName()
+ + Feign.configKey(hardCodedTarget.type(), method));
// resource default is HttpMethod:protocol://url
String resourceName = methodMetadata.template().method().toUpperCase() + ":"
+ hardCodedTarget.url() + methodMetadata.template().path();
diff --git a/spring-cloud-alibaba-sentinel/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/spring-cloud-alibaba-sentinel/src/main/resources/META-INF/additional-spring-configuration-metadata.json
index 21713de7..ec4c63d7 100644
--- a/spring-cloud-alibaba-sentinel/src/main/resources/META-INF/additional-spring-configuration-metadata.json
+++ b/spring-cloud-alibaba-sentinel/src/main/resources/META-INF/additional-spring-configuration-metadata.json
@@ -88,6 +88,18 @@
"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
+ },
+ {
+ "defaultValue": "false",
+ "name": "feign.sentinel.enabled",
+ "description": "If true, an OpenFeign client will be wrapped with a Sentinel circuit breaker.",
+ "type": "java.lang.Boolean"
}
]
}
diff --git a/spring-cloud-alibaba-sentinel/src/main/resources/META-INF/spring.factories b/spring-cloud-alibaba-sentinel/src/main/resources/META-INF/spring.factories
index d964e3b0..20f314cc 100644
--- a/spring-cloud-alibaba-sentinel/src/main/resources/META-INF/spring.factories
+++ b/spring-cloud-alibaba-sentinel/src/main/resources/META-INF/spring.factories
@@ -1,5 +1,6 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.alibaba.sentinel.SentinelWebAutoConfiguration,\
+org.springframework.cloud.alibaba.sentinel.SentinelWebFluxAutoConfiguration,\
org.springframework.cloud.alibaba.sentinel.endpoint.SentinelEndpointAutoConfiguration,\
org.springframework.cloud.alibaba.sentinel.custom.SentinelAutoConfiguration,\
org.springframework.cloud.alibaba.sentinel.feign.SentinelFeignAutoConfiguration
diff --git a/spring-cloud-alibaba-sentinel/src/test/java/org/springframework/cloud/alibaba/sentinel/SentinelFeignTests.java b/spring-cloud-alibaba-sentinel/src/test/java/org/springframework/cloud/alibaba/sentinel/SentinelFeignTests.java
index b7d03670..accd536f 100644
--- a/spring-cloud-alibaba-sentinel/src/test/java/org/springframework/cloud/alibaba/sentinel/SentinelFeignTests.java
+++ b/spring-cloud-alibaba-sentinel/src/test/java/org/springframework/cloud/alibaba/sentinel/SentinelFeignTests.java
@@ -60,6 +60,9 @@ public class SentinelFeignTests {
@Autowired
private BarService barService;
+ @Autowired
+ private BazService bazService;
+
@Before
public void setUp() {
FlowRule rule1 = new FlowRule();
@@ -83,7 +86,14 @@ public class SentinelFeignTests {
rule3.setLimitApp("default");
rule3.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT);
rule3.setStrategy(RuleConstant.STRATEGY_DIRECT);
- FlowRuleManager.loadRules(Arrays.asList(rule1, rule2, rule3));
+ FlowRule rule4 = new FlowRule();
+ rule4.setGrade(RuleConstant.FLOW_GRADE_QPS);
+ rule4.setCount(0);
+ rule4.setResource("GET:http://baz-service/baz");
+ rule4.setLimitApp("default");
+ rule4.setControlBehavior(RuleConstant.CONTROL_BEHAVIOR_DEFAULT);
+ rule4.setStrategy(RuleConstant.STRATEGY_DIRECT);
+ FlowRuleManager.loadRules(Arrays.asList(rule1, rule2, rule3,rule4));
}
@Test
@@ -101,6 +111,9 @@ public class SentinelFeignTests {
assertThatExceptionOfType(Exception.class).isThrownBy(() -> {
barService.bar();
});
+ assertThatExceptionOfType(Exception.class).isThrownBy(() -> {
+ bazService.baz();
+ });
assertNotEquals("ToString method invoke was not in SentinelInvocationHandler",
echoService.toString(), fooService.toString());
@@ -146,6 +159,15 @@ public class SentinelFeignTests {
String bar();
}
+ public interface BazService {
+ @RequestMapping(path = "baz")
+ String baz();
+ }
+
+ @FeignClient(value = "baz-service")
+ public interface BazClient extends BazService {
+ }
+
public static class EchoServiceFallback implements EchoService {
@Override
diff --git a/spring-cloud-alibaba-sentinel/src/test/java/org/springframework/cloud/alibaba/sentinel/endpoint/SentinelHealthIndicatorTests.java b/spring-cloud-alibaba-sentinel/src/test/java/org/springframework/cloud/alibaba/sentinel/endpoint/SentinelHealthIndicatorTests.java
new file mode 100644
index 00000000..6245c4a1
--- /dev/null
+++ b/spring-cloud-alibaba-sentinel/src/test/java/org/springframework/cloud/alibaba/sentinel/endpoint/SentinelHealthIndicatorTests.java
@@ -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 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 dataSourceDetailMap = (Map) 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 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 dataSourceDetailMap = (Map) 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"));
+ }
+}
diff --git a/spring-cloud-alicloud-acm/src/main/java/org/springframework/cloud/alicloud/acm/endpoint/AcmEndpoint.java b/spring-cloud-alicloud-acm/src/main/java/org/springframework/cloud/alicloud/acm/endpoint/AcmEndpoint.java
index 0133fe24..6952fa89 100644
--- a/spring-cloud-alicloud-acm/src/main/java/org/springframework/cloud/alicloud/acm/endpoint/AcmEndpoint.java
+++ b/spring-cloud-alicloud-acm/src/main/java/org/springframework/cloud/alicloud/acm/endpoint/AcmEndpoint.java
@@ -44,7 +44,12 @@ public class AcmEndpoint {
private final AcmPropertySourceRepository propertySourceRepository;
- private DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+ private ThreadLocal dateFormat = new ThreadLocal() {
+ @Override
+ protected DateFormat initialValue() {
+ return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+ }
+ };
public AcmEndpoint(AcmProperties properties, AcmRefreshHistory refreshHistory,
AcmPropertySourceRepository propertySourceRepository) {
@@ -65,7 +70,7 @@ public class AcmEndpoint {
for (AcmPropertySource ps : all) {
Map source = new HashMap<>();
source.put("dataId", ps.getDataId());
- source.put("lastSynced", dateFormat.format(ps.getTimestamp()));
+ source.put("lastSynced", dateFormat.get().format(ps.getTimestamp()));
sources.add(source);
}
runtime.put("sources", sources);
diff --git a/spring-cloud-alicloud-acm/src/main/java/org/springframework/cloud/alicloud/acm/refresh/AcmRefreshHistory.java b/spring-cloud-alicloud-acm/src/main/java/org/springframework/cloud/alicloud/acm/refresh/AcmRefreshHistory.java
index 439e6360..19197b89 100644
--- a/spring-cloud-alicloud-acm/src/main/java/org/springframework/cloud/alicloud/acm/refresh/AcmRefreshHistory.java
+++ b/spring-cloud-alicloud-acm/src/main/java/org/springframework/cloud/alicloud/acm/refresh/AcmRefreshHistory.java
@@ -30,10 +30,15 @@ public class AcmRefreshHistory {
private LinkedList records = new LinkedList<>();
- private DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+ private ThreadLocal dateFormat = new ThreadLocal() {
+ @Override
+ protected DateFormat initialValue() {
+ return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+ }
+ };
public void add(String dataId, String md5) {
- records.addFirst(new Record(dateFormat.format(new Date()), dataId, md5));
+ records.addFirst(new Record(dateFormat.get().format(new Date()), dataId, md5));
if (records.size() > MAX_SIZE) {
records.removeLast();
}
diff --git a/spring-cloud-alicloud-context/pom.xml b/spring-cloud-alicloud-context/pom.xml
index 911cabb5..ab98c6f3 100644
--- a/spring-cloud-alicloud-context/pom.xml
+++ b/spring-cloud-alicloud-context/pom.xml
@@ -50,12 +50,6 @@
provided