diff --git a/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/support/wrapper/MockClusterInvokerTest.java b/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/support/wrapper/MockClusterInvokerTest.java index feff78eca5..df1240cf81 100644 --- a/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/support/wrapper/MockClusterInvokerTest.java +++ b/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/support/wrapper/MockClusterInvokerTest.java @@ -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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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"; } diff --git a/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/support/wrapper/MockProviderRpcExceptionTest.java b/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/support/wrapper/MockProviderRpcExceptionTest.java index 39459fbfbf..6a46312a26 100644 --- a/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/support/wrapper/MockProviderRpcExceptionTest.java +++ b/dubbo-cluster/src/test/java/org/apache/dubbo/rpc/cluster/support/wrapper/MockProviderRpcExceptionTest.java @@ -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 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"; } diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/constants/CommonConstants.java b/dubbo-common/src/main/java/org/apache/dubbo/common/constants/CommonConstants.java index 4f831b4df8..1145f9eec9 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/common/constants/CommonConstants.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/constants/CommonConstants.java @@ -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"; diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/ApplicationConfig.java b/dubbo-common/src/main/java/org/apache/dubbo/config/ApplicationConfig.java index f77d3f1232..3ecef218d1 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/config/ApplicationConfig.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/config/ApplicationConfig.java @@ -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(); diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ServiceConfig.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ServiceConfig.java index 3b5edc163e..44f7730922 100644 --- a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ServiceConfig.java +++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ServiceConfig.java @@ -217,7 +217,8 @@ public class ServiceConfig extends ServiceConfigBase { public void exported() { List exportedURLs = this.getExportedUrls(); exportedURLs.forEach(url -> { - ServiceNameMapping.getExtension(getApplication().getParameters().get(MAPPING_KEY)).map(url); + Map 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 extends ServiceConfigBase { /** * 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 extends ServiceConfigBase { } private void postProcessConfig() { - List configPostProcessors =ExtensionLoader.getExtensionLoader(ConfigPostProcessor.class) + List configPostProcessors = ExtensionLoader.getExtensionLoader(ConfigPostProcessor.class) .getActivateExtension(URL.valueOf("configPostProcessor://"), (String[]) null); configPostProcessors.forEach(component -> component.postProcessServiceConfig(this)); } diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/bootstrap/DubboBootstrap.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/bootstrap/DubboBootstrap.java index 48bc28133d..a4116f49ae 100644 --- a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/bootstrap/DubboBootstrap.java +++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/bootstrap/DubboBootstrap.java @@ -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(); diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/bootstrap/builders/ApplicationBuilder.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/bootstrap/builders/ApplicationBuilder.java index 33e6f54a95..9c17afa7a0 100644 --- a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/bootstrap/builders/ApplicationBuilder.java +++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/bootstrap/builders/ApplicationBuilder.java @@ -51,6 +51,10 @@ public class ApplicationBuilder extends AbstractBuilder 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 + *

