@@ -19,6 +19,7 @@ | |||
<module>sentinel-datasource-redis</module> | |||
<module>sentinel-annotation-aspectj</module> | |||
<module>sentinel-parameter-flow-control</module> | |||
<module>sentinel-datasource-spring-cloud-config</module> | |||
<module>sentinel-datasource-consul</module> | |||
</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 |