convert(String source) {
+ return JSON.parseArray(source, FlowRule.class);
+ }
+ });
+ FlowRuleManager.register2Property(dataSource.getProperty());
+```
+
+If the client want to perceive the remote config changed, it can binding a git webhook callback with the ```com.alibaba.csp.sentinel.datasource.spring.cloud.config.SentinelRuleLocator.refresh```
+ API. Like test demo ```com.alibaba.csp.sentinel.datasource.spring.cloud.config.test.SpringCouldDataSourceTest.refresh``` do.
+
+
+We offer test cases and demo in:
+[com.alibaba.csp.sentinel.datasource.spring.cloud.config.test].
+When you run test cases, please follow the steps:
+
+```
+//first start config server
+com.alibaba.csp.sentinel.datasource.spring.cloud.config.server.ConfigServer
+
+//second start config client
+com.alibaba.csp.sentinel.datasource.spring.cloud.config.client.ConfigClient
+
+//third run test cases and demo
+com.alibaba.csp.sentinel.datasource.spring.cloud.config.test.SentinelRuleLocatorTests
+com.alibaba.csp.sentinel.datasource.spring.cloud.config.test.SpringCouldDataSourceTest
+```
\ No newline at end of file
diff --git a/sentinel-extension/sentinel-datasource-spring-cloud-config/pom.xml b/sentinel-extension/sentinel-datasource-spring-cloud-config/pom.xml
new file mode 100644
index 00000000..16e3ddd3
--- /dev/null
+++ b/sentinel-extension/sentinel-datasource-spring-cloud-config/pom.xml
@@ -0,0 +1,88 @@
+
+
+
+ sentinel-extension
+ com.alibaba.csp
+ 1.7.0-SNAPSHOT
+
+ 4.0.0
+
+ sentinel-datasource-spring-cloud-config
+ jar
+
+
+ 2.0.0.RELEASE
+
+
+
+
+
+ com.alibaba.csp
+ sentinel-datasource-extension
+
+
+
+ org.springframework.cloud
+ spring-cloud-starter-config
+ ${spring.cloud.version}
+
+
+
+ org.springframework.retry
+ spring-retry
+ 1.2.4.RELEASE
+
+
+ org.springframework
+ spring-core
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ ${spring.cloud.version}
+ test
+
+
+
+ org.springframework.cloud
+ spring-cloud-config-server
+ ${spring.cloud.version}
+ test
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+ ${spring.cloud.version}
+ test
+
+
+
+ org.springframework
+ spring-expression
+ 5.1.8.RELEASE
+ test
+
+
+
+ com.alibaba
+ fastjson
+ test
+
+
+
+ junit
+ junit
+ test
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/sentinel-extension/sentinel-datasource-spring-cloud-config/src/main/java/com/alibaba/csp/sentinel/datasource/spring/cloud/config/SentinelRuleLocator.java b/sentinel-extension/sentinel-datasource-spring-cloud-config/src/main/java/com/alibaba/csp/sentinel/datasource/spring/cloud/config/SentinelRuleLocator.java
new file mode 100644
index 00000000..ca33dc5d
--- /dev/null
+++ b/sentinel-extension/sentinel-datasource-spring-cloud-config/src/main/java/com/alibaba/csp/sentinel/datasource/spring/cloud/config/SentinelRuleLocator.java
@@ -0,0 +1,294 @@
+package com.alibaba.csp.sentinel.datasource.spring.cloud.config;
+
+import com.alibaba.csp.sentinel.log.RecordLog;
+import org.springframework.cloud.bootstrap.config.PropertySourceLocator;
+import org.springframework.cloud.config.client.ConfigClientProperties;
+import org.springframework.cloud.config.client.ConfigClientStateHolder;
+import org.springframework.cloud.config.environment.Environment;
+import org.springframework.cloud.config.environment.PropertySource;
+import org.springframework.core.annotation.Order;
+import org.springframework.core.env.CompositePropertySource;
+import org.springframework.core.env.MapPropertySource;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.HttpRequest;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.ResponseEntity;
+import org.springframework.http.client.ClientHttpRequestExecution;
+import org.springframework.http.client.ClientHttpRequestInterceptor;
+import org.springframework.http.client.ClientHttpResponse;
+import org.springframework.http.client.SimpleClientHttpRequestFactory;
+import org.springframework.retry.annotation.Retryable;
+import org.springframework.util.Base64Utils;
+import org.springframework.util.StringUtils;
+import org.springframework.web.client.HttpClientErrorException;
+import org.springframework.web.client.HttpServerErrorException;
+import org.springframework.web.client.ResourceAccessException;
+import org.springframework.web.client.RestTemplate;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.springframework.cloud.config.client.ConfigClientProperties.*;
+
+/**
+ *
+ * {@link SentinelRuleLocator} which pull sentinel rules from remote server.
+ * It retrieve configurations of spring-cloud-config client configurations from {@link org.springframework.core.env.Environment}
+ * Such as spring.cloud.config.uri=uri, spring.cloud.config.profile=profile .... and so on.
+ * When pull rules successfully, save to {@link SentinelRuleStorage} for ${@link SpringCloudConfigDataSource} retrieve.
+ *
+ *
+ * @author lianglin
+ * @since 1.7.0
+ */
+@Order(0)
+public class SentinelRuleLocator implements PropertySourceLocator {
+
+
+ private RestTemplate restTemplate;
+ private ConfigClientProperties defaultProperties;
+ private org.springframework.core.env.Environment environment;
+
+ public SentinelRuleLocator(ConfigClientProperties defaultProperties, org.springframework.core.env.Environment environment) {
+ this.defaultProperties = defaultProperties;
+ this.environment = environment;
+ }
+
+
+ /**
+ * Responsible for pull data from remote server
+ *
+ * @param environment
+ * @return correct data if success else a empty propertySource or null
+ */
+ @Override
+ @Retryable(interceptor = "configServerRetryInterceptor")
+ public org.springframework.core.env.PropertySource> locate(
+ org.springframework.core.env.Environment environment) {
+ ConfigClientProperties properties = this.defaultProperties.override(environment);
+ CompositePropertySource composite = new CompositePropertySource("configService");
+ RestTemplate restTemplate = this.restTemplate == null
+ ? getSecureRestTemplate(properties)
+ : this.restTemplate;
+ Exception error = null;
+ String errorBody = null;
+ try {
+ String[] labels = new String[]{""};
+ if (StringUtils.hasText(properties.getLabel())) {
+ labels = StringUtils
+ .commaDelimitedListToStringArray(properties.getLabel());
+ }
+ String state = ConfigClientStateHolder.getState();
+ // Try all the labels until one works
+ for (String label : labels) {
+ Environment result = getRemoteEnvironment(restTemplate, properties,
+ label.trim(), state);
+ if (result != null) {
+ log(result);
+ // result.getPropertySources() can be null if using xml
+ if (result.getPropertySources() != null) {
+ for (PropertySource source : result.getPropertySources()) {
+ @SuppressWarnings("unchecked")
+ Map map = (Map) source
+ .getSource();
+ composite.addPropertySource(
+ new MapPropertySource(source.getName(), map));
+ }
+ }
+ SentinelRuleStorage.setRulesSource(composite);
+ return composite;
+ }
+ }
+ } catch (HttpServerErrorException e) {
+ error = e;
+ if (MediaType.APPLICATION_JSON
+ .includes(e.getResponseHeaders().getContentType())) {
+ errorBody = e.getResponseBodyAsString();
+ }
+ } catch (Exception e) {
+ error = e;
+ }
+ if (properties.isFailFast()) {
+ throw new IllegalStateException(
+ "Could not locate PropertySource and the fail fast property is set, failing",
+ error);
+ }
+ RecordLog.warn("Could not locate PropertySource: " + (errorBody == null
+ ? error == null ? "label not found" : error.getMessage()
+ : errorBody));
+ return null;
+
+ }
+
+ public org.springframework.core.env.PropertySource> refresh() {
+ return locate(environment);
+ }
+
+ private void log(Environment result) {
+
+ RecordLog.info(String.format(
+ "Located environment: name=%s, profiles=%s, label=%s, version=%s, state=%s",
+ result.getName(),
+ result.getProfiles() == null ? ""
+ : Arrays.asList(result.getProfiles()),
+ result.getLabel(), result.getVersion(), result.getState()));
+
+ List propertySourceList = result.getPropertySources();
+ if (propertySourceList != null) {
+ int propertyCount = 0;
+ for (PropertySource propertySource : propertySourceList) {
+ propertyCount += propertySource.getSource().size();
+ }
+ RecordLog.info(String.format(
+ "Environment %s has %d property sources with %d properties.",
+ result.getName(), result.getPropertySources().size(),
+ propertyCount));
+ }
+
+
+ }
+
+
+ private Environment getRemoteEnvironment(RestTemplate restTemplate,
+ ConfigClientProperties properties, String label, String state) {
+ String path = "/{name}/{profile}";
+ String name = properties.getName();
+ String profile = properties.getProfile();
+ String token = properties.getToken();
+ int noOfUrls = properties.getUri().length;
+ if (noOfUrls > 1) {
+ RecordLog.info("Multiple Config Server Urls found listed.");
+ }
+
+ RecordLog.info("properties = {0},label={1}, state={2}", properties, label, state);
+
+ Object[] args = new String[]{name, profile};
+ if (StringUtils.hasText(label)) {
+ if (label.contains("/")) {
+ label = label.replace("/", "(_)");
+ }
+ args = new String[]{name, profile, label};
+ path = path + "/{label}";
+ }
+ ResponseEntity response = null;
+
+ for (int i = 0; i < noOfUrls; i++) {
+ Credentials credentials = properties.getCredentials(i);
+ String uri = credentials.getUri();
+ String username = credentials.getUsername();
+ String password = credentials.getPassword();
+
+ RecordLog.info("Fetching config from server at : " + uri);
+
+ try {
+ HttpHeaders headers = new HttpHeaders();
+ addAuthorizationToken(properties, headers, username, password);
+ if (StringUtils.hasText(token)) {
+ headers.add(TOKEN_HEADER, token);
+ }
+ if (StringUtils.hasText(state) && properties.isSendState()) {
+ headers.add(STATE_HEADER, state);
+ }
+
+ final HttpEntity entity = new HttpEntity<>((Void) null, headers);
+ response = restTemplate.exchange(uri + path, HttpMethod.GET, entity,
+ Environment.class, args);
+ } catch (HttpClientErrorException e) {
+ if (e.getStatusCode() != HttpStatus.NOT_FOUND) {
+ throw e;
+ }
+ } catch (ResourceAccessException e) {
+ RecordLog.info("Connect Timeout Exception on Url - " + uri
+ + ". Will be trying the next url if available");
+ if (i == noOfUrls - 1) {
+ throw e;
+ } else {
+ continue;
+ }
+ }
+
+ if (response == null || response.getStatusCode() != HttpStatus.OK) {
+ return null;
+ }
+
+ Environment result = response.getBody();
+ return result;
+ }
+
+ return null;
+ }
+
+
+ private RestTemplate getSecureRestTemplate(ConfigClientProperties client) {
+ SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
+ if (client.getRequestReadTimeout() < 0) {
+ throw new IllegalStateException("Invalid Value for Read Timeout set.");
+ }
+ requestFactory.setReadTimeout(client.getRequestReadTimeout());
+ RestTemplate template = new RestTemplate(requestFactory);
+ Map headers = new HashMap<>(client.getHeaders());
+ if (headers.containsKey(AUTHORIZATION)) {
+ // To avoid redundant addition of header
+ headers.remove(AUTHORIZATION);
+ }
+ if (!headers.isEmpty()) {
+ template.setInterceptors(Arrays.asList(
+ new GenericRequestHeaderInterceptor(headers)));
+ }
+
+ return template;
+ }
+
+ private void addAuthorizationToken(ConfigClientProperties configClientProperties,
+ HttpHeaders httpHeaders, String username, String password) {
+ String authorization = configClientProperties.getHeaders().get(AUTHORIZATION);
+
+ if (password != null && authorization != null) {
+ throw new IllegalStateException(
+ "You must set either 'password' or 'authorization'");
+ }
+
+ if (password != null) {
+ byte[] token = Base64Utils.encode((username + ":" + password).getBytes());
+ httpHeaders.add("Authorization", "Basic " + new String(token));
+ } else if (authorization != null) {
+ httpHeaders.add("Authorization", authorization);
+ }
+
+ }
+
+ public void setRestTemplate(RestTemplate restTemplate) {
+ this.restTemplate = restTemplate;
+ }
+
+
+ public static class GenericRequestHeaderInterceptor
+ implements ClientHttpRequestInterceptor {
+
+ private final Map headers;
+
+ public GenericRequestHeaderInterceptor(Map headers) {
+ this.headers = headers;
+ }
+
+ @Override
+ public ClientHttpResponse intercept(HttpRequest request, byte[] body,
+ ClientHttpRequestExecution execution) throws IOException {
+ for (Map.Entry header : headers.entrySet()) {
+ request.getHeaders().add(header.getKey(), header.getValue());
+ }
+ return execution.execute(request, body);
+ }
+
+ protected Map getHeaders() {
+ return headers;
+ }
+
+ }
+}
diff --git a/sentinel-extension/sentinel-datasource-spring-cloud-config/src/main/java/com/alibaba/csp/sentinel/datasource/spring/cloud/config/SentinelRuleStorage.java b/sentinel-extension/sentinel-datasource-spring-cloud-config/src/main/java/com/alibaba/csp/sentinel/datasource/spring/cloud/config/SentinelRuleStorage.java
new file mode 100644
index 00000000..9e8884cf
--- /dev/null
+++ b/sentinel-extension/sentinel-datasource-spring-cloud-config/src/main/java/com/alibaba/csp/sentinel/datasource/spring/cloud/config/SentinelRuleStorage.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * 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 com.alibaba.csp.sentinel.datasource.spring.cloud.config;
+
+import org.springframework.core.env.PropertySource;
+
+/**
+ * Storage data pull from spring-config-cloud server
+ * And notice ${@link SpringCloudConfigDataSource} update latest values
+ *
+ * @author lianglin
+ * @since 1.7.0
+ */
+public class SentinelRuleStorage {
+
+ public static PropertySource> rulesSource;
+
+ public static void setRulesSource(PropertySource> source) {
+ rulesSource = source;
+ noticeSpringCloudDataSource();
+ }
+
+ public static String retrieveRule(String ruleKey) {
+ return rulesSource == null ? null : (String) rulesSource.getProperty(ruleKey);
+ }
+
+ private static void noticeSpringCloudDataSource(){
+ SpringCloudConfigDataSource.updateValues();
+ }
+
+}
diff --git a/sentinel-extension/sentinel-datasource-spring-cloud-config/src/main/java/com/alibaba/csp/sentinel/datasource/spring/cloud/config/SpringCloudConfigDataSource.java b/sentinel-extension/sentinel-datasource-spring-cloud-config/src/main/java/com/alibaba/csp/sentinel/datasource/spring/cloud/config/SpringCloudConfigDataSource.java
new file mode 100644
index 00000000..085e3634
--- /dev/null
+++ b/sentinel-extension/sentinel-datasource-spring-cloud-config/src/main/java/com/alibaba/csp/sentinel/datasource/spring/cloud/config/SpringCloudConfigDataSource.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * 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 com.alibaba.csp.sentinel.datasource.spring.cloud.config;
+
+import com.alibaba.csp.sentinel.datasource.AbstractDataSource;
+import com.alibaba.csp.sentinel.datasource.Converter;
+import com.alibaba.csp.sentinel.log.RecordLog;
+import com.alibaba.csp.sentinel.util.StringUtil;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * ${@link SpringCloudConfigDataSource} A read-only {@code DataSource} with spring-cloud-config backend
+ * It retrieve the spring-cloud-config data stored in ${@link SentinelRuleStorage}
+ * When the data in backend has been modified, ${@link SentinelRuleStorage} will invoke ${@link SpringCloudConfigDataSource#updateValues()}
+ * to dynamic update values
+ *
+ * @author lianglin
+ * @since 1.7.0
+ */
+public class SpringCloudConfigDataSource extends AbstractDataSource {
+
+
+ private final static Map listeners;
+
+ static {
+ listeners = new ConcurrentHashMap<>();
+ }
+
+ private String ruleKey;
+
+
+ public SpringCloudConfigDataSource(final String ruleKey, Converter converter) {
+ super(converter);
+ if (StringUtil.isBlank(ruleKey)) {
+ throw new IllegalArgumentException(String.format("Bad argument: ruleKey=[%s]", ruleKey));
+ }
+
+ this.ruleKey = ruleKey;
+ loadInitialConfig();
+ initListener();
+ }
+
+ private void loadInitialConfig() {
+ try {
+ T newValue = loadConfig();
+ if (newValue == null) {
+ RecordLog.warn("[SpringCloudConfigDataSource] WARN: initial application is null, you may have to check your data source");
+ }
+ getProperty().updateValue(newValue);
+ } catch (Exception ex) {
+ RecordLog.warn("[SpringCloudConfigDataSource] Error when loading initial application", ex);
+ }
+ }
+
+ private void initListener() {
+ listeners.put(this, new SpringConfigListener(this));
+ }
+
+ @Override
+ public String readSource() {
+ return SentinelRuleStorage.retrieveRule(ruleKey);
+ }
+
+ @Override
+ public void close() throws Exception {
+ listeners.remove(this);
+ }
+
+ public static void updateValues() {
+ for (SpringConfigListener listener : listeners.values()) {
+ listener.listenChanged();
+ }
+ }
+
+ private static class SpringConfigListener {
+
+ private SpringCloudConfigDataSource dataSource;
+
+ public SpringConfigListener(SpringCloudConfigDataSource dataSource) {
+ this.dataSource = dataSource;
+ }
+
+ public void listenChanged() {
+ try {
+ Object newValue = dataSource.loadConfig();
+ dataSource.getProperty().updateValue(newValue);
+ } catch (Exception e) {
+ RecordLog.warn("[SpringConfigListener] load config error: ", e);
+ }
+ }
+
+ }
+}
+
diff --git a/sentinel-extension/sentinel-datasource-spring-cloud-config/src/main/java/com/alibaba/csp/sentinel/datasource/spring/cloud/config/config/DataSourceBootstrapConfiguration.java b/sentinel-extension/sentinel-datasource-spring-cloud-config/src/main/java/com/alibaba/csp/sentinel/datasource/spring/cloud/config/config/DataSourceBootstrapConfiguration.java
new file mode 100644
index 00000000..a1c3390d
--- /dev/null
+++ b/sentinel-extension/sentinel-datasource-spring-cloud-config/src/main/java/com/alibaba/csp/sentinel/datasource/spring/cloud/config/config/DataSourceBootstrapConfiguration.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * 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 com.alibaba.csp.sentinel.datasource.spring.cloud.config.config;
+
+import com.alibaba.csp.sentinel.datasource.spring.cloud.config.SentinelRuleLocator;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cloud.config.client.ConfigClientProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.env.ConfigurableEnvironment;
+
+/**
+ *
+ * Define the configuration Loaded when spring application start.
+ * Put it in META-INF/spring.factories, it will be auto loaded by Spring
+ *
+ *
+ * @author lianglin
+ * @since 1.7.0
+ */
+@Configuration
+public class DataSourceBootstrapConfiguration {
+
+ @Autowired
+ private ConfigurableEnvironment environment;
+
+ @Bean
+ public SentinelRuleLocator sentinelPropertySourceLocator(ConfigClientProperties properties) {
+ SentinelRuleLocator locator = new SentinelRuleLocator(
+ properties, environment);
+ return locator;
+ }
+
+
+}
diff --git a/sentinel-extension/sentinel-datasource-spring-cloud-config/src/main/resources/META-INF/spring.factories b/sentinel-extension/sentinel-datasource-spring-cloud-config/src/main/resources/META-INF/spring.factories
new file mode 100644
index 00000000..31c250fc
--- /dev/null
+++ b/sentinel-extension/sentinel-datasource-spring-cloud-config/src/main/resources/META-INF/spring.factories
@@ -0,0 +1,2 @@
+org.springframework.cloud.bootstrap.BootstrapConfiguration=\
+com.alibaba.csp.sentinel.datasource.spring.cloud.config.config.DataSourceBootstrapConfiguration
diff --git a/sentinel-extension/sentinel-datasource-spring-cloud-config/src/test/java/com/alibaba/csp/sentinel/datasource/spring/cloud/config/SimpleSpringApplication.java b/sentinel-extension/sentinel-datasource-spring-cloud-config/src/test/java/com/alibaba/csp/sentinel/datasource/spring/cloud/config/SimpleSpringApplication.java
new file mode 100644
index 00000000..b4a7a0bd
--- /dev/null
+++ b/sentinel-extension/sentinel-datasource-spring-cloud-config/src/test/java/com/alibaba/csp/sentinel/datasource/spring/cloud/config/SimpleSpringApplication.java
@@ -0,0 +1,32 @@
+/*
+ * 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 com.alibaba.csp.sentinel.datasource.spring.cloud.config;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+/**
+ * @author lianglin
+ * @since 1.7.0
+ */
+@SpringBootApplication
+public abstract class SimpleSpringApplication {
+ public static void main(String[] args) {
+ SpringApplication.run(SimpleSpringApplication.class);
+ }
+
+}
diff --git a/sentinel-extension/sentinel-datasource-spring-cloud-config/src/test/java/com/alibaba/csp/sentinel/datasource/spring/cloud/config/client/ConfigClient.java b/sentinel-extension/sentinel-datasource-spring-cloud-config/src/test/java/com/alibaba/csp/sentinel/datasource/spring/cloud/config/client/ConfigClient.java
new file mode 100644
index 00000000..03c5ebd3
--- /dev/null
+++ b/sentinel-extension/sentinel-datasource-spring-cloud-config/src/test/java/com/alibaba/csp/sentinel/datasource/spring/cloud/config/client/ConfigClient.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * 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 com.alibaba.csp.sentinel.datasource.spring.cloud.config.client;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.PropertySource;
+
+/**
+ * @author lianglin
+ * @since 1.7.0
+ */
+@SpringBootApplication
+@ComponentScan("com.alibaba.csp.sentinel.datasource.spring.cloud.config.test")
+@PropertySource("classpath:config-client-application.properties")
+public class ConfigClient {
+ public static void main(String[] args) {
+ SpringApplication.run(ConfigClient.class);
+ }
+}
diff --git a/sentinel-extension/sentinel-datasource-spring-cloud-config/src/test/java/com/alibaba/csp/sentinel/datasource/spring/cloud/config/server/ConfigServer.java b/sentinel-extension/sentinel-datasource-spring-cloud-config/src/test/java/com/alibaba/csp/sentinel/datasource/spring/cloud/config/server/ConfigServer.java
new file mode 100644
index 00000000..14d46fb2
--- /dev/null
+++ b/sentinel-extension/sentinel-datasource-spring-cloud-config/src/test/java/com/alibaba/csp/sentinel/datasource/spring/cloud/config/server/ConfigServer.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * 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 com.alibaba.csp.sentinel.datasource.spring.cloud.config.server;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cloud.config.server.EnableConfigServer;
+import org.springframework.context.annotation.PropertySource;
+
+/**
+ * @author lianglin
+ * @since 1.7.0
+ */
+@EnableConfigServer
+@SpringBootApplication
+@PropertySource("classpath:config-server-application.properties")
+public class ConfigServer {
+ public static void main(String[] args) {
+ SpringApplication.run(ConfigServer.class);
+
+ }
+}
diff --git a/sentinel-extension/sentinel-datasource-spring-cloud-config/src/test/java/com/alibaba/csp/sentinel/datasource/spring/cloud/config/test/SentinelRuleLocatorTests.java b/sentinel-extension/sentinel-datasource-spring-cloud-config/src/test/java/com/alibaba/csp/sentinel/datasource/spring/cloud/config/test/SentinelRuleLocatorTests.java
new file mode 100644
index 00000000..a6fbf73e
--- /dev/null
+++ b/sentinel-extension/sentinel-datasource-spring-cloud-config/src/test/java/com/alibaba/csp/sentinel/datasource/spring/cloud/config/test/SentinelRuleLocatorTests.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2018-2019 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
+ *
+ * https://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 com.alibaba.csp.sentinel.datasource.spring.cloud.config.test;
+
+import com.alibaba.csp.sentinel.datasource.spring.cloud.config.SentinelRuleLocator;
+import com.alibaba.csp.sentinel.datasource.spring.cloud.config.SentinelRuleStorage;
+import com.alibaba.csp.sentinel.datasource.spring.cloud.config.config.DataSourceBootstrapConfiguration;
+import com.alibaba.csp.sentinel.datasource.spring.cloud.config.server.ConfigServer;
+import com.alibaba.csp.sentinel.util.StringUtil;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.cloud.config.client.ConfigClientProperties;
+import org.springframework.core.env.Environment;
+import org.springframework.test.context.junit4.SpringRunner;
+
+/**
+ * @author lianglin
+ * @since 1.7.0
+ */
+@RunWith(SpringRunner.class)
+@SpringBootTest(classes = DataSourceBootstrapConfiguration.class, properties = {
+ "spring.application.name=sentinel"
+})
+public class SentinelRuleLocatorTests {
+
+
+ @Autowired
+ private SentinelRuleLocator sentinelRulesSourceLocator;
+
+ @Autowired
+ private Environment environment;
+
+ @Test
+ public void testAutoLoad() {
+ Assert.assertTrue(sentinelRulesSourceLocator != null);
+ Assert.assertTrue(environment != null);
+ }
+
+
+ /**
+ * Before run this test case, please start the Config Server ${@link ConfigServer}
+ */
+ public void testLocate() {
+ ConfigClientProperties configClientProperties = new ConfigClientProperties(environment);
+ configClientProperties.setLabel("master");
+ configClientProperties.setProfile("dev");
+ configClientProperties.setUri(new String[]{"http://localhost:10086/"});
+ SentinelRuleLocator sentinelRulesSourceLocator = new SentinelRuleLocator(configClientProperties, environment);
+ sentinelRulesSourceLocator.locate(environment);
+ Assert.assertTrue(StringUtil.isNotBlank(SentinelRuleStorage.retrieveRule("flow_rule")));
+
+ }
+
+}
diff --git a/sentinel-extension/sentinel-datasource-spring-cloud-config/src/test/java/com/alibaba/csp/sentinel/datasource/spring/cloud/config/test/SpringCouldDataSourceTest.java b/sentinel-extension/sentinel-datasource-spring-cloud-config/src/test/java/com/alibaba/csp/sentinel/datasource/spring/cloud/config/test/SpringCouldDataSourceTest.java
new file mode 100644
index 00000000..589a3ba9
--- /dev/null
+++ b/sentinel-extension/sentinel-datasource-spring-cloud-config/src/test/java/com/alibaba/csp/sentinel/datasource/spring/cloud/config/test/SpringCouldDataSourceTest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 1999-2018 Alibaba Group Holding Ltd.
+ *
+ * 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 com.alibaba.csp.sentinel.datasource.spring.cloud.config.test;
+
+import com.alibaba.csp.sentinel.datasource.Converter;
+import com.alibaba.csp.sentinel.datasource.spring.cloud.config.SentinelRuleLocator;
+import com.alibaba.csp.sentinel.datasource.spring.cloud.config.SpringCloudConfigDataSource;
+import com.alibaba.csp.sentinel.datasource.spring.cloud.config.client.ConfigClient;
+import com.alibaba.csp.sentinel.datasource.spring.cloud.config.server.ConfigServer;
+import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
+import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
+import com.alibaba.fastjson.JSON;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+/**
+ * Before test, please start ${@link ConfigServer} and ${@link ConfigClient}
+ *
+ * @author lianglin
+ * @since 1.7.0
+ */
+@RestController
+@RequestMapping(value = "/test/dataSource/")
+public class SpringCouldDataSourceTest {
+
+
+ @Autowired
+ private SentinelRuleLocator locator;
+
+ Converter> converter = new Converter>() {
+ @Override
+ public List convert(String source) {
+ return JSON.parseArray(source, FlowRule.class);
+ }
+ };
+
+
+ @GetMapping("/get")
+ @ResponseBody
+ public List get() {
+ SpringCloudConfigDataSource dataSource = new SpringCloudConfigDataSource("flow_rule", converter);
+ FlowRuleManager.register2Property(dataSource.getProperty());
+ return FlowRuleManager.getRules();
+ }
+
+ /**
+ * WebHook refresh config
+ */
+ @GetMapping("/refresh")
+ @ResponseBody
+ public List refresh() {
+ locator.refresh();
+ return FlowRuleManager.getRules();
+ }
+}
diff --git a/sentinel-extension/sentinel-datasource-spring-cloud-config/src/test/resources/bootstrap.yml b/sentinel-extension/sentinel-datasource-spring-cloud-config/src/test/resources/bootstrap.yml
new file mode 100644
index 00000000..150d37eb
--- /dev/null
+++ b/sentinel-extension/sentinel-datasource-spring-cloud-config/src/test/resources/bootstrap.yml
@@ -0,0 +1,10 @@
+spring:
+ application:
+ name: sentinel
+ cloud:
+ config:
+ uri: http://localhost:10086/
+ profile: dev
+ label: master
+
+
diff --git a/sentinel-extension/sentinel-datasource-spring-cloud-config/src/test/resources/config-client-application.properties b/sentinel-extension/sentinel-datasource-spring-cloud-config/src/test/resources/config-client-application.properties
new file mode 100644
index 00000000..0528bbde
--- /dev/null
+++ b/sentinel-extension/sentinel-datasource-spring-cloud-config/src/test/resources/config-client-application.properties
@@ -0,0 +1,3 @@
+spring.application.name=sentinel
+server.port=8080
+
diff --git a/sentinel-extension/sentinel-datasource-spring-cloud-config/src/test/resources/config-server-application.properties b/sentinel-extension/sentinel-datasource-spring-cloud-config/src/test/resources/config-server-application.properties
new file mode 100644
index 00000000..4a0b7745
--- /dev/null
+++ b/sentinel-extension/sentinel-datasource-spring-cloud-config/src/test/resources/config-server-application.properties
@@ -0,0 +1,4 @@
+spring.cloud.config.server.git.uri=git@github.com:linlinisme/spring-cloud-config-datasource.git
+spring.cloud.config.server.git.search-paths=sentinel
+server.port=10086
+spring.cloud.config.label=master