This commit is contained in:
shuhongfan
2023-09-04 16:40:17 +08:00
commit cf5ac25c14
8267 changed files with 1305066 additions and 0 deletions

View File

@@ -0,0 +1,37 @@
# Sentinel DataSource Etcd
Sentinel DataSource Etcd provides integration with etcd so that etcd
can be the dynamic rule data source of Sentinel. The data source uses push model (watcher).
To use Sentinel DataSource Etcd, you should add the following dependency:
```xml
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-etcd</artifactId>
<version>x.y.z</version>
</dependency>
```
We could configure Etcd connection configuration by config file (for example `sentinel.properties`):
```
csp.sentinel.etcd.endpoints=http://ip1:port1,http://ip2:port2
csp.sentinel.etcd.user=your_user
csp.sentinel.etcd.password=your_password
csp.sentinel.etcd.charset=your_charset
csp.sentinel.etcd.auth.enable=true # if ture, then open user/password or ssl check
csp.sentinel.etcd.authority=authority # ssl
```
Or we could configure via JVM -D args or via `SentinelConfig.setConfig(key, value)`.
Then we can create an `EtcdDataSource` and register to rule managers. For instance:
```java
// `rule_key` is the rule config key
ReadableDataSource<String, List<FlowRule>> flowRuleEtcdDataSource = new EtcdDataSource<>(rule_key, (rule) -> JSON.parseArray(rule, FlowRule.class));
FlowRuleManager.register2Property(flowRuleEtcdDataSource.getProperty());
```
We've also provided an example: [sentinel-demo-etcd-datasource](https://github.com/alibaba/Sentinel/tree/master/sentinel-demo/sentinel-demo-etcd-datasource)

View File

