@@ -19,6 +19,7 @@ | |||||
<module>sentinel-datasource-redis</module> | <module>sentinel-datasource-redis</module> | ||||
<module>sentinel-annotation-aspectj</module> | <module>sentinel-annotation-aspectj</module> | ||||
<module>sentinel-parameter-flow-control</module> | <module>sentinel-parameter-flow-control</module> | ||||
<module>sentinel-datasource-spring-cloud-config</module> | |||||
<module>sentinel-datasource-consul</module> | <module>sentinel-datasource-consul</module> | ||||
</modules> | </modules> | ||||
@@ -0,0 +1,48 @@ | |||||
# Sentinel DataSource SpringCloudConfig | |||||
Sentinel DataSource SpringCloudConfig provides integration with SpringCloudConfig so that SpringCloudConfig | |||||
can be the dynamic rule data source of Sentinel. | |||||
To use Sentinel DataSource SpringCloudConfig, you should add the following dependency: | |||||
```xml | |||||
<dependency> | |||||
<groupId>com.alibaba.csp</groupId> | |||||
<artifactId>sentinel-datasource-spring-cloud-config</artifactId> | |||||
<version>x.y.z</version> | |||||
</dependency> | |||||
``` | |||||
Then you can create an `SpringCloudConfigDataSource` and register to rule managers. | |||||
For instance: | |||||
```Java | |||||
//flow_rule is the propery key in SpringConfigConfig | |||||
SpringCloudConfigDataSource dataSource = new SpringCloudConfigDataSource("flow_rule", new Converter<String, List<FlowRule>>() { | |||||
@Override | |||||
public List<FlowRule> 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 | |||||
``` |
@@ -0,0 +1,88 @@ | |||||
<?xml version="1.0" encoding="UTF-8"?> | |||||
<project xmlns="http://maven.apache.org/POM/4.0.0" | |||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | |||||
<parent> | |||||
<artifactId>sentinel-extension</artifactId> | |||||
<groupId>com.alibaba.csp</groupId> | |||||
<version>1.7.0-SNAPSHOT</version> | |||||
</parent> | |||||
<modelVersion>4.0.0</modelVersion> | |||||
<artifactId>sentinel-datasource-spring-cloud-config</artifactId> | |||||
<packaging>jar</packaging> | |||||
<properties> | |||||
<spring.cloud.version>2.0.0.RELEASE</spring.cloud.version> | |||||
</properties> | |||||
<dependencies> | |||||
<dependency> | |||||
<groupId>com.alibaba.csp</groupId> | |||||
<artifactId>sentinel-datasource-extension</artifactId> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>org.springframework.cloud</groupId> | |||||
<artifactId>spring-cloud-starter-config</artifactId> | |||||
<version>${spring.cloud.version}</version> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>org.springframework.retry</groupId> | |||||
<artifactId>spring-retry</artifactId> | |||||
<version>1.2.4.RELEASE</version> | |||||
<exclusions> | |||||
<exclusion> | |||||
<groupId>org.springframework</groupId> | |||||
<artifactId>spring-core</artifactId> | |||||
</exclusion> | |||||
</exclusions> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>org.springframework.boot</groupId> | |||||
<artifactId>spring-boot-starter-test</artifactId> | |||||
<version>${spring.cloud.version}</version> | |||||
<scope>test</scope> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>org.springframework.cloud</groupId> | |||||
<artifactId>spring-cloud-config-server</artifactId> | |||||
<version>${spring.cloud.version}</version> | |||||
<scope>test</scope> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>org.springframework.boot</groupId> | |||||
<artifactId>spring-boot-starter-web</artifactId> | |||||
<version>${spring.cloud.version}</version> | |||||
<scope>test</scope> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>org.springframework</groupId> | |||||
<artifactId>spring-expression</artifactId> | |||||
<version>5.1.8.RELEASE</version> | |||||
<scope>test</scope> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>com.alibaba</groupId> | |||||
<artifactId>fastjson</artifactId> | |||||
<scope>test</scope> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>junit</groupId> | |||||
<artifactId>junit</artifactId> | |||||
<scope>test</scope> | |||||
</dependency> | |||||
</dependencies> | |||||
</project> |
@@ -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.*; | |||||
/** | |||||
* <p> | |||||
* {@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. | |||||
* </p> | |||||
* | |||||
* @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<String, Object> map = (Map<String, Object>) 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<PropertySource> 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<Environment> 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<Void> 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<String, String> headers = new HashMap<>(client.getHeaders()); | |||||
if (headers.containsKey(AUTHORIZATION)) { | |||||
// To avoid redundant addition of header | |||||
headers.remove(AUTHORIZATION); | |||||
} | |||||
if (!headers.isEmpty()) { | |||||
template.setInterceptors(Arrays.<ClientHttpRequestInterceptor>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<String, String> headers; | |||||
public GenericRequestHeaderInterceptor(Map<String, String> headers) { | |||||
this.headers = headers; | |||||
} | |||||
@Override | |||||
public ClientHttpResponse intercept(HttpRequest request, byte[] body, | |||||
ClientHttpRequestExecution execution) throws IOException { | |||||
for (Map.Entry<String, String> header : headers.entrySet()) { | |||||
request.getHeaders().add(header.getKey(), header.getValue()); | |||||
} | |||||
return execution.execute(request, body); | |||||
} | |||||
protected Map<String, String> getHeaders() { | |||||
return headers; | |||||
} | |||||
} | |||||
} |
@@ -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(); | |||||
} | |||||
} |
@@ -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<T> extends AbstractDataSource<String, T> { | |||||
private final static Map<SpringCloudConfigDataSource, SpringConfigListener> listeners; | |||||
static { | |||||
listeners = new ConcurrentHashMap<>(); | |||||
} | |||||
private String ruleKey; | |||||
public SpringCloudConfigDataSource(final String ruleKey, Converter<String, T> 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); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
@@ -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; | |||||
/** | |||||
* <p> | |||||
* Define the configuration Loaded when spring application start. | |||||
* Put it in META-INF/spring.factories, it will be auto loaded by Spring | |||||
* </p> | |||||
* | |||||
* @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; | |||||
} | |||||
} |
@@ -0,0 +1,2 @@ | |||||
org.springframework.cloud.bootstrap.BootstrapConfiguration=\ | |||||
com.alibaba.csp.sentinel.datasource.spring.cloud.config.config.DataSourceBootstrapConfiguration |
@@ -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); | |||||
} | |||||
} |
@@ -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); | |||||
} | |||||
} |
@@ -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); | |||||
} | |||||
} |
@@ -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"))); | |||||
} | |||||
} |
@@ -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<String, List<FlowRule>> converter = new Converter<String, List<FlowRule>>() { | |||||
@Override | |||||
public List<FlowRule> convert(String source) { | |||||
return JSON.parseArray(source, FlowRule.class); | |||||
} | |||||
}; | |||||
@GetMapping("/get") | |||||
@ResponseBody | |||||
public List<FlowRule> get() { | |||||
SpringCloudConfigDataSource dataSource = new SpringCloudConfigDataSource("flow_rule", converter); | |||||
FlowRuleManager.register2Property(dataSource.getProperty()); | |||||
return FlowRuleManager.getRules(); | |||||
} | |||||
/** | |||||
* WebHook refresh config | |||||
*/ | |||||
@GetMapping("/refresh") | |||||
@ResponseBody | |||||
public List<FlowRule> refresh() { | |||||
locator.refresh(); | |||||
return FlowRuleManager.getRules(); | |||||
} | |||||
} |
@@ -0,0 +1,10 @@ | |||||
spring: | |||||
application: | |||||
name: sentinel | |||||
cloud: | |||||
config: | |||||
uri: http://localhost:10086/ | |||||
profile: dev | |||||
label: master | |||||
@@ -0,0 +1,3 @@ | |||||
spring.application.name=sentinel | |||||
server.port=8080 | |||||
@@ -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 |