+ * 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 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 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 protocolConfigs) { + if (CollectionUtils.isEmpty(protocolConfigs)) { + return null; + } + + for (ProtocolConfig protocolConfig : protocolConfigs) { + if (DUBBO_PROTOCOL.equalsIgnoreCase(protocolConfig.getName())) { + return protocolConfig; + } + } + + return null; + } } diff --git a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/AbstractInterfaceConfigTest.java b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/AbstractInterfaceConfigTest.java index 2bcd96071b..3a4a83c462 100644 --- a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/AbstractInterfaceConfigTest.java +++ b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/AbstractInterfaceConfigTest.java @@ -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); } diff --git a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/DubboBootstrapTest.java b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/DubboBootstrapTest.java index 6136dd4684..e329f23511 100644 --- a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/DubboBootstrapTest.java +++ b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/DubboBootstrapTest.java @@ -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 diff --git a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/builders/ApplicationBuilderTest.java b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/builders/ApplicationBuilderTest.java index 6c37eae859..0ced7cfded 100644 --- a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/builders/ApplicationBuilderTest.java +++ b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/builders/ApplicationBuilderTest.java @@ -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); } diff --git a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/event/listener/PublishingServiceDefinitionListenerTest.java b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/event/listener/PublishingServiceDefinitionListenerTest.java index e7e53767df..3190f34b58 100644 --- a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/event/listener/PublishingServiceDefinitionListenerTest.java +++ b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/event/listener/PublishingServiceDefinitionListenerTest.java @@ -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 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)); } } diff --git a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/mock/MockServiceDiscovery.java b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/mock/MockServiceDiscovery.java new file mode 100644 index 0000000000..33f1a16aef --- /dev/null +++ b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/mock/MockServiceDiscovery.java @@ -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 getServices() { + return new HashSet<>(); + } + + @Override + public URL getUrl() { + return registryURL; + } + + @Override + public ServiceInstance getLocalInstance() { + return serviceInstance; + } +} diff --git a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/metadata/MetadataServiceExporterTest.java b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/metadata/MetadataServiceExporterTest.java index 8096276f19..aaabe02867 100644 --- a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/metadata/MetadataServiceExporterTest.java +++ b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/metadata/MetadataServiceExporterTest.java @@ -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)); diff --git a/dubbo-config/dubbo-config-api/src/test/resources/META-INF/services/org.apache.dubbo.registry.client.ServiceDiscovery b/dubbo-config/dubbo-config-api/src/test/resources/META-INF/services/org.apache.dubbo.registry.client.ServiceDiscovery new file mode 100644 index 0000000000..22ceb1663d --- /dev/null +++ b/dubbo-config/dubbo-config-api/src/test/resources/META-INF/services/org.apache.dubbo.registry.client.ServiceDiscovery @@ -0,0 +1 @@ +mockregistry=org.apache.dubbo.config.mock.MockServiceDiscovery \ No newline at end of file diff --git a/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/registry/MockServiceDiscovery.java b/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/registry/MockServiceDiscovery.java new file mode 100644 index 0000000000..1511b0e4ab --- /dev/null +++ b/dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/registry/MockServiceDiscovery.java @@ -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 getServices() { + return new HashSet<>(); + } + + @Override + public URL getUrl() { + return registryURL; + } + + @Override + public ServiceInstance getLocalInstance() { + return serviceInstance; + } +} diff --git a/dubbo-config/dubbo-config-spring/src/test/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscovery b/dubbo-config/dubbo-config-spring/src/test/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscovery new file mode 100644 index 0000000000..cea6b8483d --- /dev/null +++ b/dubbo-config/dubbo-config-spring/src/test/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscovery @@ -0,0 +1 @@ +mock=org.apache.dubbo.config.spring.registry.MockServiceDiscovery \ No newline at end of file diff --git a/dubbo-dependencies-bom/pom.xml b/dubbo-dependencies-bom/pom.xml index 634586d4ae..8d84b38d8b 100644 --- a/dubbo-dependencies-bom/pom.xml +++ b/dubbo-dependencies-bom/pom.xml @@ -92,9 +92,9 @@ 4.3.16.RELEASE 3.20.0-GA 3.2.5.Final - 4.1.25.Final + 4.1.51.Final 1.1.7 - 2.1.4 + 2.4.4 4.5.3 4.4.6 1.2.70 @@ -129,9 +129,9 @@ 2.0 3.0.19.Final 8.5.31 - 0.4.1 + 0.5.3 1.3.1 - 1.22.1 + 1.31.1 1.7.25 1.2 @@ -145,13 +145,16 @@ 1.9.12 + + 4.10.3 + 1.0.8 2.2.7 1.2.0 1.11.2 - 0.3.0 + 0.5.3 3.2.8 1.5.19 4.3.16.RELEASE @@ -468,11 +471,6 @@ - - io.etcd - jetcd-launcher - ${jetcd_version} - org.slf4j @@ -703,6 +701,17 @@ grpc-grpclb ${grpc.version} + + io.fabric8 + kubernetes-client + ${fabric8_kubernetes_version} + + + io.fabric8 + kubernetes-server-mock + test + ${fabric8_kubernetes_version} + diff --git a/dubbo-distribution/dubbo-all/pom.xml b/dubbo-distribution/dubbo-all/pom.xml index 5f679306bb..e8e4406211 100644 --- a/dubbo-distribution/dubbo-all/pom.xml +++ b/dubbo-distribution/dubbo-all/pom.xml @@ -157,6 +157,20 @@ compile true + + org.apache.dubbo + dubbo-registry-kubernetes + ${project.version} + compile + true + + + org.apache.dubbo + dubbo-registry-dns + ${project.version} + compile + true + org.apache.dubbo dubbo-monitor-api @@ -358,6 +372,8 @@ org.apache.dubbo:dubbo-registry-zookeeper org.apache.dubbo:dubbo-registry-nacos org.apache.dubbo:dubbo-registry-multiple + org.apache.dubbo:dubbo-registry-kubernetes + org.apache.dubbo:dubbo-registry-dns org.apache.dubbo:dubbo-monitor-api org.apache.dubbo:dubbo-monitor-default org.apache.dubbo:dubbo-container-api diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/InstanceMetadataChangedListener.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/InstanceMetadataChangedListener.java new file mode 100644 index 0000000000..5536e53e0d --- /dev/null +++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/InstanceMetadataChangedListener.java @@ -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

