merge k8s feature (#7104)

This commit is contained in:
ken.lj 2021-01-15 23:48:28 +08:00 committed by GitHub
parent 829c0df273
commit 43e5edaefe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
83 changed files with 3397 additions and 174 deletions

View File

@ -38,7 +38,9 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static org.apache.dubbo.common.constants.CommonConstants.PATH_KEY;
import static org.apache.dubbo.rpc.Constants.MOCK_KEY;
import static org.apache.dubbo.rpc.cluster.Constants.REFER_KEY;
public class MockClusterInvokerTest {
@ -55,7 +57,8 @@ public class MockClusterInvokerTest {
@Test
public void testMockInvokerInvoke_normal() {
URL url = URL.valueOf("remote://1.2.3.4/" + IHelloService.class.getName());
url = url.addParameter(MOCK_KEY, "fail");
url = url.addParameter(MOCK_KEY, "fail")
.addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()));
Invoker<IHelloService> cluster = getClusterInvoker(url);
URL mockUrl = URL.valueOf("mock://localhost/" + IHelloService.class.getName()
+ "?getSomething.mock=return aa");
@ -84,6 +87,7 @@ public class MockClusterInvokerTest {
public void testMockInvokerInvoke_failmock() {
URL url = URL.valueOf("remote://1.2.3.4/" + IHelloService.class.getName())
.addParameter(MOCK_KEY, "fail:return null")
.addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()))
.addParameter("invoke_return_error", "true");
URL mockUrl = URL.valueOf("mock://localhost/" + IHelloService.class.getName()
+ "?getSomething.mock=return aa").addParameters(url.getParameters());
@ -118,7 +122,8 @@ public class MockClusterInvokerTest {
@Test
public void testMockInvokerInvoke_forcemock() {
URL url = URL.valueOf("remote://1.2.3.4/" + IHelloService.class.getName());
url = url.addParameter(MOCK_KEY, "force:return null");
url = url.addParameter(MOCK_KEY, "force:return null")
.addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()));
URL mockUrl = URL.valueOf("mock://localhost/" + IHelloService.class.getName()
+ "?getSomething.mock=return aa&getSomething3xx.mock=return xx")
@ -150,7 +155,9 @@ public class MockClusterInvokerTest {
@Test
public void testMockInvokerInvoke_forcemock_defaultreturn() {
URL url = URL.valueOf("remote://1.2.3.4/" + IHelloService.class.getName());
url = url.addParameter(MOCK_KEY, "force");
url = url.addParameter(MOCK_KEY, "force")
.addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()));
Invoker<IHelloService> cluster = getClusterInvoker(url);
URL mockUrl = URL.valueOf("mock://localhost/" + IHelloService.class.getName()
+ "?getSomething.mock=return aa&getSomething3xx.mock=return xx&sayHello.mock=return ")
@ -173,7 +180,8 @@ public class MockClusterInvokerTest {
public void testMockInvokerFromOverride_Invoke_Fock_someMethods() {
URL url = URL.valueOf("remote://1.2.3.4/" + IHelloService.class.getName())
.addParameter("getSomething.mock", "fail:return x")
.addParameter("getSomething2.mock", "force:return y");
.addParameter("getSomething2.mock", "force:return y")
.addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()));
Invoker<IHelloService> cluster = getClusterInvoker(url);
//Configured with mock
RpcInvocation invocation = new RpcInvocation();
@ -208,6 +216,7 @@ public class MockClusterInvokerTest {
URL url = URL.valueOf("remote://1.2.3.4/" + IHelloService.class.getName())
.addParameter("getSomething.mock", "fail:return x")
.addParameter("getSomething2.mock", "force:return y")
.addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()))
.addParameter("invoke_return_error", "true");
Invoker<IHelloService> cluster = getClusterInvoker(url);
//Configured with mock
@ -242,6 +251,7 @@ public class MockClusterInvokerTest {
.addParameter("mock", "fail:return null")
.addParameter("getSomething.mock", "fail:return x")
.addParameter("getSomething2.mock", "force:return y")
.addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()))
.addParameter("invoke_return_error", "true");
Invoker<IHelloService> cluster = getClusterInvoker(url);
//Configured with mock
@ -278,6 +288,7 @@ public class MockClusterInvokerTest {
.addParameter("mock", "fail:return z")
.addParameter("getSomething.mock", "fail:return x")
.addParameter("getSomething2.mock", "force:return y")
.addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()))
.addParameter("invoke_return_error", "true");
Invoker<IHelloService> cluster = getClusterInvoker(url);
//Configured with mock
@ -314,6 +325,7 @@ public class MockClusterInvokerTest {
.addParameter("mock", "force:return z")
.addParameter("getSomething.mock", "fail:return x")
.addParameter("getSomething2.mock", "force:return y")
.addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()))
.addParameter("invoke_return_error", "true");
Invoker<IHelloService> cluster = getClusterInvoker(url);
//Configured with mock
@ -348,6 +360,7 @@ public class MockClusterInvokerTest {
public void testMockInvokerFromOverride_Invoke_Fock_Default() {
URL url = URL.valueOf("remote://1.2.3.4/" + IHelloService.class.getName())
.addParameter("mock", "fail:return x")
.addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()))
.addParameter("invoke_return_error", "true");
Invoker<IHelloService> cluster = getClusterInvoker(url);
//Configured with mock
@ -376,6 +389,7 @@ public class MockClusterInvokerTest {
public void testMockInvokerFromOverride_Invoke_checkCompatible_return() {
URL url = URL.valueOf("remote://1.2.3.4/" + IHelloService.class.getName())
.addParameter("getSomething.mock", "return x")
.addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()))
.addParameter("invoke_return_error", "true");
Invoker<IHelloService> cluster = getClusterInvoker(url);
//Configured with mock
@ -402,6 +416,7 @@ public class MockClusterInvokerTest {
public void testMockInvokerFromOverride_Invoke_checkCompatible_ImplMock() {
URL url = URL.valueOf("remote://1.2.3.4/" + IHelloService.class.getName())
.addParameter("mock", "true")
.addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()))
.addParameter("invoke_return_error", "true");
Invoker<IHelloService> cluster = getClusterInvoker(url);
//Configured with mock
@ -418,6 +433,7 @@ public class MockClusterInvokerTest {
public void testMockInvokerFromOverride_Invoke_checkCompatible_ImplMock2() {
URL url = URL.valueOf("remote://1.2.3.4/" + IHelloService.class.getName())
.addParameter("mock", "fail")
.addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()))
.addParameter("invoke_return_error", "true");
Invoker<IHelloService> cluster = getClusterInvoker(url);
//Configured with mock
@ -433,6 +449,7 @@ public class MockClusterInvokerTest {
@Test
public void testMockInvokerFromOverride_Invoke_checkCompatible_ImplMock3() {
URL url = URL.valueOf("remote://1.2.3.4/" + IHelloService.class.getName())
.addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()))
.addParameter("mock", "force");
Invoker<IHelloService> cluster = getClusterInvoker(url);
//Configured with mock
@ -446,6 +463,7 @@ public class MockClusterInvokerTest {
public void testMockInvokerFromOverride_Invoke_check_String() {
URL url = URL.valueOf("remote://1.2.3.4/" + IHelloService.class.getName())
.addParameter("getSomething.mock", "force:return 1688")
.addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()))
.addParameter("invoke_return_error", "true");
Invoker<IHelloService> cluster = getClusterInvoker(url);
//Configured with mock
@ -459,6 +477,7 @@ public class MockClusterInvokerTest {
@Test
public void testMockInvokerFromOverride_Invoke_check_int() {
URL url = URL.valueOf("remote://1.2.3.4/" + IHelloService.class.getName())
.addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()))
.addParameter("getInt1.mock", "force:return 1688")
.addParameter("invoke_return_error", "true");
Invoker<IHelloService> cluster = getClusterInvoker(url);
@ -473,6 +492,7 @@ public class MockClusterInvokerTest {
@Test
public void testMockInvokerFromOverride_Invoke_check_boolean() {
URL url = URL.valueOf("remote://1.2.3.4/" + IHelloService.class.getName())
.addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()))
.addParameter("getBoolean1.mock", "force:return true")
.addParameter("invoke_return_error", "true");
Invoker<IHelloService> cluster = getClusterInvoker(url);
@ -487,6 +507,7 @@ public class MockClusterInvokerTest {
@Test
public void testMockInvokerFromOverride_Invoke_check_Boolean() {
URL url = URL.valueOf("remote://1.2.3.4/" + IHelloService.class.getName())
.addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()))
.addParameter("getBoolean2.mock", "force:return true")
.addParameter("invoke_return_error", "true");
Invoker<IHelloService> cluster = getClusterInvoker(url);
@ -502,6 +523,7 @@ public class MockClusterInvokerTest {
public void testMockInvokerFromOverride_Invoke_check_ListString_empty() {
URL url = URL.valueOf("remote://1.2.3.4/" + IHelloService.class.getName())
.addParameter("getListString.mock", "force:return empty")
.addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()))
.addParameter("invoke_return_error", "true");
Invoker<IHelloService> cluster = getClusterInvoker(url);
//Configured with mock
@ -516,6 +538,7 @@ public class MockClusterInvokerTest {
public void testMockInvokerFromOverride_Invoke_check_ListString() {
URL url = URL.valueOf("remote://1.2.3.4/" + IHelloService.class.getName())
.addParameter("getListString.mock", "force:return [\"hi\",\"hi2\"]")
.addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()))
.addParameter("invoke_return_error", "true");
Invoker<IHelloService> cluster = getClusterInvoker(url);
//Configured with mock
@ -532,6 +555,7 @@ public class MockClusterInvokerTest {
public void testMockInvokerFromOverride_Invoke_check_ListPojo_empty() {
URL url = URL.valueOf("remote://1.2.3.4/" + IHelloService.class.getName())
.addParameter("getUsers.mock", "force:return empty")
.addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()))
.addParameter("invoke_return_error", "true");
Invoker<IHelloService> cluster = getClusterInvoker(url);
//Configured with mock
@ -546,6 +570,7 @@ public class MockClusterInvokerTest {
public void testMockInvokerFromOverride_Invoke_check_ListPojo() {
URL url = URL.valueOf("remote://1.2.3.4/" + IHelloService.class.getName())
.addParameter("getUsers.mock", "force:return [{id:1, name:\"hi1\"}, {id:2, name:\"hi2\"}]")
.addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()))
.addParameter("invoke_return_error", "true");
Invoker<IHelloService> cluster = getClusterInvoker(url);
//Configured with mock
@ -562,6 +587,7 @@ public class MockClusterInvokerTest {
public void testMockInvokerFromOverride_Invoke_check_ListPojo_error() {
URL url = URL.valueOf("remote://1.2.3.4/" + IHelloService.class.getName())
.addParameter("getUsers.mock", "force:return [{id:x, name:\"hi1\"}]")
.addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()))
.addParameter("invoke_return_error", "true");
Invoker<IHelloService> cluster = getClusterInvoker(url);
//Configured with mock
@ -577,6 +603,7 @@ public class MockClusterInvokerTest {
public void testMockInvokerFromOverride_Invoke_force_throw() {
URL url = URL.valueOf("remote://1.2.3.4/" + IHelloService.class.getName())
.addParameter("getBoolean2.mock", "force:throw ")
.addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()))
.addParameter("invoke_return_error", "true");
Invoker<IHelloService> cluster = getClusterInvoker(url);
//Configured with mock
@ -594,6 +621,7 @@ public class MockClusterInvokerTest {
public void testMockInvokerFromOverride_Invoke_force_throwCustemException() throws Throwable {
URL url = URL.valueOf("remote://1.2.3.4/" + IHelloService.class.getName())
.addParameter("getBoolean2.mock", "force:throw org.apache.dubbo.rpc.cluster.support.wrapper.MyMockException")
.addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()))
.addParameter("invoke_return_error", "true");
Invoker<IHelloService> cluster = getClusterInvoker(url);
//Configured with mock
@ -611,6 +639,7 @@ public class MockClusterInvokerTest {
public void testMockInvokerFromOverride_Invoke_force_throwCustemExceptionNotFound() {
URL url = URL.valueOf("remote://1.2.3.4/" + IHelloService.class.getName())
.addParameter("getBoolean2.mock", "force:throw java.lang.RuntimeException2")
.addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()))
.addParameter("invoke_return_error", "true");
Invoker<IHelloService> cluster = getClusterInvoker(url);
//Configured with mock
@ -628,6 +657,7 @@ public class MockClusterInvokerTest {
public void testMockInvokerFromOverride_Invoke_mock_false() {
URL url = URL.valueOf("remote://1.2.3.4/" + IHelloService.class.getName())
.addParameter("mock", "false")
.addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()))
.addParameter("invoke_return_error", "true");
Invoker<IHelloService> cluster = getClusterInvoker(url);
//Configured with mock
@ -708,7 +738,7 @@ public class MockClusterInvokerTest {
return "something3";
}
public String getSomething4(){
public String getSomething4() {
throw new RpcException("getSomething4|RpcException");
}
@ -754,7 +784,7 @@ public class MockClusterInvokerTest {
return "something3mock";
}
public String getSomething4(){
public String getSomething4() {
return "something4mock";
}

View File

@ -37,6 +37,7 @@ import java.util.Arrays;
import java.util.List;
import static org.apache.dubbo.rpc.Constants.MOCK_KEY;
import static org.apache.dubbo.rpc.cluster.Constants.REFER_KEY;
public class MockProviderRpcExceptionTest {
@ -53,7 +54,8 @@ public class MockProviderRpcExceptionTest {
@Test
public void testMockInvokerProviderRpcException() {
URL url = URL.valueOf("remote://1.2.3.4/" + IHelloRpcService.class.getName());
url = url.addParameter(MOCK_KEY, "true").addParameter("invoke_return_error", "true");
url = url.addParameter(MOCK_KEY, "true").addParameter("invoke_return_error", "true")
.addParameter(REFER_KEY, "path%3dorg.apache.dubbo.rpc.cluster.support.wrapper.MockProviderRpcExceptionTest%24IHelloRpcService");
Invoker<IHelloRpcService> cluster = getClusterInvoker(url);
RpcInvocation invocation = new RpcInvocation();
invocation.setMethodName("getSomething4");
@ -130,7 +132,7 @@ public class MockProviderRpcExceptionTest {
return "something3";
}
public String getSomething4(){
public String getSomething4() {
throw new RpcException("getSomething4|RpcException");
}
@ -176,7 +178,7 @@ public class MockProviderRpcExceptionTest {
return "something3mock";
}
public String getSomething4(){
public String getSomething4() {
return "something4mock";
}

View File

@ -363,6 +363,12 @@ public interface CommonConstants {
*/
String DEFAULT_SERVICE_NAME_MAPPING_PROPERTIES_PATH = "META-INF/dubbo/service-name-mapping.properties";
String QOS_LIVE_PROBE_EXTENSION = "dubbo.application.liveness-probe";
String QOS_READY_PROBE_EXTENSION = "dubbo.application.readiness-probe";
String QOS_STARTUP_PROBE_EXTENSION = "dubbo.application.startup-probe";
String REGISTRY_DELAY_NOTIFICATION_KEY = "delay-notification";
String CACHE_CLEAR_TASK_INTERVAL = "dubbo.application.url.cache.task.interval";

View File

@ -172,6 +172,20 @@ public class ApplicationConfig extends AbstractConfig {
*/
private String protocol;
/**
* Metadata Service, used in Service Discovery
*/
private Integer metadataServicePort;
/**
* used to set extensions of probe in qos
*/
private String livenessProbe;
private String readinessProbe;
private String startupProbe;
public ApplicationConfig() {
}
@ -485,6 +499,42 @@ public class ApplicationConfig extends AbstractConfig {
this.protocol = protocol;
}
@Parameter(key = "metadata-service-port")
public Integer getMetadataServicePort() {
return metadataServicePort;
}
public void setMetadataServicePort(Integer metadataServicePort) {
this.metadataServicePort = metadataServicePort;
}
@Parameter(key = "liveness-probe")
public String getLivenessProbe() {
return livenessProbe;
}
public void setLivenessProbe(String livenessProbe) {
this.livenessProbe = livenessProbe;
}
@Parameter(key = "readiness-probe")
public String getReadinessProbe() {
return readinessProbe;
}
public void setReadinessProbe(String readinessProbe) {
this.readinessProbe = readinessProbe;
}
@Parameter(key = "startup-probe")
public String getStartupProbe() {
return startupProbe;
}
public void setStartupProbe(String startupProbe) {
this.startupProbe = startupProbe;
}
@Override
public void refresh() {
super.refresh();

View File

@ -217,7 +217,8 @@ public class ServiceConfig<T> extends ServiceConfigBase<T> {
public void exported() {
List<URL> exportedURLs = this.getExportedUrls();
exportedURLs.forEach(url -> {
ServiceNameMapping.getExtension(getApplication().getParameters().get(MAPPING_KEY)).map(url);
Map<String, String> parameters = getApplication().getParameters();
ServiceNameMapping.getExtension(parameters != null ? parameters.get(MAPPING_KEY) : null).map(url);
});
// dispatch a ServiceConfigExportedEvent since 2.7.4
dispatch(new ServiceConfigExportedEvent(this));
@ -435,7 +436,7 @@ public class ServiceConfig<T> extends ServiceConfigBase<T> {
/**
* Here the token value configured by the provider is used to assign the value to ServiceConfig#token
*/
if(ConfigUtils.isEmpty(token) && provider != null) {
if (ConfigUtils.isEmpty(token) && provider != null) {
token = provider.getToken();
}
@ -713,7 +714,7 @@ public class ServiceConfig<T> extends ServiceConfigBase<T> {
}
private void postProcessConfig() {
List<ConfigPostProcessor> configPostProcessors =ExtensionLoader.getExtensionLoader(ConfigPostProcessor.class)
List<ConfigPostProcessor> configPostProcessors = ExtensionLoader.getExtensionLoader(ConfigPostProcessor.class)
.getActivateExtension(URL.valueOf("configPostProcessor://"), (String[]) null);
configPostProcessors.forEach(component -> component.postProcessServiceConfig(this));
}

View File

@ -170,10 +170,12 @@ public class DubboBootstrap extends GenericEventListener {
private AtomicBoolean started = new AtomicBoolean(false);
private AtomicBoolean ready = new AtomicBoolean(true);
private AtomicBoolean startup = new AtomicBoolean(true);
private AtomicBoolean destroyed = new AtomicBoolean(false);
private AtomicBoolean shutdown = new AtomicBoolean(false);
private volatile ServiceInstance serviceInstance;
private volatile MetadataService metadataService;
@ -878,7 +880,7 @@ public class DubboBootstrap extends GenericEventListener {
*/
public DubboBootstrap start() {
if (started.compareAndSet(false, true)) {
ready.set(false);
startup.set(false);
initialize();
if (logger.isInfoEnabled()) {
logger.info(NAME + " is starting...");
@ -902,13 +904,13 @@ public class DubboBootstrap extends GenericEventListener {
} catch (Exception e) {
logger.warn(NAME + " exportAsync occurred an exception.");
}
ready.set(true);
startup.set(true);
if (logger.isInfoEnabled()) {
logger.info(NAME + " is ready.");
}
}).start();
} else {
ready.set(true);
startup.set(true);
if (logger.isInfoEnabled()) {
logger.info(NAME + " is ready.");
}
@ -973,8 +975,12 @@ public class DubboBootstrap extends GenericEventListener {
return started.get();
}
public boolean isReady() {
return ready.get();
public boolean isStartup() {
return startup.get();
}
public boolean isShutdown() {
return shutdown.get();
}
public DubboBootstrap stop() throws IllegalStateException {
@ -1197,7 +1203,8 @@ public class DubboBootstrap extends GenericEventListener {
}
public void destroy() {
if (destroyLock.tryLock()) {
if (destroyLock.tryLock()
&& shutdown.compareAndSet(false, true)) {
try {
DubboShutdownHook.destroyAll();

View File

@ -51,6 +51,10 @@ public class ApplicationBuilder extends AbstractBuilder<ApplicationConfig, Appli
private Boolean qosAcceptForeignIp;
private Map<String, String> parameters;
private String shutwait;
private Integer metadataServicePort;
private String livenessProbe;
private String readinessProbe;
private String startupProbe;
public static ApplicationBuilder newBuilder() {
return new ApplicationBuilder();
@ -172,6 +176,26 @@ public class ApplicationBuilder extends AbstractBuilder<ApplicationConfig, Appli
return getThis();
}
public ApplicationBuilder metadataServicePort(Integer metadataServicePort) {
this.metadataServicePort = metadataServicePort;
return getThis();
}
public ApplicationBuilder livenessProbe(String livenessProbe) {
this.livenessProbe = livenessProbe;
return getThis();
}
public ApplicationBuilder readinessProbe(String readinessProbe) {
this.readinessProbe = readinessProbe;
return getThis();
}
public ApplicationBuilder startupProbe(String startupProbe) {
this.startupProbe = startupProbe;
return getThis();
}
public ApplicationConfig build() {
ApplicationConfig config = new ApplicationConfig();
super.build(config);
@ -193,6 +217,10 @@ public class ApplicationBuilder extends AbstractBuilder<ApplicationConfig, Appli
config.setQosEnable(this.qosEnable);
config.setQosPort(this.qosPort);
config.setQosAcceptForeignIp(this.qosAcceptForeignIp);
config.setMetadataServicePort(this.metadataServicePort);
config.setLivenessProbe(this.livenessProbe);
config.setReadinessProbe(this.readinessProbe);
config.setStartupProbe(this.startupProbe);
config.setParameters(this.parameters);
if (!StringUtils.isEmpty(shutwait)) {
config.setShutwait(shutwait);

View File

@ -19,7 +19,10 @@ package org.apache.dubbo.config.metadata;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.logger.Logger;
import org.apache.dubbo.common.logger.LoggerFactory;
import org.apache.dubbo.common.utils.CollectionUtils;
import org.apache.dubbo.config.ApplicationConfig;
import org.apache.dubbo.config.ArgumentConfig;
import org.apache.dubbo.config.MethodConfig;
import org.apache.dubbo.config.ProtocolConfig;
import org.apache.dubbo.config.RegistryConfig;
import org.apache.dubbo.config.ServiceConfig;
@ -28,10 +31,12 @@ import org.apache.dubbo.metadata.MetadataService;
import org.apache.dubbo.metadata.MetadataServiceExporter;
import org.apache.dubbo.rpc.model.ApplicationModel;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static java.util.Collections.emptyList;
import static org.apache.dubbo.common.constants.CommonConstants.DUBBO;
import static org.apache.dubbo.common.constants.CommonConstants.DUBBO_PROTOCOL;
/**
* {@link MetadataServiceExporter} implementation based on {@link ConfigManager Dubbo configurations}, the clients
@ -73,6 +78,7 @@ public class ConfigurableMetadataServiceExporter implements MetadataServiceExpor
serviceConfig.setRef(metadataService);
serviceConfig.setGroup(getApplicationConfig().getName());
serviceConfig.setVersion(metadataService.version());
serviceConfig.setMethods(generateMethodConfig());
// export
serviceConfig.export();
@ -92,6 +98,28 @@ public class ConfigurableMetadataServiceExporter implements MetadataServiceExpor
return this;
}
/**
* Generate Method Config for Service Discovery Metadata <p/>
* <p>
* Make {@link MetadataService} support argument callback,
* used to notify {@link org.apache.dubbo.registry.client.ServiceInstance}'s
* metadata change event
*
* @since 3.0
*/
private List<MethodConfig> generateMethodConfig() {
MethodConfig methodConfig = new MethodConfig();
methodConfig.setName("getAndListenInstanceMetadata");
ArgumentConfig argumentConfig = new ArgumentConfig();
argumentConfig.setIndex(1);
argumentConfig.setCallback(true);
methodConfig.setArguments(Collections.singletonList(argumentConfig));
return Collections.singletonList(methodConfig);
}
@Override
public ConfigurableMetadataServiceExporter unexport() {
if (isExported()) {
@ -115,10 +143,44 @@ public class ConfigurableMetadataServiceExporter implements MetadataServiceExpor
private ProtocolConfig generateMetadataProtocol() {
ProtocolConfig defaultProtocol = new ProtocolConfig();
defaultProtocol.setName(DUBBO);
// defaultProtocol.setHost() ?
// auto-increment port
defaultProtocol.setPort(-1);
Integer port = getApplicationConfig().getMetadataServicePort();
if (port == null || port < -1) {
if (logger.isInfoEnabled()) {
logger.info("Metadata Service Port hasn't been set will use default protocol defined in protocols.");
}
List<ProtocolConfig> defaultProtocols = ApplicationModel.getConfigManager().getDefaultProtocols();
ProtocolConfig dubboProtocol = findDubboProtocol(defaultProtocols);
if (dubboProtocol != null) {
logger.info("Using dubbo protocol " + dubboProtocol + " to export MetadataService.");
return dubboProtocol;
} else {
defaultProtocol.setName(DUBBO_PROTOCOL);
defaultProtocol.setPort(-1);
}
} else {
defaultProtocol.setName(DUBBO_PROTOCOL);
defaultProtocol.setPort(port);
}
logger.info("Using dubbo protocol " + defaultProtocol + " to export MetadataService.");
return defaultProtocol;
}
private ProtocolConfig findDubboProtocol(List<ProtocolConfig> protocolConfigs) {
if (CollectionUtils.isEmpty(protocolConfigs)) {
return null;
}
for (ProtocolConfig protocolConfig : protocolConfigs) {
if (DUBBO_PROTOCOL.equalsIgnoreCase(protocolConfig.getName())) {
return protocolConfig;
}
}
return null;
}
}

View File

@ -24,6 +24,7 @@ import org.apache.dubbo.config.mock.GreetingLocal3;
import org.apache.dubbo.config.mock.GreetingMock1;
import org.apache.dubbo.config.mock.GreetingMock2;
import org.apache.dubbo.config.utils.ConfigValidationUtils;
import org.apache.dubbo.rpc.model.ApplicationModel;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
@ -41,12 +42,14 @@ public class AbstractInterfaceConfigTest {
@BeforeAll
public static void setUp(@TempDir Path folder) {
ApplicationModel.reset();
dubboProperties = folder.resolve(CommonConstants.DUBBO_PROPERTIES_KEY).toFile();
System.setProperty(CommonConstants.DUBBO_PROPERTIES_KEY, dubboProperties.getAbsolutePath());
}
@AfterAll
public static void tearDown() {
ApplicationModel.reset();
System.clearProperty(CommonConstants.DUBBO_PROPERTIES_KEY);
}

View File

@ -25,6 +25,7 @@ import org.apache.dubbo.config.MonitorConfig;
import org.apache.dubbo.config.utils.ConfigValidationUtils;
import org.apache.dubbo.monitor.MonitorService;
import org.apache.dubbo.registry.RegistryService;
import org.apache.dubbo.rpc.model.ApplicationModel;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
@ -55,13 +56,14 @@ public class DubboBootstrapTest {
@BeforeAll
public static void setUp(@TempDir Path folder) {
ApplicationModel.reset();
dubboProperties = folder.resolve(CommonConstants.DUBBO_PROPERTIES_KEY).toFile();
System.setProperty(CommonConstants.DUBBO_PROPERTIES_KEY, dubboProperties.getAbsolutePath());
}
@AfterEach
public void tearDown() throws IOException {
ApplicationModel.reset();
}
@Test

View File

@ -213,6 +213,34 @@ class ApplicationBuilderTest {
Assertions.assertEquals("ONE", parameters.get("num"));
}
@Test
void metadataServicePort() {
ApplicationBuilder builder = new ApplicationBuilder();
builder.metadataServicePort(12345);
Assertions.assertEquals(12345, builder.build().getMetadataServicePort());
}
@Test
void livenessProbe() {
ApplicationBuilder builder = new ApplicationBuilder();
builder.livenessProbe("TestProbe");
Assertions.assertEquals("TestProbe", builder.build().getLivenessProbe());
}
@Test
void readinessProbe() {
ApplicationBuilder builder = new ApplicationBuilder();
builder.readinessProbe("TestProbe");
Assertions.assertEquals("TestProbe", builder.build().getReadinessProbe());
}
@Test
void startupProbe() {
ApplicationBuilder builder = new ApplicationBuilder();
builder.startupProbe("TestProbe");
Assertions.assertEquals("TestProbe", builder.build().getStartupProbe());
}
@Test
void build() {
MonitorConfig monitor = new MonitorConfig("monitor-addr");
@ -223,7 +251,8 @@ class ApplicationBuilderTest {
.environment("develop").compiler("compiler").logger("log4j").monitor(monitor).isDefault(false)
.dumpDirectory("dumpDirectory").qosEnable(true).qosPort(8080).qosAcceptForeignIp(false)
.shutwait("shutwait").registryIds("registryIds").addRegistry(registry)
.appendParameter("default.num", "one");
.appendParameter("default.num", "one").metadataServicePort(12345)
.livenessProbe("liveness").readinessProbe("readiness").startupProbe("startup");
ApplicationConfig config = builder.build();
ApplicationConfig config2 = builder.build();
@ -249,6 +278,10 @@ class ApplicationBuilderTest {
Assertions.assertSame(registry, config.getRegistry());
Assertions.assertTrue(config.getParameters().containsKey("default.num"));
Assertions.assertEquals("one", config.getParameters().get("default.num"));
Assertions.assertEquals(12345, config.getMetadataServicePort());
Assertions.assertEquals("liveness", config.getLivenessProbe());
Assertions.assertEquals("readiness", config.getReadinessProbe());
Assertions.assertEquals("startup", config.getStartupProbe());
Assertions.assertNotSame(config, config2);
}

View File

@ -16,7 +16,6 @@
*/
package org.apache.dubbo.config.event.listener;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.config.ApplicationConfig;
import org.apache.dubbo.config.RegistryConfig;
import org.apache.dubbo.config.ServiceConfig;
@ -25,7 +24,8 @@ import org.apache.dubbo.config.bootstrap.EchoServiceImpl;
import org.apache.dubbo.config.context.ConfigManager;
import org.apache.dubbo.config.event.ServiceConfigExportedEvent;
import org.apache.dubbo.metadata.WritableMetadataService;
import org.apache.dubbo.metadata.definition.model.FullServiceDefinition;
import org.apache.dubbo.metadata.definition.ServiceDefinitionBuilder;
import org.apache.dubbo.metadata.definition.model.ServiceDefinition;
import org.apache.dubbo.rpc.model.ApplicationModel;
import com.google.gson.Gson;
@ -33,14 +33,7 @@ import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_METADATA_STORAGE_TYPE;
import static org.apache.dubbo.common.constants.CommonConstants.PID_KEY;
import static org.apache.dubbo.common.constants.CommonConstants.TIMESTAMP_KEY;
import static org.apache.dubbo.metadata.definition.ServiceDefinitionBuilder.buildFullDefinition;
import static org.apache.dubbo.remoting.Constants.BIND_IP_KEY;
import static org.apache.dubbo.remoting.Constants.BIND_PORT_KEY;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
@ -80,15 +73,8 @@ public class PublishingServiceDefinitionListenerTest {
String serviceDefinition = writableMetadataService.getServiceDefinition(EchoService.class.getName());
List<URL> exportedUrls = serviceConfig.getExportedUrls();
ServiceDefinition serviceDefinitionBuild = ServiceDefinitionBuilder.build(serviceConfig.getInterfaceClass());
FullServiceDefinition fullServiceDefinition = buildFullDefinition(
serviceConfig.getInterfaceClass(),
exportedUrls.get(0)
.removeParameters(PID_KEY, TIMESTAMP_KEY, BIND_IP_KEY, BIND_PORT_KEY, TIMESTAMP_KEY)
.getParameters()
);
assertEquals(serviceDefinition, new Gson().toJson(fullServiceDefinition));
assertEquals(serviceDefinition, new Gson().toJson(serviceDefinitionBuild));
}
}

View File

@ -0,0 +1,69 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.dubbo.config.mock;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.registry.client.ServiceDiscovery;
import org.apache.dubbo.registry.client.ServiceInstance;
import java.util.HashSet;
import java.util.Set;
public class MockServiceDiscovery implements ServiceDiscovery {
private URL registryURL;
private ServiceInstance serviceInstance;
@Override
public void initialize(URL registryURL) throws Exception {
this.registryURL = registryURL;
}
@Override
public void destroy() throws Exception {
}
@Override
public void register(ServiceInstance serviceInstance) throws RuntimeException {
this.serviceInstance = serviceInstance;
}
@Override
public void update(ServiceInstance serviceInstance) throws RuntimeException {
this.serviceInstance = serviceInstance;
}
@Override
public void unregister(ServiceInstance serviceInstance) throws RuntimeException {
this.serviceInstance = null;
}
@Override
public Set<String> getServices() {
return new HashSet<>();
}
@Override
public URL getUrl() {
return registryURL;
}
@Override
public ServiceInstance getLocalInstance() {
return serviceInstance;
}
}

View File

@ -16,12 +16,20 @@
*/
package org.apache.dubbo.metadata;
import org.apache.dubbo.config.ApplicationConfig;
import org.apache.dubbo.config.ProtocolConfig;
import org.apache.dubbo.config.RegistryConfig;
import org.apache.dubbo.config.metadata.ConfigurableMetadataServiceExporter;
import org.apache.dubbo.rpc.model.ApplicationModel;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import static org.apache.dubbo.common.constants.CommonConstants.COMPOSITE_METADATA_STORAGE_TYPE;
import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_METADATA_STORAGE_TYPE;
import static org.apache.dubbo.common.constants.CommonConstants.REMOTE_METADATA_STORAGE_TYPE;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
@ -31,10 +39,28 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
*/
public class MetadataServiceExporterTest {
@BeforeAll
public static void init() {
ApplicationModel.getConfigManager().setApplication(new ApplicationConfig("Test"));
ApplicationModel.getConfigManager().addRegistry(new RegistryConfig("multicast://224.5.6.7:1234"));
ApplicationModel.getConfigManager().addProtocol(new ProtocolConfig("injvm"));
}
@AfterAll
public static void destroy() {
ApplicationModel.getConfigManager().setApplication(null);
ApplicationModel.reset();
}
@Test
public void test() {
MetadataServiceExporter exporter = MetadataServiceExporter.getExtension(null);
assertEquals(exporter, MetadataServiceExporter.getDefaultExtension());
MetadataService metadataService = Mockito.mock(MetadataService.class);
MetadataServiceExporter exporter = new ConfigurableMetadataServiceExporter(metadataService);
exporter.export();
assertTrue(exporter.isExported());
exporter.unexport();
assertTrue(exporter.supports(DEFAULT_METADATA_STORAGE_TYPE));
assertTrue(exporter.supports(REMOTE_METADATA_STORAGE_TYPE));
assertTrue(exporter.supports(COMPOSITE_METADATA_STORAGE_TYPE));

View File

@ -0,0 +1 @@
mockregistry=org.apache.dubbo.config.mock.MockServiceDiscovery

View File

@ -0,0 +1,69 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.dubbo.config.spring.registry;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.registry.client.ServiceDiscovery;
import org.apache.dubbo.registry.client.ServiceInstance;
import java.util.HashSet;
import java.util.Set;
public class MockServiceDiscovery implements ServiceDiscovery {
private URL registryURL;
private ServiceInstance serviceInstance;
@Override
public void initialize(URL registryURL) throws Exception {
this.registryURL = registryURL;
}
@Override
public void destroy() throws Exception {
}
@Override
public void register(ServiceInstance serviceInstance) throws RuntimeException {
this.serviceInstance = serviceInstance;
}
@Override
public void update(ServiceInstance serviceInstance) throws RuntimeException {
this.serviceInstance = serviceInstance;
}
@Override
public void unregister(ServiceInstance serviceInstance) throws RuntimeException {
this.serviceInstance = null;
}
@Override
public Set<String> getServices() {
return new HashSet<>();
}
@Override
public URL getUrl() {
return registryURL;
}
@Override
public ServiceInstance getLocalInstance() {
return serviceInstance;
}
}

View File

@ -0,0 +1 @@
mock=org.apache.dubbo.config.spring.registry.MockServiceDiscovery

View File

@ -92,9 +92,9 @@
<spring_version>4.3.16.RELEASE</spring_version>
<javassist_version>3.20.0-GA</javassist_version>
<netty_version>3.2.5.Final</netty_version>
<netty4_version>4.1.25.Final</netty4_version>
<netty4_version>4.1.51.Final</netty4_version>
<mina_version>1.1.7</mina_version>
<grizzly_version>2.1.4</grizzly_version>
<grizzly_version>2.4.4</grizzly_version>
<httpclient_version>4.5.3</httpclient_version>
<httpcore_version>4.4.6</httpcore_version>
<fastjson_version>1.2.70</fastjson_version>
@ -129,9 +129,9 @@
<rs_api_version>2.0</rs_api_version>
<resteasy_version>3.0.19.Final</resteasy_version>
<tomcat_embed_version>8.5.31</tomcat_embed_version>
<jetcd_version>0.4.1</jetcd_version>
<jetcd_version>0.5.3</jetcd_version>
<nacos_version>1.3.1</nacos_version>
<grpc.version>1.22.1</grpc.version>
<grpc.version>1.31.1</grpc.version>
<!-- Log libs -->
<slf4j_version>1.7.25</slf4j_version>
<jcl_version>1.2</jcl_version>
@ -145,13 +145,16 @@
<!-- Eureka -->
<eureka.version>1.9.12</eureka.version>
<!-- Fabric8 for Kubernetes -->
<fabric8_kubernetes_version>4.10.3</fabric8_kubernetes_version>
<!-- Alibaba -->
<alibaba_spring_context_support_version>1.0.8</alibaba_spring_context_support_version>
<jaxb_version>2.2.7</jaxb_version>
<activation_version>1.2.0</activation_version>
<test_container_version>1.11.2</test_container_version>
<etcd_launcher_version>0.3.0</etcd_launcher_version>
<etcd_launcher_version>0.5.3</etcd_launcher_version>
<hessian_lite_version>3.2.8</hessian_lite_version>
<swagger_version>1.5.19</swagger_version>
<spring_test_version>4.3.16.RELEASE</spring_test_version>
@ -468,11 +471,6 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.etcd</groupId>
<artifactId>jetcd-launcher</artifactId>
<version>${jetcd_version}</version>
</dependency>
<!-- Log libs -->
<dependency>
<groupId>org.slf4j</groupId>
@ -703,6 +701,17 @@
<artifactId>grpc-grpclb</artifactId>
<version>${grpc.version}</version>
</dependency>
<dependency>
<groupId>io.fabric8</groupId>
<artifactId>kubernetes-client</artifactId>
<version>${fabric8_kubernetes_version}</version>
</dependency>
<dependency>
<groupId>io.fabric8</groupId>
<artifactId>kubernetes-server-mock</artifactId>
<scope>test</scope>
<version>${fabric8_kubernetes_version}</version>
</dependency>
</dependencies>
</dependencyManagement>

View File

@ -157,6 +157,20 @@
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-registry-kubernetes</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-registry-dns</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-monitor-api</artifactId>
@ -358,6 +372,8 @@
<include>org.apache.dubbo:dubbo-registry-zookeeper</include>
<include>org.apache.dubbo:dubbo-registry-nacos</include>
<include>org.apache.dubbo:dubbo-registry-multiple</include>
<include>org.apache.dubbo:dubbo-registry-kubernetes</include>
<include>org.apache.dubbo:dubbo-registry-dns</include>
<include>org.apache.dubbo:dubbo-monitor-api</include>
<include>org.apache.dubbo:dubbo-monitor-default</include>
<include>org.apache.dubbo:dubbo-container-api</include>

View File

@ -0,0 +1,35 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.dubbo.metadata;
public interface InstanceMetadataChangedListener {
/**
* Call when metadata in provider side update <p/>
* Used to notify consumer to update metadata of ServiceInstance
*
* @param metadata latest metadata
*/
void onEvent(String metadata);
/**
* Echo test
* Used to check consumer still online
*/
default String echo(String msg) {
return msg;
}
}

View File

@ -233,4 +233,46 @@ public interface MetadataService {
static SortedSet<String> toSortedStrings(Stream<URL> stream) {
return unmodifiableSortedSet(stream.map(URL::toFullString).collect(TreeSet::new, Set::add, Set::addAll));
}
/**
* Export Metadata in Service Instance of Service Discovery
* <p>
* Used for consumer to get Service Instance Metadata
* if Registry is unsupported with publishing metadata
*
* @param instanceMetadata {@link Map} of provider Service Instance Metadata
* @since 3.0
*/
default void exportInstanceMetadata(String instanceMetadata) {
throw new UnsupportedOperationException("This operation is not supported for consumer.");
}
/**
* Get all Metadata listener from local
* <p>
* Used for consumer to get Service Instance Metadata
* if Registry is unsupported with publishing metadata
*
* @return {@link Map} of {@link InstanceMetadataChangedListener}
* @since 3.0
*/
default Map<String, InstanceMetadataChangedListener> getInstanceMetadataChangedListenerMap() {
throw new UnsupportedOperationException("This operation is not supported for consumer.");
}
/**
* 1. Fetch Metadata in Service Instance of Service Discovery
* 2. Add a metadata change listener
* <p>
* Used for consumer to get Service Instance Metadata
* if Registry is unsupported with publishing metadata
*
* @param consumerId consumerId
* @param listener {@link InstanceMetadataChangedListener} used to notify event
* @return {@link Map} of provider Service Instance Metadata
* @since 3.0
*/
default String getAndListenInstanceMetadata(String consumerId, InstanceMetadataChangedListener listener) {
throw new UnsupportedOperationException("This operation is not supported for consumer.");
}
}

View File

@ -25,6 +25,7 @@ public class CommandContext {
private Channel remote;
private boolean isHttp;
private Object originRequest;
private int httpCode = 200;
public CommandContext(String commandName) {
this.commandName = commandName;
@ -75,4 +76,12 @@ public class CommandContext {
public void setOriginRequest(Object originRequest) {
this.originRequest = originRequest;
}
public int getHttpCode() {
return httpCode;
}
public void setHttpCode(int httpCode) {
this.httpCode = httpCode;
}
}

View File

@ -0,0 +1,52 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.dubbo.qos.command.impl;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.constants.CommonConstants;
import org.apache.dubbo.common.extension.ExtensionLoader;
import org.apache.dubbo.qos.command.BaseCommand;
import org.apache.dubbo.qos.command.CommandContext;
import org.apache.dubbo.qos.command.annotation.Cmd;
import org.apache.dubbo.qos.probe.LivenessProbe;
import org.apache.dubbo.rpc.model.ApplicationModel;
import java.util.List;
@Cmd(name = "live", summary = "Judge if service is alive? ")
public class Live implements BaseCommand {
@Override
public String execute(CommandContext commandContext, String[] args) {
URL url = URL.valueOf("application://")
.addParameter(CommonConstants.QOS_LIVE_PROBE_EXTENSION, ApplicationModel.getApplicationConfig().getLivenessProbe());
List<LivenessProbe> livenessProbes = ExtensionLoader.getExtensionLoader(LivenessProbe.class)
.getActivateExtension(url, CommonConstants.QOS_LIVE_PROBE_EXTENSION);
if (!livenessProbes.isEmpty()) {
for (LivenessProbe livenessProbe : livenessProbes) {
if (!livenessProbe.check()) {
// 503 Service Unavailable
commandContext.setHttpCode(503);
return "false";
}
}
}
// 200 OK
commandContext.setHttpCode(200);
return "true";
}
}

View File

@ -16,17 +16,38 @@
*/
package org.apache.dubbo.qos.command.impl;
import org.apache.dubbo.config.bootstrap.DubboBootstrap;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.constants.CommonConstants;
import org.apache.dubbo.common.extension.ExtensionLoader;
import org.apache.dubbo.qos.command.BaseCommand;
import org.apache.dubbo.qos.command.CommandContext;
import org.apache.dubbo.qos.command.annotation.Cmd;
import org.apache.dubbo.qos.probe.ReadinessProbe;
import org.apache.dubbo.rpc.model.ApplicationModel;
@Cmd(name = "start",summary = "Judge if service has started? ")
import java.util.List;
@Cmd(name = "ready", summary = "Judge if service is ready to work? ")
public class Ready implements BaseCommand {
@Override
public String execute(CommandContext commandContext, String[] args) {
return DubboBootstrap.getInstance().isReady() ? "true" : "false";
URL url = URL.valueOf("application://")
.addParameter(CommonConstants.QOS_READY_PROBE_EXTENSION, ApplicationModel.getApplicationConfig().getReadinessProbe());
List<ReadinessProbe> readinessProbes = ExtensionLoader.getExtensionLoader(ReadinessProbe.class)
.getActivateExtension(url, CommonConstants.QOS_READY_PROBE_EXTENSION);
if (!readinessProbes.isEmpty()) {
for (ReadinessProbe readinessProbe : readinessProbes) {
if (!readinessProbe.check()) {
// 503 Service Unavailable
commandContext.setHttpCode(503);
return "false";
}
}
}
// 200 OK
commandContext.setHttpCode(200);
return "true";
}
}

View File

@ -0,0 +1,52 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.dubbo.qos.command.impl;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.constants.CommonConstants;
import org.apache.dubbo.common.extension.ExtensionLoader;
import org.apache.dubbo.qos.command.BaseCommand;
import org.apache.dubbo.qos.command.CommandContext;
import org.apache.dubbo.qos.command.annotation.Cmd;
import org.apache.dubbo.qos.probe.StartupProbe;
import org.apache.dubbo.rpc.model.ApplicationModel;
import java.util.List;
@Cmd(name = "startup", summary = "Judge if service has started? ")
public class Startup implements BaseCommand {
@Override
public String execute(CommandContext commandContext, String[] args) {
URL url = URL.valueOf("application://")
.addParameter(CommonConstants.QOS_STARTUP_PROBE_EXTENSION, ApplicationModel.getApplicationConfig().getStartupProbe());
List<StartupProbe> startupProbes = ExtensionLoader.getExtensionLoader(StartupProbe.class)
.getActivateExtension(url, CommonConstants.QOS_STARTUP_PROBE_EXTENSION);
if (!startupProbes.isEmpty()) {
for (StartupProbe startupProbe : startupProbes) {
if (!startupProbe.check()) {
// 503 Service Unavailable
commandContext.setHttpCode(503);
return "false";
}
}
}
// 200 OK
commandContext.setHttpCode(200);
return "true";
}
}

View File

@ -0,0 +1,38 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.dubbo.qos.probe;
import org.apache.dubbo.common.extension.SPI;
/**
* A probe to indicate whether program is alive
* </p>
* If one or more spi return false, 'live' command in dubbo-qos
* will return false. This can be extend with custom program and developers
* can implement this to customize life cycle.
*
* @since 3.0
*/
@SPI
public interface LivenessProbe {
/**
* Check if program is alive
*
* @return {@link boolean} result of probe
*/
boolean check();
}

View File

@ -0,0 +1,38 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.dubbo.qos.probe;
import org.apache.dubbo.common.extension.SPI;
/**
* A probe to indicate whether program is ready
* </p>
* If one or more spi return false, 'ready' command in dubbo-qos
* will return false. This can be extend with custom program and developers
* can implement this to customize life cycle.
*
* @since 3.0
*/
@SPI
public interface ReadinessProbe {
/**
* Check if program is Ready
*
* @return {@link boolean} result of probe
*/
boolean check();
}

View File

@ -0,0 +1,38 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.dubbo.qos.probe;
import org.apache.dubbo.common.extension.SPI;
/**
* A probe to indicate whether program is startup
* </p>
* If one or more spi return false, 'startup' command in dubbo-qos
* will return false. This can be extend with custom program and developers
* can implement this to customize life cycle.
*
* @since 3.0
*/
@SPI
public interface StartupProbe {
/**
* Check if program has been startup
*
* @return {@link boolean} result of probe
*/
boolean check();
}

View File

@ -0,0 +1,29 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.dubbo.qos.probe.impl;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.config.bootstrap.DubboBootstrap;
import org.apache.dubbo.qos.probe.ReadinessProbe;
@Activate
public class BootstrapReadinessProbe implements ReadinessProbe {
@Override
public boolean check() {
return !DubboBootstrap.getInstance().isShutdown();
}
}

View File

@ -0,0 +1,30 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.dubbo.qos.probe.impl;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.config.bootstrap.DubboBootstrap;
import org.apache.dubbo.qos.probe.StartupProbe;
@Activate
public class BootstrapStartupProbe implements StartupProbe {
@Override
public boolean check() {
return DubboBootstrap.getInstance().isStartup();
}
}

View File

@ -0,0 +1,52 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.dubbo.qos.probe.impl;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.qos.probe.ReadinessProbe;
import org.apache.dubbo.rpc.model.ApplicationModel;
import org.apache.dubbo.rpc.model.ProviderModel;
import org.apache.dubbo.rpc.model.ServiceRepository;
import java.util.Collection;
import java.util.List;
@Activate
public class ProviderReadinessProbe implements ReadinessProbe {
private static ServiceRepository serviceRepository = ApplicationModel.getServiceRepository();
@Override
public boolean check() {
Collection<ProviderModel> providerModelList = serviceRepository.getExportedServices();
if (providerModelList.isEmpty()) {
return true;
}
boolean hasService = false;
for (ProviderModel providerModel : providerModelList) {
List<ProviderModel.RegisterStatedURL> statedUrls = providerModel.getStatedUrl();
for (ProviderModel.RegisterStatedURL statedUrl : statedUrls) {
if (statedUrl.isRegistered()) {
hasService = true;
break;
}
}
}
return hasService;
}
}

View File

@ -30,6 +30,7 @@ import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
@ -52,19 +53,37 @@ public class HttpProcessHandler extends SimpleChannelInboundHandler<HttpRequest>
private static CommandExecutor commandExecutor = new DefaultCommandExecutor();
private static FullHttpResponse http(int httpCode, String result) {
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.valueOf(httpCode)
, Unpooled.wrappedBuffer(result.getBytes()));
HttpHeaders httpHeaders = response.headers();
httpHeaders.set(HttpHeaderNames.CONTENT_TYPE, "text/plain");
httpHeaders.set(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes());
return response;
}
private static FullHttpResponse http404() {
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.NOT_FOUND);
HttpHeaders httpHeaders = response.headers();
httpHeaders.set(HttpHeaderNames.CONTENT_TYPE, "text/plain");
httpHeaders.set(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes());
return response;
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, HttpRequest msg) throws Exception {
CommandContext commandContext = HttpCommandDecoder.decode(msg);
// return 404 when fail to construct command context
if (commandContext == null) {
log.warn("can not found commandContext url: " + msg.getUri());
log.warn("can not found commandContext url: " + msg.uri());
FullHttpResponse response = http404();
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
} else {
commandContext.setRemote(ctx.channel());
try {
String result = commandExecutor.execute(commandContext);
FullHttpResponse response = http200(result);
int httpCode = commandContext.getHttpCode();
FullHttpResponse response = http(httpCode, result);
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
} catch (NoSuchCommandException ex) {
log.error("can not find commandContext: " + commandContext, ex);
@ -72,36 +91,10 @@ public class HttpProcessHandler extends SimpleChannelInboundHandler<HttpRequest>
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
} catch (Exception qosEx) {
log.error("execute commandContext: " + commandContext + " got exception", qosEx);
FullHttpResponse response = http500(qosEx.getMessage());
FullHttpResponse response = http(500, qosEx.getMessage());
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
}
}
}
private static final FullHttpResponse http200(String result) {
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK,
Unpooled.wrappedBuffer(result.getBytes()));
HttpHeaders httpHeaders = response.headers();
httpHeaders.set(HttpHeaders.Names.CONTENT_TYPE, "text/plain");
httpHeaders.set(HttpHeaders.Names.CONTENT_LENGTH, response.content().readableBytes());
return response;
}
private static final FullHttpResponse http404() {
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.NOT_FOUND);
HttpHeaders httpHeaders = response.headers();
httpHeaders.set(HttpHeaders.Names.CONTENT_TYPE, "text/plain");
httpHeaders.set(HttpHeaders.Names.CONTENT_LENGTH, response.content().readableBytes());
return response;
}
private static final FullHttpResponse http500(String errorMessage) {
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.INTERNAL_SERVER_ERROR
, Unpooled.wrappedBuffer(errorMessage.getBytes()));
HttpHeaders httpHeaders = response.headers();
httpHeaders.set(HttpHeaders.Names.CONTENT_TYPE, "text/plain");
httpHeaders.set(HttpHeaders.Names.CONTENT_LENGTH, response.content().readableBytes());
return response;
}
}

View File

@ -8,5 +8,7 @@ offline=org.apache.dubbo.qos.command.impl.Offline
offlineApp=org.apache.dubbo.qos.command.impl.OfflineApp
offlineInterface=org.apache.dubbo.qos.command.impl.OfflineInterface
ready=org.apache.dubbo.qos.command.impl.Ready
startup=org.apache.dubbo.qos.command.impl.Startup
live=org.apache.dubbo.qos.command.impl.Live
version=org.apache.dubbo.qos.command.impl.Version
publish-metadata=org.apache.dubbo.qos.command.impl.PublishMetadata

View File

@ -0,0 +1,2 @@
bootstrap = org.apache.dubbo.qos.probe.impl.BootstrapReadinessProbe
provider = org.apache.dubbo.qos.probe.impl.ProviderReadinessProbe

View File

@ -0,0 +1 @@
bootstrap = org.apache.dubbo.qos.probe.impl.BootstrapStartupProbe

View File

@ -18,12 +18,16 @@ package org.apache.dubbo.qos.command.util;
import org.apache.dubbo.qos.command.GreetingCommand;
import org.apache.dubbo.qos.command.impl.Help;
import org.apache.dubbo.qos.command.impl.Live;
import org.apache.dubbo.qos.command.impl.Ls;
import org.apache.dubbo.qos.command.impl.Offline;
import org.apache.dubbo.qos.command.impl.Online;
import org.apache.dubbo.qos.command.impl.PublishMetadata;
import org.apache.dubbo.qos.command.impl.Quit;
import org.apache.dubbo.qos.command.impl.Ready;
import org.apache.dubbo.qos.command.impl.Startup;
import org.apache.dubbo.qos.command.impl.Version;
import org.junit.jupiter.api.Test;
import java.util.List;
@ -47,7 +51,7 @@ public class CommandHelperTest {
List<Class<?>> classes = CommandHelper.getAllCommandClass();
assertThat(classes,
containsInAnyOrder(GreetingCommand.class, Help.class, Ls.class, Offline.class, Online.class, Quit.class,
Ready.class, Version.class));
Live.class, Ready.class, Startup.class, Version.class, PublishMetadata.class));
}
@Test

View File

@ -88,4 +88,14 @@ public interface Constants {
String REGISTRY_RETRY_PERIOD_KEY = "retry.period";
String SESSION_TIMEOUT_KEY = "session";
/**
* To decide the frequency of checking Distributed Service Discovery Registry callback hook (in ms)
*/
String ECHO_POLLING_CYCLE_KEY = "echoPollingCycle";
/**
* Default value for check frequency: 60000 (ms)
*/
int DEFAULT_ECHO_POLLING_CYCLE = 60000;
}

View File

@ -0,0 +1,290 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.dubbo.registry.client;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.constants.CommonConstants;
import org.apache.dubbo.common.logger.Logger;
import org.apache.dubbo.common.logger.LoggerFactory;
import org.apache.dubbo.common.utils.NamedThreadFactory;
import org.apache.dubbo.common.utils.NetUtils;
import org.apache.dubbo.common.utils.StringUtils;
import org.apache.dubbo.metadata.InstanceMetadataChangedListener;
import org.apache.dubbo.metadata.MetadataService;
import org.apache.dubbo.metadata.RevisionResolver;
import org.apache.dubbo.metadata.WritableMetadataService;
import org.apache.dubbo.registry.Constants;
import org.apache.dubbo.registry.client.event.ServiceInstancesChangedEvent;
import org.apache.dubbo.registry.client.event.listener.ServiceInstancesChangedListener;
import org.apache.dubbo.registry.client.metadata.MetadataUtils;
import org.apache.dubbo.rpc.RpcException;
import org.apache.dubbo.rpc.model.ApplicationModel;
import com.alibaba.fastjson.JSONObject;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public abstract class SelfHostMetaServiceDiscovery implements ServiceDiscovery {
private final Logger logger = LoggerFactory.getLogger(getClass());
private URL registryURL;
/**
* Echo check if consumer is still work
* echo task may take a lot of time when consumer offline, create a new ScheduledThreadPool
*/
private final ScheduledExecutorService echoCheckExecutor = Executors.newScheduledThreadPool(1, new NamedThreadFactory("Dubbo-Registry-EchoCheck-Consumer"));
// =================================== Provider side =================================== //
private ServiceInstance serviceInstance;
/**
* Local {@link ServiceInstance} Metadata's revision
*/
private String lastMetadataRevision;
// =================================== Consumer side =================================== //
/**
* Local Cache of {@link ServiceInstance} Metadata
* <p>
* Key - {@link ServiceInstance} ID ( usually ip + port )
* Value - Json processed metadata string
*/
private final ConcurrentHashMap<String, String> metadataMap = new ConcurrentHashMap<>();
/**
* Local Cache of {@link ServiceInstance}
* <p>
* Key - Service Name
* Value - List {@link ServiceInstance}
*/
private final ConcurrentHashMap<String, List<ServiceInstance>> cachedServiceInstances = new ConcurrentHashMap<>();
/**
* Local Cache of Service's {@link ServiceInstance} list revision,
* used to check if {@link ServiceInstance} list has been updated
* <p>
* Key - ServiceName
* Value - a revision calculate from {@link List} of {@link ServiceInstance}
*/
private final ConcurrentHashMap<String, String> serviceInstanceRevisionMap = new ConcurrentHashMap<>();
@Override
public void initialize(URL registryURL) throws Exception {
this.registryURL = registryURL;
doInitialize(registryURL);
long echoPollingCycle = registryURL.getParameter(Constants.ECHO_POLLING_CYCLE_KEY, Constants.DEFAULT_ECHO_POLLING_CYCLE);
// Echo check: test if consumer is offline, remove MetadataChangeListener,
// reduce the probability of failure when metadata update
echoCheckExecutor.scheduleAtFixedRate(() -> {
WritableMetadataService metadataService = WritableMetadataService.getDefaultExtension();
Map<String, InstanceMetadataChangedListener> listenerMap = metadataService.getInstanceMetadataChangedListenerMap();
Iterator<Map.Entry<String, InstanceMetadataChangedListener>> iterator = listenerMap.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, InstanceMetadataChangedListener> entry = iterator.next();
try {
entry.getValue().echo(CommonConstants.DUBBO);
} catch (RpcException e) {
if (logger.isInfoEnabled()) {
logger.info("Send echo message to consumer error. Possible cause: consumer is offline.");
}
iterator.remove();
}
}
}, echoPollingCycle, echoPollingCycle, TimeUnit.MILLISECONDS);
}
@Override
public void destroy() throws Exception {
doDestroy();
metadataMap.clear();
serviceInstanceRevisionMap.clear();
echoCheckExecutor.shutdown();
}
private void updateMetadata(ServiceInstance serviceInstance) {
WritableMetadataService metadataService = WritableMetadataService.getDefaultExtension();
String metadataString = JSONObject.toJSONString(serviceInstance.getMetadata());
String metadataRevision = RevisionResolver.calRevision(metadataString);
// check if metadata updated
if (!metadataRevision.equalsIgnoreCase(lastMetadataRevision)) {
logger.info("Update Service Instance Metadata of DNS registry. Newer metadata: " + metadataString);
if (logger.isDebugEnabled()) {
logger.debug("Update Service Instance Metadata of DNS registry. Newer metadata: " + metadataString);
}
lastMetadataRevision = metadataRevision;
// save newest metadata to local
metadataService.exportInstanceMetadata(metadataString);
// notify to consumer
Map<String, InstanceMetadataChangedListener> listenerMap = metadataService.getInstanceMetadataChangedListenerMap();
Iterator<Map.Entry<String, InstanceMetadataChangedListener>> iterator = listenerMap.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, InstanceMetadataChangedListener> entry = iterator.next();
try {
entry.getValue().onEvent(metadataString);
} catch (RpcException e) {
logger.warn("Notify to consumer error. Possible cause: consumer is offline.");
// remove listener if consumer is offline
iterator.remove();
}
}
}
}
@Override
public void register(ServiceInstance serviceInstance) throws RuntimeException {
this.serviceInstance = serviceInstance;
updateMetadata(serviceInstance);
doRegister(serviceInstance);
}
@Override
public void update(ServiceInstance serviceInstance) throws RuntimeException {
this.serviceInstance = serviceInstance;
updateMetadata(serviceInstance);
doUpdate(serviceInstance);
}
@Override
public void unregister(ServiceInstance serviceInstance) throws RuntimeException {
doUnregister(serviceInstance);
this.serviceInstance = null;
// notify empty message to consumer
WritableMetadataService metadataService = WritableMetadataService.getDefaultExtension();
metadataService.exportInstanceMetadata("");
metadataService.getInstanceMetadataChangedListenerMap().forEach((consumerId, listener) -> listener.onEvent(""));
metadataService.getInstanceMetadataChangedListenerMap().clear();
}
@Override
public ServiceInstance getLocalInstance() {
return serviceInstance;
}
@Override
public URL getUrl() {
return registryURL;
}
@SuppressWarnings("unchecked")
public final void fillServiceInstance(DefaultServiceInstance serviceInstance) {
String hostId = serviceInstance.getId();
if (metadataMap.containsKey(hostId)) {
// Use cached metadata.
// Metadata will be updated by provider callback
String metadataString = metadataMap.get(hostId);
serviceInstance.setMetadata(JSONObject.parseObject(metadataString, Map.class));
} else {
// refer from MetadataUtils, this proxy is different from the one used to refer exportedURL
MetadataService metadataService = MetadataUtils.getMetadataServiceProxy(serviceInstance, this);
String consumerId = ApplicationModel.getName() + NetUtils.getLocalHost();
String metadata = metadataService.getAndListenInstanceMetadata(
consumerId, metadataString -> {
logger.info("Receive callback: " + metadataString + serviceInstance);
if (StringUtils.isEmpty(metadataString)) {
// provider is shutdown
metadataMap.remove(hostId);
} else {
metadataMap.put(hostId, metadataString);
}
});
metadataMap.put(hostId, metadata);
serviceInstance.setMetadata(JSONObject.parseObject(metadata, Map.class));
}
}
public final void notifyListener(String serviceName, ServiceInstancesChangedListener listener, List<ServiceInstance> instances) {
String serviceInstanceRevision = RevisionResolver.calRevision(JSONObject.toJSONString(instances));
boolean changed = !serviceInstanceRevision.equalsIgnoreCase(
serviceInstanceRevisionMap.put(serviceName, serviceInstanceRevision));
if (logger.isDebugEnabled()) {
logger.debug("Poll DNS data. Service Instance changed: " + changed + " Service Name: " + serviceName);
}
if (changed) {
List<ServiceInstance> oldServiceInstances = cachedServiceInstances.getOrDefault(serviceName, new LinkedList<>());
// remove expired invoker
Set<ServiceInstance> allServiceInstances = new HashSet<>(oldServiceInstances.size() + instances.size());
allServiceInstances.addAll(oldServiceInstances);
allServiceInstances.addAll(instances);
allServiceInstances.removeAll(oldServiceInstances);
allServiceInstances.forEach(removedServiceInstance -> {
MetadataUtils.destroyMetadataServiceProxy(removedServiceInstance, this);
});
cachedServiceInstances.put(serviceName, instances);
listener.onEvent(new ServiceInstancesChangedEvent(serviceName, instances));
}
}
public void doInitialize(URL registryURL) throws Exception {
}
public void doDestroy() throws Exception {
}
public void doRegister(ServiceInstance serviceInstance) throws RuntimeException {
}
public void doUpdate(ServiceInstance serviceInstance) throws RuntimeException {
}
public void doUnregister(ServiceInstance serviceInstance) throws RuntimeException {
}
/**
* UT used only
*/
@Deprecated
public final ConcurrentHashMap<String, List<ServiceInstance>> getCachedServiceInstances() {
return cachedServiceInstances;
}
}

View File

@ -33,7 +33,11 @@ import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import static org.apache.dubbo.common.constants.CommonConstants.METADATA_KEY;
import static org.apache.dubbo.common.constants.CommonConstants.REMOTE_METADATA_STORAGE_TYPE;
import static org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils.METADATA_SERVICE_URLS_PROPERTY_NAME;
public class MetadataUtils {
@ -42,6 +46,10 @@ public class MetadataUtils {
public static ConcurrentMap<String, MetadataService> metadataServiceProxies = new ConcurrentHashMap<>();
public static ConcurrentMap<String, Invoker<?>> metadataServiceInvokers = new ConcurrentHashMap<>();
public static ConcurrentMap<String, Lock> metadataServiceLocks = new ConcurrentHashMap<>();
private static final ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
@ -72,39 +80,69 @@ public class MetadataUtils {
// store in local
WritableMetadataService.getDefaultExtension().publishServiceDefinition(url);
// send to remote
// if (REMOTE_METADATA_STORAGE_TYPE.equals(url.getParameter(METADATA_KEY))) {
getRemoteMetadataService().publishServiceDefinition(url);
// }
if (REMOTE_METADATA_STORAGE_TYPE.equalsIgnoreCase(url.getParameter(METADATA_KEY))) {
getRemoteMetadataService().publishServiceDefinition(url);
}
}
public static String computeKey(ServiceInstance serviceInstance) {
return serviceInstance.getServiceName() + "##" + serviceInstance.getId() + "##" +
ServiceInstanceMetadataUtils.getExportedServicesRevision(serviceInstance);
}
public static MetadataService getMetadataServiceProxy(ServiceInstance instance, ServiceDiscovery serviceDiscovery) {
String key = instance.getServiceName() + "##" +
ServiceInstanceMetadataUtils.getExportedServicesRevision(instance);
return metadataServiceProxies.computeIfAbsent(key, k -> {
MetadataServiceURLBuilder builder = null;
ExtensionLoader<MetadataServiceURLBuilder> loader
= ExtensionLoader.getExtensionLoader(MetadataServiceURLBuilder.class);
String key = computeKey(instance);
Lock lock = metadataServiceLocks.computeIfAbsent(key, k -> new ReentrantLock());
Map<String, String> metadata = instance.getMetadata();
// METADATA_SERVICE_URLS_PROPERTY_NAME is a unique key exists only on instances of spring-cloud-alibaba.
String dubboURLsJSON = metadata.get(METADATA_SERVICE_URLS_PROPERTY_NAME);
if (StringUtils.isNotEmpty(dubboURLsJSON)) {
builder = loader.getExtension(SpringCloudMetadataServiceURLBuilder.NAME);
} else {
builder = loader.getExtension(StandardMetadataServiceURLBuilder.NAME);
lock.lock();
try {
return metadataServiceProxies.computeIfAbsent(key, k -> referProxy(k, instance));
} finally {
lock.unlock();
}
}
public static void destroyMetadataServiceProxy(ServiceInstance instance, ServiceDiscovery serviceDiscovery) {
String key = computeKey(instance);
Lock lock = metadataServiceLocks.computeIfAbsent(key, k -> new ReentrantLock());
lock.lock();
try {
if (metadataServiceProxies.containsKey(key)) {
metadataServiceProxies.remove(key);
Invoker<?> invoker = metadataServiceInvokers.remove(key);
invoker.destroy();
}
} finally {
lock.unlock();
}
}
List<URL> urls = builder.build(instance);
if (CollectionUtils.isEmpty(urls)) {
throw new IllegalStateException("You have enabled introspection service discovery mode for instance "
+ instance + ", but no metadata service can build from it.");
}
private static MetadataService referProxy(String key, ServiceInstance instance) {
MetadataServiceURLBuilder builder = null;
ExtensionLoader<MetadataServiceURLBuilder> loader
= ExtensionLoader.getExtensionLoader(MetadataServiceURLBuilder.class);
// Simply rely on the first metadata url, as stated in MetadataServiceURLBuilder.
Invoker<MetadataService> invoker = protocol.refer(MetadataService.class, urls.get(0));
Map<String, String> metadata = instance.getMetadata();
// METADATA_SERVICE_URLS_PROPERTY_NAME is a unique key exists only on instances of spring-cloud-alibaba.
String dubboURLsJSON = metadata.get(METADATA_SERVICE_URLS_PROPERTY_NAME);
if (metadata.isEmpty() || StringUtils.isEmpty(dubboURLsJSON)) {
builder = loader.getExtension(StandardMetadataServiceURLBuilder.NAME);
} else {
builder = loader.getExtension(SpringCloudMetadataServiceURLBuilder.NAME);
}
return proxyFactory.getProxy(invoker);
});
List<URL> urls = builder.build(instance);
if (CollectionUtils.isEmpty(urls)) {
throw new IllegalStateException("You have enabled introspection service discovery mode for instance "
+ instance + ", but no metadata service can build from it.");
}
// Simply rely on the first metadata url, as stated in MetadataServiceURLBuilder.
Invoker<MetadataService> invoker = protocol.refer(MetadataService.class, urls.get(0));
metadataServiceInvokers.put(key, invoker);
return proxyFactory.getProxy(invoker);
}
public static void saveMetadataURL(URL url) {

View File

@ -19,19 +19,25 @@ package org.apache.dubbo.registry.client.metadata;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.URLBuilder;
import org.apache.dubbo.common.config.ConfigurationUtils;
import org.apache.dubbo.common.logger.Logger;
import org.apache.dubbo.common.logger.LoggerFactory;
import org.apache.dubbo.metadata.MetadataService;
import org.apache.dubbo.registry.client.ServiceInstance;
import org.apache.dubbo.remoting.Constants;
import org.apache.dubbo.rpc.model.ApplicationModel;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import static org.apache.dubbo.common.constants.CommonConstants.CONSUMER;
import static org.apache.dubbo.common.constants.CommonConstants.DUBBO_PROTOCOL;
import static org.apache.dubbo.common.constants.CommonConstants.GROUP_KEY;
import static org.apache.dubbo.common.constants.CommonConstants.PORT_KEY;
import static org.apache.dubbo.common.constants.CommonConstants.PROTOCOL_KEY;
import static org.apache.dubbo.common.constants.CommonConstants.SIDE_KEY;
import static org.apache.dubbo.common.constants.CommonConstants.TIMEOUT_KEY;
import static org.apache.dubbo.common.constants.CommonConstants.VERSION_KEY;
import static org.apache.dubbo.metadata.MetadataConstants.DEFAULT_METADATA_TIMEOUT_VALUE;
import static org.apache.dubbo.metadata.MetadataConstants.METADATA_PROXY_TIMEOUT_KEY;
import static org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils.getMetadataServiceURLsParams;
@ -43,7 +49,9 @@ import static org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataU
* @since 2.7.5
*/
public class StandardMetadataServiceURLBuilder implements MetadataServiceURLBuilder {
private final Logger logger = LoggerFactory.getLogger(getClass());
public static final String NAME = "standard";
/**
@ -61,24 +69,66 @@ public class StandardMetadataServiceURLBuilder implements MetadataServiceURLBuil
String serviceName = serviceInstance.getServiceName();
String host = serviceInstance.getHost();
URLBuilder urlBuilder = new URLBuilder();
for (Map.Entry<String, String> entry : paramsMap.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
if (key.equals(PORT_KEY)) {
urlBuilder.setPort(Integer.parseInt(value));
} else if (key.equals(PROTOCOL_KEY)) {
urlBuilder.setProtocol(value);
} else {
urlBuilder.addParameter(key, value);
}
}
urlBuilder.setHost(host).setPath(MetadataService.class.getName())
.addParameter(TIMEOUT_KEY, ConfigurationUtils.get(METADATA_PROXY_TIMEOUT_KEY, DEFAULT_METADATA_TIMEOUT_VALUE))
.addParameter(SIDE_KEY, CONSUMER)
.addParameter(GROUP_KEY, serviceName);
urls.add(urlBuilder.build());
if (paramsMap.isEmpty()) {
// ServiceInstance Metadata is empty. Happened when registry not support metadata write.
urls.add(generateUrlWithoutMetadata(serviceName, host, serviceInstance.getPort()));
} else {
urls.add(generateWithMetadata(serviceName, host, paramsMap));
}
return urls;
}
private URL generateWithMetadata(String serviceName, String host, Map<String, String> params) {
String protocol = params.get(PROTOCOL_KEY);
int port = Integer.parseInt(params.get(PORT_KEY));
URLBuilder urlBuilder = new URLBuilder()
.setHost(host)
.setPort(port)
.setProtocol(protocol)
.setPath(MetadataService.class.getName())
.addParameter(TIMEOUT_KEY, ConfigurationUtils.get(METADATA_PROXY_TIMEOUT_KEY, DEFAULT_METADATA_TIMEOUT_VALUE))
.addParameter(SIDE_KEY, CONSUMER);
// add parameters
params.forEach(urlBuilder::addParameter);
// add the default parameters
urlBuilder.addParameter(GROUP_KEY, serviceName);
return urlBuilder.build();
}
private URL generateUrlWithoutMetadata(String serviceName, String host, Integer instancePort) {
Integer port = ApplicationModel.getApplicationConfig().getMetadataServicePort();
if (port == null || port < 1) {
logger.warn("Metadata Service Port is not provided, since DNS is not able to negotiate the metadata port " +
"between Provider and Consumer, will try to use instance port as the default metadata port.");
port = instancePort;
}
if (port == null || port < 1) {
String message = "Metadata Service Port should be specified for consumer. " +
"Please set dubbo.application.metadataServicePort and " +
"make sure it has been set on provider side. " +
"ServiceName: " + serviceName + " Host: " + host;
throw new IllegalStateException(message);
}
URLBuilder urlBuilder = new URLBuilder()
.setHost(host)
.setPort(port)
.setProtocol(DUBBO_PROTOCOL)
.setPath(MetadataService.class.getName())
.addParameter(TIMEOUT_KEY, ConfigurationUtils.get(METADATA_PROXY_TIMEOUT_KEY, DEFAULT_METADATA_TIMEOUT_VALUE))
.addParameter(Constants.RECONNECT_KEY, false)
.addParameter(SIDE_KEY, CONSUMER)
.addParameter(GROUP_KEY, serviceName)
.addParameter(VERSION_KEY, MetadataService.VERSION);
// add ServiceInstance Metadata notify support
urlBuilder.addParameter("getAndListenInstanceMetadata.1.callback", true);
return urlBuilder.build();
}
}

View File

@ -21,6 +21,7 @@ import org.apache.dubbo.common.logger.Logger;
import org.apache.dubbo.common.logger.LoggerFactory;
import org.apache.dubbo.common.utils.CollectionUtils;
import org.apache.dubbo.common.utils.StringUtils;
import org.apache.dubbo.metadata.InstanceMetadataChangedListener;
import org.apache.dubbo.metadata.MetadataInfo;
import org.apache.dubbo.metadata.MetadataInfo.ServiceInfo;
import org.apache.dubbo.metadata.MetadataService;
@ -82,9 +83,12 @@ public class InMemoryWritableMetadataService implements WritableMetadataService
URL metadataServiceURL;
ConcurrentMap<String, MetadataInfo> metadataInfos;
final Semaphore metadataSemaphore = new Semaphore(0);
final Map<String, Set<String>> serviceToAppsMapping = new HashMap<>();
String instanceMetadata;
ConcurrentMap<String, InstanceMetadataChangedListener> instanceMetadataChangedListenerMap = new ConcurrentHashMap<>();
// ==================================================================================== //
// =================================== Subscription =================================== //
@ -240,6 +244,22 @@ public class InMemoryWritableMetadataService implements WritableMetadataService
return null;
}
@Override
public void exportInstanceMetadata(String metadata) {
this.instanceMetadata = metadata;
}
@Override
public Map<String, InstanceMetadataChangedListener> getInstanceMetadataChangedListenerMap() {
return instanceMetadataChangedListenerMap;
}
@Override
public String getAndListenInstanceMetadata(String consumerId, InstanceMetadataChangedListener listener) {
instanceMetadataChangedListenerMap.put(consumerId, listener);
return instanceMetadata;
}
@Override
public MetadataInfo getDefaultMetadataInfo() {
if (CollectionUtils.isEmptyMap(metadataInfos)) {
@ -379,5 +399,4 @@ public class InMemoryWritableMetadataService implements WritableMetadataService
return o1.toFullString().compareTo(o2.toFullString());
}
}
}

View File

@ -60,15 +60,22 @@ public class RemoteMetadataServiceImpl {
SubscriberMetadataIdentifier identifier = new SubscriberMetadataIdentifier(serviceName, metadataInfo.calAndGetRevision());
metadataInfo.calAndGetRevision();
metadataInfo.getExtendParams().put(REGISTRY_CLUSTER_KEY, registryCluster);
MetadataReport metadataReport = getMetadataReports().get(registryCluster);
if (metadataReport == null) {
metadataReport = getMetadataReports().entrySet().iterator().next().getValue();
if (getMetadataReports().size() > 0) {
MetadataReport metadataReport = getMetadataReports().get(registryCluster);
if (metadataReport == null) {
metadataReport = getMetadataReports().entrySet().iterator().next().getValue();
}
logger.info("Publishing metadata to " + metadataReport.getClass().getSimpleName());
if (logger.isDebugEnabled()) {
logger.debug(metadataInfo.toString());
}
metadataReport.publishAppMetadata(identifier, metadataInfo);
} else {
if (logger.isInfoEnabled()) {
logger.info("Remote Metadata Report Server not hasn't been configured. " +
"Only publish Metadata to local.");
}
}
logger.info("Publishing metadata to " + metadataReport.getClass().getSimpleName());
if (logger.isDebugEnabled()) {
logger.debug(metadataInfo.toString());
}
metadataReport.publishAppMetadata(identifier, metadataInfo);
metadataInfo.markReported();
}
});
@ -80,6 +87,8 @@ public class RemoteMetadataServiceImpl {
String registryCluster = instance.getExtendParams().get(REGISTRY_CLUSTER_KEY);
checkRemoteConfigured();
MetadataReport metadataReport = getMetadataReports().get(registryCluster);
if (metadataReport == null) {
metadataReport = getMetadataReports().entrySet().iterator().next().getValue();
@ -87,8 +96,20 @@ public class RemoteMetadataServiceImpl {
return metadataReport.getAppMetadata(identifier, instance.getExtendParams());
}
private void checkRemoteConfigured() {
if (getMetadataReports().size() == 0) {
String msg = "Remote Metadata Report Server not hasn't been configured. " +
"Unable to get Metadata from remote!";
logger.error(msg);
throw new IllegalStateException(msg);
}
}
public void publishServiceDefinition(URL url) {
String side = url.getSide();
checkRemoteConfigured();
if (PROVIDER_SIDE.equalsIgnoreCase(side)) {
//TODO, the params part is duplicate with that stored by exportURL(url), can be further optimized in the future.
publishProvider(url);

View File

@ -47,4 +47,4 @@ public interface MigrationClusterInvoker<T> extends ClusterInvoker<T> {
void refreshServiceDiscoveryInvokerOnMappingCallback(boolean forceMigrate);
void reRefer(URL newSubscribeUrl);
}
}

View File

@ -44,6 +44,8 @@ public class InMemoryServiceDiscovery implements ServiceDiscovery {
private ServiceInstance serviceInstance;
private URL registryURL;
@Override
public Set<String> getServices() {
return repository.keySet();
@ -70,6 +72,11 @@ public class InMemoryServiceDiscovery implements ServiceDiscovery {
return new DefaultPage<>(offset, pageSize, data, totalSize);
}
@Override
public URL getUrl() {
return registryURL;
}
@Override
public ServiceInstance getLocalInstance() {
return serviceInstance;
@ -104,7 +111,7 @@ public class InMemoryServiceDiscovery implements ServiceDiscovery {
@Override
public void initialize(URL registryURL) throws Exception {
this.registryURL = registryURL;
}
@Override

View File

@ -18,9 +18,11 @@ package org.apache.dubbo.registry.support;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.utils.CollectionUtils;
import org.apache.dubbo.config.ApplicationConfig;
import org.apache.dubbo.metadata.WritableMetadataService;
import org.apache.dubbo.registry.NotifyListener;
import org.apache.dubbo.registry.client.ServiceDiscoveryRegistry;
import org.apache.dubbo.rpc.model.ApplicationModel;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@ -38,6 +40,7 @@ import static org.apache.dubbo.common.constants.CommonConstants.PROVIDER_SIDE;
import static org.apache.dubbo.common.constants.RegistryConstants.REGISTRY_TYPE_KEY;
import static org.apache.dubbo.common.constants.RegistryConstants.SERVICE_REGISTRY_TYPE;
import static org.apache.dubbo.common.constants.RegistryConstants.SUBSCRIBED_SERVICE_NAMES_KEY;
import static org.apache.dubbo.rpc.Constants.ID_KEY;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
@ -51,6 +54,7 @@ public class ServiceOrientedRegistryTest {
private static final URL registryURL = valueOf("in-memory://localhost:12345")
.addParameter(REGISTRY_TYPE_KEY, SERVICE_REGISTRY_TYPE)
.addParameter(ID_KEY, "org.apache.dubbo.config.RegistryConfig#0")
.addParameter(SUBSCRIBED_SERVICE_NAMES_KEY, "a, b , c,d,e ,");
private static final String SERVICE_INTERFACE = "org.apache.dubbo.metadata.MetadataService";
@ -81,6 +85,7 @@ public class ServiceOrientedRegistryTest {
registry = ServiceDiscoveryRegistry.create(registryURL);
metadataService = WritableMetadataService.getDefaultExtension();
notifyListener = new MyNotifyListener();
ApplicationModel.getConfigManager().setApplication(new ApplicationConfig("Test"));
}
@Test

View File

@ -0,0 +1,90 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You 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:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-registry</artifactId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>dubbo-registry-dns</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>The DNS registry module of Dubbo project</description>
<dependencies>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-registry-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-config-api</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-rpc-dubbo</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-serialization-hessian2</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-remoting-netty4</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-registry-multicast</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,58 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.dubbo.registry.dns;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.registry.NotifyListener;
import org.apache.dubbo.registry.support.FailbackRegistry;
/**
* Empty implements for DNS <br/>
* DNS only support `Service Discovery` mode register <br/>
* Used to compat past version like 2.6.x, 2.7.x with interface level register <br/>
* {@link DNSServiceDiscovery} is the real implementation of DNS
*/
public class DNSRegistry extends FailbackRegistry {
public DNSRegistry(URL url) {
super(url);
}
@Override
public boolean isAvailable() {
return true;
}
@Override
public void doRegister(URL url) {
}
@Override
public void doUnregister(URL url) {
}
@Override
public void doSubscribe(URL url, NotifyListener listener) {
}
@Override
public void doUnsubscribe(URL url, NotifyListener listener) {
}
}

View File

@ -0,0 +1,34 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.dubbo.registry.dns;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.registry.Registry;
import org.apache.dubbo.registry.support.AbstractRegistryFactory;
public class DNSRegistryFactory extends AbstractRegistryFactory {
@Override
protected String createRegistryCacheKey(URL url) {
return url.toFullString();
}
@Override
protected Registry createRegistry(URL url) {
return new DNSRegistry(url);
}
}

View File

@ -0,0 +1,152 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.dubbo.registry.dns;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.logger.Logger;
import org.apache.dubbo.common.logger.LoggerFactory;
import org.apache.dubbo.common.utils.NamedThreadFactory;
import org.apache.dubbo.registry.client.DefaultServiceInstance;
import org.apache.dubbo.registry.client.SelfHostMetaServiceDiscovery;
import org.apache.dubbo.registry.client.ServiceInstance;
import org.apache.dubbo.registry.client.event.listener.ServiceInstancesChangedListener;
import org.apache.dubbo.registry.dns.util.DNSClientConst;
import org.apache.dubbo.registry.dns.util.DNSResolver;
import org.apache.dubbo.registry.dns.util.ResolveResult;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
public class DNSServiceDiscovery extends SelfHostMetaServiceDiscovery {
private final Logger logger = LoggerFactory.getLogger(getClass());
/**
* DNS properties
*/
private String addressPrefix;
private String addressSuffix;
private long pollingCycle;
private DNSResolver dnsResolver;
/**
* Polling task ScheduledFuture, used to stop task when destroy
*/
private final ConcurrentHashMap<String, ScheduledFuture<?>> pollingExecutorMap = new ConcurrentHashMap<>();
/**
* Polling check provider ExecutorService
*/
private ScheduledExecutorService pollingExecutorService;
@Override
public void doInitialize(URL registryURL) throws Exception {
this.addressPrefix = registryURL.getParameter(DNSClientConst.ADDRESS_PREFIX, "");
this.addressSuffix = registryURL.getParameter(DNSClientConst.ADDRESS_SUFFIX, "");
this.pollingCycle = registryURL.getParameter(DNSClientConst.DNS_POLLING_CYCLE, DNSClientConst.DEFAULT_DNS_POLLING_CYCLE);
String nameserver = registryURL.getHost();
int port = registryURL.getPort();
int maxQueriesPerResolve = registryURL.getParameter(DNSClientConst.MAX_QUERIES_PER_RESOLVE, 10);
this.dnsResolver = new DNSResolver(nameserver, port, maxQueriesPerResolve);
int scheduledThreadPoolSize = registryURL.getParameter(DNSClientConst.DNS_POLLING_POOL_SIZE_KEY, DNSClientConst.DEFAULT_DNS_POLLING_POOL_SIZE);
// polling task may take a lot of time, create a new ScheduledThreadPool
pollingExecutorService = Executors.newScheduledThreadPool(scheduledThreadPoolSize, new NamedThreadFactory("Dubbo-DNS-Poll"));
}
@Override
public void doDestroy() throws Exception {
dnsResolver.destroy();
pollingExecutorMap.forEach((serviceName, scheduledFuture) -> scheduledFuture.cancel(true));
pollingExecutorMap.clear();
pollingExecutorService.shutdown();
}
@Override
public Set<String> getServices() {
// it is impossible for dns to discover service names
return Collections.singleton("Unsupported Method");
}
@Override
public List<ServiceInstance> getInstances(String serviceName) throws NullPointerException {
String serviceAddress = addressPrefix + serviceName + addressSuffix;
ResolveResult resolveResult = dnsResolver.resolve(serviceAddress);
return toServiceInstance(serviceName, resolveResult);
}
@Override
public void addServiceInstancesChangedListener(ServiceInstancesChangedListener listener) throws NullPointerException, IllegalArgumentException {
listener.getServiceNames().forEach(serviceName -> {
ScheduledFuture<?> scheduledFuture = pollingExecutorService.scheduleAtFixedRate(() -> {
List<ServiceInstance> instances = getInstances(serviceName);
instances.sort(Comparator.comparingInt(ServiceInstance::hashCode));
notifyListener(serviceName, listener, instances);
},
pollingCycle, pollingCycle, TimeUnit.MILLISECONDS);
pollingExecutorMap.put(serviceName, scheduledFuture);
});
}
/**
* UT used only
*/
@Deprecated
public void setDnsResolver(DNSResolver dnsResolver) {
this.dnsResolver = dnsResolver;
}
private List<ServiceInstance> toServiceInstance(String serviceName, ResolveResult resolveResult) {
int port;
if (resolveResult.getPort().size() > 0) {
// use first as default
port = resolveResult.getPort().get(0);
} else {
// not support SRV record
port = 20880;
}
List<ServiceInstance> instanceList = new LinkedList<>();
for (String host : resolveResult.getHostnameList()) {
DefaultServiceInstance serviceInstance = new DefaultServiceInstance(serviceName, host, port);
fillServiceInstance(serviceInstance);
instanceList.add(serviceInstance);
}
return instanceList;
}
}

View File

@ -0,0 +1,28 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.dubbo.registry.dns;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.registry.client.AbstractServiceDiscoveryFactory;
import org.apache.dubbo.registry.client.ServiceDiscovery;
public class DNSServiceDiscoveryFactory extends AbstractServiceDiscoveryFactory {
@Override
protected ServiceDiscovery createDiscovery(URL registryURL) {
return new DNSServiceDiscovery();
}
}

View File

@ -0,0 +1,47 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.dubbo.registry.dns.util;
public class DNSClientConst {
public final static String ADDRESS_PREFIX = "addressPrefix";
public final static String ADDRESS_SUFFIX = "addressSuffix";
public final static String MAX_QUERIES_PER_RESOLVE = "maxQueriesPerResolve";
/**
* To decide the frequency of execute DNS poll (in ms)
*/
public final static String DNS_POLLING_CYCLE = "dnsPollingCycle";
/**
* Default value for check frequency: 60000 (ms)
*/
public final static int DEFAULT_DNS_POLLING_CYCLE = 60000;
/**
* To decide how many threads used to execute DNS poll
*/
public final static String DNS_POLLING_POOL_SIZE_KEY = "dnsPollingPoolSize";
/**
* Default value for DNS pool thread: 1
*/
public final static int DEFAULT_DNS_POLLING_POOL_SIZE = 1;
}

View File

@ -0,0 +1,120 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.dubbo.registry.dns.util;
import org.apache.dubbo.common.logger.Logger;
import org.apache.dubbo.common.logger.LoggerFactory;
import io.netty.buffer.ByteBuf;
import io.netty.channel.AddressedEnvelope;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioDatagramChannel;
import io.netty.handler.codec.dns.DefaultDnsQuestion;
import io.netty.handler.codec.dns.DnsRawRecord;
import io.netty.handler.codec.dns.DnsRecordType;
import io.netty.handler.codec.dns.DnsResponse;
import io.netty.handler.codec.dns.DnsSection;
import io.netty.resolver.ResolvedAddressTypes;
import io.netty.resolver.dns.DnsNameResolver;
import io.netty.resolver.dns.DnsNameResolverBuilder;
import io.netty.util.concurrent.Future;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.util.List;
import java.util.stream.Collectors;
import static io.netty.resolver.dns.DnsServerAddresses.sequential;
public class DNSResolver {
private final Logger logger = LoggerFactory.getLogger(getClass());
private final DnsNameResolver resolver;
private static final EventLoopGroup GROUP = new NioEventLoopGroup(1);
public DNSResolver(String nameserver, int port, int maxQueriesPerResolve) {
this.resolver = newResolver(nameserver, port, maxQueriesPerResolve);
}
public ResolveResult resolve(String path) {
ResolveResult recordList = new ResolveResult();
try {
Future<List<InetAddress>> hostFuture = resolver.resolveAll(path);
Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> srvFuture =
resolver.query(new DefaultDnsQuestion(path, DnsRecordType.SRV));
try {
recordList.getHostnameList()
.addAll(hostFuture
.sync().getNow()
.stream()
.map(InetAddress::getHostAddress)
.collect(Collectors.toList()));
DnsResponse srvResponse = srvFuture.sync().getNow().content();
for (int i = 0; i < srvResponse.count(DnsSection.ANSWER); i++) {
DnsRawRecord record = srvResponse.recordAt(DnsSection.ANSWER, i);
ByteBuf buf = record.content();
// Priority
buf.readUnsignedShort();
// Weight
buf.readUnsignedShort();
// Port
int port = buf.readUnsignedShort();
recordList.getPort().add(port);
}
} catch (InterruptedException e) {
logger.warn("Waiting DNS resolve interrupted. " + e.getLocalizedMessage());
}
} catch (Throwable t) {
if (t instanceof UnknownHostException) {
if (logger.isInfoEnabled()) {
logger.info(t.getLocalizedMessage());
}
} else {
logger.error(t.getLocalizedMessage());
}
}
return recordList;
}
public void destroy() {
resolver.close();
}
private static DnsNameResolver newResolver(String nameserver, int port, int maxQueriesPerResolve) {
return new DnsNameResolverBuilder(GROUP.next())
.channelType(NioDatagramChannel.class)
.maxQueriesPerResolve(maxQueriesPerResolve)
.decodeIdn(true)
.optResourceEnabled(false)
.ndots(1)
.resolvedAddressTypes(ResolvedAddressTypes.IPV4_PREFERRED)
// ignore cache
.ttl(0, 1)
.nameServerProvider((hostname) -> sequential(new InetSocketAddress(nameserver, port)).stream())
.build();
}
}

View File

@ -0,0 +1,66 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.dubbo.registry.dns.util;
import java.util.LinkedList;
import java.util.List;
import java.util.Objects;
public class ResolveResult {
private List<String> hostnameList = new LinkedList<>();
private List<Integer> port = new LinkedList<>();
public List<String> getHostnameList() {
return hostnameList;
}
public void setHostnameList(List<String> hostnameList) {
this.hostnameList = hostnameList;
}
public List<Integer> getPort() {
return port;
}
public void setPort(List<Integer> port) {
this.port = port;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ResolveResult that = (ResolveResult) o;
return Objects.equals(hostnameList, that.hostnameList) &&
Objects.equals(port, that.port);
}
@Override
public int hashCode() {
return Objects.hash(hostnameList, port);
}
@Override
public String toString() {
return "ResolveResult{" +
"hostnameList=" + hostnameList +
", port=" + port +
'}';
}
}

View File

@ -0,0 +1 @@
dns=org.apache.dubbo.registry.dns.DNSRegistryFactory

View File

@ -0,0 +1 @@
dns=org.apache.dubbo.registry.dns.DNSServiceDiscovery

View File

@ -0,0 +1 @@
dns=org.apache.dubbo.registry.dns.DNSServiceDiscoveryFactory

View File

@ -0,0 +1,191 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.dubbo.registry.dns;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.utils.NetUtils;
import org.apache.dubbo.config.ApplicationConfig;
import org.apache.dubbo.config.ArgumentConfig;
import org.apache.dubbo.config.MethodConfig;
import org.apache.dubbo.config.ProtocolConfig;
import org.apache.dubbo.config.RegistryConfig;
import org.apache.dubbo.config.ServiceConfig;
import org.apache.dubbo.metadata.InstanceMetadataChangedListener;
import org.apache.dubbo.metadata.MetadataService;
import org.apache.dubbo.metadata.WritableMetadataService;
import org.apache.dubbo.registry.Constants;
import org.apache.dubbo.registry.client.DefaultServiceInstance;
import org.apache.dubbo.registry.client.ServiceDiscovery;
import org.apache.dubbo.registry.client.ServiceInstance;
import org.apache.dubbo.registry.client.event.ServiceInstancesChangedEvent;
import org.apache.dubbo.registry.client.event.listener.ServiceInstancesChangedListener;
import org.apache.dubbo.registry.dns.util.DNSClientConst;
import org.apache.dubbo.registry.dns.util.DNSResolver;
import org.apache.dubbo.registry.dns.util.ResolveResult;
import org.apache.dubbo.rpc.model.ApplicationModel;
import com.alibaba.fastjson.JSONObject;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import static org.apache.dubbo.common.constants.CommonConstants.DUBBO_PROTOCOL;
import static org.apache.dubbo.metadata.MetadataConstants.METADATA_PROXY_TIMEOUT_KEY;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class DNSServiceDiscoveryTest {
@BeforeEach
public void setup() {
ApplicationModel.reset();
ApplicationConfig applicationConfig = new ApplicationConfig("Test");
ApplicationModel.getConfigManager().setApplication(applicationConfig);
}
@AfterEach
public void destroy() {
ApplicationModel.reset();
}
@Test
public void testProvider() throws Exception {
ServiceDiscovery dnsServiceDiscovery = new DNSServiceDiscovery();
URL registryURL = URL.valueOf("dns://");
dnsServiceDiscovery.initialize(registryURL);
assertEquals(registryURL, dnsServiceDiscovery.getUrl());
ServiceInstance serviceInstance = new DefaultServiceInstance("TestService", "localhost", 12345);
serviceInstance.getMetadata().put("a", "b");
dnsServiceDiscovery.register(serviceInstance);
WritableMetadataService metadataService = WritableMetadataService.getDefaultExtension();
InstanceMetadataChangedListener changeListener = Mockito.mock(InstanceMetadataChangedListener.class);
String metadataString = metadataService
.getAndListenInstanceMetadata("test", changeListener);
assertEquals(JSONObject.toJSONString(serviceInstance.getMetadata()), metadataString);
assertEquals(serviceInstance, dnsServiceDiscovery.getLocalInstance());
dnsServiceDiscovery.unregister(serviceInstance);
Mockito.verify(changeListener, Mockito.times(1)).onEvent(Mockito.any());
metadataService.getInstanceMetadataChangedListenerMap().clear();
metadataService.exportInstanceMetadata(null);
dnsServiceDiscovery.destroy();
}
@Test
public void testConsumer() throws Exception {
DNSServiceDiscovery dnsServiceDiscovery = new DNSServiceDiscovery();
URL registryURL = URL.valueOf("dns://")
.addParameter(DNSClientConst.DNS_POLLING_CYCLE, 100)
.addParameter(Constants.ECHO_POLLING_CYCLE_KEY, 100);
ApplicationModel.getEnvironment().getAppExternalConfigurationMap()
.put(METADATA_PROXY_TIMEOUT_KEY, String.valueOf(500));
dnsServiceDiscovery.initialize(registryURL);
WritableMetadataService metadataService = WritableMetadataService.getDefaultExtension();
ServiceInstance serviceInstance = new DefaultServiceInstance("TestService", "localhost", 12345);
serviceInstance.getMetadata().put("a", "b");
dnsServiceDiscovery.register(serviceInstance);
int port = NetUtils.getAvailablePort();
ApplicationModel.getApplicationConfig().setMetadataServicePort(port);
WritableMetadataService spiedMetadataService = Mockito.spy(metadataService);
ServiceConfig<MetadataService> serviceConfig = exportMockMetadataService(spiedMetadataService, port);
DNSResolver dnsResolver = Mockito.mock(DNSResolver.class);
ResolveResult resolveResult = new ResolveResult();
resolveResult.getHostnameList().add("127.0.0.1");
Mockito.when(dnsResolver.resolve("Test.Service.")).thenReturn(resolveResult);
dnsServiceDiscovery.setDnsResolver(dnsResolver);
List<ServiceInstance> serviceInstances = dnsServiceDiscovery.getInstances("Test.Service.");
assertEquals("b", serviceInstances.get(0).getMetadata("a"));
Set<String> serviceNames = new HashSet<>();
serviceNames.add("Test.Service.");
ServiceInstancesChangedListener changedListener = Mockito.spy(new ServiceInstancesChangedListener(serviceNames, null));
Mockito.doNothing().when(changedListener).onEvent(Mockito.any());
serviceInstance.getMetadata().put("a", "c");
dnsServiceDiscovery.update(serviceInstance);
serviceInstances = dnsServiceDiscovery.getInstances("Test.Service.");
assertEquals("c", serviceInstances.get(0).getMetadata("a"));
dnsServiceDiscovery.addServiceInstancesChangedListener(changedListener);
ArgumentCaptor<ServiceInstancesChangedEvent> argument = ArgumentCaptor.forClass(ServiceInstancesChangedEvent.class);
Mockito.verify(changedListener, Mockito.timeout(1000)).onEvent(argument.capture());
assertEquals("c", argument.getValue().getServiceInstances().get(0).getMetadata("a"));
Mockito.when(dnsResolver.resolve("Test.Service.")).thenReturn(new ResolveResult());
Thread.sleep(1000);
assertTrue(dnsServiceDiscovery.getCachedServiceInstances().get("Test.Service.").isEmpty());
metadataService.exportInstanceMetadata(null);
metadataService.getInstanceMetadataChangedListenerMap().clear();
serviceConfig.unexport();
dnsServiceDiscovery.destroy();
ApplicationModel.getEnvironment().getAppExternalConfigurationMap()
.remove(METADATA_PROXY_TIMEOUT_KEY, String.valueOf(100));
}
private ServiceConfig<MetadataService> exportMockMetadataService(MetadataService metadataService, int port) {
ServiceConfig<MetadataService> serviceConfig = new ServiceConfig<>();
serviceConfig.setProtocol(new ProtocolConfig(DUBBO_PROTOCOL, port));
serviceConfig.setRegistry(new RegistryConfig("239.255.255.255", "multicast"));
serviceConfig.setInterface(MetadataService.class);
serviceConfig.setRef(metadataService);
serviceConfig.setGroup("Test.Service.");
serviceConfig.setVersion(MetadataService.VERSION);
MethodConfig methodConfig = new MethodConfig();
methodConfig.setName("getAndListenInstanceMetadata");
ArgumentConfig argumentConfig = new ArgumentConfig();
argumentConfig.setIndex(1);
argumentConfig.setCallback(true);
methodConfig.setArguments(Collections.singletonList(argumentConfig));
serviceConfig.setMethods(Collections.singletonList(methodConfig));
serviceConfig.export();
return serviceConfig;
}
}

View File

@ -0,0 +1,33 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.dubbo.registry.dns.util;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
public class DNSResolverTest {
@Test
public void testResolve() {
DNSResolver dnsResolver = new DNSResolver("8.8.8.8", 53, 1);
ResolveResult resolve = dnsResolver.resolve("aliyun.com");
Assertions.assertTrue(resolve.getHostnameList().size() > 0);
resolve = dnsResolver.resolve("unknowhost.unknowhost.unknowhost");
Assertions.assertEquals(0, resolve.getHostnameList().size());
}
}

View File

@ -0,0 +1,73 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Licensed to the Apache Software Foundation (ASF) under one or more
~ contributor license agreements. See the NOTICE file distributed with
~ this work for additional information regarding copyright ownership.
~ The ASF licenses this file to You 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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-registry</artifactId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>dubbo-registry-kubernetes</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>The Kubernetes registry module of Dubbo project</description>
<dependencies>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-registry-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.fabric8</groupId>
<artifactId>kubernetes-client</artifactId>
</dependency>
<dependency>
<groupId>io.fabric8</groupId>
<artifactId>kubernetes-server-mock</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>3.4.6</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>3.4.6</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,58 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.dubbo.registry.kubernetes;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.registry.NotifyListener;
import org.apache.dubbo.registry.support.FailbackRegistry;
/**
* Empty implements for Kubernetes <br/>
* Kubernetes only support `Service Discovery` mode register <br/>
* Used to compat past version like 2.6.x, 2.7.x with interface level register <br/>
* {@link KubernetesServiceDiscovery} is the real implementation of Kubernetes
*/
public class KubernetesRegistry extends FailbackRegistry {
public KubernetesRegistry(URL url) {
super(url);
}
@Override
public boolean isAvailable() {
return true;
}
@Override
public void doRegister(URL url) {
}
@Override
public void doUnregister(URL url) {
}
@Override
public void doSubscribe(URL url, NotifyListener listener) {
}
@Override
public void doUnsubscribe(URL url, NotifyListener listener) {
}
}

View File

@ -0,0 +1,34 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.dubbo.registry.kubernetes;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.registry.Registry;
import org.apache.dubbo.registry.support.AbstractRegistryFactory;
public class KubernetesRegistryFactory extends AbstractRegistryFactory {
@Override
protected String createRegistryCacheKey(URL url) {
return url.toFullString();
}
@Override
protected Registry createRegistry(URL url) {
return new KubernetesRegistry(url);
}
}

View File

@ -0,0 +1,404 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.dubbo.registry.kubernetes;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.logger.Logger;
import org.apache.dubbo.common.logger.LoggerFactory;
import org.apache.dubbo.common.utils.StringUtils;
import org.apache.dubbo.registry.client.DefaultServiceInstance;
import org.apache.dubbo.registry.client.ServiceDiscovery;
import org.apache.dubbo.registry.client.ServiceInstance;
import org.apache.dubbo.registry.client.event.ServiceInstancesChangedEvent;
import org.apache.dubbo.registry.client.event.listener.ServiceInstancesChangedListener;
import org.apache.dubbo.registry.kubernetes.util.KubernetesClientConst;
import org.apache.dubbo.registry.kubernetes.util.KubernetesConfigUtils;
import com.alibaba.fastjson.JSONObject;
import io.fabric8.kubernetes.api.model.EndpointAddress;
import io.fabric8.kubernetes.api.model.EndpointPort;
import io.fabric8.kubernetes.api.model.EndpointSubset;
import io.fabric8.kubernetes.api.model.Endpoints;
import io.fabric8.kubernetes.api.model.Pod;
import io.fabric8.kubernetes.api.model.Service;
import io.fabric8.kubernetes.client.Config;
import io.fabric8.kubernetes.client.DefaultKubernetesClient;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.KubernetesClientException;
import io.fabric8.kubernetes.client.Watch;
import io.fabric8.kubernetes.client.Watcher;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;
public class KubernetesServiceDiscovery implements ServiceDiscovery {
private final Logger logger = LoggerFactory.getLogger(getClass());
private KubernetesClient kubernetesClient;
private String currentHostname;
private ServiceInstance localServiceInstance;
private URL registryURL;
private String namespace;
private boolean enableRegister;
public final static String KUBERNETES_PROPERTIES_KEY = "io.dubbo/metadata";
private final static ConcurrentHashMap<String, Watch> SERVICE_WATCHER = new ConcurrentHashMap<>(64);
private final static ConcurrentHashMap<String, Watch> PODS_WATCHER = new ConcurrentHashMap<>(64);
private final static ConcurrentHashMap<String, Watch> ENDPOINTS_WATCHER = new ConcurrentHashMap<>(64);
private final static ConcurrentHashMap<String, AtomicLong> SERVICE_UPDATE_TIME = new ConcurrentHashMap<>(64);
@Override
public void initialize(URL registryURL) throws Exception {
Config config = KubernetesConfigUtils.createKubernetesConfig(registryURL);
this.kubernetesClient = new DefaultKubernetesClient(config);
this.currentHostname = System.getenv("HOSTNAME");
this.registryURL = registryURL;
this.namespace = config.getNamespace();
this.enableRegister = registryURL.getParameter(KubernetesClientConst.ENABLE_REGISTER, true);
boolean availableAccess;
try {
availableAccess = kubernetesClient.pods().withName(currentHostname).get() != null;
} catch (Throwable e) {
availableAccess = false;
}
if (!availableAccess) {
String message = "Unable to access api server. " +
"Please check your url config." +
" Master URL: " + config.getMasterUrl() +
" Hostname: " + currentHostname;
logger.error(message);
}
}
@Override
public void destroy() throws Exception {
SERVICE_WATCHER.forEach((k, v) -> v.close());
SERVICE_WATCHER.clear();
PODS_WATCHER.forEach((k, v) -> v.close());
PODS_WATCHER.clear();
ENDPOINTS_WATCHER.forEach((k, v) -> v.close());
ENDPOINTS_WATCHER.clear();
kubernetesClient.close();
}
@Override
public void register(ServiceInstance serviceInstance) throws RuntimeException {
localServiceInstance = serviceInstance;
if (enableRegister) {
kubernetesClient
.pods()
.inNamespace(namespace)
.withName(currentHostname)
.edit()
.editOrNewMetadata()
.addToAnnotations(KUBERNETES_PROPERTIES_KEY, JSONObject.toJSONString(serviceInstance.getMetadata()))
.endMetadata()
.done();
if (logger.isInfoEnabled()) {
logger.info("Write Current Service Instance Metadata to Kubernetes pod. " +
"Current pod name: " + currentHostname);
}
}
}
@Override
public void update(ServiceInstance serviceInstance) throws RuntimeException {
register(serviceInstance);
}
@Override
public void unregister(ServiceInstance serviceInstance) throws RuntimeException {
localServiceInstance = null;
if (enableRegister) {
kubernetesClient
.pods()
.inNamespace(namespace)
.withName(currentHostname)
.edit()
.editOrNewMetadata()
.removeFromAnnotations(KUBERNETES_PROPERTIES_KEY)
.endMetadata()
.done();
if (logger.isInfoEnabled()) {
logger.info("Remove Current Service Instance from Kubernetes pod. Current pod name: " + currentHostname);
}
}
}
@Override
public Set<String> getServices() {
return kubernetesClient
.services()
.inNamespace(namespace)
.list()
.getItems()
.stream()
.map(service -> service.getMetadata().getName())
.collect(Collectors.toSet());
}
@Override
public ServiceInstance getLocalInstance() {
return localServiceInstance;
}
@Override
public List<ServiceInstance> getInstances(String serviceName) throws NullPointerException {
Endpoints endpoints =
kubernetesClient
.endpoints()
.inNamespace(namespace)
.withName(serviceName)
.get();
return toServiceInstance(endpoints, serviceName);
}
@Override
public void addServiceInstancesChangedListener(ServiceInstancesChangedListener listener) throws NullPointerException, IllegalArgumentException {
listener.getServiceNames().forEach(serviceName -> {
SERVICE_UPDATE_TIME.put(serviceName, new AtomicLong(0L));
// Watch Service Endpoint Modification
watchEndpoints(listener, serviceName);
// Watch Pods Modification, happens when ServiceInstance updated
watchPods(listener, serviceName);
// Watch Service Modification, happens when Service Selector updated, used to update pods watcher
watchService(listener, serviceName);
});
}
private void watchEndpoints(ServiceInstancesChangedListener listener, String serviceName) {
Watch watch = kubernetesClient
.endpoints()
.inNamespace(namespace)
.withName(serviceName)
.watch(new Watcher<Endpoints>() {
@Override
public void eventReceived(Action action, Endpoints resource) {
if (logger.isDebugEnabled()) {
logger.debug("Received Endpoint Event. Event type: " + action.name() +
". Current pod name: " + currentHostname);
}
notifyServiceChanged(serviceName, listener);
}
@Override
public void onClose(KubernetesClientException cause) {
// ignore
}
});
ENDPOINTS_WATCHER.put(serviceName, watch);
}
private void watchPods(ServiceInstancesChangedListener listener, String serviceName) {
Map<String, String> serviceSelector = getServiceSelector(serviceName);
if (serviceSelector == null) {
return;
}
Watch watch = kubernetesClient
.pods()
.inNamespace(namespace)
.withLabels(serviceSelector)
.watch(new Watcher<Pod>() {
@Override
public void eventReceived(Action action, Pod resource) {
if (Action.MODIFIED.equals(action)) {
if (logger.isDebugEnabled()) {
logger.debug("Received Pods Update Event. Current pod name: " + currentHostname);
}
notifyServiceChanged(serviceName, listener);
}
}
@Override
public void onClose(KubernetesClientException cause) {
// ignore
}
});
PODS_WATCHER.put(serviceName, watch);
}
private void watchService(ServiceInstancesChangedListener listener, String serviceName) {
Watch watch = kubernetesClient
.services()
.inNamespace(namespace)
.withName(serviceName)
.watch(new Watcher<Service>() {
@Override
public void eventReceived(Action action, Service resource) {
if (Action.MODIFIED.equals(action)) {
if (logger.isDebugEnabled()) {
logger.debug("Received Service Update Event. Update Pods Watcher. " +
"Current pod name: " + currentHostname);
}
if (PODS_WATCHER.containsKey(serviceName)) {
PODS_WATCHER.get(serviceName).close();
PODS_WATCHER.remove(serviceName);
}
watchPods(listener, serviceName);
}
}
@Override
public void onClose(KubernetesClientException cause) {
// ignore
}
});
SERVICE_WATCHER.put(serviceName, watch);
}
private void notifyServiceChanged(String serviceName, ServiceInstancesChangedListener listener) {
long receivedTime = System.nanoTime();
ServiceInstancesChangedEvent event;
event = new ServiceInstancesChangedEvent(serviceName, getInstances(serviceName));
AtomicLong updateTime = SERVICE_UPDATE_TIME.get(serviceName);
long lastUpdateTime = updateTime.get();
if (lastUpdateTime <= receivedTime) {
if (updateTime.compareAndSet(lastUpdateTime, receivedTime)) {
listener.onEvent(event);
return;
}
}
if (logger.isInfoEnabled()) {
logger.info("Discard Service Instance Data. " +
"Possible Cause: Newer message has been processed or Failed to update time record by CAS. " +
"Current Data received time: " + receivedTime + ". " +
"Newer Data received time: " + lastUpdateTime + ".");
}
}
@Override
public URL getUrl() {
return registryURL;
}
private Map<String, String> getServiceSelector(String serviceName) {
Service service = kubernetesClient.services().inNamespace(namespace).withName(serviceName).get();
if (service == null) {
return null;
}
return service.getSpec().getSelector();
}
private List<ServiceInstance> toServiceInstance(Endpoints endpoints, String serviceName) {
Map<String, String> serviceSelector = getServiceSelector(serviceName);
if (serviceSelector == null) {
return new LinkedList<>();
}
Map<String, Pod> pods = kubernetesClient
.pods()
.inNamespace(namespace)
.withLabels(serviceSelector)
.list()
.getItems()
.stream()
.collect(
Collectors.toMap(
pod -> pod.getMetadata().getName(),
pod -> pod));
List<ServiceInstance> instances = new LinkedList<>();
Set<Integer> instancePorts = new HashSet<>();
for (EndpointSubset endpointSubset : endpoints.getSubsets()) {
instancePorts.addAll(
endpointSubset.getPorts()
.stream().map(EndpointPort::getPort)
.collect(Collectors.toSet()));
}
for (EndpointSubset endpointSubset : endpoints.getSubsets()) {
for (EndpointAddress address : endpointSubset.getAddresses()) {
Pod pod = pods.get(address.getTargetRef().getName());
String ip = address.getIp();
if (pod == null) {
logger.warn("Unable to match Kubernetes Endpoint address with Pod. " +
"EndpointAddress Hostname: " + address.getTargetRef().getName());
continue;
}
instancePorts.forEach(port -> {
ServiceInstance serviceInstance = new DefaultServiceInstance(serviceName, ip, port);
String properties = pod.getMetadata().getAnnotations().get(KUBERNETES_PROPERTIES_KEY);
if (StringUtils.isNotEmpty(properties)) {
serviceInstance.getMetadata().putAll(JSONObject.parseObject(properties, Map.class));
instances.add(serviceInstance);
} else {
logger.warn("Unable to find Service Instance metadata in Pod Annotations. " +
"Possibly cause: provider has not been initialized successfully. " +
"EndpointAddress Hostname: " + address.getTargetRef().getName());
}
});
}
}
return instances;
}
/**
* UT used only
*/
@Deprecated
public void setCurrentHostname(String currentHostname) {
this.currentHostname = currentHostname;
}
/**
* UT used only
*/
@Deprecated
public void setKubernetesClient(KubernetesClient kubernetesClient) {
this.kubernetesClient = kubernetesClient;
}
}

View File

@ -0,0 +1,28 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.dubbo.registry.kubernetes;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.registry.client.AbstractServiceDiscoveryFactory;
import org.apache.dubbo.registry.client.ServiceDiscovery;
public class KubernetesServiceDiscoveryFactory extends AbstractServiceDiscoveryFactory {
@Override
protected ServiceDiscovery createDiscovery(URL registryURL) {
return new KubernetesServiceDiscovery();
}
}

View File

@ -0,0 +1,76 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.dubbo.registry.kubernetes.util;
public class KubernetesClientConst {
public final static String ENABLE_REGISTER = "enableRegister";
public final static String TRUST_CERTS = "trustCerts";
public final static String USE_HTTPS = "useHttps";
public static final String HTTP2_DISABLE = "http2Disable";
public final static String NAMESPACE = "namespace";
public final static String API_VERSION = "apiVersion";
public final static String CA_CERT_FILE = "caCertFile";
public final static String CA_CERT_DATA = "caCertData";
public final static String CLIENT_CERT_FILE = "clientCertFile";
public final static String CLIENT_CERT_DATA = "clientCertData";
public final static String CLIENT_KEY_FILE = "clientKeyFile";
public final static String CLIENT_KEY_DATA = "clientKeyData";
public final static String CLIENT_KEY_ALGO = "clientKeyAlgo";
public final static String CLIENT_KEY_PASSPHRASE = "clientKeyPassphrase";
public final static String OAUTH_TOKEN = "oauthToken";
public final static String USERNAME = "username";
public final static String PASSWORD = "password";
public final static String WATCH_RECONNECT_INTERVAL = "watchReconnectInterval";
public final static String WATCH_RECONNECT_LIMIT = "watchReconnectLimit";
public final static String CONNECTION_TIMEOUT = "connectionTimeout";
public final static String REQUEST_TIMEOUT = "requestTimeout";
public final static String ROLLING_TIMEOUT = "rollingTimeout";
public final static String LOGGING_INTERVAL = "loggingInterval";
public final static String HTTP_PROXY = "httpProxy";
public final static String HTTPS_PROXY = "httpsProxy";
public final static String PROXY_USERNAME = "proxyUsername";
public final static String PROXY_PASSWORD = "proxyPassword";
public final static String NO_PROXY = "noProxy";
}

View File

@ -0,0 +1,111 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.dubbo.registry.kubernetes.util;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.utils.StringUtils;
import io.fabric8.kubernetes.client.Config;
import io.fabric8.kubernetes.client.ConfigBuilder;
import java.util.Base64;
public class KubernetesConfigUtils {
public static Config createKubernetesConfig(URL url) {
// Init default config
Config base = Config.autoConfigure(null);
// replace config with parameters if presents
return new ConfigBuilder(base)
.withMasterUrl(buildMasterUrl(url))
.withApiVersion(url.getParameter(KubernetesClientConst.API_VERSION,
base.getApiVersion()))
.withNamespace(url.getParameter(KubernetesClientConst.NAMESPACE,
base.getNamespace()))
.withUsername(url.getParameter(KubernetesClientConst.USERNAME,
base.getUsername()))
.withPassword(url.getParameter(KubernetesClientConst.PASSWORD,
base.getPassword()))
.withOauthToken(url.getParameter(KubernetesClientConst.OAUTH_TOKEN,
base.getOauthToken()))
.withCaCertFile(url.getParameter(KubernetesClientConst.CA_CERT_FILE,
base.getCaCertFile()))
.withCaCertData(url.getParameter(KubernetesClientConst.CA_CERT_DATA,
decodeBase64(base.getCaCertData())))
.withClientKeyFile(url.getParameter(KubernetesClientConst.CLIENT_KEY_FILE,
base.getClientKeyFile()))
.withClientKeyData(url.getParameter(KubernetesClientConst.CLIENT_KEY_DATA,
decodeBase64(base.getClientKeyData())))
.withClientCertFile(url.getParameter(KubernetesClientConst.CLIENT_CERT_FILE,
base.getClientCertFile()))
.withClientCertData(url.getParameter(KubernetesClientConst.CLIENT_CERT_DATA,
decodeBase64(base.getClientCertData())))
.withClientKeyAlgo(url.getParameter(KubernetesClientConst.CLIENT_KEY_ALGO,
base.getClientKeyAlgo()))
.withClientKeyPassphrase(url.getParameter(KubernetesClientConst.CLIENT_KEY_PASSPHRASE,
base.getClientKeyPassphrase()))
.withConnectionTimeout(url.getParameter(KubernetesClientConst.CONNECTION_TIMEOUT,
base.getConnectionTimeout()))
.withRequestTimeout(url.getParameter(KubernetesClientConst.REQUEST_TIMEOUT,
base.getRequestTimeout()))
.withRollingTimeout(url.getParameter(KubernetesClientConst.ROLLING_TIMEOUT,
base.getRollingTimeout()))
.withWatchReconnectInterval(url.getParameter(KubernetesClientConst.WATCH_RECONNECT_INTERVAL,
base.getWatchReconnectInterval()))
.withWatchReconnectLimit(url.getParameter(KubernetesClientConst.WATCH_RECONNECT_LIMIT,
base.getWatchReconnectLimit()))
.withLoggingInterval(url.getParameter(KubernetesClientConst.LOGGING_INTERVAL,
base.getLoggingInterval()))
.withTrustCerts(url.getParameter(KubernetesClientConst.TRUST_CERTS,
base.isTrustCerts()))
.withHttp2Disable(url.getParameter(KubernetesClientConst.HTTP2_DISABLE,
base.isTrustCerts()))
.withHttpProxy(url.getParameter(KubernetesClientConst.HTTP_PROXY,
base.getHttpProxy()))
.withHttpsProxy(url.getParameter(KubernetesClientConst.HTTPS_PROXY,
base.getHttpsProxy()))
.withProxyUsername(url.getParameter(KubernetesClientConst.PROXY_USERNAME,
base.getProxyUsername()))
.withProxyPassword(url.getParameter(KubernetesClientConst.PROXY_PASSWORD,
base.getProxyPassword()))
.withNoProxy(url.getParameter(KubernetesClientConst.NO_PROXY,
base.getNoProxy()))
.build();
}
private static String buildMasterUrl(URL url) {
return (url.getParameter(KubernetesClientConst.USE_HTTPS, true) ?
"https://" : "http://")
+ url.getHost() + ":" + url.getPort();
}
private static String decodeBase64(String str) {
return StringUtils.isNotEmpty(str) ?
new String(Base64.getDecoder().decode(str)) :
"";
}
}

View File

@ -0,0 +1 @@
kubernetes=org.apache.dubbo.registry.kubernetes.KubernetesRegistryFactory

View File

@ -0,0 +1 @@
kubernetes=org.apache.dubbo.registry.kubernetes.KubernetesServiceDiscovery

View File

@ -0,0 +1 @@
kubernetes=org.apache.dubbo.registry.kubernetes.KubernetesServiceDiscoveryFactory

View File

@ -0,0 +1,191 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.dubbo.registry.kubernetes;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.registry.client.DefaultServiceInstance;
import org.apache.dubbo.registry.client.ServiceInstance;
import org.apache.dubbo.registry.client.event.ServiceInstancesChangedEvent;
import org.apache.dubbo.registry.client.event.listener.ServiceInstancesChangedListener;
import org.apache.dubbo.registry.kubernetes.util.KubernetesClientConst;
import io.fabric8.kubernetes.api.model.Endpoints;
import io.fabric8.kubernetes.api.model.EndpointsBuilder;
import io.fabric8.kubernetes.api.model.Pod;
import io.fabric8.kubernetes.api.model.PodBuilder;
import io.fabric8.kubernetes.api.model.Service;
import io.fabric8.kubernetes.api.model.ServiceBuilder;
import io.fabric8.kubernetes.client.Config;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.server.mock.KubernetesServer;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
@ExtendWith({MockitoExtension.class})
public class KubernetesServiceDiscoveryTest {
public KubernetesServer mockServer = new KubernetesServer(true, true);
private KubernetesClient mockClient;
private ServiceInstancesChangedListener mockListener = Mockito.mock(ServiceInstancesChangedListener.class);
private URL serverUrl;
private Map<String, String> selector;
@BeforeEach
public void setUp() {
mockServer.before();
mockClient = mockServer.getClient();
serverUrl = URL.valueOf(mockClient.getConfiguration().getMasterUrl())
.setProtocol("kubernetes")
.addParameter(KubernetesClientConst.TRUST_CERTS, "true")
.addParameter(KubernetesClientConst.HTTP2_DISABLE, "true");
System.setProperty(Config.KUBERNETES_AUTH_TRYKUBECONFIG_SYSTEM_PROPERTY, "false");
System.setProperty(Config.KUBERNETES_AUTH_TRYSERVICEACCOUNT_SYSTEM_PROPERTY, "false");
selector = new HashMap<>(4);
selector.put("l", "v");
Pod pod = new PodBuilder()
.withNewMetadata().withName("TestServer").withLabels(selector).endMetadata()
.build();
Service service = new ServiceBuilder()
.withNewMetadata().withName("TestService").endMetadata()
.withNewSpec().withSelector(selector).endSpec().build();
Endpoints endPoints = new EndpointsBuilder()
.withNewMetadata().withName("TestService").endMetadata()
.addNewSubset()
.addNewAddress().withIp("ip1")
.withNewTargetRef().withUid("uid1").withName("TestServer").endTargetRef().endAddress()
.addNewPort("Test", "Test", 12345, "TCP").endSubset()
.build();
mockClient.pods().create(pod);
mockClient.services().create(service);
mockClient.endpoints().create(endPoints);
}
@AfterEach
public void destroy() {
mockServer.after();
}
@Test
public void testEndpointsUpdate() throws Exception {
KubernetesServiceDiscovery serviceDiscovery = new KubernetesServiceDiscovery();
serviceDiscovery.initialize(serverUrl);
serviceDiscovery.setCurrentHostname("TestServer");
serviceDiscovery.setKubernetesClient(mockClient);
ServiceInstance serviceInstance = new DefaultServiceInstance("TestService", "Test", 12345);
serviceDiscovery.register(serviceInstance);
HashSet<String> serviceList = new HashSet<>(4);
serviceList.add("TestService");
Mockito.when(mockListener.getServiceNames()).thenReturn(serviceList);
Mockito.doNothing().when(mockListener).onEvent(Mockito.any());
serviceDiscovery.addServiceInstancesChangedListener(mockListener);
mockClient.endpoints().withName("TestService").edit().editFirstSubset()
.addNewAddress().withIp("ip2")
.withNewTargetRef().withUid("uid2").withName("TestServer").endTargetRef().endAddress()
.addNewPort("Test", "Test", 12345, "TCP").endSubset()
.done();
Thread.sleep(5000);
ArgumentCaptor<ServiceInstancesChangedEvent> eventArgumentCaptor =
ArgumentCaptor.forClass(ServiceInstancesChangedEvent.class);
Mockito.verify(mockListener).onEvent(eventArgumentCaptor.capture());
Assertions.assertEquals(2, eventArgumentCaptor.getValue().getServiceInstances().size());
serviceDiscovery.unregister(serviceInstance);
serviceDiscovery.destroy();
}
@Test
public void testPodsUpdate() throws Exception {
KubernetesServiceDiscovery serviceDiscovery = new KubernetesServiceDiscovery();
serviceDiscovery.initialize(serverUrl);
serviceDiscovery.setCurrentHostname("TestServer");
serviceDiscovery.setKubernetesClient(mockClient);
ServiceInstance serviceInstance = new DefaultServiceInstance("TestService", "Test", 12345);
serviceDiscovery.register(serviceInstance);
HashSet<String> serviceList = new HashSet<>(4);
serviceList.add("TestService");
Mockito.when(mockListener.getServiceNames()).thenReturn(serviceList);
Mockito.doNothing().when(mockListener).onEvent(Mockito.any());
serviceDiscovery.addServiceInstancesChangedListener(mockListener);
serviceInstance = new DefaultServiceInstance("TestService", "Test12345", 12345);
serviceDiscovery.update(serviceInstance);
Thread.sleep(5000);
ArgumentCaptor<ServiceInstancesChangedEvent> eventArgumentCaptor =
ArgumentCaptor.forClass(ServiceInstancesChangedEvent.class);
Mockito.verify(mockListener).onEvent(eventArgumentCaptor.capture());
Assertions.assertEquals(1, eventArgumentCaptor.getValue().getServiceInstances().size());
serviceDiscovery.unregister(serviceInstance);
serviceDiscovery.destroy();
}
@Test
public void testGetInstance() throws Exception {
KubernetesServiceDiscovery serviceDiscovery = new KubernetesServiceDiscovery();
serviceDiscovery.initialize(serverUrl);
serviceDiscovery.setCurrentHostname("TestServer");
serviceDiscovery.setKubernetesClient(mockClient);
ServiceInstance serviceInstance = new DefaultServiceInstance("TestService", "Test", 12345);
serviceDiscovery.register(serviceInstance);
serviceDiscovery.update(serviceInstance);
Assertions.assertEquals(1, serviceDiscovery.getServices().size());
Assertions.assertEquals(1, serviceDiscovery.getInstances("TestService").size());
Assertions.assertEquals(serviceInstance, serviceDiscovery.getLocalInstance());
serviceDiscovery.unregister(serviceInstance);
serviceDiscovery.destroy();
}
}

View File

@ -0,0 +1,72 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.dubbo.registry.multicast;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.registry.client.ServiceDiscovery;
import org.apache.dubbo.registry.client.ServiceInstance;
import java.util.Collections;
import java.util.Set;
/**
* TODO: make multicast protocol support Service Discovery
*/
public class MulticastServiceDiscovery implements ServiceDiscovery {
private URL registryURL;
private ServiceInstance serviceInstance;
@Override
public void initialize(URL registryURL) throws Exception {
this.registryURL = registryURL;
}
@Override
public void destroy() throws Exception {
}
@Override
public void register(ServiceInstance serviceInstance) throws RuntimeException {
this.serviceInstance = serviceInstance;
}
@Override
public void update(ServiceInstance serviceInstance) throws RuntimeException {
this.serviceInstance = serviceInstance;
}
@Override
public void unregister(ServiceInstance serviceInstance) throws RuntimeException {
this.serviceInstance = null;
}
@Override
public Set<String> getServices() {
return Collections.singleton("Unsupported Operation");
}
@Override
public URL getUrl() {
return registryURL;
}
@Override
public ServiceInstance getLocalInstance() {
return serviceInstance;
}
}

View File

@ -0,0 +1,28 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.dubbo.registry.multicast;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.registry.client.ServiceDiscovery;
import org.apache.dubbo.registry.client.ServiceDiscoveryFactory;
public class MulticastServiceDiscoveryFactory implements ServiceDiscoveryFactory {
@Override
public ServiceDiscovery getServiceDiscovery(URL registryURL) {
return new MulticastServiceDiscovery();
}
}

View File

@ -0,0 +1 @@
multicast=org.apache.dubbo.registry.multicast.MulticastServiceDiscovery

View File

@ -0,0 +1 @@
multicast=org.apache.dubbo.registry.multicast.MulticastServiceDiscoveryFactory

View File

@ -30,6 +30,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import static org.apache.dubbo.common.constants.RegistryConstants.EMPTY_PROTOCOL;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
@ -39,7 +40,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
public class MulticastRegistryTest {
private String service = "org.apache.dubbo.test.injvmServie";
private URL registryUrl = URL.valueOf("multicast://239.255.255.255/");
private URL registryUrl = URL.valueOf("multicast://239.239.239.239/");
private URL serviceUrl = URL.valueOf("dubbo://" + NetUtils.getLocalHost() + "/" + service
+ "?methods=test1,test2");
private URL adminUrl = URL.valueOf("dubbo://" + NetUtils.getLocalHost() + "/*");
@ -79,7 +80,7 @@ public class MulticastRegistryTest {
@Test
public void testGetCustomPort() {
int port = NetUtils.getAvailablePort();
URL customPortUrl = URL.valueOf("multicast://239.255.255.255:" + port);
URL customPortUrl = URL.valueOf("multicast://239.239.239.239:" + port);
MulticastRegistry multicastRegistry = new MulticastRegistry(customPortUrl);
assertThat(multicastRegistry.getUrl().getPort(), is(port));
}
@ -132,15 +133,20 @@ public class MulticastRegistryTest {
@Test
public void testSubscribe() {
// verify listener
registry.subscribe(consumerUrl, new NotifyListener() {
@Override
public void notify(List<URL> urls) {
assertEquals(serviceUrl.toFullString(), urls.get(0).toFullString());
final URL[] notifyUrl = new URL[1];
for (int i = 0; i < 10; i++) {
registry.register(serviceUrl);
registry.subscribe(consumerUrl, urls -> {
notifyUrl[0] = urls.get(0);
Map<URL, Set<NotifyListener>> subscribed = registry.getSubscribed();
assertEquals(consumerUrl, subscribed.keySet().iterator().next());
});
if (!EMPTY_PROTOCOL.equalsIgnoreCase(notifyUrl[0].getProtocol())) {
break;
}
});
}
assertEquals(serviceUrl.toFullString(), notifyUrl[0].toFullString());
}
/**

View File

@ -33,6 +33,7 @@ import java.util.Map;
import static java.util.Arrays.asList;
import static org.apache.dubbo.common.utils.NetUtils.getAvailablePort;
import static org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils.INSTANCE_REVISION_UPDATED_KEY;
import static org.apache.dubbo.registry.zookeeper.util.CuratorFrameworkUtils.generateId;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
@ -73,7 +74,7 @@ public class ZookeeperServiceDiscoveryTest {
}
@Test
public void testRegistration() {
public void testRegistration() throws InterruptedException {
DefaultServiceInstance serviceInstance = createServiceInstance(SERVICE_NAME, LOCALHOST, NetUtils.getAvailablePort());
@ -87,6 +88,7 @@ public class ZookeeperServiceDiscoveryTest {
Map<String, String> metadata = new HashMap<>();
metadata.put("message", "Hello,World");
serviceInstance.setMetadata(metadata);
serviceInstance.getExtendParams().put(INSTANCE_REVISION_UPDATED_KEY, "true");
discovery.update(serviceInstance);

View File

@ -35,5 +35,7 @@
<module>dubbo-registry-zookeeper</module>
<module>dubbo-registry-nacos</module>
<module>dubbo-registry-multiple</module>
<module>dubbo-registry-kubernetes</module>
<module>dubbo-registry-dns</module>
</modules>
</project>

View File

@ -20,6 +20,7 @@ package org.apache.dubbo.remoting.exchange.support.header;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.logger.Logger;
import org.apache.dubbo.common.logger.LoggerFactory;
import org.apache.dubbo.common.utils.NetUtils;
import org.apache.dubbo.remoting.Channel;
import org.apache.dubbo.remoting.Constants;
import org.apache.dubbo.remoting.RemotingException;
@ -35,6 +36,7 @@ import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
public class HeartbeatHandlerTest {
@ -61,9 +63,13 @@ public class HeartbeatHandlerTest {
@Test
public void testServerHeartbeat() throws Exception {
URL serverURL = URL.valueOf("header://localhost:55555?transporter=netty3");
serverURL = serverURL.addParameter(Constants.HEARTBEAT_KEY, 1000);
TestHeartbeatHandler handler = new TestHeartbeatHandler();
URL serverURL = URL.valueOf("telnet://localhost:" + NetUtils.getAvailablePort(56780))
.addParameter(Constants.EXCHANGER_KEY, HeaderExchanger.NAME)
.addParameter(Constants.TRANSPORTER_KEY, "netty3")
.addParameter(Constants.HEARTBEAT_KEY, 1000);
CountDownLatch connect = new CountDownLatch(1);
CountDownLatch disconnect = new CountDownLatch(1);
TestHeartbeatHandler handler = new TestHeartbeatHandler(connect, disconnect);
server = Exchangers.bind(serverURL, handler);
System.out.println("Server bind successfully");
@ -75,21 +81,25 @@ public class HeartbeatHandlerTest {
serverURL = serverURL.addParameter(Constants.RECONNECT_KEY, false);
client = Exchangers.connect(serverURL);
Thread.sleep(10000);
disconnect.await();
Assertions.assertTrue(handler.disconnectCount > 0);
System.out.println("disconnect count " + handler.disconnectCount);
}
@Test
public void testHeartbeat() throws Exception {
URL serverURL = URL.valueOf("header://localhost:55556?transporter=netty3");
serverURL = serverURL.addParameter(Constants.HEARTBEAT_KEY, 1000);
TestHeartbeatHandler handler = new TestHeartbeatHandler();
URL serverURL = URL.valueOf("telnet://localhost:" + NetUtils.getAvailablePort(56785))
.addParameter(Constants.EXCHANGER_KEY, HeaderExchanger.NAME)
.addParameter(Constants.TRANSPORTER_KEY, "netty3")
.addParameter(Constants.HEARTBEAT_KEY, 1000);
CountDownLatch connect = new CountDownLatch(1);
CountDownLatch disconnect = new CountDownLatch(1);
TestHeartbeatHandler handler = new TestHeartbeatHandler(connect, disconnect);
server = Exchangers.bind(serverURL, handler);
System.out.println("Server bind successfully");
client = Exchangers.connect(serverURL);
Thread.sleep(10000);
connect.await();
System.err.println("++++++++++++++ disconnect count " + handler.disconnectCount);
System.err.println("++++++++++++++ connect count " + handler.connectCount);
Assertions.assertEquals(0, handler.disconnectCount);
@ -99,15 +109,19 @@ public class HeartbeatHandlerTest {
@Test
public void testClientHeartbeat() throws Exception {
FakeChannelHandlers.setTestingChannelHandlers();
URL serverURL = URL.valueOf("header://localhost:55557?transporter=netty3");
TestHeartbeatHandler handler = new TestHeartbeatHandler();
URL serverURL = URL.valueOf("telnet://localhost:" + NetUtils.getAvailablePort(56790))
.addParameter(Constants.EXCHANGER_KEY, HeaderExchanger.NAME)
.addParameter(Constants.TRANSPORTER_KEY, "netty3");
CountDownLatch connect = new CountDownLatch(1);
CountDownLatch disconnect = new CountDownLatch(1);
TestHeartbeatHandler handler = new TestHeartbeatHandler(connect, disconnect);
server = Exchangers.bind(serverURL, handler);
System.out.println("Server bind successfully");
FakeChannelHandlers.resetChannelHandlers();
serverURL = serverURL.addParameter(Constants.HEARTBEAT_KEY, 1000);
client = Exchangers.connect(serverURL);
Thread.sleep(10000);
connect.await();
Assertions.assertTrue(handler.connectCount > 0);
System.out.println("connect count " + handler.connectCount);
}
@ -116,6 +130,13 @@ public class HeartbeatHandlerTest {
public int disconnectCount = 0;
public int connectCount = 0;
private CountDownLatch connectCountDownLatch;
private CountDownLatch disconnectCountDownLatch;
public TestHeartbeatHandler(CountDownLatch connectCountDownLatch, CountDownLatch disconnectCountDownLatch) {
this.connectCountDownLatch = connectCountDownLatch;
this.disconnectCountDownLatch = disconnectCountDownLatch;
}
public CompletableFuture<Object> reply(ExchangeChannel channel, Object request) throws RemotingException {
return CompletableFuture.completedFuture(request);
@ -124,11 +145,13 @@ public class HeartbeatHandlerTest {
@Override
public void connected(Channel channel) throws RemotingException {
++connectCount;
connectCountDownLatch.countDown();
}
@Override
public void disconnected(Channel channel) throws RemotingException {
++disconnectCount;
disconnectCountDownLatch.countDown();
}
@Override

View File

@ -47,7 +47,7 @@ public class ClientReconnectTest {
Client client = startClient(port, 200);
Assertions.assertFalse(client.isConnected());
RemotingServer server = startServer(port);
for (int i = 0; i < 100 && !client.isConnected(); i++) {
for (int i = 0; i < 1000 && !client.isConnected(); i++) {
Thread.sleep(10);
}
Assertions.assertTrue(client.isConnected());
@ -70,7 +70,8 @@ public class ClientReconnectTest {
public Client startClient(int port, int heartbeat) throws RemotingException {
final String url = "exchange://127.0.0.1:" + port + "/client.reconnect.test?check=false&client=netty3&" + Constants.HEARTBEAT_KEY + "=" + heartbeat;
final String url = "exchange://127.0.0.1:" + port + "/client.reconnect.test?check=false&client=netty3&" +
Constants.HEARTBEAT_KEY + "=" + heartbeat;
return Exchangers.connect(url);
}

View File

@ -44,8 +44,8 @@ public class ThreadNameTest {
@BeforeEach
public void before() throws Exception {
int port = NetUtils.getAvailablePort();
serverURL = URL.valueOf("netty://localhost?side=provider").setPort(port);
clientURL = URL.valueOf("netty://localhost?side=consumer").setPort(port);
serverURL = URL.valueOf("telnet://localhost?side=provider").setPort(port);
clientURL = URL.valueOf("telnet://localhost?side=consumer").setPort(port);
serverHandler = new ThreadNameVerifyHandler(serverRegex, false);
clientHandler = new ThreadNameVerifyHandler(clientRegex, true);

View File

@ -35,7 +35,7 @@ public class NettyTransporterTest {
@Test
public void shouldAbleToBindNetty4() throws Exception {
int port = NetUtils.getAvailablePort();
URL url = new URL("http", "localhost", port,
URL url = new URL("telnet", "localhost", port,
new String[]{Constants.BIND_PORT_KEY, String.valueOf(port)});
RemotingServer server = new NettyTransporter().bind(url, new ChannelHandlerAdapter());
@ -48,7 +48,7 @@ public class NettyTransporterTest {
final CountDownLatch lock = new CountDownLatch(1);
int port = NetUtils.getAvailablePort();
URL url = new URL("http", "localhost", port,
URL url = new URL("telnet", "localhost", port,
new String[]{Constants.BIND_PORT_KEY, String.valueOf(port)});
new NettyTransporter().bind(url, new ChannelHandlerAdapter() {

View File

@ -24,6 +24,7 @@ import org.apache.dubbo.remoting.exchange.ExchangeChannel;
import org.apache.dubbo.remoting.exchange.ExchangeServer;
import org.apache.dubbo.remoting.exchange.Exchangers;
import org.apache.dubbo.remoting.exchange.support.ReplierDispatcher;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
@ -31,7 +32,11 @@ import org.junit.jupiter.api.Test;
import java.io.Serializable;
import java.util.Random;
import java.util.concurrent.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import static org.junit.jupiter.api.Assertions.fail;
@ -54,13 +59,13 @@ public class ReplierDispatcherTest {
ReplierDispatcher dispatcher = new ReplierDispatcher();
dispatcher.addReplier(RpcMessage.class, new RpcMessageHandler());
dispatcher.addReplier(Data.class, (channel, msg) -> new StringMessage("hello world"));
exchangeServer = Exchangers.bind(URL.valueOf("dubbo://localhost:" + port), dispatcher);
exchangeServer = Exchangers.bind(URL.valueOf("exchange://localhost:" + port + "?" + CommonConstants.TIMEOUT_KEY + "=60000"), dispatcher);
}
@Test
public void testDataPackage() throws Exception {
ExchangeChannel client = Exchangers.connect(URL.valueOf("dubbo://localhost:" + port));
ExchangeChannel client = Exchangers.connect(URL.valueOf("exchange://localhost:" + port + "?" + CommonConstants.TIMEOUT_KEY + "=60000"));
Random random = new Random();
for (int i = 5; i < 100; i++) {
StringBuilder sb = new StringBuilder();
@ -91,7 +96,7 @@ public class ReplierDispatcherTest {
}
void clientExchangeInfo(int port) throws Exception {
ExchangeChannel client = Exchangers.connect(URL.valueOf("dubbo://localhost:" + port + "?" + CommonConstants.TIMEOUT_KEY + "=5000"));
ExchangeChannel client = Exchangers.connect(URL.valueOf("exchange://localhost:" + port + "?" + CommonConstants.TIMEOUT_KEY + "=5000"));
clients.put(Thread.currentThread().getName(), client);
MockResult result = (MockResult) client.request(new RpcMessage(DemoService.class.getName(), "plus", new Class<?>[]{int.class, int.class}, new Object[]{55, 25})).get();
Assertions.assertEquals(result.getResult(), 80);

View File

@ -581,6 +581,8 @@
</exclude>
<exclude>.github/**</exclude>
<exclude>compiler/**</exclude>
<!-- exclude mockito extensions spi files -->
<exclude>**/mockito-extensions/*</exclude>
</excludes>
</configuration>
</execution>