From 30158bcac0225afda543a839dc35f0c2ce348552 Mon Sep 17 00:00:00 2001 From: "Lin.Liang" <546648227@qq.com> Date: Tue, 20 Aug 2019 09:39:27 +0800 Subject: [PATCH] Add Spring Cloud Config data source extension (#899) --- sentinel-extension/pom.xml | 1 + .../README.md | 48 +++ .../pom.xml | 88 ++++++ .../cloud/config/SentinelRuleLocator.java | 294 ++++++++++++++++++ .../cloud/config/SentinelRuleStorage.java | 44 +++ .../config/SpringCloudConfigDataSource.java | 109 +++++++ .../DataSourceBootstrapConfiguration.java | 48 +++ .../main/resources/META-INF/spring.factories | 2 + .../cloud/config/SimpleSpringApplication.java | 32 ++ .../cloud/config/client/ConfigClient.java | 34 ++ .../cloud/config/server/ConfigServer.java | 35 +++ .../config/test/SentinelRuleLocatorTests.java | 71 +++++ .../test/SpringCouldDataSourceTest.java | 74 +++++ .../src/test/resources/bootstrap.yml | 10 + .../config-client-application.properties | 3 + .../config-server-application.properties | 4 + 16 files changed, 897 insertions(+) create mode 100644 sentinel-extension/sentinel-datasource-spring-cloud-config/README.md create mode 100644 sentinel-extension/sentinel-datasource-spring-cloud-config/pom.xml create mode 100644 sentinel-extension/sentinel-datasource-spring-cloud-config/src/main/java/com/alibaba/csp/sentinel/datasource/spring/cloud/config/SentinelRuleLocator.java create mode 100644 sentinel-extension/sentinel-datasource-spring-cloud-config/src/main/java/com/alibaba/csp/sentinel/datasource/spring/cloud/config/SentinelRuleStorage.java create mode 100644 sentinel-extension/sentinel-datasource-spring-cloud-config/src/main/java/com/alibaba/csp/sentinel/datasource/spring/cloud/config/SpringCloudConfigDataSource.java create mode 100644 sentinel-extension/sentinel-datasource-spring-cloud-config/src/main/java/com/alibaba/csp/sentinel/datasource/spring/cloud/config/config/DataSourceBootstrapConfiguration.java create mode 100644 sentinel-extension/sentinel-datasource-spring-cloud-config/src/main/resources/META-INF/spring.factories create mode 100644 sentinel-extension/sentinel-datasource-spring-cloud-config/src/test/java/com/alibaba/csp/sentinel/datasource/spring/cloud/config/SimpleSpringApplication.java create mode 100644 sentinel-extension/sentinel-datasource-spring-cloud-config/src/test/java/com/alibaba/csp/sentinel/datasource/spring/cloud/config/client/ConfigClient.java create mode 100644 sentinel-extension/sentinel-datasource-spring-cloud-config/src/test/java/com/alibaba/csp/sentinel/datasource/spring/cloud/config/server/ConfigServer.java create mode 100644 sentinel-extension/sentinel-datasource-spring-cloud-config/src/test/java/com/alibaba/csp/sentinel/datasource/spring/cloud/config/test/SentinelRuleLocatorTests.java create mode 100644 sentinel-extension/sentinel-datasource-spring-cloud-config/src/test/java/com/alibaba/csp/sentinel/datasource/spring/cloud/config/test/SpringCouldDataSourceTest.java create mode 100644 sentinel-extension/sentinel-datasource-spring-cloud-config/src/test/resources/bootstrap.yml create mode 100644 sentinel-extension/sentinel-datasource-spring-cloud-config/src/test/resources/config-client-application.properties create mode 100644 sentinel-extension/sentinel-datasource-spring-cloud-config/src/test/resources/config-server-application.properties diff --git a/sentinel-extension/pom.xml b/sentinel-extension/pom.xml index f7ae6b6a..5649d2e3 100755 --- a/sentinel-extension/pom.xml +++ b/sentinel-extension/pom.xml @@ -19,6 +19,7 @@ sentinel-datasource-redis sentinel-annotation-aspectj sentinel-parameter-flow-control + sentinel-datasource-spring-cloud-config sentinel-datasource-consul diff --git a/sentinel-extension/sentinel-datasource-spring-cloud-config/README.md b/sentinel-extension/sentinel-datasource-spring-cloud-config/README.md new file mode 100644 index 00000000..5637536d --- /dev/null +++ b/sentinel-extension/sentinel-datasource-spring-cloud-config/README.md @@ -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 + + com.alibaba.csp + sentinel-datasource-spring-cloud-config + x.y.z + +``` + +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>() { + @Override + public List convert(String source) { + return JSON.parseArray(source, FlowRule.class); + } + }); + FlowRuleManager.register2Property(dataSource.getProperty()); +``` + +If the client want to perceive the remote config changed, it can binding a git webhook callback with the ```com.alibaba.csp.sentinel.datasource.spring.cloud.config.SentinelRuleLocator.refresh``` + API. Like test demo ```com.alibaba.csp.sentinel.datasource.spring.cloud.config.test.SpringCouldDataSourceTest.refresh``` do. + + +We offer test cases and demo in: +[com.alibaba.csp.sentinel.datasource.spring.cloud.config.test]. +When you run test cases, please follow the steps: + +``` +//first start config server +com.alibaba.csp.sentinel.datasource.spring.cloud.config.server.ConfigServer + +//second start config client +com.alibaba.csp.sentinel.datasource.spring.cloud.config.client.ConfigClient + +//third run test cases and demo +com.alibaba.csp.sentinel.datasource.spring.cloud.config.test.SentinelRuleLocatorTests +com.alibaba.csp.sentinel.datasource.spring.cloud.config.test.SpringCouldDataSourceTest +``` \ No newline at end of file diff --git a/sentinel-extension/sentinel-datasource-spring-cloud-config/pom.xml b/sentinel-extension/sentinel-datasource-spring-cloud-config/pom.xml new file mode 100644 index 00000000..16e3ddd3 --- /dev/null +++ b/sentinel-extension/sentinel-datasource-spring-cloud-config/pom.xml @@ -0,0 +1,88 @@ + + + + sentinel-extension + com.alibaba.csp + 1.7.0-SNAPSHOT + + 4.0.0 + + sentinel-datasource-spring-cloud-config + jar + + + 2.0.0.RELEASE + + + + + + com.alibaba.csp + sentinel-datasource-extension + + + + org.springframework.cloud + spring-cloud-starter-config + ${spring.cloud.version} + + + + org.springframework.retry + spring-retry + 1.2.4.RELEASE + + + org.springframework + spring-core + + + + + + org.springframework.boot + spring-boot-starter-test + ${spring.cloud.version} + test + + + + org.springframework.cloud + spring-cloud-config-server + ${spring.cloud.version} + test + + + + org.springframework.boot + spring-boot-starter-web + ${spring.cloud.version} + test + + + + org.springframework + spring-expression + 5.1.8.RELEASE + test + + + + com.alibaba + fastjson + test + + + + junit + junit + test + + + + + + + \ No newline at end of file diff --git a/sentinel-extension/sentinel-datasource-spring-cloud-config/src/main/java/com/alibaba/csp/sentinel/datasource/spring/cloud/config/SentinelRuleLocator.java b/sentinel-extension/sentinel-datasource-spring-cloud-config/src/main/java/com/alibaba/csp/sentinel/datasource/spring/cloud/config/SentinelRuleLocator.java new file mode 100644 index 00000000..ca33dc5d --- /dev/null +++ b/sentinel-extension/sentinel-datasource-spring-cloud-config/src/main/java/com/alibaba/csp/sentinel/datasource/spring/cloud/config/SentinelRuleLocator.java @@ -0,0 +1,294 @@ +package com.alibaba.csp.sentinel.datasource.spring.cloud.config; + +import com.alibaba.csp.sentinel.log.RecordLog; +import org.springframework.cloud.bootstrap.config.PropertySourceLocator; +import org.springframework.cloud.config.client.ConfigClientProperties; +import org.springframework.cloud.config.client.ConfigClientStateHolder; +import org.springframework.cloud.config.environment.Environment; +import org.springframework.cloud.config.environment.PropertySource; +import org.springframework.core.annotation.Order; +import org.springframework.core.env.CompositePropertySource; +import org.springframework.core.env.MapPropertySource; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpRequest; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.http.client.ClientHttpRequestExecution; +import org.springframework.http.client.ClientHttpRequestInterceptor; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.http.client.SimpleClientHttpRequestFactory; +import org.springframework.retry.annotation.Retryable; +import org.springframework.util.Base64Utils; +import org.springframework.util.StringUtils; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.HttpServerErrorException; +import org.springframework.web.client.ResourceAccessException; +import org.springframework.web.client.RestTemplate; + +import java.io.IOException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.springframework.cloud.config.client.ConfigClientProperties.*; + +/** + *

+ * {@link SentinelRuleLocator} which pull sentinel rules from remote server. + * It retrieve configurations of spring-cloud-config client configurations from {@link org.springframework.core.env.Environment} + * Such as spring.cloud.config.uri=uri, spring.cloud.config.profile=profile .... and so on. + * When pull rules successfully, save to {@link SentinelRuleStorage} for ${@link SpringCloudConfigDataSource} retrieve. + *

+ * + * @author lianglin + * @since 1.7.0 + */ +@Order(0) +public class SentinelRuleLocator implements PropertySourceLocator { + + + private RestTemplate restTemplate; + private ConfigClientProperties defaultProperties; + private org.springframework.core.env.Environment environment; + + public SentinelRuleLocator(ConfigClientProperties defaultProperties, org.springframework.core.env.Environment environment) { + this.defaultProperties = defaultProperties; + this.environment = environment; + } + + + /** + * Responsible for pull data from remote server + * + * @param environment + * @return correct data if success else a empty propertySource or null + */ + @Override + @Retryable(interceptor = "configServerRetryInterceptor") + public org.springframework.core.env.PropertySource locate( + org.springframework.core.env.Environment environment) { + ConfigClientProperties properties = this.defaultProperties.override(environment); + CompositePropertySource composite = new CompositePropertySource("configService"); + RestTemplate restTemplate = this.restTemplate == null + ? getSecureRestTemplate(properties) + : this.restTemplate; + Exception error = null; + String errorBody = null; + try { + String[] labels = new String[]{""}; + if (StringUtils.hasText(properties.getLabel())) { + labels = StringUtils + .commaDelimitedListToStringArray(properties.getLabel()); + } + String state = ConfigClientStateHolder.getState(); + // Try all the labels until one works + for (String label : labels) { + Environment result = getRemoteEnvironment(restTemplate, properties, + label.trim(), state); + if (result != null) { + log(result); + // result.getPropertySources() can be null if using xml + if (result.getPropertySources() != null) { + for (PropertySource source : result.getPropertySources()) { + @SuppressWarnings("unchecked") + Map map = (Map) source + .getSource(); + composite.addPropertySource( + new MapPropertySource(source.getName(), map)); + } + } + SentinelRuleStorage.setRulesSource(composite); + return composite; + } + } + } catch (HttpServerErrorException e) { + error = e; + if (MediaType.APPLICATION_JSON + .includes(e.getResponseHeaders().getContentType())) { + errorBody = e.getResponseBodyAsString(); + } + } catch (Exception e) { + error = e; + } + if (properties.isFailFast()) { + throw new IllegalStateException( + "Could not locate PropertySource and the fail fast property is set, failing", + error); + } + RecordLog.warn("Could not locate PropertySource: " + (errorBody == null + ? error == null ? "label not found" : error.getMessage() + : errorBody)); + return null; + + } + + public org.springframework.core.env.PropertySource refresh() { + return locate(environment); + } + + private void log(Environment result) { + + RecordLog.info(String.format( + "Located environment: name=%s, profiles=%s, label=%s, version=%s, state=%s", + result.getName(), + result.getProfiles() == null ? "" + : Arrays.asList(result.getProfiles()), + result.getLabel(), result.getVersion(), result.getState())); + + List propertySourceList = result.getPropertySources(); + if (propertySourceList != null) { + int propertyCount = 0; + for (PropertySource propertySource : propertySourceList) { + propertyCount += propertySource.getSource().size(); + } + RecordLog.info(String.format( + "Environment %s has %d property sources with %d properties.", + result.getName(), result.getPropertySources().size(), + propertyCount)); + } + + + } + + + private Environment getRemoteEnvironment(RestTemplate restTemplate, + ConfigClientProperties properties, String label, String state) { + String path = "/{name}/{profile}"; + String name = properties.getName(); + String profile = properties.getProfile(); + String token = properties.getToken(); + int noOfUrls = properties.getUri().length; + if (noOfUrls > 1) { + RecordLog.info("Multiple Config Server Urls found listed."); + } + + RecordLog.info("properties = {0},label={1}, state={2}", properties, label, state); + + Object[] args = new String[]{name, profile}; + if (StringUtils.hasText(label)) { + if (label.contains("/")) { + label = label.replace("/", "(_)"); + } + args = new String[]{name, profile, label}; + path = path + "/{label}"; + } + ResponseEntity response = null; + + for (int i = 0; i < noOfUrls; i++) { + Credentials credentials = properties.getCredentials(i); + String uri = credentials.getUri(); + String username = credentials.getUsername(); + String password = credentials.getPassword(); + + RecordLog.info("Fetching config from server at : " + uri); + + try { + HttpHeaders headers = new HttpHeaders(); + addAuthorizationToken(properties, headers, username, password); + if (StringUtils.hasText(token)) { + headers.add(TOKEN_HEADER, token); + } + if (StringUtils.hasText(state) && properties.isSendState()) { + headers.add(STATE_HEADER, state); + } + + final HttpEntity entity = new HttpEntity<>((Void) null, headers); + response = restTemplate.exchange(uri + path, HttpMethod.GET, entity, + Environment.class, args); + } catch (HttpClientErrorException e) { + if (e.getStatusCode() != HttpStatus.NOT_FOUND) { + throw e; + } + } catch (ResourceAccessException e) { + RecordLog.info("Connect Timeout Exception on Url - " + uri + + ". Will be trying the next url if available"); + if (i == noOfUrls - 1) { + throw e; + } else { + continue; + } + } + + if (response == null || response.getStatusCode() != HttpStatus.OK) { + return null; + } + + Environment result = response.getBody(); + return result; + } + + return null; + } + + + private RestTemplate getSecureRestTemplate(ConfigClientProperties client) { + SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory(); + if (client.getRequestReadTimeout() < 0) { + throw new IllegalStateException("Invalid Value for Read Timeout set."); + } + requestFactory.setReadTimeout(client.getRequestReadTimeout()); + RestTemplate template = new RestTemplate(requestFactory); + Map headers = new HashMap<>(client.getHeaders()); + if (headers.containsKey(AUTHORIZATION)) { + // To avoid redundant addition of header + headers.remove(AUTHORIZATION); + } + if (!headers.isEmpty()) { + template.setInterceptors(Arrays.asList( + new GenericRequestHeaderInterceptor(headers))); + } + + return template; + } + + private void addAuthorizationToken(ConfigClientProperties configClientProperties, + HttpHeaders httpHeaders, String username, String password) { + String authorization = configClientProperties.getHeaders().get(AUTHORIZATION); + + if (password != null && authorization != null) { + throw new IllegalStateException( + "You must set either 'password' or 'authorization'"); + } + + if (password != null) { + byte[] token = Base64Utils.encode((username + ":" + password).getBytes()); + httpHeaders.add("Authorization", "Basic " + new String(token)); + } else if (authorization != null) { + httpHeaders.add("Authorization", authorization); + } + + } + + public void setRestTemplate(RestTemplate restTemplate) { + this.restTemplate = restTemplate; + } + + + public static class GenericRequestHeaderInterceptor + implements ClientHttpRequestInterceptor { + + private final Map headers; + + public GenericRequestHeaderInterceptor(Map headers) { + this.headers = headers; + } + + @Override + public ClientHttpResponse intercept(HttpRequest request, byte[] body, + ClientHttpRequestExecution execution) throws IOException { + for (Map.Entry header : headers.entrySet()) { + request.getHeaders().add(header.getKey(), header.getValue()); + } + return execution.execute(request, body); + } + + protected Map getHeaders() { + return headers; + } + + } +} diff --git a/sentinel-extension/sentinel-datasource-spring-cloud-config/src/main/java/com/alibaba/csp/sentinel/datasource/spring/cloud/config/SentinelRuleStorage.java b/sentinel-extension/sentinel-datasource-spring-cloud-config/src/main/java/com/alibaba/csp/sentinel/datasource/spring/cloud/config/SentinelRuleStorage.java new file mode 100644 index 00000000..9e8884cf --- /dev/null +++ b/sentinel-extension/sentinel-datasource-spring-cloud-config/src/main/java/com/alibaba/csp/sentinel/datasource/spring/cloud/config/SentinelRuleStorage.java @@ -0,0 +1,44 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.datasource.spring.cloud.config; + +import org.springframework.core.env.PropertySource; + +/** + * Storage data pull from spring-config-cloud server + * And notice ${@link SpringCloudConfigDataSource} update latest values + * + * @author lianglin + * @since 1.7.0 + */ +public class SentinelRuleStorage { + + public static PropertySource rulesSource; + + public static void setRulesSource(PropertySource source) { + rulesSource = source; + noticeSpringCloudDataSource(); + } + + public static String retrieveRule(String ruleKey) { + return rulesSource == null ? null : (String) rulesSource.getProperty(ruleKey); + } + + private static void noticeSpringCloudDataSource(){ + SpringCloudConfigDataSource.updateValues(); + } + +} diff --git a/sentinel-extension/sentinel-datasource-spring-cloud-config/src/main/java/com/alibaba/csp/sentinel/datasource/spring/cloud/config/SpringCloudConfigDataSource.java b/sentinel-extension/sentinel-datasource-spring-cloud-config/src/main/java/com/alibaba/csp/sentinel/datasource/spring/cloud/config/SpringCloudConfigDataSource.java new file mode 100644 index 00000000..085e3634 --- /dev/null +++ b/sentinel-extension/sentinel-datasource-spring-cloud-config/src/main/java/com/alibaba/csp/sentinel/datasource/spring/cloud/config/SpringCloudConfigDataSource.java @@ -0,0 +1,109 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.datasource.spring.cloud.config; + +import com.alibaba.csp.sentinel.datasource.AbstractDataSource; +import com.alibaba.csp.sentinel.datasource.Converter; +import com.alibaba.csp.sentinel.log.RecordLog; +import com.alibaba.csp.sentinel.util.StringUtil; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * ${@link SpringCloudConfigDataSource} A read-only {@code DataSource} with spring-cloud-config backend + * It retrieve the spring-cloud-config data stored in ${@link SentinelRuleStorage} + * When the data in backend has been modified, ${@link SentinelRuleStorage} will invoke ${@link SpringCloudConfigDataSource#updateValues()} + * to dynamic update values + * + * @author lianglin + * @since 1.7.0 + */ +public class SpringCloudConfigDataSource extends AbstractDataSource { + + + private final static Map listeners; + + static { + listeners = new ConcurrentHashMap<>(); + } + + private String ruleKey; + + + public SpringCloudConfigDataSource(final String ruleKey, Converter converter) { + super(converter); + if (StringUtil.isBlank(ruleKey)) { + throw new IllegalArgumentException(String.format("Bad argument: ruleKey=[%s]", ruleKey)); + } + + this.ruleKey = ruleKey; + loadInitialConfig(); + initListener(); + } + + private void loadInitialConfig() { + try { + T newValue = loadConfig(); + if (newValue == null) { + RecordLog.warn("[SpringCloudConfigDataSource] WARN: initial application is null, you may have to check your data source"); + } + getProperty().updateValue(newValue); + } catch (Exception ex) { + RecordLog.warn("[SpringCloudConfigDataSource] Error when loading initial application", ex); + } + } + + private void initListener() { + listeners.put(this, new SpringConfigListener(this)); + } + + @Override + public String readSource() { + return SentinelRuleStorage.retrieveRule(ruleKey); + } + + @Override + public void close() throws Exception { + listeners.remove(this); + } + + public static void updateValues() { + for (SpringConfigListener listener : listeners.values()) { + listener.listenChanged(); + } + } + + private static class SpringConfigListener { + + private SpringCloudConfigDataSource dataSource; + + public SpringConfigListener(SpringCloudConfigDataSource dataSource) { + this.dataSource = dataSource; + } + + public void listenChanged() { + try { + Object newValue = dataSource.loadConfig(); + dataSource.getProperty().updateValue(newValue); + } catch (Exception e) { + RecordLog.warn("[SpringConfigListener] load config error: ", e); + } + } + + } +} + diff --git a/sentinel-extension/sentinel-datasource-spring-cloud-config/src/main/java/com/alibaba/csp/sentinel/datasource/spring/cloud/config/config/DataSourceBootstrapConfiguration.java b/sentinel-extension/sentinel-datasource-spring-cloud-config/src/main/java/com/alibaba/csp/sentinel/datasource/spring/cloud/config/config/DataSourceBootstrapConfiguration.java new file mode 100644 index 00000000..a1c3390d --- /dev/null +++ b/sentinel-extension/sentinel-datasource-spring-cloud-config/src/main/java/com/alibaba/csp/sentinel/datasource/spring/cloud/config/config/DataSourceBootstrapConfiguration.java @@ -0,0 +1,48 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.datasource.spring.cloud.config.config; + +import com.alibaba.csp.sentinel.datasource.spring.cloud.config.SentinelRuleLocator; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.cloud.config.client.ConfigClientProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.env.ConfigurableEnvironment; + +/** + *

+ * Define the configuration Loaded when spring application start. + * Put it in META-INF/spring.factories, it will be auto loaded by Spring + *

+ * + * @author lianglin + * @since 1.7.0 + */ +@Configuration +public class DataSourceBootstrapConfiguration { + + @Autowired + private ConfigurableEnvironment environment; + + @Bean + public SentinelRuleLocator sentinelPropertySourceLocator(ConfigClientProperties properties) { + SentinelRuleLocator locator = new SentinelRuleLocator( + properties, environment); + return locator; + } + + +} diff --git a/sentinel-extension/sentinel-datasource-spring-cloud-config/src/main/resources/META-INF/spring.factories b/sentinel-extension/sentinel-datasource-spring-cloud-config/src/main/resources/META-INF/spring.factories new file mode 100644 index 00000000..31c250fc --- /dev/null +++ b/sentinel-extension/sentinel-datasource-spring-cloud-config/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.cloud.bootstrap.BootstrapConfiguration=\ +com.alibaba.csp.sentinel.datasource.spring.cloud.config.config.DataSourceBootstrapConfiguration diff --git a/sentinel-extension/sentinel-datasource-spring-cloud-config/src/test/java/com/alibaba/csp/sentinel/datasource/spring/cloud/config/SimpleSpringApplication.java b/sentinel-extension/sentinel-datasource-spring-cloud-config/src/test/java/com/alibaba/csp/sentinel/datasource/spring/cloud/config/SimpleSpringApplication.java new file mode 100644 index 00000000..b4a7a0bd --- /dev/null +++ b/sentinel-extension/sentinel-datasource-spring-cloud-config/src/test/java/com/alibaba/csp/sentinel/datasource/spring/cloud/config/SimpleSpringApplication.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2018 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.alibaba.csp.sentinel.datasource.spring.cloud.config; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * @author lianglin + * @since 1.7.0 + */ +@SpringBootApplication +public abstract class SimpleSpringApplication { + public static void main(String[] args) { + SpringApplication.run(SimpleSpringApplication.class); + } + +} diff --git a/sentinel-extension/sentinel-datasource-spring-cloud-config/src/test/java/com/alibaba/csp/sentinel/datasource/spring/cloud/config/client/ConfigClient.java b/sentinel-extension/sentinel-datasource-spring-cloud-config/src/test/java/com/alibaba/csp/sentinel/datasource/spring/cloud/config/client/ConfigClient.java new file mode 100644 index 00000000..03c5ebd3 --- /dev/null +++ b/sentinel-extension/sentinel-datasource-spring-cloud-config/src/test/java/com/alibaba/csp/sentinel/datasource/spring/cloud/config/client/ConfigClient.java @@ -0,0 +1,34 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.datasource.spring.cloud.config.client; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.PropertySource; + +/** + * @author lianglin + * @since 1.7.0 + */ +@SpringBootApplication +@ComponentScan("com.alibaba.csp.sentinel.datasource.spring.cloud.config.test") +@PropertySource("classpath:config-client-application.properties") +public class ConfigClient { + public static void main(String[] args) { + SpringApplication.run(ConfigClient.class); + } +} diff --git a/sentinel-extension/sentinel-datasource-spring-cloud-config/src/test/java/com/alibaba/csp/sentinel/datasource/spring/cloud/config/server/ConfigServer.java b/sentinel-extension/sentinel-datasource-spring-cloud-config/src/test/java/com/alibaba/csp/sentinel/datasource/spring/cloud/config/server/ConfigServer.java new file mode 100644 index 00000000..14d46fb2 --- /dev/null +++ b/sentinel-extension/sentinel-datasource-spring-cloud-config/src/test/java/com/alibaba/csp/sentinel/datasource/spring/cloud/config/server/ConfigServer.java @@ -0,0 +1,35 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.datasource.spring.cloud.config.server; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.config.server.EnableConfigServer; +import org.springframework.context.annotation.PropertySource; + +/** + * @author lianglin + * @since 1.7.0 + */ +@EnableConfigServer +@SpringBootApplication +@PropertySource("classpath:config-server-application.properties") +public class ConfigServer { + public static void main(String[] args) { + SpringApplication.run(ConfigServer.class); + + } +} diff --git a/sentinel-extension/sentinel-datasource-spring-cloud-config/src/test/java/com/alibaba/csp/sentinel/datasource/spring/cloud/config/test/SentinelRuleLocatorTests.java b/sentinel-extension/sentinel-datasource-spring-cloud-config/src/test/java/com/alibaba/csp/sentinel/datasource/spring/cloud/config/test/SentinelRuleLocatorTests.java new file mode 100644 index 00000000..a6fbf73e --- /dev/null +++ b/sentinel-extension/sentinel-datasource-spring-cloud-config/src/test/java/com/alibaba/csp/sentinel/datasource/spring/cloud/config/test/SentinelRuleLocatorTests.java @@ -0,0 +1,71 @@ +/* + * Copyright 2018-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.alibaba.csp.sentinel.datasource.spring.cloud.config.test; + +import com.alibaba.csp.sentinel.datasource.spring.cloud.config.SentinelRuleLocator; +import com.alibaba.csp.sentinel.datasource.spring.cloud.config.SentinelRuleStorage; +import com.alibaba.csp.sentinel.datasource.spring.cloud.config.config.DataSourceBootstrapConfiguration; +import com.alibaba.csp.sentinel.datasource.spring.cloud.config.server.ConfigServer; +import com.alibaba.csp.sentinel.util.StringUtil; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.cloud.config.client.ConfigClientProperties; +import org.springframework.core.env.Environment; +import org.springframework.test.context.junit4.SpringRunner; + +/** + * @author lianglin + * @since 1.7.0 + */ +@RunWith(SpringRunner.class) +@SpringBootTest(classes = DataSourceBootstrapConfiguration.class, properties = { + "spring.application.name=sentinel" +}) +public class SentinelRuleLocatorTests { + + + @Autowired + private SentinelRuleLocator sentinelRulesSourceLocator; + + @Autowired + private Environment environment; + + @Test + public void testAutoLoad() { + Assert.assertTrue(sentinelRulesSourceLocator != null); + Assert.assertTrue(environment != null); + } + + + /** + * Before run this test case, please start the Config Server ${@link ConfigServer} + */ + public void testLocate() { + ConfigClientProperties configClientProperties = new ConfigClientProperties(environment); + configClientProperties.setLabel("master"); + configClientProperties.setProfile("dev"); + configClientProperties.setUri(new String[]{"http://localhost:10086/"}); + SentinelRuleLocator sentinelRulesSourceLocator = new SentinelRuleLocator(configClientProperties, environment); + sentinelRulesSourceLocator.locate(environment); + Assert.assertTrue(StringUtil.isNotBlank(SentinelRuleStorage.retrieveRule("flow_rule"))); + + } + +} diff --git a/sentinel-extension/sentinel-datasource-spring-cloud-config/src/test/java/com/alibaba/csp/sentinel/datasource/spring/cloud/config/test/SpringCouldDataSourceTest.java b/sentinel-extension/sentinel-datasource-spring-cloud-config/src/test/java/com/alibaba/csp/sentinel/datasource/spring/cloud/config/test/SpringCouldDataSourceTest.java new file mode 100644 index 00000000..589a3ba9 --- /dev/null +++ b/sentinel-extension/sentinel-datasource-spring-cloud-config/src/test/java/com/alibaba/csp/sentinel/datasource/spring/cloud/config/test/SpringCouldDataSourceTest.java @@ -0,0 +1,74 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.datasource.spring.cloud.config.test; + +import com.alibaba.csp.sentinel.datasource.Converter; +import com.alibaba.csp.sentinel.datasource.spring.cloud.config.SentinelRuleLocator; +import com.alibaba.csp.sentinel.datasource.spring.cloud.config.SpringCloudConfigDataSource; +import com.alibaba.csp.sentinel.datasource.spring.cloud.config.client.ConfigClient; +import com.alibaba.csp.sentinel.datasource.spring.cloud.config.server.ConfigServer; +import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; +import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; +import com.alibaba.fastjson.JSON; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +/** + * Before test, please start ${@link ConfigServer} and ${@link ConfigClient} + * + * @author lianglin + * @since 1.7.0 + */ +@RestController +@RequestMapping(value = "/test/dataSource/") +public class SpringCouldDataSourceTest { + + + @Autowired + private SentinelRuleLocator locator; + + Converter> converter = new Converter>() { + @Override + public List convert(String source) { + return JSON.parseArray(source, FlowRule.class); + } + }; + + + @GetMapping("/get") + @ResponseBody + public List get() { + SpringCloudConfigDataSource dataSource = new SpringCloudConfigDataSource("flow_rule", converter); + FlowRuleManager.register2Property(dataSource.getProperty()); + return FlowRuleManager.getRules(); + } + + /** + * WebHook refresh config + */ + @GetMapping("/refresh") + @ResponseBody + public List refresh() { + locator.refresh(); + return FlowRuleManager.getRules(); + } +} diff --git a/sentinel-extension/sentinel-datasource-spring-cloud-config/src/test/resources/bootstrap.yml b/sentinel-extension/sentinel-datasource-spring-cloud-config/src/test/resources/bootstrap.yml new file mode 100644 index 00000000..150d37eb --- /dev/null +++ b/sentinel-extension/sentinel-datasource-spring-cloud-config/src/test/resources/bootstrap.yml @@ -0,0 +1,10 @@ +spring: + application: + name: sentinel + cloud: + config: + uri: http://localhost:10086/ + profile: dev + label: master + + diff --git a/sentinel-extension/sentinel-datasource-spring-cloud-config/src/test/resources/config-client-application.properties b/sentinel-extension/sentinel-datasource-spring-cloud-config/src/test/resources/config-client-application.properties new file mode 100644 index 00000000..0528bbde --- /dev/null +++ b/sentinel-extension/sentinel-datasource-spring-cloud-config/src/test/resources/config-client-application.properties @@ -0,0 +1,3 @@ +spring.application.name=sentinel +server.port=8080 + diff --git a/sentinel-extension/sentinel-datasource-spring-cloud-config/src/test/resources/config-server-application.properties b/sentinel-extension/sentinel-datasource-spring-cloud-config/src/test/resources/config-server-application.properties new file mode 100644 index 00000000..4a0b7745 --- /dev/null +++ b/sentinel-extension/sentinel-datasource-spring-cloud-config/src/test/resources/config-server-application.properties @@ -0,0 +1,4 @@ +spring.cloud.config.server.git.uri=git@github.com:linlinisme/spring-cloud-config-datasource.git +spring.cloud.config.server.git.search-paths=sentinel +server.port=10086 +spring.cloud.config.label=master