+ * 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; + } +} diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/MetadataService.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/MetadataService.java index 24dc82d85d..c945d737d6 100644 --- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/MetadataService.java +++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/MetadataService.java @@ -233,4 +233,46 @@ public interface MetadataService { static SortedSet toSortedStrings(Stream stream) { return unmodifiableSortedSet(stream.map(URL::toFullString).collect(TreeSet::new, Set::add, Set::addAll)); } + + /** + * Export Metadata in Service Instance of Service Discovery + *

+ * 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 + *

+ * 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 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 + *

+ * 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."); + } } diff --git a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/CommandContext.java b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/CommandContext.java index 0bd427b654..0c2612a5c4 100644 --- a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/CommandContext.java +++ b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/CommandContext.java @@ -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; + } } diff --git a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/Live.java b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/Live.java new file mode 100644 index 0000000000..d355845d12 --- /dev/null +++ b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/Live.java @@ -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 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"; + } +} diff --git a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/Ready.java b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/Ready.java index 06457f6bd8..109adbc2d8 100644 --- a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/Ready.java +++ b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/Ready.java @@ -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 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"; } } diff --git a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/Startup.java b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/Startup.java new file mode 100644 index 0000000000..e9a5b54bc6 --- /dev/null +++ b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/command/impl/Startup.java @@ -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 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"; + } +} diff --git a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/probe/LivenessProbe.java b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/probe/LivenessProbe.java new file mode 100644 index 0000000000..15484d3884 --- /dev/null +++ b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/probe/LivenessProbe.java @@ -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 + *

+ * 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(); +} diff --git a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/probe/ReadinessProbe.java b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/probe/ReadinessProbe.java new file mode 100644 index 0000000000..d988f38827 --- /dev/null +++ b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/probe/ReadinessProbe.java @@ -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 + *

+ * 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(); +} diff --git a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/probe/StartupProbe.java b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/probe/StartupProbe.java new file mode 100644 index 0000000000..45bdd4b9a8 --- /dev/null +++ b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/probe/StartupProbe.java @@ -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 + *

+ * 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(); +} diff --git a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/probe/impl/BootstrapReadinessProbe.java b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/probe/impl/BootstrapReadinessProbe.java new file mode 100644 index 0000000000..da807029a5 --- /dev/null +++ b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/probe/impl/BootstrapReadinessProbe.java @@ -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(); + } +} diff --git a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/probe/impl/BootstrapStartupProbe.java b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/probe/impl/BootstrapStartupProbe.java new file mode 100644 index 0000000000..74105e71f7 --- /dev/null +++ b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/probe/impl/BootstrapStartupProbe.java @@ -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(); + } +} diff --git a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/probe/impl/ProviderReadinessProbe.java b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/probe/impl/ProviderReadinessProbe.java new file mode 100644 index 0000000000..58251075fe --- /dev/null +++ b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/probe/impl/ProviderReadinessProbe.java @@ -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 providerModelList = serviceRepository.getExportedServices(); + if (providerModelList.isEmpty()) { + return true; + } + + boolean hasService = false; + for (ProviderModel providerModel : providerModelList) { + List statedUrls = providerModel.getStatedUrl(); + for (ProviderModel.RegisterStatedURL statedUrl : statedUrls) { + if (statedUrl.isRegistered()) { + hasService = true; + break; + } + } + } + + return hasService; + } +} diff --git a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/server/handler/HttpProcessHandler.java b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/server/handler/HttpProcessHandler.java index 408e1857c9..07af64d432 100644 --- a/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/server/handler/HttpProcessHandler.java +++ b/dubbo-plugin/dubbo-qos/src/main/java/org/apache/dubbo/qos/server/handler/HttpProcessHandler.java @@ -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 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 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; - } - } diff --git a/dubbo-plugin/dubbo-qos/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.qos.command.BaseCommand b/dubbo-plugin/dubbo-qos/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.qos.command.BaseCommand index b4f0322328..089762dd53 100644 --- a/dubbo-plugin/dubbo-qos/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.qos.command.BaseCommand +++ b/dubbo-plugin/dubbo-qos/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.qos.command.BaseCommand @@ -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 diff --git a/dubbo-plugin/dubbo-qos/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.qos.probe.ReadinessProbe b/dubbo-plugin/dubbo-qos/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.qos.probe.ReadinessProbe new file mode 100644 index 0000000000..702ab02872 --- /dev/null +++ b/dubbo-plugin/dubbo-qos/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.qos.probe.ReadinessProbe @@ -0,0 +1,2 @@ +bootstrap = org.apache.dubbo.qos.probe.impl.BootstrapReadinessProbe +provider = org.apache.dubbo.qos.probe.impl.ProviderReadinessProbe \ No newline at end of file diff --git a/dubbo-plugin/dubbo-qos/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.qos.probe.StartupProbe b/dubbo-plugin/dubbo-qos/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.qos.probe.StartupProbe new file mode 100644 index 0000000000..1095150fc7 --- /dev/null +++ b/dubbo-plugin/dubbo-qos/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.qos.probe.StartupProbe @@ -0,0 +1 @@ +bootstrap = org.apache.dubbo.qos.probe.impl.BootstrapStartupProbe \ No newline at end of file diff --git a/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/command/util/CommandHelperTest.java b/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/command/util/CommandHelperTest.java index 812d5c7b1e..a1e03a26ee 100644 --- a/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/command/util/CommandHelperTest.java +++ b/dubbo-plugin/dubbo-qos/src/test/java/org/apache/dubbo/qos/command/util/CommandHelperTest.java @@ -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> 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 diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/Constants.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/Constants.java index 1bf41688c9..eee0665582 100644 --- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/Constants.java +++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/Constants.java @@ -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; } diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/SelfHostMetaServiceDiscovery.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/SelfHostMetaServiceDiscovery.java new file mode 100644 index 0000000000..25ab77e5bf --- /dev/null +++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/SelfHostMetaServiceDiscovery.java @@ -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 + *

+ * Key - {@link ServiceInstance} ID ( usually ip + port ) + * Value - Json processed metadata string + */ + private final ConcurrentHashMap metadataMap = new ConcurrentHashMap<>(); + + /** + * Local Cache of {@link ServiceInstance} + *

+ * Key - Service Name + * Value - List {@link ServiceInstance} + */ + private final ConcurrentHashMap> cachedServiceInstances = new ConcurrentHashMap<>(); + + /** + * Local Cache of Service's {@link ServiceInstance} list revision, + * used to check if {@link ServiceInstance} list has been updated + *

+ * Key - ServiceName + * Value - a revision calculate from {@link List} of {@link ServiceInstance} + */ + private final ConcurrentHashMap 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 listenerMap = metadataService.getInstanceMetadataChangedListenerMap(); + Iterator> iterator = listenerMap.entrySet().iterator(); + + while (iterator.hasNext()) { + Map.Entry 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 listenerMap = metadataService.getInstanceMetadataChangedListenerMap(); + Iterator> iterator = listenerMap.entrySet().iterator(); + + while (iterator.hasNext()) { + Map.Entry 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 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 oldServiceInstances = cachedServiceInstances.getOrDefault(serviceName, new LinkedList<>()); + + // remove expired invoker + Set 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> getCachedServiceInstances() { + return cachedServiceInstances; + } +} diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/MetadataUtils.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/MetadataUtils.java index 0321a16871..27f66f5d9a 100644 --- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/MetadataUtils.java +++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/MetadataUtils.java @@ -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 metadataServiceProxies = new ConcurrentHashMap<>(); + public static ConcurrentMap> metadataServiceInvokers = new ConcurrentHashMap<>(); + + public static ConcurrentMap 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 loader - = ExtensionLoader.getExtensionLoader(MetadataServiceURLBuilder.class); + String key = computeKey(instance); + Lock lock = metadataServiceLocks.computeIfAbsent(key, k -> new ReentrantLock()); - Map 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 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 loader + = ExtensionLoader.getExtensionLoader(MetadataServiceURLBuilder.class); - // Simply rely on the first metadata url, as stated in MetadataServiceURLBuilder. - Invoker invoker = protocol.refer(MetadataService.class, urls.get(0)); + Map 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 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 invoker = protocol.refer(MetadataService.class, urls.get(0)); + metadataServiceInvokers.put(key, invoker); + + return proxyFactory.getProxy(invoker); } public static void saveMetadataURL(URL url) { diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/StandardMetadataServiceURLBuilder.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/StandardMetadataServiceURLBuilder.java index 8adf71adc7..a7dda4b75f 100644 --- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/StandardMetadataServiceURLBuilder.java +++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/StandardMetadataServiceURLBuilder.java @@ -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 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 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(); + } } diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/store/InMemoryWritableMetadataService.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/store/InMemoryWritableMetadataService.java index e31745a575..fdecb76ee4 100644 --- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/store/InMemoryWritableMetadataService.java +++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/store/InMemoryWritableMetadataService.java @@ -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 metadataInfos; final Semaphore metadataSemaphore = new Semaphore(0); - final Map> serviceToAppsMapping = new HashMap<>(); + String instanceMetadata; + ConcurrentMap 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 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()); } } - } diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/store/RemoteMetadataServiceImpl.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/store/RemoteMetadataServiceImpl.java index 25bc03cf5b..bca273bdcd 100644 --- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/store/RemoteMetadataServiceImpl.java +++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/store/RemoteMetadataServiceImpl.java @@ -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); diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/migration/MigrationClusterInvoker.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/migration/MigrationClusterInvoker.java index 91fa942b6e..2efae4c3d4 100644 --- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/migration/MigrationClusterInvoker.java +++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/migration/MigrationClusterInvoker.java @@ -47,4 +47,4 @@ public interface MigrationClusterInvoker extends ClusterInvoker { void refreshServiceDiscoveryInvokerOnMappingCallback(boolean forceMigrate); void reRefer(URL newSubscribeUrl); -} +} \ No newline at end of file diff --git a/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/InMemoryServiceDiscovery.java b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/InMemoryServiceDiscovery.java index 93043bafea..20eeed30be 100644 --- a/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/InMemoryServiceDiscovery.java +++ b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/InMemoryServiceDiscovery.java @@ -44,6 +44,8 @@ public class InMemoryServiceDiscovery implements ServiceDiscovery { private ServiceInstance serviceInstance; + private URL registryURL; + @Override public Set 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 diff --git a/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/support/ServiceOrientedRegistryTest.java b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/support/ServiceOrientedRegistryTest.java index e65a271129..bc0906f090 100644 --- a/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/support/ServiceOrientedRegistryTest.java +++ b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/support/ServiceOrientedRegistryTest.java @@ -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 diff --git a/dubbo-registry/dubbo-registry-dns/pom.xml b/dubbo-registry/dubbo-registry-dns/pom.xml new file mode 100644 index 0000000000..54e526c661 --- /dev/null +++ b/dubbo-registry/dubbo-registry-dns/pom.xml @@ -0,0 +1,90 @@ + + + + 4.0.0 + + + org.apache.dubbo + dubbo-registry + ${revision} + ../pom.xml + + + dubbo-registry-dns + jar + ${project.artifactId} + The DNS registry module of Dubbo project + + + + org.apache.dubbo + dubbo-registry-api + ${project.version} + + + + org.apache.dubbo + dubbo-common + ${project.version} + + + + io.netty + netty-all + + + + org.apache.dubbo + dubbo-config-api + ${project.version} + test + + + + org.apache.dubbo + dubbo-rpc-dubbo + ${project.version} + test + + + + org.apache.dubbo + dubbo-serialization-hessian2 + ${project.version} + test + + + + org.apache.dubbo + dubbo-remoting-netty4 + ${project.version} + test + + + + org.apache.dubbo + dubbo-registry-multicast + ${project.version} + test + + + + + \ No newline at end of file diff --git a/dubbo-registry/dubbo-registry-dns/src/main/java/org/apache/dubbo/registry/dns/DNSRegistry.java b/dubbo-registry/dubbo-registry-dns/src/main/java/org/apache/dubbo/registry/dns/DNSRegistry.java new file mode 100644 index 0000000000..79fa0cd88d --- /dev/null +++ b/dubbo-registry/dubbo-registry-dns/src/main/java/org/apache/dubbo/registry/dns/DNSRegistry.java @@ -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
+ * DNS only support `Service Discovery` mode register
+ * Used to compat past version like 2.6.x, 2.7.x with interface level register
+ * {@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) { + + } +} diff --git a/dubbo-registry/dubbo-registry-dns/src/main/java/org/apache/dubbo/registry/dns/DNSRegistryFactory.java b/dubbo-registry/dubbo-registry-dns/src/main/java/org/apache/dubbo/registry/dns/DNSRegistryFactory.java new file mode 100644 index 0000000000..870485e369 --- /dev/null +++ b/dubbo-registry/dubbo-registry-dns/src/main/java/org/apache/dubbo/registry/dns/DNSRegistryFactory.java @@ -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); + } +} diff --git a/dubbo-registry/dubbo-registry-dns/src/main/java/org/apache/dubbo/registry/dns/DNSServiceDiscovery.java b/dubbo-registry/dubbo-registry-dns/src/main/java/org/apache/dubbo/registry/dns/DNSServiceDiscovery.java new file mode 100644 index 0000000000..a4d30d633e --- /dev/null +++ b/dubbo-registry/dubbo-registry-dns/src/main/java/org/apache/dubbo/registry/dns/DNSServiceDiscovery.java @@ -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> 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 getServices() { + // it is impossible for dns to discover service names + return Collections.singleton("Unsupported Method"); + } + + @Override + public List 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 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 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 instanceList = new LinkedList<>(); + + for (String host : resolveResult.getHostnameList()) { + DefaultServiceInstance serviceInstance = new DefaultServiceInstance(serviceName, host, port); + fillServiceInstance(serviceInstance); + instanceList.add(serviceInstance); + } + + return instanceList; + } +} diff --git a/dubbo-registry/dubbo-registry-dns/src/main/java/org/apache/dubbo/registry/dns/DNSServiceDiscoveryFactory.java b/dubbo-registry/dubbo-registry-dns/src/main/java/org/apache/dubbo/registry/dns/DNSServiceDiscoveryFactory.java new file mode 100644 index 0000000000..57eba849a2 --- /dev/null +++ b/dubbo-registry/dubbo-registry-dns/src/main/java/org/apache/dubbo/registry/dns/DNSServiceDiscoveryFactory.java @@ -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(); + } +} diff --git a/dubbo-registry/dubbo-registry-dns/src/main/java/org/apache/dubbo/registry/dns/util/DNSClientConst.java b/dubbo-registry/dubbo-registry-dns/src/main/java/org/apache/dubbo/registry/dns/util/DNSClientConst.java new file mode 100644 index 0000000000..0fc8ada5fe --- /dev/null +++ b/dubbo-registry/dubbo-registry-dns/src/main/java/org/apache/dubbo/registry/dns/util/DNSClientConst.java @@ -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; + +} diff --git a/dubbo-registry/dubbo-registry-dns/src/main/java/org/apache/dubbo/registry/dns/util/DNSResolver.java b/dubbo-registry/dubbo-registry-dns/src/main/java/org/apache/dubbo/registry/dns/util/DNSResolver.java new file mode 100644 index 0000000000..77458737cd --- /dev/null +++ b/dubbo-registry/dubbo-registry-dns/src/main/java/org/apache/dubbo/registry/dns/util/DNSResolver.java @@ -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> hostFuture = resolver.resolveAll(path); + Future> 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(); + } +} diff --git a/dubbo-registry/dubbo-registry-dns/src/main/java/org/apache/dubbo/registry/dns/util/ResolveResult.java b/dubbo-registry/dubbo-registry-dns/src/main/java/org/apache/dubbo/registry/dns/util/ResolveResult.java new file mode 100644 index 0000000000..a02b122c8a --- /dev/null +++ b/dubbo-registry/dubbo-registry-dns/src/main/java/org/apache/dubbo/registry/dns/util/ResolveResult.java @@ -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 hostnameList = new LinkedList<>(); + + private List port = new LinkedList<>(); + + public List getHostnameList() { + return hostnameList; + } + + public void setHostnameList(List hostnameList) { + this.hostnameList = hostnameList; + } + + public List getPort() { + return port; + } + + public void setPort(List 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 + + '}'; + } +} diff --git a/dubbo-registry/dubbo-registry-dns/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.RegistryFactory b/dubbo-registry/dubbo-registry-dns/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.RegistryFactory new file mode 100644 index 0000000000..69ca00793f --- /dev/null +++ b/dubbo-registry/dubbo-registry-dns/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.RegistryFactory @@ -0,0 +1 @@ +dns=org.apache.dubbo.registry.dns.DNSRegistryFactory \ No newline at end of file diff --git a/dubbo-registry/dubbo-registry-dns/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscovery b/dubbo-registry/dubbo-registry-dns/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscovery new file mode 100644 index 0000000000..3adb9f4cda --- /dev/null +++ b/dubbo-registry/dubbo-registry-dns/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscovery @@ -0,0 +1 @@ +dns=org.apache.dubbo.registry.dns.DNSServiceDiscovery \ No newline at end of file diff --git a/dubbo-registry/dubbo-registry-dns/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscoveryFactory b/dubbo-registry/dubbo-registry-dns/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscoveryFactory new file mode 100644 index 0000000000..900e78c82f --- /dev/null +++ b/dubbo-registry/dubbo-registry-dns/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscoveryFactory @@ -0,0 +1 @@ +dns=org.apache.dubbo.registry.dns.DNSServiceDiscoveryFactory \ No newline at end of file diff --git a/dubbo-registry/dubbo-registry-dns/src/test/java/org/apache/dubbo/registry/dns/DNSServiceDiscoveryTest.java b/dubbo-registry/dubbo-registry-dns/src/test/java/org/apache/dubbo/registry/dns/DNSServiceDiscoveryTest.java new file mode 100644 index 0000000000..cc6610bbca --- /dev/null +++ b/dubbo-registry/dubbo-registry-dns/src/test/java/org/apache/dubbo/registry/dns/DNSServiceDiscoveryTest.java @@ -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 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 serviceInstances = dnsServiceDiscovery.getInstances("Test.Service."); + assertEquals("b", serviceInstances.get(0).getMetadata("a")); + + Set 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 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 exportMockMetadataService(MetadataService metadataService, int port) { + ServiceConfig 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; + } +} diff --git a/dubbo-registry/dubbo-registry-dns/src/test/java/org/apache/dubbo/registry/dns/util/DNSResolverTest.java b/dubbo-registry/dubbo-registry-dns/src/test/java/org/apache/dubbo/registry/dns/util/DNSResolverTest.java new file mode 100644 index 0000000000..8624a7f741 --- /dev/null +++ b/dubbo-registry/dubbo-registry-dns/src/test/java/org/apache/dubbo/registry/dns/util/DNSResolverTest.java @@ -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()); + } +} diff --git a/dubbo-registry/dubbo-registry-kubernetes/pom.xml b/dubbo-registry/dubbo-registry-kubernetes/pom.xml new file mode 100644 index 0000000000..799e45e5cd --- /dev/null +++ b/dubbo-registry/dubbo-registry-kubernetes/pom.xml @@ -0,0 +1,73 @@ + + + + 4.0.0 + + org.apache.dubbo + dubbo-registry + ${revision} + ../pom.xml + + + dubbo-registry-kubernetes + jar + ${project.artifactId} + The Kubernetes registry module of Dubbo project + + + + org.apache.dubbo + dubbo-registry-api + ${project.version} + + + + org.apache.dubbo + dubbo-common + ${project.version} + + + + io.fabric8 + kubernetes-client + + + + io.fabric8 + kubernetes-server-mock + test + + + + org.mockito + mockito-core + 3.4.6 + test + + + org.mockito + mockito-junit-jupiter + 3.4.6 + test + + + + + \ No newline at end of file diff --git a/dubbo-registry/dubbo-registry-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/KubernetesRegistry.java b/dubbo-registry/dubbo-registry-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/KubernetesRegistry.java new file mode 100644 index 0000000000..b221c89c2b --- /dev/null +++ b/dubbo-registry/dubbo-registry-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/KubernetesRegistry.java @@ -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
+ * Kubernetes only support `Service Discovery` mode register
+ * Used to compat past version like 2.6.x, 2.7.x with interface level register
+ * {@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) { + + } +} diff --git a/dubbo-registry/dubbo-registry-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/KubernetesRegistryFactory.java b/dubbo-registry/dubbo-registry-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/KubernetesRegistryFactory.java new file mode 100644 index 0000000000..ab9af54c10 --- /dev/null +++ b/dubbo-registry/dubbo-registry-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/KubernetesRegistryFactory.java @@ -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); + } +} diff --git a/dubbo-registry/dubbo-registry-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/KubernetesServiceDiscovery.java b/dubbo-registry/dubbo-registry-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/KubernetesServiceDiscovery.java new file mode 100644 index 0000000000..f75837ae4e --- /dev/null +++ b/dubbo-registry/dubbo-registry-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/KubernetesServiceDiscovery.java @@ -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 SERVICE_WATCHER = new ConcurrentHashMap<>(64); + + private final static ConcurrentHashMap PODS_WATCHER = new ConcurrentHashMap<>(64); + + private final static ConcurrentHashMap ENDPOINTS_WATCHER = new ConcurrentHashMap<>(64); + + private final static ConcurrentHashMap 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 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 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() { + @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 serviceSelector = getServiceSelector(serviceName); + if (serviceSelector == null) { + return; + } + + Watch watch = kubernetesClient + .pods() + .inNamespace(namespace) + .withLabels(serviceSelector) + .watch(new Watcher() { + @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() { + @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 getServiceSelector(String serviceName) { + Service service = kubernetesClient.services().inNamespace(namespace).withName(serviceName).get(); + if (service == null) { + return null; + } + return service.getSpec().getSelector(); + } + + private List toServiceInstance(Endpoints endpoints, String serviceName) { + Map serviceSelector = getServiceSelector(serviceName); + if (serviceSelector == null) { + return new LinkedList<>(); + } + Map pods = kubernetesClient + .pods() + .inNamespace(namespace) + .withLabels(serviceSelector) + .list() + .getItems() + .stream() + .collect( + Collectors.toMap( + pod -> pod.getMetadata().getName(), + pod -> pod)); + + List instances = new LinkedList<>(); + Set 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; + } +} diff --git a/dubbo-registry/dubbo-registry-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/KubernetesServiceDiscoveryFactory.java b/dubbo-registry/dubbo-registry-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/KubernetesServiceDiscoveryFactory.java new file mode 100644 index 0000000000..88d50e27c8 --- /dev/null +++ b/dubbo-registry/dubbo-registry-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/KubernetesServiceDiscoveryFactory.java @@ -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(); + } +} diff --git a/dubbo-registry/dubbo-registry-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/util/KubernetesClientConst.java b/dubbo-registry/dubbo-registry-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/util/KubernetesClientConst.java new file mode 100644 index 0000000000..527d2fa167 --- /dev/null +++ b/dubbo-registry/dubbo-registry-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/util/KubernetesClientConst.java @@ -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"; +} diff --git a/dubbo-registry/dubbo-registry-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/util/KubernetesConfigUtils.java b/dubbo-registry/dubbo-registry-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/util/KubernetesConfigUtils.java new file mode 100644 index 0000000000..83b7eb0c25 --- /dev/null +++ b/dubbo-registry/dubbo-registry-kubernetes/src/main/java/org/apache/dubbo/registry/kubernetes/util/KubernetesConfigUtils.java @@ -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)) : + ""; + } +} diff --git a/dubbo-registry/dubbo-registry-kubernetes/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.RegistryFactory b/dubbo-registry/dubbo-registry-kubernetes/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.RegistryFactory new file mode 100644 index 0000000000..94177d81ea --- /dev/null +++ b/dubbo-registry/dubbo-registry-kubernetes/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.RegistryFactory @@ -0,0 +1 @@ +kubernetes=org.apache.dubbo.registry.kubernetes.KubernetesRegistryFactory \ No newline at end of file diff --git a/dubbo-registry/dubbo-registry-kubernetes/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscovery b/dubbo-registry/dubbo-registry-kubernetes/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscovery new file mode 100644 index 0000000000..3e1b88e44c --- /dev/null +++ b/dubbo-registry/dubbo-registry-kubernetes/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscovery @@ -0,0 +1 @@ +kubernetes=org.apache.dubbo.registry.kubernetes.KubernetesServiceDiscovery \ No newline at end of file diff --git a/dubbo-registry/dubbo-registry-kubernetes/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscoveryFactory b/dubbo-registry/dubbo-registry-kubernetes/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscoveryFactory new file mode 100644 index 0000000000..4301ab8b4b --- /dev/null +++ b/dubbo-registry/dubbo-registry-kubernetes/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscoveryFactory @@ -0,0 +1 @@ +kubernetes=org.apache.dubbo.registry.kubernetes.KubernetesServiceDiscoveryFactory \ No newline at end of file diff --git a/dubbo-registry/dubbo-registry-kubernetes/src/test/java/org/apache/dubbo/registry/kubernetes/KubernetesServiceDiscoveryTest.java b/dubbo-registry/dubbo-registry-kubernetes/src/test/java/org/apache/dubbo/registry/kubernetes/KubernetesServiceDiscoveryTest.java new file mode 100644 index 0000000000..d3d8c9bd6e --- /dev/null +++ b/dubbo-registry/dubbo-registry-kubernetes/src/test/java/org/apache/dubbo/registry/kubernetes/KubernetesServiceDiscoveryTest.java @@ -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 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 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 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 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 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(); + } +} diff --git a/dubbo-registry/dubbo-registry-kubernetes/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/dubbo-registry/dubbo-registry-kubernetes/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker new file mode 100644 index 0000000000..ca6ee9cea8 --- /dev/null +++ b/dubbo-registry/dubbo-registry-kubernetes/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker @@ -0,0 +1 @@ +mock-maker-inline \ No newline at end of file diff --git a/dubbo-registry/dubbo-registry-multicast/src/main/java/org/apache/dubbo/registry/multicast/MulticastServiceDiscovery.java b/dubbo-registry/dubbo-registry-multicast/src/main/java/org/apache/dubbo/registry/multicast/MulticastServiceDiscovery.java new file mode 100644 index 0000000000..234fdcb284 --- /dev/null +++ b/dubbo-registry/dubbo-registry-multicast/src/main/java/org/apache/dubbo/registry/multicast/MulticastServiceDiscovery.java @@ -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 getServices() { + return Collections.singleton("Unsupported Operation"); + } + + @Override + public URL getUrl() { + return registryURL; + } + + @Override + public ServiceInstance getLocalInstance() { + return serviceInstance; + } +} diff --git a/dubbo-registry/dubbo-registry-multicast/src/main/java/org/apache/dubbo/registry/multicast/MulticastServiceDiscoveryFactory.java b/dubbo-registry/dubbo-registry-multicast/src/main/java/org/apache/dubbo/registry/multicast/MulticastServiceDiscoveryFactory.java new file mode 100644 index 0000000000..7bef1f5a35 --- /dev/null +++ b/dubbo-registry/dubbo-registry-multicast/src/main/java/org/apache/dubbo/registry/multicast/MulticastServiceDiscoveryFactory.java @@ -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(); + } +} diff --git a/dubbo-registry/dubbo-registry-multicast/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscovery b/dubbo-registry/dubbo-registry-multicast/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscovery new file mode 100644 index 0000000000..091b549072 --- /dev/null +++ b/dubbo-registry/dubbo-registry-multicast/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscovery @@ -0,0 +1 @@ +multicast=org.apache.dubbo.registry.multicast.MulticastServiceDiscovery \ No newline at end of file diff --git a/dubbo-registry/dubbo-registry-multicast/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscoveryFactory b/dubbo-registry/dubbo-registry-multicast/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscoveryFactory new file mode 100644 index 0000000000..03eb6fa67b --- /dev/null +++ b/dubbo-registry/dubbo-registry-multicast/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.registry.client.ServiceDiscoveryFactory @@ -0,0 +1 @@ +multicast=org.apache.dubbo.registry.multicast.MulticastServiceDiscoveryFactory \ No newline at end of file diff --git a/dubbo-registry/dubbo-registry-multicast/src/test/java/org/apache/dubbo/registry/multicast/MulticastRegistryTest.java b/dubbo-registry/dubbo-registry-multicast/src/test/java/org/apache/dubbo/registry/multicast/MulticastRegistryTest.java index 76eece027e..f0b9f1e338 100644 --- a/dubbo-registry/dubbo-registry-multicast/src/test/java/org/apache/dubbo/registry/multicast/MulticastRegistryTest.java +++ b/dubbo-registry/dubbo-registry-multicast/src/test/java/org/apache/dubbo/registry/multicast/MulticastRegistryTest.java @@ -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 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> subscribed = registry.getSubscribed(); assertEquals(consumerUrl, subscribed.keySet().iterator().next()); + }); + if (!EMPTY_PROTOCOL.equalsIgnoreCase(notifyUrl[0].getProtocol())) { + break; } - }); + } + assertEquals(serviceUrl.toFullString(), notifyUrl[0].toFullString()); } /** diff --git a/dubbo-registry/dubbo-registry-zookeeper/src/test/java/org/apache/dubbo/registry/zookeeper/ZookeeperServiceDiscoveryTest.java b/dubbo-registry/dubbo-registry-zookeeper/src/test/java/org/apache/dubbo/registry/zookeeper/ZookeeperServiceDiscoveryTest.java index 0a30a0ad70..5f3b68085c 100644 --- a/dubbo-registry/dubbo-registry-zookeeper/src/test/java/org/apache/dubbo/registry/zookeeper/ZookeeperServiceDiscoveryTest.java +++ b/dubbo-registry/dubbo-registry-zookeeper/src/test/java/org/apache/dubbo/registry/zookeeper/ZookeeperServiceDiscoveryTest.java @@ -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 metadata = new HashMap<>(); metadata.put("message", "Hello,World"); serviceInstance.setMetadata(metadata); + serviceInstance.getExtendParams().put(INSTANCE_REVISION_UPDATED_KEY, "true"); discovery.update(serviceInstance); diff --git a/dubbo-registry/pom.xml b/dubbo-registry/pom.xml index 3f9b247117..db3ee35bf2 100644 --- a/dubbo-registry/pom.xml +++ b/dubbo-registry/pom.xml @@ -35,5 +35,7 @@ dubbo-registry-zookeeper dubbo-registry-nacos dubbo-registry-multiple + dubbo-registry-kubernetes + dubbo-registry-dns diff --git a/dubbo-remoting/dubbo-remoting-netty/src/test/java/org/apache/dubbo/remoting/exchange/support/header/HeartbeatHandlerTest.java b/dubbo-remoting/dubbo-remoting-netty/src/test/java/org/apache/dubbo/remoting/exchange/support/header/HeartbeatHandlerTest.java index 7e88e7c132..eda4b60ebf 100644 --- a/dubbo-remoting/dubbo-remoting-netty/src/test/java/org/apache/dubbo/remoting/exchange/support/header/HeartbeatHandlerTest.java +++ b/dubbo-remoting/dubbo-remoting-netty/src/test/java/org/apache/dubbo/remoting/exchange/support/header/HeartbeatHandlerTest.java @@ -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 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 diff --git a/dubbo-remoting/dubbo-remoting-netty/src/test/java/org/apache/dubbo/remoting/transport/netty/ClientReconnectTest.java b/dubbo-remoting/dubbo-remoting-netty/src/test/java/org/apache/dubbo/remoting/transport/netty/ClientReconnectTest.java index 65fa6a0ebd..a75a4292f0 100644 --- a/dubbo-remoting/dubbo-remoting-netty/src/test/java/org/apache/dubbo/remoting/transport/netty/ClientReconnectTest.java +++ b/dubbo-remoting/dubbo-remoting-netty/src/test/java/org/apache/dubbo/remoting/transport/netty/ClientReconnectTest.java @@ -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); } diff --git a/dubbo-remoting/dubbo-remoting-netty/src/test/java/org/apache/dubbo/remoting/transport/netty/ThreadNameTest.java b/dubbo-remoting/dubbo-remoting-netty/src/test/java/org/apache/dubbo/remoting/transport/netty/ThreadNameTest.java index 1228fe042a..001b56ad2a 100644 --- a/dubbo-remoting/dubbo-remoting-netty/src/test/java/org/apache/dubbo/remoting/transport/netty/ThreadNameTest.java +++ b/dubbo-remoting/dubbo-remoting-netty/src/test/java/org/apache/dubbo/remoting/transport/netty/ThreadNameTest.java @@ -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); diff --git a/dubbo-remoting/dubbo-remoting-netty4/src/test/java/org/apache/dubbo/remoting/transport/netty4/NettyTransporterTest.java b/dubbo-remoting/dubbo-remoting-netty4/src/test/java/org/apache/dubbo/remoting/transport/netty4/NettyTransporterTest.java index 74e263e0b4..0b13de1536 100644 --- a/dubbo-remoting/dubbo-remoting-netty4/src/test/java/org/apache/dubbo/remoting/transport/netty4/NettyTransporterTest.java +++ b/dubbo-remoting/dubbo-remoting-netty4/src/test/java/org/apache/dubbo/remoting/transport/netty4/NettyTransporterTest.java @@ -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() { diff --git a/dubbo-remoting/dubbo-remoting-netty4/src/test/java/org/apache/dubbo/remoting/transport/netty4/ReplierDispatcherTest.java b/dubbo-remoting/dubbo-remoting-netty4/src/test/java/org/apache/dubbo/remoting/transport/netty4/ReplierDispatcherTest.java index 3ed2acabcd..37940e65a1 100644 --- a/dubbo-remoting/dubbo-remoting-netty4/src/test/java/org/apache/dubbo/remoting/transport/netty4/ReplierDispatcherTest.java +++ b/dubbo-remoting/dubbo-remoting-netty4/src/test/java/org/apache/dubbo/remoting/transport/netty4/ReplierDispatcherTest.java @@ -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); diff --git a/pom.xml b/pom.xml index e2c22aa076..0fdcc22f0e 100644 --- a/pom.xml +++ b/pom.xml @@ -581,6 +581,8 @@ .github/** compiler/** + + **/mockito-extensions/*