chore(project): 添加项目配置文件和忽略规则
- 添加 Babel 配置文件支持 ES6+ 语法转换 - 添加 ESLint 忽略规则和配置文件 - 添加 Git 忽略规则文件 - 添加 Travis CI 配置文件 - 添加 1.4.2 版本变更日志文件 - 添加 Helm 图表辅助模板文件 - 添加 Helm 忽略规则文件
This commit is contained in:
60
discovery/seata-discovery-etcd3/pom.xml
Normal file
60
discovery/seata-discovery-etcd3/pom.xml
Normal file
@@ -0,0 +1,60 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ Copyright 1999-2019 Seata.io Group.
|
||||
~
|
||||
~ 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.
|
||||
-->
|
||||
<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>
|
||||
<groupId>io.seata</groupId>
|
||||
<artifactId>seata-discovery</artifactId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<artifactId>seata-discovery-etcd3</artifactId>
|
||||
<name>seata-discovery-etcd3 ${project.version}</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>seata-discovery-core</artifactId>
|
||||
<version>${project.parent.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.etcd</groupId>
|
||||
<artifactId>jetcd-core</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
</dependency>
|
||||
<!--test-->
|
||||
<dependency>
|
||||
<groupId>io.etcd</groupId>
|
||||
<artifactId>jetcd-launcher</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>testcontainers</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* 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 io.seata.discovery.registry.etcd3;
|
||||
|
||||
import io.seata.common.loader.LoadLevel;
|
||||
import io.seata.discovery.registry.RegistryProvider;
|
||||
import io.seata.discovery.registry.RegistryService;
|
||||
|
||||
/**
|
||||
* @author xingfudeshi@gmail.com
|
||||
*/
|
||||
@LoadLevel(name = "Etcd3", order = 1)
|
||||
public class EtcdRegistryProvider implements RegistryProvider {
|
||||
@Override
|
||||
public RegistryService provide() {
|
||||
return EtcdRegistryServiceImpl.getInstance();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,439 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* 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 io.seata.discovery.registry.etcd3;
|
||||
|
||||
|
||||
import io.etcd.jetcd.ByteSequence;
|
||||
import io.etcd.jetcd.Client;
|
||||
import io.etcd.jetcd.Lease;
|
||||
import io.etcd.jetcd.Watch;
|
||||
import io.etcd.jetcd.kv.GetResponse;
|
||||
import io.etcd.jetcd.lease.LeaseTimeToLiveResponse;
|
||||
import io.etcd.jetcd.options.GetOption;
|
||||
import io.etcd.jetcd.options.LeaseOption;
|
||||
import io.etcd.jetcd.options.PutOption;
|
||||
import io.etcd.jetcd.options.WatchOption;
|
||||
import io.etcd.jetcd.watch.WatchResponse;
|
||||
import io.seata.common.exception.ShouldNeverHappenException;
|
||||
import io.seata.common.thread.NamedThreadFactory;
|
||||
import io.seata.common.util.NetUtil;
|
||||
import io.seata.common.util.StringUtils;
|
||||
import io.seata.config.Configuration;
|
||||
import io.seata.config.ConfigurationFactory;
|
||||
import io.seata.discovery.registry.RegistryService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static io.netty.util.CharsetUtil.UTF_8;
|
||||
|
||||
/**
|
||||
* @author xingfudeshi@gmail.com
|
||||
*/
|
||||
public class EtcdRegistryServiceImpl implements RegistryService<Watch.Listener> {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(EtcdRegistryServiceImpl.class);
|
||||
private static final Configuration FILE_CONFIG = ConfigurationFactory.CURRENT_FILE_INSTANCE;
|
||||
private static final String FILE_ROOT_REGISTRY = "registry";
|
||||
private static final String FILE_CONFIG_SPLIT_CHAR = ".";
|
||||
private static final String REGISTRY_TYPE = "etcd3";
|
||||
private static final String SERVER_ADDR_KEY = "serverAddr";
|
||||
private static final String REGISTRY_CLUSTER = "cluster";
|
||||
private static final String DEFAULT_CLUSTER_NAME = "default";
|
||||
private static final String REGISTRY_KEY_PREFIX = "registry-seata-";
|
||||
private static final String FILE_CONFIG_KEY_PREFIX = FILE_ROOT_REGISTRY + FILE_CONFIG_SPLIT_CHAR + REGISTRY_TYPE + FILE_CONFIG_SPLIT_CHAR;
|
||||
private static final int MAP_INITIAL_CAPACITY = 8;
|
||||
private static final int THREAD_POOL_SIZE = 2;
|
||||
private ExecutorService executorService;
|
||||
/**
|
||||
* TTL for lease
|
||||
*/
|
||||
private static final long TTL = 10;
|
||||
/**
|
||||
* interval for life keep
|
||||
*/
|
||||
private final static long LIFE_KEEP_INTERVAL = 5;
|
||||
/**
|
||||
* critical value for life keep
|
||||
*/
|
||||
private final static long LIFE_KEEP_CRITICAL = 6;
|
||||
private static volatile EtcdRegistryServiceImpl instance;
|
||||
private static volatile Client client;
|
||||
private ConcurrentMap<String, Pair<Long /*revision*/, List<InetSocketAddress>>> clusterAddressMap;
|
||||
private ConcurrentMap<String, Set<Watch.Listener>> listenerMap;
|
||||
private ConcurrentMap<String, EtcdWatcher> watcherMap;
|
||||
private static long leaseId = 0;
|
||||
private EtcdLifeKeeper lifeKeeper = null;
|
||||
private Future<Boolean> lifeKeeperFuture = null;
|
||||
/**
|
||||
* a endpoint for unit testing
|
||||
*/
|
||||
public static final String TEST_ENDPONT = "etcd-test-lancher-endpoint";
|
||||
|
||||
|
||||
private EtcdRegistryServiceImpl() {
|
||||
clusterAddressMap = new ConcurrentHashMap<>(MAP_INITIAL_CAPACITY);
|
||||
listenerMap = new ConcurrentHashMap<>(MAP_INITIAL_CAPACITY);
|
||||
watcherMap = new ConcurrentHashMap<>(MAP_INITIAL_CAPACITY);
|
||||
executorService = new ThreadPoolExecutor(THREAD_POOL_SIZE, THREAD_POOL_SIZE, Integer.MAX_VALUE, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(), new NamedThreadFactory("registry-etcd3", THREAD_POOL_SIZE));
|
||||
}
|
||||
|
||||
/**
|
||||
* get etcd registry service instance
|
||||
*
|
||||
* @return instance
|
||||
*/
|
||||
static EtcdRegistryServiceImpl getInstance() {
|
||||
if (instance == null) {
|
||||
synchronized (EtcdRegistryServiceImpl.class) {
|
||||
if (instance == null) {
|
||||
instance = new EtcdRegistryServiceImpl();
|
||||
}
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void register(InetSocketAddress address) throws Exception {
|
||||
NetUtil.validAddress(address);
|
||||
doRegister(address);
|
||||
}
|
||||
|
||||
/**
|
||||
* do registry
|
||||
*
|
||||
* @param address
|
||||
*/
|
||||
private void doRegister(InetSocketAddress address) throws Exception {
|
||||
PutOption putOption = PutOption.newBuilder().withLeaseId(getLeaseId()).build();
|
||||
getClient().getKVClient().put(buildRegistryKey(address), buildRegistryValue(address), putOption).get();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void unregister(InetSocketAddress address) throws Exception {
|
||||
NetUtil.validAddress(address);
|
||||
doUnregister(address);
|
||||
}
|
||||
|
||||
/**
|
||||
* do unregister
|
||||
*
|
||||
* @param address
|
||||
* @throws Exception
|
||||
*/
|
||||
private void doUnregister(InetSocketAddress address) throws Exception {
|
||||
getClient().getKVClient().delete(buildRegistryKey(address)).get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void subscribe(String cluster, Watch.Listener listener) throws Exception {
|
||||
listenerMap.computeIfAbsent(cluster, key -> new HashSet<>())
|
||||
.add(listener);
|
||||
EtcdWatcher watcher = watcherMap.computeIfAbsent(cluster, w -> new EtcdWatcher(cluster, listener));
|
||||
executorService.submit(watcher);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unsubscribe(String cluster, Watch.Listener listener) throws Exception {
|
||||
Set<Watch.Listener> subscribeSet = listenerMap.get(cluster);
|
||||
if (subscribeSet != null) {
|
||||
Set<Watch.Listener> newSubscribeSet = subscribeSet.stream()
|
||||
.filter(eventListener -> !eventListener.equals(listener))
|
||||
.collect(Collectors.toSet());
|
||||
listenerMap.put(cluster, newSubscribeSet);
|
||||
}
|
||||
watcherMap.remove(cluster).stop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<InetSocketAddress> lookup(String key) throws Exception {
|
||||
final String cluster = getServiceGroup(key);
|
||||
if (cluster == null) {
|
||||
return null;
|
||||
}
|
||||
if (!listenerMap.containsKey(cluster)) {
|
||||
//1.refresh
|
||||
refreshCluster(cluster);
|
||||
//2.subscribe
|
||||
subscribe(cluster, new Watch.Listener() {
|
||||
@Override
|
||||
public void onNext(WatchResponse response) {
|
||||
try {
|
||||
refreshCluster(cluster);
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("etcd watch listener", e);
|
||||
throw new RuntimeException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable throwable) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCompleted() {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
Pair<Long, List<InetSocketAddress>> pair = clusterAddressMap.get(cluster);
|
||||
return Objects.isNull(pair) ? Collections.emptyList() : pair.getValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws Exception {
|
||||
if (lifeKeeper != null) {
|
||||
lifeKeeper.stop();
|
||||
if (lifeKeeperFuture != null) {
|
||||
lifeKeeperFuture.get(3, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* refresh cluster
|
||||
*
|
||||
* @param cluster
|
||||
* @throws Exception
|
||||
*/
|
||||
private void refreshCluster(String cluster) throws Exception {
|
||||
if (cluster == null) {
|
||||
return;
|
||||
}
|
||||
//1.get all available registries
|
||||
GetOption getOption = GetOption.newBuilder().withPrefix(buildRegistryKeyPrefix(cluster)).build();
|
||||
GetResponse getResponse = getClient().getKVClient().get(buildRegistryKeyPrefix(cluster), getOption).get();
|
||||
//2.add to list
|
||||
List<InetSocketAddress> instanceList = getResponse.getKvs().stream().map(keyValue -> {
|
||||
String[] instanceInfo = keyValue.getValue().toString(UTF_8).split(":");
|
||||
return new InetSocketAddress(instanceInfo[0], Integer.parseInt(instanceInfo[1]));
|
||||
}).collect(Collectors.toList());
|
||||
clusterAddressMap.put(cluster, new Pair<>(getResponse.getHeader().getRevision(), instanceList));
|
||||
}
|
||||
|
||||
/**
|
||||
* get client
|
||||
*
|
||||
* @return client
|
||||
*/
|
||||
private Client getClient() {
|
||||
if (client == null) {
|
||||
synchronized (EtcdRegistryServiceImpl.class) {
|
||||
if (client == null) {
|
||||
String testEndpoint = System.getProperty(TEST_ENDPONT);
|
||||
if (StringUtils.isNotBlank(testEndpoint)) {
|
||||
client = Client.builder().endpoints(testEndpoint).build();
|
||||
} else {
|
||||
client = Client.builder().endpoints(FILE_CONFIG.getConfig(FILE_CONFIG_KEY_PREFIX + SERVER_ADDR_KEY)).build();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return client;
|
||||
}
|
||||
|
||||
/**
|
||||
* get cluster name
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private String getClusterName() {
|
||||
String clusterConfigName = String.join(FILE_CONFIG_SPLIT_CHAR, FILE_ROOT_REGISTRY, REGISTRY_TYPE, REGISTRY_CLUSTER);
|
||||
return FILE_CONFIG.getConfig(clusterConfigName, DEFAULT_CLUSTER_NAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* create a new lease id or return a existing lease id
|
||||
*/
|
||||
private long getLeaseId() throws Exception {
|
||||
if (0 == leaseId) {
|
||||
//create a new lease
|
||||
leaseId = getClient().getLeaseClient().grant(TTL).get().getID();
|
||||
lifeKeeper = new EtcdLifeKeeper(leaseId);
|
||||
lifeKeeperFuture = executorService.submit(lifeKeeper);
|
||||
}
|
||||
return leaseId;
|
||||
}
|
||||
|
||||
/**
|
||||
* build registry key
|
||||
*
|
||||
* @return registry key
|
||||
*/
|
||||
private ByteSequence buildRegistryKey(InetSocketAddress address) {
|
||||
return ByteSequence.from(REGISTRY_KEY_PREFIX + getClusterName() + "-" + NetUtil.toStringAddress(address), UTF_8);
|
||||
}
|
||||
|
||||
/**
|
||||
* build registry key prefix
|
||||
*
|
||||
* @return registry key prefix
|
||||
*/
|
||||
private ByteSequence buildRegistryKeyPrefix(String cluster) {
|
||||
return ByteSequence.from(REGISTRY_KEY_PREFIX + cluster, UTF_8);
|
||||
}
|
||||
|
||||
/**
|
||||
* build registry value
|
||||
*
|
||||
* @param address
|
||||
* @return registry value
|
||||
*/
|
||||
private ByteSequence buildRegistryValue(InetSocketAddress address) {
|
||||
return ByteSequence.from(NetUtil.toStringAddress(address), UTF_8);
|
||||
}
|
||||
|
||||
/**
|
||||
* the type etcd life keeper
|
||||
*/
|
||||
private class EtcdLifeKeeper implements Callable<Boolean> {
|
||||
private final long leaseId;
|
||||
private final Lease leaseClient;
|
||||
private boolean running;
|
||||
|
||||
|
||||
public EtcdLifeKeeper(long leaseId) {
|
||||
this.leaseClient = getClient().getLeaseClient();
|
||||
this.leaseId = leaseId;
|
||||
this.running = true;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* process
|
||||
*/
|
||||
private void process() {
|
||||
for (; ; ) {
|
||||
try {
|
||||
//1.get TTL
|
||||
LeaseTimeToLiveResponse leaseTimeToLiveResponse = this.leaseClient.timeToLive(this.leaseId, LeaseOption.DEFAULT).get();
|
||||
final long tTl = leaseTimeToLiveResponse.getTTl();
|
||||
if (tTl <= LIFE_KEEP_CRITICAL) {
|
||||
//2.refresh the TTL
|
||||
this.leaseClient.keepAliveOnce(this.leaseId).get();
|
||||
}
|
||||
TimeUnit.SECONDS.sleep(LIFE_KEEP_INTERVAL);
|
||||
} catch (Exception e) {
|
||||
LOGGER.error("EtcdLifeKeeper", e);
|
||||
throw new ShouldNeverHappenException("failed to renewal the lease.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* stop this task
|
||||
*/
|
||||
public void stop() {
|
||||
this.running = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean call() {
|
||||
if (this.running) {
|
||||
process();
|
||||
}
|
||||
return this.running;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* the type etcd watcher
|
||||
*/
|
||||
private class EtcdWatcher implements Runnable {
|
||||
private final Watch.Listener listener;
|
||||
private Watch.Watcher watcher;
|
||||
private String cluster;
|
||||
|
||||
public EtcdWatcher(String cluster, Watch.Listener listener) {
|
||||
this.cluster = cluster;
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Watch watchClient = getClient().getWatchClient();
|
||||
WatchOption.Builder watchOptionBuilder = WatchOption.newBuilder().withPrefix(buildRegistryKeyPrefix(cluster));
|
||||
Pair<Long /*revision*/, List<InetSocketAddress>> addressPair = clusterAddressMap.get(cluster);
|
||||
if (Objects.nonNull(addressPair)) {
|
||||
// Maybe addressPair isn't newest now, but it's ok
|
||||
watchOptionBuilder.withRevision(addressPair.getKey());
|
||||
}
|
||||
this.watcher = watchClient.watch(buildRegistryKeyPrefix(cluster), watchOptionBuilder.build(), this.listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* stop this task
|
||||
*/
|
||||
public void stop() {
|
||||
this.watcher.close();
|
||||
}
|
||||
}
|
||||
|
||||
private static class Pair<K,V> {
|
||||
|
||||
/**
|
||||
* Key of this <code>Pair</code>.
|
||||
*/
|
||||
private K key;
|
||||
|
||||
/**
|
||||
* Value of this this <code>Pair</code>.
|
||||
*/
|
||||
private V value;
|
||||
|
||||
/**
|
||||
* Creates a new pair
|
||||
* @param key The key for this pair
|
||||
* @param value The value to use for this pair
|
||||
*/
|
||||
public Pair(K key, V value) {
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the key for this pair.
|
||||
* @return key for this pair
|
||||
*/
|
||||
public K getKey() { return key; }
|
||||
|
||||
/**
|
||||
* Gets the value for this pair.
|
||||
* @return value for this pair
|
||||
*/
|
||||
public V getValue() { return value; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
io.seata.discovery.registry.etcd3.EtcdRegistryProvider
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* 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 io.seata.discovery.registry.etcd;
|
||||
|
||||
import io.seata.discovery.registry.etcd3.EtcdRegistryProvider;
|
||||
import io.seata.discovery.registry.etcd3.EtcdRegistryServiceImpl;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author xingfudeshi@gmail.com
|
||||
* the type etcd registry provider test
|
||||
*/
|
||||
public class EtcdRegistryProviderTest {
|
||||
/**
|
||||
* test provide
|
||||
*/
|
||||
@Test
|
||||
public void testProvide() {
|
||||
assertThat(new EtcdRegistryProvider().provide()).isInstanceOf(EtcdRegistryServiceImpl.class);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
/*
|
||||
* Copyright 1999-2019 Seata.io Group.
|
||||
*
|
||||
* 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 io.seata.discovery.registry.etcd;
|
||||
|
||||
import io.etcd.jetcd.ByteSequence;
|
||||
import io.etcd.jetcd.Client;
|
||||
import io.etcd.jetcd.Watch;
|
||||
import io.etcd.jetcd.launcher.junit.EtcdClusterResource;
|
||||
import io.etcd.jetcd.options.DeleteOption;
|
||||
import io.etcd.jetcd.options.GetOption;
|
||||
import io.etcd.jetcd.watch.WatchResponse;
|
||||
import io.seata.discovery.registry.etcd3.EtcdRegistryProvider;
|
||||
import io.seata.discovery.registry.etcd3.EtcdRegistryServiceImpl;
|
||||
import io.seata.discovery.registry.RegistryService;
|
||||
import org.junit.Rule;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static io.netty.util.CharsetUtil.UTF_8;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author xingfudeshi@gmail.com
|
||||
*/
|
||||
@Disabled
|
||||
public class EtcdRegistryServiceImplTest {
|
||||
private static final String REGISTRY_KEY_PREFIX = "registry-seata-";
|
||||
private static final String CLUSTER_NAME = "default";
|
||||
@Rule
|
||||
private final static EtcdClusterResource etcd = new EtcdClusterResource(CLUSTER_NAME, 1);
|
||||
|
||||
private final Client client = Client.builder().endpoints(etcd.cluster().getClientEndpoints()).build();
|
||||
private final static String HOST = "127.0.0.1";
|
||||
private final static int PORT = 8091;
|
||||
|
||||
@BeforeAll
|
||||
public static void beforeClass() throws Exception {
|
||||
System.setProperty(EtcdRegistryServiceImpl.TEST_ENDPONT, etcd.cluster().getClientEndpoints().get(0).toString());
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
public static void afterClass() throws Exception {
|
||||
System.setProperty(EtcdRegistryServiceImpl.TEST_ENDPONT, "");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRegister() throws Exception {
|
||||
RegistryService registryService = new EtcdRegistryProvider().provide();
|
||||
InetSocketAddress inetSocketAddress = new InetSocketAddress(HOST, PORT);
|
||||
//1.register
|
||||
registryService.register(inetSocketAddress);
|
||||
//2.get instance information
|
||||
GetOption getOption = GetOption.newBuilder().withPrefix(buildRegistryKeyPrefix()).build();
|
||||
long count = client.getKVClient().get(buildRegistryKeyPrefix(), getOption).get().getKvs().stream().filter(keyValue -> {
|
||||
String[] instanceInfo = keyValue.getValue().toString(UTF_8).split(":");
|
||||
return HOST.equals(instanceInfo[0]) && PORT == Integer.parseInt(instanceInfo[1]);
|
||||
}).count();
|
||||
assertThat(count).isEqualTo(1);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testUnregister() throws Exception {
|
||||
RegistryService registryService = new EtcdRegistryProvider().provide();
|
||||
InetSocketAddress inetSocketAddress = new InetSocketAddress(HOST, PORT);
|
||||
//1.register
|
||||
registryService.register(inetSocketAddress);
|
||||
//2.get instance information
|
||||
GetOption getOption = GetOption.newBuilder().withPrefix(buildRegistryKeyPrefix()).build();
|
||||
long count = client.getKVClient().get(buildRegistryKeyPrefix(), getOption).get().getKvs().stream().filter(keyValue -> {
|
||||
String[] instanceInfo = keyValue.getValue().toString(UTF_8).split(":");
|
||||
return HOST.equals(instanceInfo[0]) && PORT == Integer.parseInt(instanceInfo[1]);
|
||||
}).count();
|
||||
assertThat(count).isEqualTo(1);
|
||||
//3.unregister
|
||||
registryService.unregister(inetSocketAddress);
|
||||
//4.again get instance information
|
||||
getOption = GetOption.newBuilder().withPrefix(buildRegistryKeyPrefix()).build();
|
||||
count = client.getKVClient().get(buildRegistryKeyPrefix(), getOption).get().getKvs().stream().filter(keyValue -> {
|
||||
String[] instanceInfo = keyValue.getValue().toString(UTF_8).split(":");
|
||||
return HOST.equals(instanceInfo[0]) && PORT == Integer.parseInt(instanceInfo[1]);
|
||||
}).count();
|
||||
assertThat(count).isEqualTo(0);
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSubscribe() throws Exception {
|
||||
RegistryService registryService = new EtcdRegistryProvider().provide();
|
||||
InetSocketAddress inetSocketAddress = new InetSocketAddress(HOST, PORT);
|
||||
//1.register
|
||||
registryService.register(inetSocketAddress);
|
||||
//2.subscribe
|
||||
EtcdListener etcdListener = new EtcdListener();
|
||||
registryService.subscribe(CLUSTER_NAME, etcdListener);
|
||||
//3.delete instance,see if the listener can be notified
|
||||
DeleteOption deleteOption = DeleteOption.newBuilder().withPrefix(buildRegistryKeyPrefix()).build();
|
||||
client.getKVClient().delete(buildRegistryKeyPrefix(), deleteOption).get();
|
||||
assertThat(etcdListener.isNotified()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnsubscribe() throws Exception {
|
||||
RegistryService registryService = new EtcdRegistryProvider().provide();
|
||||
InetSocketAddress inetSocketAddress = new InetSocketAddress(HOST, PORT);
|
||||
//1.register
|
||||
registryService.register(inetSocketAddress);
|
||||
//2.subscribe
|
||||
EtcdListener etcdListener = new EtcdListener();
|
||||
registryService.subscribe(CLUSTER_NAME, etcdListener);
|
||||
//3.delete instance,see if the listener can be notified
|
||||
DeleteOption deleteOption = DeleteOption.newBuilder().withPrefix(buildRegistryKeyPrefix()).build();
|
||||
client.getKVClient().delete(buildRegistryKeyPrefix(), deleteOption).get();
|
||||
assertThat(etcdListener.isNotified()).isTrue();
|
||||
//4.unsubscribe
|
||||
registryService.unsubscribe(CLUSTER_NAME, etcdListener);
|
||||
//5.reset
|
||||
etcdListener.reset();
|
||||
//6.put instance,the listener should not be notified
|
||||
client.getKVClient().put(buildRegistryKeyPrefix(), ByteSequence.from("test", UTF_8)).get();
|
||||
assertThat(etcdListener.isNotified()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLookup() throws Exception {
|
||||
RegistryService registryService = new EtcdRegistryProvider().provide();
|
||||
InetSocketAddress inetSocketAddress = new InetSocketAddress(HOST, PORT);
|
||||
//1.register
|
||||
registryService.register(inetSocketAddress);
|
||||
//2.lookup
|
||||
List<InetSocketAddress> inetSocketAddresses = registryService.lookup("my_test_tx_group");
|
||||
assertThat(inetSocketAddresses).size().isEqualTo(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* build registry key prefix
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private ByteSequence buildRegistryKeyPrefix() {
|
||||
return ByteSequence.from(REGISTRY_KEY_PREFIX, UTF_8);
|
||||
}
|
||||
|
||||
/**
|
||||
* etcd listener
|
||||
*/
|
||||
private static class EtcdListener implements Watch.Listener {
|
||||
private boolean notified = false;
|
||||
|
||||
@Override
|
||||
public void onNext(WatchResponse response) {
|
||||
notified = true;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Throwable throwable) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCompleted() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return
|
||||
*/
|
||||
public boolean isNotified() throws InterruptedException {
|
||||
TimeUnit.SECONDS.sleep(3);
|
||||
return notified;
|
||||
}
|
||||
|
||||
/**
|
||||
* reset
|
||||
*/
|
||||
private void reset() {
|
||||
this.notified = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user