@@ -0,0 +1,63 @@
<?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-etcd</artifactId>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<jetcd.version>0.3.0</jetcd.version>
</properties>
<dependencies>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-extension</artifactId>
</dependency>
<dependency>
<groupId>io.etcd</groupId>
<artifactId>jetcd-core</artifactId>
<version>${jetcd.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,68 @@
/*
* 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.etcd;
import com.alibaba.csp.sentinel.config.SentinelConfig;
import com.alibaba.csp.sentinel.util.StringUtil;
/**
* Etcd connection configuration.
*
* @author lianglin
* @since 1.7.0
*/
public final class EtcdConfig {
public final static String END_POINTS = "csp.sentinel.etcd.endpoints";
public final static String USER = "csp.sentinel.etcd.user";
public final static String PASSWORD = "csp.sentinel.etcd.password";
public final static String CHARSET = "csp.sentinel.etcd.charset";
public final static String AUTH_ENABLE = "csp.sentinel.etcd.auth.enable";
public final static String AUTHORITY = "csp.sentinel.etcd.authority";
private final static String ENABLED = "true";
public static String getEndPoints() {
return SentinelConfig.getConfig(END_POINTS);
}
public static String getUser() {
return SentinelConfig.getConfig(USER);
}
public static String getPassword() {
return SentinelConfig.getConfig(PASSWORD);
}
public static String getCharset() {
String etcdCharset = SentinelConfig.getConfig(CHARSET);
if (StringUtil.isNotBlank(etcdCharset)) {
return etcdCharset;
}
return SentinelConfig.charset();
}
public static boolean isAuthEnable() {
return ENABLED.equalsIgnoreCase(SentinelConfig.getConfig(AUTH_ENABLE));
}
public static String getAuthority() {
return SentinelConfig.getConfig(AUTHORITY);
}
private EtcdConfig() {}
}

View File

@@ -0,0 +1,124 @@
/*
* 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.etcd;
import com.alibaba.csp.sentinel.datasource.AbstractDataSource;
import com.alibaba.csp.sentinel.datasource.Converter;
import com.alibaba.csp.sentinel.log.RecordLog;
import io.etcd.jetcd.ByteSequence;
import io.etcd.jetcd.Client;
import io.etcd.jetcd.KeyValue;
import io.etcd.jetcd.Watch;
import io.etcd.jetcd.kv.GetResponse;
import io.etcd.jetcd.watch.WatchEvent;
import java.nio.charset.Charset;
import java.util.List;
import java.util.concurrent.CompletableFuture;
/**
* A read-only {@code DataSource} with Etcd backend. When the data in Etcd backend has been modified,
* Etcd will automatically push the new value so that the dynamic configuration can be real-time.
*
* @author lianglin
* @since 1.7.0
*/
public class EtcdDataSource<T> extends AbstractDataSource<String, T> {
private final Client client;
private Watch.Watcher watcher;
private final String key;
private Charset charset = Charset.forName(EtcdConfig.getCharset());
/**
* Create an etcd data-source. The connection configuration will be retrieved from {@link EtcdConfig}.
*
* @param key config key
* @param parser data parser
*/
public EtcdDataSource(String key, Converter<String, T> parser) {
super(parser);
if (!EtcdConfig.isAuthEnable()) {
this.client = Client.builder()
.endpoints(EtcdConfig.getEndPoints().split(",")).build();
} else {
this.client = Client.builder()
.endpoints(EtcdConfig.getEndPoints().split(","))
.user(ByteSequence.from(EtcdConfig.getUser(), charset))
.password(ByteSequence.from(EtcdConfig.getPassword(), charset))
.authority(EtcdConfig.getAuthority())
.build();
}
this.key = key;
loadInitialConfig();
initWatcher();
}
private void loadInitialConfig() {
try {
T newValue = loadConfig();
if (newValue == null) {
RecordLog.warn(
"[EtcdDataSource] Initial configuration is null, you may have to check your data source");
}
getProperty().updateValue(newValue);
} catch (Exception ex) {
RecordLog.warn("[EtcdDataSource] Error when loading initial configuration", ex);
}
}
private void initWatcher() {
watcher = client.getWatchClient().watch(ByteSequence.from(key, charset), (watchResponse) -> {
for (WatchEvent watchEvent : watchResponse.getEvents()) {
WatchEvent.EventType eventType = watchEvent.getEventType();
if (eventType == WatchEvent.EventType.PUT) {
try {
T newValue = loadConfig();
getProperty().updateValue(newValue);
} catch (Exception e) {
RecordLog.warn("[EtcdDataSource] Failed to update config", e);
}
} else if (eventType == WatchEvent.EventType.DELETE) {
RecordLog.info("[EtcdDataSource] Cleaning config for key <{}>", key);
getProperty().updateValue(null);
}
}
});
}
@Override
public String readSource() throws Exception {
CompletableFuture<GetResponse> responseFuture = client.getKVClient().get(ByteSequence.from(key, charset));
List<KeyValue> kvs = responseFuture.get().getKvs();
return kvs.size() == 0 ? null : kvs.get(0).getValue().toString(charset);
}
@Override
public void close() {
if (watcher != null) {
try {
watcher.close();
} catch (Exception ex) {
RecordLog.info("[EtcdDataSource] Failed to close watcher", ex);
}
}
if (client != null) {
client.close();
}
}
}

View File

@@ -0,0 +1,123 @@
/*
* 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.etcd;
import com.alibaba.csp.sentinel.config.SentinelConfig;
import com.alibaba.csp.sentinel.datasource.ReadableDataSource;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager;
import com.alibaba.fastjson.JSON;
import io.etcd.jetcd.ByteSequence;
import io.etcd.jetcd.Client;
import io.etcd.jetcd.KV;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
/**
* @author lianglin
* @since 1.7.0
*/
@Ignore(value = "Before run this test, you need to set up your etcd server.")
public class EtcdDataSourceTest {
private final String endPoints = "http://127.0.0.1:2379";
@Before
public void setUp() {
SentinelConfig.setConfig(EtcdConfig.END_POINTS, endPoints);
FlowRuleManager.loadRules(new ArrayList<>());
}
@After
public void tearDown() {
SentinelConfig.setConfig(EtcdConfig.END_POINTS, "");
FlowRuleManager.loadRules(new ArrayList<>());
}
@Test
public void testReadSource() throws Exception {
EtcdDataSource dataSource = new EtcdDataSource("foo", value -> value);
KV kvClient = Client.builder()
.endpoints(endPoints)
.build().getKVClient();
kvClient.put(ByteSequence.from("foo".getBytes()), ByteSequence.from("test".getBytes()));
Assert.assertNotNull(dataSource.readSource().equals("test"));
kvClient.put(ByteSequence.from("foo".getBytes()), ByteSequence.from("test2".getBytes()));
Assert.assertNotNull(dataSource.getProperty().equals("test2"));
}
@Test
public void testDynamicUpdate() throws InterruptedException {
String demo_key = "etcd_demo_key";
ReadableDataSource<String, List<FlowRule>> flowRuleEtcdDataSource = new EtcdDataSource<>(demo_key, (value) -> JSON.parseArray(value, FlowRule.class));
FlowRuleManager.register2Property(flowRuleEtcdDataSource.getProperty());
KV kvClient = Client.builder()
.endpoints(endPoints)
.build().getKVClient();
final String rule1 = "[\n"
+ " {\n"
+ " \"resource\": \"TestResource\",\n"
+ " \"controlBehavior\": 0,\n"
+ " \"count\": 5.0,\n"
+ " \"grade\": 1,\n"
+ " \"limitApp\": \"default\",\n"
+ " \"strategy\": 0\n"
+ " }\n"
+ "]";
kvClient.put(ByteSequence.from(demo_key.getBytes()), ByteSequence.from(rule1.getBytes()));
Thread.sleep(1000);
FlowRule flowRule = FlowRuleManager.getRules().get(0);
Assert.assertTrue(flowRule.getResource().equals("TestResource"));
Assert.assertTrue(flowRule.getCount() == 5.0);
Assert.assertTrue(flowRule.getGrade() == 1);
final String rule2 = "[\n"
+ " {\n"
+ " \"resource\": \"TestResource\",\n"
+ " \"controlBehavior\": 0,\n"
+ " \"count\": 6.0,\n"
+ " \"grade\": 3,\n"
+ " \"limitApp\": \"default\",\n"
+ " \"strategy\": 0\n"
+ " }\n"
+ "]";
kvClient.put(ByteSequence.from(demo_key.getBytes()), ByteSequence.from(rule2.getBytes()));
Thread.sleep(1000);
flowRule = FlowRuleManager.getRules().get(0);
Assert.assertTrue(flowRule.getResource().equals("TestResource"));
Assert.assertTrue(flowRule.getCount() == 6.0);
Assert.assertTrue(flowRule.getGrade() == 3);
}
}