init
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
# Sentinel DataSource Spring Cloud Config
|
||||
|
||||
Sentinel DataSource Spring Cloud Config provides integration with Spring Cloud Config
|
||||
so that Spring Cloud Config can be the dynamic rule data source of Sentinel.
|
||||
|
||||
To use Sentinel DataSource Spring Cloud Config, 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
|
||||
ReadableDataSource<String, List<FlowRule>> flowRuleDs = new SpringCloudConfigDataSource<>(ruleKey, s -> JSON.parseArray(s, FlowRule.class));
|
||||
FlowRuleManager.register2Property(flowRuleDs.getProperty());
|
||||
```
|
||||
|
||||
To notify the client that the remote config has changed, we could bind a git webhook callback with the
|
||||
`com.alibaba.csp.sentinel.datasource.spring.cloud.config.SentinelRuleLocator.refresh` API.
|
||||
We may refer to the the sample `com.alibaba.csp.sentinel.datasource.spring.cloud.config.test.SpringCouldDataSourceTest#refresh` in test cases.
|
||||
|
||||
We offer test cases and demo in the package: `com.alibaba.csp.sentinel.datasource.spring.cloud.config.test`.
|
||||
When you are running test cases, please follow the steps:
|
||||
|
||||
```
|
||||
// First, start the Spring Cloud config server
|
||||
com.alibaba.csp.sentinel.datasource.spring.cloud.config.server.ConfigServer
|
||||
|
||||
// Second, start the Spring Cloud config client
|
||||
com.alibaba.csp.sentinel.datasource.spring.cloud.config.client.ConfigClient
|
||||
|
||||
// Third, run the 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.8.3</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,301 @@
|
||||
/*
|
||||
* Copyright 1999-2019 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.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 pulls Sentinel rules from remote server.
|
||||
* It retrieves configurations of spring-cloud-config client configurations from
|
||||
* {@link org.springframework.core.env.Environment}, such as {@code spring.cloud.config.uri=uri},
|
||||
* {@code spring.cloud.config.profile=profile}, and so on.
|
||||
* When rules are pulled successfully, it will be stored to {@link SentinelRuleStorage}.
|
||||
* </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("Located environment: name={}, profiles={}, label={}, version={}, state={}",
|
||||
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("[SentinelRuleLocator] Environment {} has {} property sources with {} 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.debug("[SentinelRuleLocator] Multiple Config Server Urls found listed.");
|
||||
}
|
||||
|
||||
RecordLog.info("[SentinelRuleLocator] getRemoteEnvironment, properties={}, label={}, state={}",
|
||||
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("[SentinelRuleLocator] 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.warn("[SentinelRuleLocator] ConnectTimeoutException on url <{}>."
|
||||
+ " Will be trying the next url if available", uri);
|
||||
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,111 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* <p>A read-only {@code DataSource} with Spring Cloud Config backend.</p>
|
||||
* <p>
|
||||
* It retrieves the Spring Cloud Config data stored in {@link SentinelRuleStorage}.
|
||||
* When the data in the backend has been modified, {@link SentinelRuleStorage} will
|
||||
* invoke {@link SpringCloudConfigDataSource#updateValues()} to update values dynamically.
|
||||
* </p>
|
||||
* <p>
|
||||
* To notify the client that the remote config has changed, users could bind a git
|
||||
* webhook callback with the {@link SentinelRuleLocator#refresh()} API.
|
||||
* </p>
|
||||
*
|
||||
* @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 final 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
|
||||
Reference in New Issue
Block a user