merge k8s feature (#7104)
This commit is contained in:
parent
829c0df273
commit
43e5edaefe
|
|
@ -38,7 +38,9 @@ import java.util.ArrayList;
|
|||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static org.apache.dubbo.common.constants.CommonConstants.PATH_KEY;
|
||||
import static org.apache.dubbo.rpc.Constants.MOCK_KEY;
|
||||
import static org.apache.dubbo.rpc.cluster.Constants.REFER_KEY;
|
||||
|
||||
public class MockClusterInvokerTest {
|
||||
|
||||
|
|
@ -55,7 +57,8 @@ public class MockClusterInvokerTest {
|
|||
@Test
|
||||
public void testMockInvokerInvoke_normal() {
|
||||
URL url = URL.valueOf("remote://1.2.3.4/" + IHelloService.class.getName());
|
||||
url = url.addParameter(MOCK_KEY, "fail");
|
||||
url = url.addParameter(MOCK_KEY, "fail")
|
||||
.addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()));
|
||||
Invoker<IHelloService> cluster = getClusterInvoker(url);
|
||||
URL mockUrl = URL.valueOf("mock://localhost/" + IHelloService.class.getName()
|
||||
+ "?getSomething.mock=return aa");
|
||||
|
|
@ -84,6 +87,7 @@ public class MockClusterInvokerTest {
|
|||
public void testMockInvokerInvoke_failmock() {
|
||||
URL url = URL.valueOf("remote://1.2.3.4/" + IHelloService.class.getName())
|
||||
.addParameter(MOCK_KEY, "fail:return null")
|
||||
.addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()))
|
||||
.addParameter("invoke_return_error", "true");
|
||||
URL mockUrl = URL.valueOf("mock://localhost/" + IHelloService.class.getName()
|
||||
+ "?getSomething.mock=return aa").addParameters(url.getParameters());
|
||||
|
|
@ -118,7 +122,8 @@ public class MockClusterInvokerTest {
|
|||
@Test
|
||||
public void testMockInvokerInvoke_forcemock() {
|
||||
URL url = URL.valueOf("remote://1.2.3.4/" + IHelloService.class.getName());
|
||||
url = url.addParameter(MOCK_KEY, "force:return null");
|
||||
url = url.addParameter(MOCK_KEY, "force:return null")
|
||||
.addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()));
|
||||
|
||||
URL mockUrl = URL.valueOf("mock://localhost/" + IHelloService.class.getName()
|
||||
+ "?getSomething.mock=return aa&getSomething3xx.mock=return xx")
|
||||
|
|
@ -150,7 +155,9 @@ public class MockClusterInvokerTest {
|
|||
@Test
|
||||
public void testMockInvokerInvoke_forcemock_defaultreturn() {
|
||||
URL url = URL.valueOf("remote://1.2.3.4/" + IHelloService.class.getName());
|
||||
url = url.addParameter(MOCK_KEY, "force");
|
||||
url = url.addParameter(MOCK_KEY, "force")
|
||||
.addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()));
|
||||
|
||||
Invoker<IHelloService> cluster = getClusterInvoker(url);
|
||||
URL mockUrl = URL.valueOf("mock://localhost/" + IHelloService.class.getName()
|
||||
+ "?getSomething.mock=return aa&getSomething3xx.mock=return xx&sayHello.mock=return ")
|
||||
|
|
@ -173,7 +180,8 @@ public class MockClusterInvokerTest {
|
|||
public void testMockInvokerFromOverride_Invoke_Fock_someMethods() {
|
||||
URL url = URL.valueOf("remote://1.2.3.4/" + IHelloService.class.getName())
|
||||
.addParameter("getSomething.mock", "fail:return x")
|
||||
.addParameter("getSomething2.mock", "force:return y");
|
||||
.addParameter("getSomething2.mock", "force:return y")
|
||||
.addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()));
|
||||
Invoker<IHelloService> cluster = getClusterInvoker(url);
|
||||
//Configured with mock
|
||||
RpcInvocation invocation = new RpcInvocation();
|
||||
|
|
@ -208,6 +216,7 @@ public class MockClusterInvokerTest {
|
|||
URL url = URL.valueOf("remote://1.2.3.4/" + IHelloService.class.getName())
|
||||
.addParameter("getSomething.mock", "fail:return x")
|
||||
.addParameter("getSomething2.mock", "force:return y")
|
||||
.addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()))
|
||||
.addParameter("invoke_return_error", "true");
|
||||
Invoker<IHelloService> cluster = getClusterInvoker(url);
|
||||
//Configured with mock
|
||||
|
|
@ -242,6 +251,7 @@ public class MockClusterInvokerTest {
|
|||
.addParameter("mock", "fail:return null")
|
||||
.addParameter("getSomething.mock", "fail:return x")
|
||||
.addParameter("getSomething2.mock", "force:return y")
|
||||
.addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()))
|
||||
.addParameter("invoke_return_error", "true");
|
||||
Invoker<IHelloService> cluster = getClusterInvoker(url);
|
||||
//Configured with mock
|
||||
|
|
@ -278,6 +288,7 @@ public class MockClusterInvokerTest {
|
|||
.addParameter("mock", "fail:return z")
|
||||
.addParameter("getSomething.mock", "fail:return x")
|
||||
.addParameter("getSomething2.mock", "force:return y")
|
||||
.addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()))
|
||||
.addParameter("invoke_return_error", "true");
|
||||
Invoker<IHelloService> cluster = getClusterInvoker(url);
|
||||
//Configured with mock
|
||||
|
|
@ -314,6 +325,7 @@ public class MockClusterInvokerTest {
|
|||
.addParameter("mock", "force:return z")
|
||||
.addParameter("getSomething.mock", "fail:return x")
|
||||
.addParameter("getSomething2.mock", "force:return y")
|
||||
.addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()))
|
||||
.addParameter("invoke_return_error", "true");
|
||||
Invoker<IHelloService> cluster = getClusterInvoker(url);
|
||||
//Configured with mock
|
||||
|
|
@ -348,6 +360,7 @@ public class MockClusterInvokerTest {
|
|||
public void testMockInvokerFromOverride_Invoke_Fock_Default() {
|
||||
URL url = URL.valueOf("remote://1.2.3.4/" + IHelloService.class.getName())
|
||||
.addParameter("mock", "fail:return x")
|
||||
.addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()))
|
||||
.addParameter("invoke_return_error", "true");
|
||||
Invoker<IHelloService> cluster = getClusterInvoker(url);
|
||||
//Configured with mock
|
||||
|
|
@ -376,6 +389,7 @@ public class MockClusterInvokerTest {
|
|||
public void testMockInvokerFromOverride_Invoke_checkCompatible_return() {
|
||||
URL url = URL.valueOf("remote://1.2.3.4/" + IHelloService.class.getName())
|
||||
.addParameter("getSomething.mock", "return x")
|
||||
.addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()))
|
||||
.addParameter("invoke_return_error", "true");
|
||||
Invoker<IHelloService> cluster = getClusterInvoker(url);
|
||||
//Configured with mock
|
||||
|
|
@ -402,6 +416,7 @@ public class MockClusterInvokerTest {
|
|||
public void testMockInvokerFromOverride_Invoke_checkCompatible_ImplMock() {
|
||||
URL url = URL.valueOf("remote://1.2.3.4/" + IHelloService.class.getName())
|
||||
.addParameter("mock", "true")
|
||||
.addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()))
|
||||
.addParameter("invoke_return_error", "true");
|
||||
Invoker<IHelloService> cluster = getClusterInvoker(url);
|
||||
//Configured with mock
|
||||
|
|
@ -418,6 +433,7 @@ public class MockClusterInvokerTest {
|
|||
public void testMockInvokerFromOverride_Invoke_checkCompatible_ImplMock2() {
|
||||
URL url = URL.valueOf("remote://1.2.3.4/" + IHelloService.class.getName())
|
||||
.addParameter("mock", "fail")
|
||||
.addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()))
|
||||
.addParameter("invoke_return_error", "true");
|
||||
Invoker<IHelloService> cluster = getClusterInvoker(url);
|
||||
//Configured with mock
|
||||
|
|
@ -433,6 +449,7 @@ public class MockClusterInvokerTest {
|
|||
@Test
|
||||
public void testMockInvokerFromOverride_Invoke_checkCompatible_ImplMock3() {
|
||||
URL url = URL.valueOf("remote://1.2.3.4/" + IHelloService.class.getName())
|
||||
.addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()))
|
||||
.addParameter("mock", "force");
|
||||
Invoker<IHelloService> cluster = getClusterInvoker(url);
|
||||
//Configured with mock
|
||||
|
|
@ -446,6 +463,7 @@ public class MockClusterInvokerTest {
|
|||
public void testMockInvokerFromOverride_Invoke_check_String() {
|
||||
URL url = URL.valueOf("remote://1.2.3.4/" + IHelloService.class.getName())
|
||||
.addParameter("getSomething.mock", "force:return 1688")
|
||||
.addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()))
|
||||
.addParameter("invoke_return_error", "true");
|
||||
Invoker<IHelloService> cluster = getClusterInvoker(url);
|
||||
//Configured with mock
|
||||
|
|
@ -459,6 +477,7 @@ public class MockClusterInvokerTest {
|
|||
@Test
|
||||
public void testMockInvokerFromOverride_Invoke_check_int() {
|
||||
URL url = URL.valueOf("remote://1.2.3.4/" + IHelloService.class.getName())
|
||||
.addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()))
|
||||
.addParameter("getInt1.mock", "force:return 1688")
|
||||
.addParameter("invoke_return_error", "true");
|
||||
Invoker<IHelloService> cluster = getClusterInvoker(url);
|
||||
|
|
@ -473,6 +492,7 @@ public class MockClusterInvokerTest {
|
|||
@Test
|
||||
public void testMockInvokerFromOverride_Invoke_check_boolean() {
|
||||
URL url = URL.valueOf("remote://1.2.3.4/" + IHelloService.class.getName())
|
||||
.addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()))
|
||||
.addParameter("getBoolean1.mock", "force:return true")
|
||||
.addParameter("invoke_return_error", "true");
|
||||
Invoker<IHelloService> cluster = getClusterInvoker(url);
|
||||
|
|
@ -487,6 +507,7 @@ public class MockClusterInvokerTest {
|
|||
@Test
|
||||
public void testMockInvokerFromOverride_Invoke_check_Boolean() {
|
||||
URL url = URL.valueOf("remote://1.2.3.4/" + IHelloService.class.getName())
|
||||
.addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()))
|
||||
.addParameter("getBoolean2.mock", "force:return true")
|
||||
.addParameter("invoke_return_error", "true");
|
||||
Invoker<IHelloService> cluster = getClusterInvoker(url);
|
||||
|
|
@ -502,6 +523,7 @@ public class MockClusterInvokerTest {
|
|||
public void testMockInvokerFromOverride_Invoke_check_ListString_empty() {
|
||||
URL url = URL.valueOf("remote://1.2.3.4/" + IHelloService.class.getName())
|
||||
.addParameter("getListString.mock", "force:return empty")
|
||||
.addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()))
|
||||
.addParameter("invoke_return_error", "true");
|
||||
Invoker<IHelloService> cluster = getClusterInvoker(url);
|
||||
//Configured with mock
|
||||
|
|
@ -516,6 +538,7 @@ public class MockClusterInvokerTest {
|
|||
public void testMockInvokerFromOverride_Invoke_check_ListString() {
|
||||
URL url = URL.valueOf("remote://1.2.3.4/" + IHelloService.class.getName())
|
||||
.addParameter("getListString.mock", "force:return [\"hi\",\"hi2\"]")
|
||||
.addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()))
|
||||
.addParameter("invoke_return_error", "true");
|
||||
Invoker<IHelloService> cluster = getClusterInvoker(url);
|
||||
//Configured with mock
|
||||
|
|
@ -532,6 +555,7 @@ public class MockClusterInvokerTest {
|
|||
public void testMockInvokerFromOverride_Invoke_check_ListPojo_empty() {
|
||||
URL url = URL.valueOf("remote://1.2.3.4/" + IHelloService.class.getName())
|
||||
.addParameter("getUsers.mock", "force:return empty")
|
||||
.addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()))
|
||||
.addParameter("invoke_return_error", "true");
|
||||
Invoker<IHelloService> cluster = getClusterInvoker(url);
|
||||
//Configured with mock
|
||||
|
|
@ -546,6 +570,7 @@ public class MockClusterInvokerTest {
|
|||
public void testMockInvokerFromOverride_Invoke_check_ListPojo() {
|
||||
URL url = URL.valueOf("remote://1.2.3.4/" + IHelloService.class.getName())
|
||||
.addParameter("getUsers.mock", "force:return [{id:1, name:\"hi1\"}, {id:2, name:\"hi2\"}]")
|
||||
.addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()))
|
||||
.addParameter("invoke_return_error", "true");
|
||||
Invoker<IHelloService> cluster = getClusterInvoker(url);
|
||||
//Configured with mock
|
||||
|
|
@ -562,6 +587,7 @@ public class MockClusterInvokerTest {
|
|||
public void testMockInvokerFromOverride_Invoke_check_ListPojo_error() {
|
||||
URL url = URL.valueOf("remote://1.2.3.4/" + IHelloService.class.getName())
|
||||
.addParameter("getUsers.mock", "force:return [{id:x, name:\"hi1\"}]")
|
||||
.addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()))
|
||||
.addParameter("invoke_return_error", "true");
|
||||
Invoker<IHelloService> cluster = getClusterInvoker(url);
|
||||
//Configured with mock
|
||||
|
|
@ -577,6 +603,7 @@ public class MockClusterInvokerTest {
|
|||
public void testMockInvokerFromOverride_Invoke_force_throw() {
|
||||
URL url = URL.valueOf("remote://1.2.3.4/" + IHelloService.class.getName())
|
||||
.addParameter("getBoolean2.mock", "force:throw ")
|
||||
.addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()))
|
||||
.addParameter("invoke_return_error", "true");
|
||||
Invoker<IHelloService> cluster = getClusterInvoker(url);
|
||||
//Configured with mock
|
||||
|
|
@ -594,6 +621,7 @@ public class MockClusterInvokerTest {
|
|||
public void testMockInvokerFromOverride_Invoke_force_throwCustemException() throws Throwable {
|
||||
URL url = URL.valueOf("remote://1.2.3.4/" + IHelloService.class.getName())
|
||||
.addParameter("getBoolean2.mock", "force:throw org.apache.dubbo.rpc.cluster.support.wrapper.MyMockException")
|
||||
.addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()))
|
||||
.addParameter("invoke_return_error", "true");
|
||||
Invoker<IHelloService> cluster = getClusterInvoker(url);
|
||||
//Configured with mock
|
||||
|
|
@ -611,6 +639,7 @@ public class MockClusterInvokerTest {
|
|||
public void testMockInvokerFromOverride_Invoke_force_throwCustemExceptionNotFound() {
|
||||
URL url = URL.valueOf("remote://1.2.3.4/" + IHelloService.class.getName())
|
||||
.addParameter("getBoolean2.mock", "force:throw java.lang.RuntimeException2")
|
||||
.addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()))
|
||||
.addParameter("invoke_return_error", "true");
|
||||
Invoker<IHelloService> cluster = getClusterInvoker(url);
|
||||
//Configured with mock
|
||||
|
|
@ -628,6 +657,7 @@ public class MockClusterInvokerTest {
|
|||
public void testMockInvokerFromOverride_Invoke_mock_false() {
|
||||
URL url = URL.valueOf("remote://1.2.3.4/" + IHelloService.class.getName())
|
||||
.addParameter("mock", "false")
|
||||
.addParameter(REFER_KEY, URL.encode(PATH_KEY + "=" + IHelloService.class.getName()))
|
||||
.addParameter("invoke_return_error", "true");
|
||||
Invoker<IHelloService> cluster = getClusterInvoker(url);
|
||||
//Configured with mock
|
||||
|
|
@ -708,7 +738,7 @@ public class MockClusterInvokerTest {
|
|||
return "something3";
|
||||
}
|
||||
|
||||
public String getSomething4(){
|
||||
public String getSomething4() {
|
||||
throw new RpcException("getSomething4|RpcException");
|
||||
}
|
||||
|
||||
|
|
@ -754,7 +784,7 @@ public class MockClusterInvokerTest {
|
|||
return "something3mock";
|
||||
}
|
||||
|
||||
public String getSomething4(){
|
||||
public String getSomething4() {
|
||||
return "something4mock";
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ import java.util.Arrays;
|
|||
import java.util.List;
|
||||
|
||||
import static org.apache.dubbo.rpc.Constants.MOCK_KEY;
|
||||
import static org.apache.dubbo.rpc.cluster.Constants.REFER_KEY;
|
||||
|
||||
public class MockProviderRpcExceptionTest {
|
||||
|
||||
|
|
@ -53,7 +54,8 @@ public class MockProviderRpcExceptionTest {
|
|||
@Test
|
||||
public void testMockInvokerProviderRpcException() {
|
||||
URL url = URL.valueOf("remote://1.2.3.4/" + IHelloRpcService.class.getName());
|
||||
url = url.addParameter(MOCK_KEY, "true").addParameter("invoke_return_error", "true");
|
||||
url = url.addParameter(MOCK_KEY, "true").addParameter("invoke_return_error", "true")
|
||||
.addParameter(REFER_KEY, "path%3dorg.apache.dubbo.rpc.cluster.support.wrapper.MockProviderRpcExceptionTest%24IHelloRpcService");
|
||||
Invoker<IHelloRpcService> cluster = getClusterInvoker(url);
|
||||
RpcInvocation invocation = new RpcInvocation();
|
||||
invocation.setMethodName("getSomething4");
|
||||
|
|
@ -130,7 +132,7 @@ public class MockProviderRpcExceptionTest {
|
|||
return "something3";
|
||||
}
|
||||
|
||||
public String getSomething4(){
|
||||
public String getSomething4() {
|
||||
throw new RpcException("getSomething4|RpcException");
|
||||
}
|
||||
|
||||
|
|
@ -176,7 +178,7 @@ public class MockProviderRpcExceptionTest {
|
|||
return "something3mock";
|
||||
}
|
||||
|
||||
public String getSomething4(){
|
||||
public String getSomething4() {
|
||||
return "something4mock";
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -217,7 +217,8 @@ public class ServiceConfig<T> extends ServiceConfigBase<T> {
|
|||
public void exported() {
|
||||
List<URL> exportedURLs = this.getExportedUrls();
|
||||
exportedURLs.forEach(url -> {
|
||||
ServiceNameMapping.getExtension(getApplication().getParameters().get(MAPPING_KEY)).map(url);
|
||||
Map<String, String> parameters = getApplication().getParameters();
|
||||
ServiceNameMapping.getExtension(parameters != null ? parameters.get(MAPPING_KEY) : null).map(url);
|
||||
});
|
||||
// dispatch a ServiceConfigExportedEvent since 2.7.4
|
||||
dispatch(new ServiceConfigExportedEvent(this));
|
||||
|
|
@ -435,7 +436,7 @@ public class ServiceConfig<T> extends ServiceConfigBase<T> {
|
|||
/**
|
||||
* Here the token value configured by the provider is used to assign the value to ServiceConfig#token
|
||||
*/
|
||||
if(ConfigUtils.isEmpty(token) && provider != null) {
|
||||
if (ConfigUtils.isEmpty(token) && provider != null) {
|
||||
token = provider.getToken();
|
||||
}
|
||||
|
||||
|
|
@ -713,7 +714,7 @@ public class ServiceConfig<T> extends ServiceConfigBase<T> {
|
|||
}
|
||||
|
||||
private void postProcessConfig() {
|
||||
List<ConfigPostProcessor> configPostProcessors =ExtensionLoader.getExtensionLoader(ConfigPostProcessor.class)
|
||||
List<ConfigPostProcessor> configPostProcessors = ExtensionLoader.getExtensionLoader(ConfigPostProcessor.class)
|
||||
.getActivateExtension(URL.valueOf("configPostProcessor://"), (String[]) null);
|
||||
configPostProcessors.forEach(component -> component.postProcessServiceConfig(this));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -51,6 +51,10 @@ public class ApplicationBuilder extends AbstractBuilder<ApplicationConfig, Appli
|
|||
private Boolean qosAcceptForeignIp;
|
||||
private Map<String, String> parameters;
|
||||
private String shutwait;
|
||||
private Integer metadataServicePort;
|
||||
private String livenessProbe;
|
||||
private String readinessProbe;
|
||||
private String startupProbe;
|
||||
|
||||
public static ApplicationBuilder newBuilder() {
|
||||
return new ApplicationBuilder();
|
||||
|
|
@ -172,6 +176,26 @@ public class ApplicationBuilder extends AbstractBuilder<ApplicationConfig, Appli
|
|||
return getThis();
|
||||
}
|
||||
|
||||
public ApplicationBuilder metadataServicePort(Integer metadataServicePort) {
|
||||
this.metadataServicePort = metadataServicePort;
|
||||
return getThis();
|
||||
}
|
||||
|
||||
public ApplicationBuilder livenessProbe(String livenessProbe) {
|
||||
this.livenessProbe = livenessProbe;
|
||||
return getThis();
|
||||
}
|
||||
|
||||
public ApplicationBuilder readinessProbe(String readinessProbe) {
|
||||
this.readinessProbe = readinessProbe;
|
||||
return getThis();
|
||||
}
|
||||
|
||||
public ApplicationBuilder startupProbe(String startupProbe) {
|
||||
this.startupProbe = startupProbe;
|
||||
return getThis();
|
||||
}
|
||||
|
||||
public ApplicationConfig build() {
|
||||
ApplicationConfig config = new ApplicationConfig();
|
||||
super.build(config);
|
||||
|
|
@ -193,6 +217,10 @@ public class ApplicationBuilder extends AbstractBuilder<ApplicationConfig, Appli
|
|||
config.setQosEnable(this.qosEnable);
|
||||
config.setQosPort(this.qosPort);
|
||||
config.setQosAcceptForeignIp(this.qosAcceptForeignIp);
|
||||
config.setMetadataServicePort(this.metadataServicePort);
|
||||
config.setLivenessProbe(this.livenessProbe);
|
||||
config.setReadinessProbe(this.readinessProbe);
|
||||
config.setStartupProbe(this.startupProbe);
|
||||
config.setParameters(this.parameters);
|
||||
if (!StringUtils.isEmpty(shutwait)) {
|
||||
config.setShutwait(shutwait);
|
||||
|
|
|
|||
|
|
@ -19,7 +19,10 @@ package org.apache.dubbo.config.metadata;
|
|||
import org.apache.dubbo.common.URL;
|
||||
import org.apache.dubbo.common.logger.Logger;
|
||||
import org.apache.dubbo.common.logger.LoggerFactory;
|
||||
import org.apache.dubbo.common.utils.CollectionUtils;
|
||||
import org.apache.dubbo.config.ApplicationConfig;
|
||||
import org.apache.dubbo.config.ArgumentConfig;
|
||||
import org.apache.dubbo.config.MethodConfig;
|
||||
import org.apache.dubbo.config.ProtocolConfig;
|
||||
import org.apache.dubbo.config.RegistryConfig;
|
||||
import org.apache.dubbo.config.ServiceConfig;
|
||||
|
|
@ -28,10 +31,12 @@ import org.apache.dubbo.metadata.MetadataService;
|
|||
import org.apache.dubbo.metadata.MetadataServiceExporter;
|
||||
import org.apache.dubbo.rpc.model.ApplicationModel;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
import static org.apache.dubbo.common.constants.CommonConstants.DUBBO;
|
||||
import static org.apache.dubbo.common.constants.CommonConstants.DUBBO_PROTOCOL;
|
||||
|
||||
/**
|
||||
* {@link MetadataServiceExporter} implementation based on {@link ConfigManager Dubbo configurations}, the clients
|
||||
|
|
@ -73,6 +78,7 @@ public class ConfigurableMetadataServiceExporter implements MetadataServiceExpor
|
|||
serviceConfig.setRef(metadataService);
|
||||
serviceConfig.setGroup(getApplicationConfig().getName());
|
||||
serviceConfig.setVersion(metadataService.version());
|
||||
serviceConfig.setMethods(generateMethodConfig());
|
||||
|
||||
// export
|
||||
serviceConfig.export();
|
||||
|
|
@ -92,6 +98,28 @@ public class ConfigurableMetadataServiceExporter implements MetadataServiceExpor
|
|||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate Method Config for Service Discovery Metadata <p/>
|
||||
* <p>
|
||||
* Make {@link MetadataService} support argument callback,
|
||||
* used to notify {@link org.apache.dubbo.registry.client.ServiceInstance}'s
|
||||
* metadata change event
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
private List<MethodConfig> generateMethodConfig() {
|
||||
MethodConfig methodConfig = new MethodConfig();
|
||||
methodConfig.setName("getAndListenInstanceMetadata");
|
||||
|
||||
ArgumentConfig argumentConfig = new ArgumentConfig();
|
||||
argumentConfig.setIndex(1);
|
||||
argumentConfig.setCallback(true);
|
||||
|
||||
methodConfig.setArguments(Collections.singletonList(argumentConfig));
|
||||
|
||||
return Collections.singletonList(methodConfig);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConfigurableMetadataServiceExporter unexport() {
|
||||
if (isExported()) {
|
||||
|
|
@ -115,10 +143,44 @@ public class ConfigurableMetadataServiceExporter implements MetadataServiceExpor
|
|||
|
||||
private ProtocolConfig generateMetadataProtocol() {
|
||||
ProtocolConfig defaultProtocol = new ProtocolConfig();
|
||||
defaultProtocol.setName(DUBBO);
|
||||
// defaultProtocol.setHost() ?
|
||||
// auto-increment port
|
||||
defaultProtocol.setPort(-1);
|
||||
Integer port = getApplicationConfig().getMetadataServicePort();
|
||||
|
||||
if (port == null || port < -1) {
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Metadata Service Port hasn't been set will use default protocol defined in protocols.");
|
||||
}
|
||||
List<ProtocolConfig> defaultProtocols = ApplicationModel.getConfigManager().getDefaultProtocols();
|
||||
|
||||
ProtocolConfig dubboProtocol = findDubboProtocol(defaultProtocols);
|
||||
if (dubboProtocol != null) {
|
||||
logger.info("Using dubbo protocol " + dubboProtocol + " to export MetadataService.");
|
||||
return dubboProtocol;
|
||||
} else {
|
||||
defaultProtocol.setName(DUBBO_PROTOCOL);
|
||||
defaultProtocol.setPort(-1);
|
||||
}
|
||||
|
||||
} else {
|
||||
defaultProtocol.setName(DUBBO_PROTOCOL);
|
||||
defaultProtocol.setPort(port);
|
||||
}
|
||||
|
||||
logger.info("Using dubbo protocol " + defaultProtocol + " to export MetadataService.");
|
||||
|
||||
return defaultProtocol;
|
||||
}
|
||||
|
||||
private ProtocolConfig findDubboProtocol(List<ProtocolConfig> protocolConfigs) {
|
||||
if (CollectionUtils.isEmpty(protocolConfigs)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (ProtocolConfig protocolConfig : protocolConfigs) {
|
||||
if (DUBBO_PROTOCOL.equalsIgnoreCase(protocolConfig.getName())) {
|
||||
return protocolConfig;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@
|
|||
*/
|
||||
package org.apache.dubbo.config.event.listener;
|
||||
|
||||
import org.apache.dubbo.common.URL;
|
||||
import org.apache.dubbo.config.ApplicationConfig;
|
||||
import org.apache.dubbo.config.RegistryConfig;
|
||||
import org.apache.dubbo.config.ServiceConfig;
|
||||
|
|
@ -25,7 +24,8 @@ import org.apache.dubbo.config.bootstrap.EchoServiceImpl;
|
|||
import org.apache.dubbo.config.context.ConfigManager;
|
||||
import org.apache.dubbo.config.event.ServiceConfigExportedEvent;
|
||||
import org.apache.dubbo.metadata.WritableMetadataService;
|
||||
import org.apache.dubbo.metadata.definition.model.FullServiceDefinition;
|
||||
import org.apache.dubbo.metadata.definition.ServiceDefinitionBuilder;
|
||||
import org.apache.dubbo.metadata.definition.model.ServiceDefinition;
|
||||
import org.apache.dubbo.rpc.model.ApplicationModel;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
|
|
@ -33,14 +33,7 @@ import org.junit.jupiter.api.AfterEach;
|
|||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_METADATA_STORAGE_TYPE;
|
||||
import static org.apache.dubbo.common.constants.CommonConstants.PID_KEY;
|
||||
import static org.apache.dubbo.common.constants.CommonConstants.TIMESTAMP_KEY;
|
||||
import static org.apache.dubbo.metadata.definition.ServiceDefinitionBuilder.buildFullDefinition;
|
||||
import static org.apache.dubbo.remoting.Constants.BIND_IP_KEY;
|
||||
import static org.apache.dubbo.remoting.Constants.BIND_PORT_KEY;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
/**
|
||||
|
|
@ -80,15 +73,8 @@ public class PublishingServiceDefinitionListenerTest {
|
|||
|
||||
String serviceDefinition = writableMetadataService.getServiceDefinition(EchoService.class.getName());
|
||||
|
||||
List<URL> exportedUrls = serviceConfig.getExportedUrls();
|
||||
ServiceDefinition serviceDefinitionBuild = ServiceDefinitionBuilder.build(serviceConfig.getInterfaceClass());
|
||||
|
||||
FullServiceDefinition fullServiceDefinition = buildFullDefinition(
|
||||
serviceConfig.getInterfaceClass(),
|
||||
exportedUrls.get(0)
|
||||
.removeParameters(PID_KEY, TIMESTAMP_KEY, BIND_IP_KEY, BIND_PORT_KEY, TIMESTAMP_KEY)
|
||||
.getParameters()
|
||||
);
|
||||
|
||||
assertEquals(serviceDefinition, new Gson().toJson(fullServiceDefinition));
|
||||
assertEquals(serviceDefinition, new Gson().toJson(serviceDefinitionBuild));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.dubbo.config.mock;
|
||||
|
||||
import org.apache.dubbo.common.URL;
|
||||
import org.apache.dubbo.registry.client.ServiceDiscovery;
|
||||
import org.apache.dubbo.registry.client.ServiceInstance;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class MockServiceDiscovery implements ServiceDiscovery {
|
||||
private URL registryURL;
|
||||
private ServiceInstance serviceInstance;
|
||||
|
||||
@Override
|
||||
public void initialize(URL registryURL) throws Exception {
|
||||
this.registryURL = registryURL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() throws Exception {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void register(ServiceInstance serviceInstance) throws RuntimeException {
|
||||
this.serviceInstance = serviceInstance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(ServiceInstance serviceInstance) throws RuntimeException {
|
||||
this.serviceInstance = serviceInstance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregister(ServiceInstance serviceInstance) throws RuntimeException {
|
||||
this.serviceInstance = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getServices() {
|
||||
return new HashSet<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getUrl() {
|
||||
return registryURL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServiceInstance getLocalInstance() {
|
||||
return serviceInstance;
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
mockregistry=org.apache.dubbo.config.mock.MockServiceDiscovery
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.dubbo.config.spring.registry;
|
||||
|
||||
import org.apache.dubbo.common.URL;
|
||||
import org.apache.dubbo.registry.client.ServiceDiscovery;
|
||||
import org.apache.dubbo.registry.client.ServiceInstance;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class MockServiceDiscovery implements ServiceDiscovery {
|
||||
private URL registryURL;
|
||||
private ServiceInstance serviceInstance;
|
||||
|
||||
@Override
|
||||
public void initialize(URL registryURL) throws Exception {
|
||||
this.registryURL = registryURL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() throws Exception {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void register(ServiceInstance serviceInstance) throws RuntimeException {
|
||||
this.serviceInstance = serviceInstance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(ServiceInstance serviceInstance) throws RuntimeException {
|
||||
this.serviceInstance = serviceInstance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregister(ServiceInstance serviceInstance) throws RuntimeException {
|
||||
this.serviceInstance = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getServices() {
|
||||
return new HashSet<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getUrl() {
|
||||
return registryURL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServiceInstance getLocalInstance() {
|
||||
return serviceInstance;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
mock=org.apache.dubbo.config.spring.registry.MockServiceDiscovery
|
||||
|
|
@ -92,9 +92,9 @@
|
|||
<spring_version>4.3.16.RELEASE</spring_version>
|
||||
<javassist_version>3.20.0-GA</javassist_version>
|
||||
<netty_version>3.2.5.Final</netty_version>
|
||||
<netty4_version>4.1.25.Final</netty4_version>
|
||||
<netty4_version>4.1.51.Final</netty4_version>
|
||||
<mina_version>1.1.7</mina_version>
|
||||
<grizzly_version>2.1.4</grizzly_version>
|
||||
<grizzly_version>2.4.4</grizzly_version>
|
||||
<httpclient_version>4.5.3</httpclient_version>
|
||||
<httpcore_version>4.4.6</httpcore_version>
|
||||
<fastjson_version>1.2.70</fastjson_version>
|
||||
|
|
@ -129,9 +129,9 @@
|
|||
<rs_api_version>2.0</rs_api_version>
|
||||
<resteasy_version>3.0.19.Final</resteasy_version>
|
||||
<tomcat_embed_version>8.5.31</tomcat_embed_version>
|
||||
<jetcd_version>0.4.1</jetcd_version>
|
||||
<jetcd_version>0.5.3</jetcd_version>
|
||||
<nacos_version>1.3.1</nacos_version>
|
||||
<grpc.version>1.22.1</grpc.version>
|
||||
<grpc.version>1.31.1</grpc.version>
|
||||
<!-- Log libs -->
|
||||
<slf4j_version>1.7.25</slf4j_version>
|
||||
<jcl_version>1.2</jcl_version>
|
||||
|
|
@ -145,13 +145,16 @@
|
|||
<!-- Eureka -->
|
||||
<eureka.version>1.9.12</eureka.version>
|
||||
|
||||
<!-- Fabric8 for Kubernetes -->
|
||||
<fabric8_kubernetes_version>4.10.3</fabric8_kubernetes_version>
|
||||
|
||||
<!-- Alibaba -->
|
||||
<alibaba_spring_context_support_version>1.0.8</alibaba_spring_context_support_version>
|
||||
|
||||
<jaxb_version>2.2.7</jaxb_version>
|
||||
<activation_version>1.2.0</activation_version>
|
||||
<test_container_version>1.11.2</test_container_version>
|
||||
<etcd_launcher_version>0.3.0</etcd_launcher_version>
|
||||
<etcd_launcher_version>0.5.3</etcd_launcher_version>
|
||||
<hessian_lite_version>3.2.8</hessian_lite_version>
|
||||
<swagger_version>1.5.19</swagger_version>
|
||||
<spring_test_version>4.3.16.RELEASE</spring_test_version>
|
||||
|
|
@ -468,11 +471,6 @@
|
|||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.etcd</groupId>
|
||||
<artifactId>jetcd-launcher</artifactId>
|
||||
<version>${jetcd_version}</version>
|
||||
</dependency>
|
||||
<!-- Log libs -->
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
|
|
@ -703,6 +701,17 @@
|
|||
<artifactId>grpc-grpclb</artifactId>
|
||||
<version>${grpc.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.fabric8</groupId>
|
||||
<artifactId>kubernetes-client</artifactId>
|
||||
<version>${fabric8_kubernetes_version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.fabric8</groupId>
|
||||
<artifactId>kubernetes-server-mock</artifactId>
|
||||
<scope>test</scope>
|
||||
<version>${fabric8_kubernetes_version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
|
|
|
|||
|
|
@ -157,6 +157,20 @@
|
|||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.dubbo</groupId>
|
||||
<artifactId>dubbo-registry-kubernetes</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.dubbo</groupId>
|
||||
<artifactId>dubbo-registry-dns</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>compile</scope>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.dubbo</groupId>
|
||||
<artifactId>dubbo-monitor-api</artifactId>
|
||||
|
|
@ -358,6 +372,8 @@
|
|||
<include>org.apache.dubbo:dubbo-registry-zookeeper</include>
|
||||
<include>org.apache.dubbo:dubbo-registry-nacos</include>
|
||||
<include>org.apache.dubbo:dubbo-registry-multiple</include>
|
||||
<include>org.apache.dubbo:dubbo-registry-kubernetes</include>
|
||||
<include>org.apache.dubbo:dubbo-registry-dns</include>
|
||||
<include>org.apache.dubbo:dubbo-monitor-api</include>
|
||||
<include>org.apache.dubbo:dubbo-monitor-default</include>
|
||||
<include>org.apache.dubbo:dubbo-container-api</include>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.dubbo.metadata;
|
||||
|
||||
public interface InstanceMetadataChangedListener {
|
||||
/**
|
||||
* Call when metadata in provider side update <p/>
|
||||
* Used to notify consumer to update metadata of ServiceInstance
|
||||
*
|
||||
* @param metadata latest metadata
|
||||
*/
|
||||
void onEvent(String metadata);
|
||||
|
||||
/**
|
||||
* Echo test
|
||||
* Used to check consumer still online
|
||||
*/
|
||||
default String echo(String msg) {
|
||||
return msg;
|
||||
}
|
||||
}
|
||||
|
|
@ -233,4 +233,46 @@ public interface MetadataService {
|
|||
static SortedSet<String> toSortedStrings(Stream<URL> stream) {
|
||||
return unmodifiableSortedSet(stream.map(URL::toFullString).collect(TreeSet::new, Set::add, Set::addAll));
|
||||
}
|
||||
|
||||
/**
|
||||
* Export Metadata in Service Instance of Service Discovery
|
||||
* <p>
|
||||
* Used for consumer to get Service Instance Metadata
|
||||
* if Registry is unsupported with publishing metadata
|
||||
*
|
||||
* @param instanceMetadata {@link Map} of provider Service Instance Metadata
|
||||
* @since 3.0
|
||||
*/
|
||||
default void exportInstanceMetadata(String instanceMetadata) {
|
||||
throw new UnsupportedOperationException("This operation is not supported for consumer.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all Metadata listener from local
|
||||
* <p>
|
||||
* Used for consumer to get Service Instance Metadata
|
||||
* if Registry is unsupported with publishing metadata
|
||||
*
|
||||
* @return {@link Map} of {@link InstanceMetadataChangedListener}
|
||||
* @since 3.0
|
||||
*/
|
||||
default Map<String, InstanceMetadataChangedListener> getInstanceMetadataChangedListenerMap() {
|
||||
throw new UnsupportedOperationException("This operation is not supported for consumer.");
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Fetch Metadata in Service Instance of Service Discovery
|
||||
* 2. Add a metadata change listener
|
||||
* <p>
|
||||
* Used for consumer to get Service Instance Metadata
|
||||
* if Registry is unsupported with publishing metadata
|
||||
*
|
||||
* @param consumerId consumerId
|
||||
* @param listener {@link InstanceMetadataChangedListener} used to notify event
|
||||
* @return {@link Map} of provider Service Instance Metadata
|
||||
* @since 3.0
|
||||
*/
|
||||
default String getAndListenInstanceMetadata(String consumerId, InstanceMetadataChangedListener listener) {
|
||||
throw new UnsupportedOperationException("This operation is not supported for consumer.");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.dubbo.qos.command.impl;
|
||||
|
||||
import org.apache.dubbo.common.URL;
|
||||
import org.apache.dubbo.common.constants.CommonConstants;
|
||||
import org.apache.dubbo.common.extension.ExtensionLoader;
|
||||
import org.apache.dubbo.qos.command.BaseCommand;
|
||||
import org.apache.dubbo.qos.command.CommandContext;
|
||||
import org.apache.dubbo.qos.command.annotation.Cmd;
|
||||
import org.apache.dubbo.qos.probe.LivenessProbe;
|
||||
import org.apache.dubbo.rpc.model.ApplicationModel;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Cmd(name = "live", summary = "Judge if service is alive? ")
|
||||
public class Live implements BaseCommand {
|
||||
|
||||
@Override
|
||||
public String execute(CommandContext commandContext, String[] args) {
|
||||
URL url = URL.valueOf("application://")
|
||||
.addParameter(CommonConstants.QOS_LIVE_PROBE_EXTENSION, ApplicationModel.getApplicationConfig().getLivenessProbe());
|
||||
List<LivenessProbe> livenessProbes = ExtensionLoader.getExtensionLoader(LivenessProbe.class)
|
||||
.getActivateExtension(url, CommonConstants.QOS_LIVE_PROBE_EXTENSION);
|
||||
if (!livenessProbes.isEmpty()) {
|
||||
for (LivenessProbe livenessProbe : livenessProbes) {
|
||||
if (!livenessProbe.check()) {
|
||||
// 503 Service Unavailable
|
||||
commandContext.setHttpCode(503);
|
||||
return "false";
|
||||
}
|
||||
}
|
||||
}
|
||||
// 200 OK
|
||||
commandContext.setHttpCode(200);
|
||||
return "true";
|
||||
}
|
||||
}
|
||||
|
|
@ -16,17 +16,38 @@
|
|||
*/
|
||||
package org.apache.dubbo.qos.command.impl;
|
||||
|
||||
import org.apache.dubbo.config.bootstrap.DubboBootstrap;
|
||||
import org.apache.dubbo.common.URL;
|
||||
import org.apache.dubbo.common.constants.CommonConstants;
|
||||
import org.apache.dubbo.common.extension.ExtensionLoader;
|
||||
import org.apache.dubbo.qos.command.BaseCommand;
|
||||
import org.apache.dubbo.qos.command.CommandContext;
|
||||
import org.apache.dubbo.qos.command.annotation.Cmd;
|
||||
import org.apache.dubbo.qos.probe.ReadinessProbe;
|
||||
import org.apache.dubbo.rpc.model.ApplicationModel;
|
||||
|
||||
@Cmd(name = "start",summary = "Judge if service has started? ")
|
||||
import java.util.List;
|
||||
|
||||
@Cmd(name = "ready", summary = "Judge if service is ready to work? ")
|
||||
public class Ready implements BaseCommand {
|
||||
|
||||
@Override
|
||||
public String execute(CommandContext commandContext, String[] args) {
|
||||
return DubboBootstrap.getInstance().isReady() ? "true" : "false";
|
||||
URL url = URL.valueOf("application://")
|
||||
.addParameter(CommonConstants.QOS_READY_PROBE_EXTENSION, ApplicationModel.getApplicationConfig().getReadinessProbe());
|
||||
List<ReadinessProbe> readinessProbes = ExtensionLoader.getExtensionLoader(ReadinessProbe.class)
|
||||
.getActivateExtension(url, CommonConstants.QOS_READY_PROBE_EXTENSION);
|
||||
if (!readinessProbes.isEmpty()) {
|
||||
for (ReadinessProbe readinessProbe : readinessProbes) {
|
||||
if (!readinessProbe.check()) {
|
||||
// 503 Service Unavailable
|
||||
commandContext.setHttpCode(503);
|
||||
return "false";
|
||||
}
|
||||
}
|
||||
}
|
||||
// 200 OK
|
||||
commandContext.setHttpCode(200);
|
||||
return "true";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.dubbo.qos.command.impl;
|
||||
|
||||
import org.apache.dubbo.common.URL;
|
||||
import org.apache.dubbo.common.constants.CommonConstants;
|
||||
import org.apache.dubbo.common.extension.ExtensionLoader;
|
||||
import org.apache.dubbo.qos.command.BaseCommand;
|
||||
import org.apache.dubbo.qos.command.CommandContext;
|
||||
import org.apache.dubbo.qos.command.annotation.Cmd;
|
||||
import org.apache.dubbo.qos.probe.StartupProbe;
|
||||
import org.apache.dubbo.rpc.model.ApplicationModel;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Cmd(name = "startup", summary = "Judge if service has started? ")
|
||||
public class Startup implements BaseCommand {
|
||||
|
||||
@Override
|
||||
public String execute(CommandContext commandContext, String[] args) {
|
||||
URL url = URL.valueOf("application://")
|
||||
.addParameter(CommonConstants.QOS_STARTUP_PROBE_EXTENSION, ApplicationModel.getApplicationConfig().getStartupProbe());
|
||||
List<StartupProbe> startupProbes = ExtensionLoader.getExtensionLoader(StartupProbe.class)
|
||||
.getActivateExtension(url, CommonConstants.QOS_STARTUP_PROBE_EXTENSION);
|
||||
if (!startupProbes.isEmpty()) {
|
||||
for (StartupProbe startupProbe : startupProbes) {
|
||||
if (!startupProbe.check()) {
|
||||
// 503 Service Unavailable
|
||||
commandContext.setHttpCode(503);
|
||||
return "false";
|
||||
}
|
||||
}
|
||||
}
|
||||
// 200 OK
|
||||
commandContext.setHttpCode(200);
|
||||
return "true";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.dubbo.qos.probe;
|
||||
|
||||
import org.apache.dubbo.common.extension.SPI;
|
||||
|
||||
/**
|
||||
* A probe to indicate whether program is alive
|
||||
* </p>
|
||||
* If one or more spi return false, 'live' command in dubbo-qos
|
||||
* will return false. This can be extend with custom program and developers
|
||||
* can implement this to customize life cycle.
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
@SPI
|
||||
public interface LivenessProbe {
|
||||
/**
|
||||
* Check if program is alive
|
||||
*
|
||||
* @return {@link boolean} result of probe
|
||||
*/
|
||||
boolean check();
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.dubbo.qos.probe;
|
||||
|
||||
import org.apache.dubbo.common.extension.SPI;
|
||||
|
||||
/**
|
||||
* A probe to indicate whether program is ready
|
||||
* </p>
|
||||
* If one or more spi return false, 'ready' command in dubbo-qos
|
||||
* will return false. This can be extend with custom program and developers
|
||||
* can implement this to customize life cycle.
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
@SPI
|
||||
public interface ReadinessProbe {
|
||||
/**
|
||||
* Check if program is Ready
|
||||
*
|
||||
* @return {@link boolean} result of probe
|
||||
*/
|
||||
boolean check();
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.dubbo.qos.probe;
|
||||
|
||||
import org.apache.dubbo.common.extension.SPI;
|
||||
|
||||
/**
|
||||
* A probe to indicate whether program is startup
|
||||
* </p>
|
||||
* If one or more spi return false, 'startup' command in dubbo-qos
|
||||
* will return false. This can be extend with custom program and developers
|
||||
* can implement this to customize life cycle.
|
||||
*
|
||||
* @since 3.0
|
||||
*/
|
||||
@SPI
|
||||
public interface StartupProbe {
|
||||
/**
|
||||
* Check if program has been startup
|
||||
*
|
||||
* @return {@link boolean} result of probe
|
||||
*/
|
||||
boolean check();
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.dubbo.qos.probe.impl;
|
||||
|
||||
import org.apache.dubbo.common.extension.Activate;
|
||||
import org.apache.dubbo.qos.probe.ReadinessProbe;
|
||||
import org.apache.dubbo.rpc.model.ApplicationModel;
|
||||
import org.apache.dubbo.rpc.model.ProviderModel;
|
||||
import org.apache.dubbo.rpc.model.ServiceRepository;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
@Activate
|
||||
public class ProviderReadinessProbe implements ReadinessProbe {
|
||||
private static ServiceRepository serviceRepository = ApplicationModel.getServiceRepository();
|
||||
|
||||
@Override
|
||||
public boolean check() {
|
||||
Collection<ProviderModel> providerModelList = serviceRepository.getExportedServices();
|
||||
if (providerModelList.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean hasService = false;
|
||||
for (ProviderModel providerModel : providerModelList) {
|
||||
List<ProviderModel.RegisterStatedURL> statedUrls = providerModel.getStatedUrl();
|
||||
for (ProviderModel.RegisterStatedURL statedUrl : statedUrls) {
|
||||
if (statedUrl.isRegistered()) {
|
||||
hasService = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return hasService;
|
||||
}
|
||||
}
|
||||
|
|
@ -30,6 +30,7 @@ import io.netty.channel.ChannelHandlerContext;
|
|||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import io.netty.handler.codec.http.DefaultFullHttpResponse;
|
||||
import io.netty.handler.codec.http.FullHttpResponse;
|
||||
import io.netty.handler.codec.http.HttpHeaderNames;
|
||||
import io.netty.handler.codec.http.HttpHeaders;
|
||||
import io.netty.handler.codec.http.HttpRequest;
|
||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||
|
|
@ -52,19 +53,37 @@ public class HttpProcessHandler extends SimpleChannelInboundHandler<HttpRequest>
|
|||
private static CommandExecutor commandExecutor = new DefaultCommandExecutor();
|
||||
|
||||
|
||||
private static FullHttpResponse http(int httpCode, String result) {
|
||||
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.valueOf(httpCode)
|
||||
, Unpooled.wrappedBuffer(result.getBytes()));
|
||||
HttpHeaders httpHeaders = response.headers();
|
||||
httpHeaders.set(HttpHeaderNames.CONTENT_TYPE, "text/plain");
|
||||
httpHeaders.set(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes());
|
||||
return response;
|
||||
}
|
||||
|
||||
private static FullHttpResponse http404() {
|
||||
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.NOT_FOUND);
|
||||
HttpHeaders httpHeaders = response.headers();
|
||||
httpHeaders.set(HttpHeaderNames.CONTENT_TYPE, "text/plain");
|
||||
httpHeaders.set(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes());
|
||||
return response;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, HttpRequest msg) throws Exception {
|
||||
CommandContext commandContext = HttpCommandDecoder.decode(msg);
|
||||
// return 404 when fail to construct command context
|
||||
if (commandContext == null) {
|
||||
log.warn("can not found commandContext url: " + msg.getUri());
|
||||
log.warn("can not found commandContext url: " + msg.uri());
|
||||
FullHttpResponse response = http404();
|
||||
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
|
||||
} else {
|
||||
commandContext.setRemote(ctx.channel());
|
||||
try {
|
||||
String result = commandExecutor.execute(commandContext);
|
||||
FullHttpResponse response = http200(result);
|
||||
int httpCode = commandContext.getHttpCode();
|
||||
FullHttpResponse response = http(httpCode, result);
|
||||
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
|
||||
} catch (NoSuchCommandException ex) {
|
||||
log.error("can not find commandContext: " + commandContext, ex);
|
||||
|
|
@ -72,36 +91,10 @@ public class HttpProcessHandler extends SimpleChannelInboundHandler<HttpRequest>
|
|||
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
|
||||
} catch (Exception qosEx) {
|
||||
log.error("execute commandContext: " + commandContext + " got exception", qosEx);
|
||||
FullHttpResponse response = http500(qosEx.getMessage());
|
||||
FullHttpResponse response = http(500, qosEx.getMessage());
|
||||
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final FullHttpResponse http200(String result) {
|
||||
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK,
|
||||
Unpooled.wrappedBuffer(result.getBytes()));
|
||||
HttpHeaders httpHeaders = response.headers();
|
||||
httpHeaders.set(HttpHeaders.Names.CONTENT_TYPE, "text/plain");
|
||||
httpHeaders.set(HttpHeaders.Names.CONTENT_LENGTH, response.content().readableBytes());
|
||||
return response;
|
||||
}
|
||||
|
||||
private static final FullHttpResponse http404() {
|
||||
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.NOT_FOUND);
|
||||
HttpHeaders httpHeaders = response.headers();
|
||||
httpHeaders.set(HttpHeaders.Names.CONTENT_TYPE, "text/plain");
|
||||
httpHeaders.set(HttpHeaders.Names.CONTENT_LENGTH, response.content().readableBytes());
|
||||
return response;
|
||||
}
|
||||
|
||||
private static final FullHttpResponse http500(String errorMessage) {
|
||||
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.INTERNAL_SERVER_ERROR
|
||||
, Unpooled.wrappedBuffer(errorMessage.getBytes()));
|
||||
HttpHeaders httpHeaders = response.headers();
|
||||
httpHeaders.set(HttpHeaders.Names.CONTENT_TYPE, "text/plain");
|
||||
httpHeaders.set(HttpHeaders.Names.CONTENT_LENGTH, response.content().readableBytes());
|
||||
return response;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -0,0 +1,2 @@
|
|||
bootstrap = org.apache.dubbo.qos.probe.impl.BootstrapReadinessProbe
|
||||
provider = org.apache.dubbo.qos.probe.impl.ProviderReadinessProbe
|
||||
|
|
@ -0,0 +1 @@
|
|||
bootstrap = org.apache.dubbo.qos.probe.impl.BootstrapStartupProbe
|
||||
|
|
@ -18,12 +18,16 @@ package org.apache.dubbo.qos.command.util;
|
|||
|
||||
import org.apache.dubbo.qos.command.GreetingCommand;
|
||||
import org.apache.dubbo.qos.command.impl.Help;
|
||||
import org.apache.dubbo.qos.command.impl.Live;
|
||||
import org.apache.dubbo.qos.command.impl.Ls;
|
||||
import org.apache.dubbo.qos.command.impl.Offline;
|
||||
import org.apache.dubbo.qos.command.impl.Online;
|
||||
import org.apache.dubbo.qos.command.impl.PublishMetadata;
|
||||
import org.apache.dubbo.qos.command.impl.Quit;
|
||||
import org.apache.dubbo.qos.command.impl.Ready;
|
||||
import org.apache.dubbo.qos.command.impl.Startup;
|
||||
import org.apache.dubbo.qos.command.impl.Version;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
|
@ -47,7 +51,7 @@ public class CommandHelperTest {
|
|||
List<Class<?>> classes = CommandHelper.getAllCommandClass();
|
||||
assertThat(classes,
|
||||
containsInAnyOrder(GreetingCommand.class, Help.class, Ls.class, Offline.class, Online.class, Quit.class,
|
||||
Ready.class, Version.class));
|
||||
Live.class, Ready.class, Startup.class, Version.class, PublishMetadata.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,290 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.dubbo.registry.client;
|
||||
|
||||
import org.apache.dubbo.common.URL;
|
||||
import org.apache.dubbo.common.constants.CommonConstants;
|
||||
import org.apache.dubbo.common.logger.Logger;
|
||||
import org.apache.dubbo.common.logger.LoggerFactory;
|
||||
import org.apache.dubbo.common.utils.NamedThreadFactory;
|
||||
import org.apache.dubbo.common.utils.NetUtils;
|
||||
import org.apache.dubbo.common.utils.StringUtils;
|
||||
import org.apache.dubbo.metadata.InstanceMetadataChangedListener;
|
||||
import org.apache.dubbo.metadata.MetadataService;
|
||||
import org.apache.dubbo.metadata.RevisionResolver;
|
||||
import org.apache.dubbo.metadata.WritableMetadataService;
|
||||
import org.apache.dubbo.registry.Constants;
|
||||
import org.apache.dubbo.registry.client.event.ServiceInstancesChangedEvent;
|
||||
import org.apache.dubbo.registry.client.event.listener.ServiceInstancesChangedListener;
|
||||
import org.apache.dubbo.registry.client.metadata.MetadataUtils;
|
||||
import org.apache.dubbo.rpc.RpcException;
|
||||
import org.apache.dubbo.rpc.model.ApplicationModel;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public abstract class SelfHostMetaServiceDiscovery implements ServiceDiscovery {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(getClass());
|
||||
|
||||
private URL registryURL;
|
||||
|
||||
/**
|
||||
* Echo check if consumer is still work
|
||||
* echo task may take a lot of time when consumer offline, create a new ScheduledThreadPool
|
||||
*/
|
||||
private final ScheduledExecutorService echoCheckExecutor = Executors.newScheduledThreadPool(1, new NamedThreadFactory("Dubbo-Registry-EchoCheck-Consumer"));
|
||||
|
||||
// =================================== Provider side =================================== //
|
||||
|
||||
private ServiceInstance serviceInstance;
|
||||
|
||||
/**
|
||||
* Local {@link ServiceInstance} Metadata's revision
|
||||
*/
|
||||
private String lastMetadataRevision;
|
||||
|
||||
// =================================== Consumer side =================================== //
|
||||
|
||||
/**
|
||||
* Local Cache of {@link ServiceInstance} Metadata
|
||||
* <p>
|
||||
* Key - {@link ServiceInstance} ID ( usually ip + port )
|
||||
* Value - Json processed metadata string
|
||||
*/
|
||||
private final ConcurrentHashMap<String, String> metadataMap = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* Local Cache of {@link ServiceInstance}
|
||||
* <p>
|
||||
* Key - Service Name
|
||||
* Value - List {@link ServiceInstance}
|
||||
*/
|
||||
private final ConcurrentHashMap<String, List<ServiceInstance>> cachedServiceInstances = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* Local Cache of Service's {@link ServiceInstance} list revision,
|
||||
* used to check if {@link ServiceInstance} list has been updated
|
||||
* <p>
|
||||
* Key - ServiceName
|
||||
* Value - a revision calculate from {@link List} of {@link ServiceInstance}
|
||||
*/
|
||||
private final ConcurrentHashMap<String, String> serviceInstanceRevisionMap = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public void initialize(URL registryURL) throws Exception {
|
||||
this.registryURL = registryURL;
|
||||
doInitialize(registryURL);
|
||||
long echoPollingCycle = registryURL.getParameter(Constants.ECHO_POLLING_CYCLE_KEY, Constants.DEFAULT_ECHO_POLLING_CYCLE);
|
||||
|
||||
// Echo check: test if consumer is offline, remove MetadataChangeListener,
|
||||
// reduce the probability of failure when metadata update
|
||||
echoCheckExecutor.scheduleAtFixedRate(() -> {
|
||||
WritableMetadataService metadataService = WritableMetadataService.getDefaultExtension();
|
||||
Map<String, InstanceMetadataChangedListener> listenerMap = metadataService.getInstanceMetadataChangedListenerMap();
|
||||
Iterator<Map.Entry<String, InstanceMetadataChangedListener>> iterator = listenerMap.entrySet().iterator();
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
Map.Entry<String, InstanceMetadataChangedListener> entry = iterator.next();
|
||||
try {
|
||||
entry.getValue().echo(CommonConstants.DUBBO);
|
||||
} catch (RpcException e) {
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Send echo message to consumer error. Possible cause: consumer is offline.");
|
||||
}
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
}, echoPollingCycle, echoPollingCycle, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() throws Exception {
|
||||
doDestroy();
|
||||
metadataMap.clear();
|
||||
serviceInstanceRevisionMap.clear();
|
||||
echoCheckExecutor.shutdown();
|
||||
}
|
||||
|
||||
private void updateMetadata(ServiceInstance serviceInstance) {
|
||||
WritableMetadataService metadataService = WritableMetadataService.getDefaultExtension();
|
||||
String metadataString = JSONObject.toJSONString(serviceInstance.getMetadata());
|
||||
String metadataRevision = RevisionResolver.calRevision(metadataString);
|
||||
|
||||
// check if metadata updated
|
||||
if (!metadataRevision.equalsIgnoreCase(lastMetadataRevision)) {
|
||||
logger.info("Update Service Instance Metadata of DNS registry. Newer metadata: " + metadataString);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Update Service Instance Metadata of DNS registry. Newer metadata: " + metadataString);
|
||||
}
|
||||
|
||||
lastMetadataRevision = metadataRevision;
|
||||
|
||||
// save newest metadata to local
|
||||
metadataService.exportInstanceMetadata(metadataString);
|
||||
|
||||
// notify to consumer
|
||||
Map<String, InstanceMetadataChangedListener> listenerMap = metadataService.getInstanceMetadataChangedListenerMap();
|
||||
Iterator<Map.Entry<String, InstanceMetadataChangedListener>> iterator = listenerMap.entrySet().iterator();
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
Map.Entry<String, InstanceMetadataChangedListener> entry = iterator.next();
|
||||
try {
|
||||
entry.getValue().onEvent(metadataString);
|
||||
} catch (RpcException e) {
|
||||
logger.warn("Notify to consumer error. Possible cause: consumer is offline.");
|
||||
// remove listener if consumer is offline
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void register(ServiceInstance serviceInstance) throws RuntimeException {
|
||||
this.serviceInstance = serviceInstance;
|
||||
|
||||
updateMetadata(serviceInstance);
|
||||
|
||||
doRegister(serviceInstance);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(ServiceInstance serviceInstance) throws RuntimeException {
|
||||
this.serviceInstance = serviceInstance;
|
||||
|
||||
updateMetadata(serviceInstance);
|
||||
|
||||
doUpdate(serviceInstance);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregister(ServiceInstance serviceInstance) throws RuntimeException {
|
||||
doUnregister(serviceInstance);
|
||||
|
||||
this.serviceInstance = null;
|
||||
|
||||
// notify empty message to consumer
|
||||
WritableMetadataService metadataService = WritableMetadataService.getDefaultExtension();
|
||||
metadataService.exportInstanceMetadata("");
|
||||
metadataService.getInstanceMetadataChangedListenerMap().forEach((consumerId, listener) -> listener.onEvent(""));
|
||||
metadataService.getInstanceMetadataChangedListenerMap().clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServiceInstance getLocalInstance() {
|
||||
return serviceInstance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getUrl() {
|
||||
return registryURL;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public final void fillServiceInstance(DefaultServiceInstance serviceInstance) {
|
||||
String hostId = serviceInstance.getId();
|
||||
if (metadataMap.containsKey(hostId)) {
|
||||
// Use cached metadata.
|
||||
// Metadata will be updated by provider callback
|
||||
|
||||
String metadataString = metadataMap.get(hostId);
|
||||
serviceInstance.setMetadata(JSONObject.parseObject(metadataString, Map.class));
|
||||
} else {
|
||||
// refer from MetadataUtils, this proxy is different from the one used to refer exportedURL
|
||||
MetadataService metadataService = MetadataUtils.getMetadataServiceProxy(serviceInstance, this);
|
||||
|
||||
String consumerId = ApplicationModel.getName() + NetUtils.getLocalHost();
|
||||
String metadata = metadataService.getAndListenInstanceMetadata(
|
||||
consumerId, metadataString -> {
|
||||
logger.info("Receive callback: " + metadataString + serviceInstance);
|
||||
if (StringUtils.isEmpty(metadataString)) {
|
||||
// provider is shutdown
|
||||
metadataMap.remove(hostId);
|
||||
} else {
|
||||
metadataMap.put(hostId, metadataString);
|
||||
}
|
||||
});
|
||||
metadataMap.put(hostId, metadata);
|
||||
serviceInstance.setMetadata(JSONObject.parseObject(metadata, Map.class));
|
||||
}
|
||||
}
|
||||
|
||||
public final void notifyListener(String serviceName, ServiceInstancesChangedListener listener, List<ServiceInstance> instances) {
|
||||
String serviceInstanceRevision = RevisionResolver.calRevision(JSONObject.toJSONString(instances));
|
||||
boolean changed = !serviceInstanceRevision.equalsIgnoreCase(
|
||||
serviceInstanceRevisionMap.put(serviceName, serviceInstanceRevision));
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Poll DNS data. Service Instance changed: " + changed + " Service Name: " + serviceName);
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
List<ServiceInstance> oldServiceInstances = cachedServiceInstances.getOrDefault(serviceName, new LinkedList<>());
|
||||
|
||||
// remove expired invoker
|
||||
Set<ServiceInstance> allServiceInstances = new HashSet<>(oldServiceInstances.size() + instances.size());
|
||||
allServiceInstances.addAll(oldServiceInstances);
|
||||
allServiceInstances.addAll(instances);
|
||||
|
||||
allServiceInstances.removeAll(oldServiceInstances);
|
||||
|
||||
allServiceInstances.forEach(removedServiceInstance -> {
|
||||
MetadataUtils.destroyMetadataServiceProxy(removedServiceInstance, this);
|
||||
});
|
||||
|
||||
cachedServiceInstances.put(serviceName, instances);
|
||||
listener.onEvent(new ServiceInstancesChangedEvent(serviceName, instances));
|
||||
}
|
||||
}
|
||||
|
||||
public void doInitialize(URL registryURL) throws Exception {
|
||||
}
|
||||
|
||||
public void doDestroy() throws Exception {
|
||||
}
|
||||
|
||||
public void doRegister(ServiceInstance serviceInstance) throws RuntimeException {
|
||||
|
||||
}
|
||||
|
||||
public void doUpdate(ServiceInstance serviceInstance) throws RuntimeException {
|
||||
|
||||
}
|
||||
|
||||
public void doUnregister(ServiceInstance serviceInstance) throws RuntimeException {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* UT used only
|
||||
*/
|
||||
@Deprecated
|
||||
public final ConcurrentHashMap<String, List<ServiceInstance>> getCachedServiceInstances() {
|
||||
return cachedServiceInstances;
|
||||
}
|
||||
}
|
||||
|
|
@ -33,7 +33,11 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
import static org.apache.dubbo.common.constants.CommonConstants.METADATA_KEY;
|
||||
import static org.apache.dubbo.common.constants.CommonConstants.REMOTE_METADATA_STORAGE_TYPE;
|
||||
import static org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils.METADATA_SERVICE_URLS_PROPERTY_NAME;
|
||||
|
||||
public class MetadataUtils {
|
||||
|
|
@ -42,6 +46,10 @@ public class MetadataUtils {
|
|||
|
||||
public static ConcurrentMap<String, MetadataService> metadataServiceProxies = new ConcurrentHashMap<>();
|
||||
|
||||
public static ConcurrentMap<String, Invoker<?>> metadataServiceInvokers = new ConcurrentHashMap<>();
|
||||
|
||||
public static ConcurrentMap<String, Lock> metadataServiceLocks = new ConcurrentHashMap<>();
|
||||
|
||||
private static final ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
|
||||
|
||||
private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
|
||||
|
|
@ -72,39 +80,69 @@ public class MetadataUtils {
|
|||
// store in local
|
||||
WritableMetadataService.getDefaultExtension().publishServiceDefinition(url);
|
||||
// send to remote
|
||||
// if (REMOTE_METADATA_STORAGE_TYPE.equals(url.getParameter(METADATA_KEY))) {
|
||||
getRemoteMetadataService().publishServiceDefinition(url);
|
||||
// }
|
||||
if (REMOTE_METADATA_STORAGE_TYPE.equalsIgnoreCase(url.getParameter(METADATA_KEY))) {
|
||||
getRemoteMetadataService().publishServiceDefinition(url);
|
||||
}
|
||||
}
|
||||
|
||||
public static String computeKey(ServiceInstance serviceInstance) {
|
||||
return serviceInstance.getServiceName() + "##" + serviceInstance.getId() + "##" +
|
||||
ServiceInstanceMetadataUtils.getExportedServicesRevision(serviceInstance);
|
||||
}
|
||||
|
||||
public static MetadataService getMetadataServiceProxy(ServiceInstance instance, ServiceDiscovery serviceDiscovery) {
|
||||
String key = instance.getServiceName() + "##" +
|
||||
ServiceInstanceMetadataUtils.getExportedServicesRevision(instance);
|
||||
return metadataServiceProxies.computeIfAbsent(key, k -> {
|
||||
MetadataServiceURLBuilder builder = null;
|
||||
ExtensionLoader<MetadataServiceURLBuilder> loader
|
||||
= ExtensionLoader.getExtensionLoader(MetadataServiceURLBuilder.class);
|
||||
String key = computeKey(instance);
|
||||
Lock lock = metadataServiceLocks.computeIfAbsent(key, k -> new ReentrantLock());
|
||||
|
||||
Map<String, String> metadata = instance.getMetadata();
|
||||
// METADATA_SERVICE_URLS_PROPERTY_NAME is a unique key exists only on instances of spring-cloud-alibaba.
|
||||
String dubboURLsJSON = metadata.get(METADATA_SERVICE_URLS_PROPERTY_NAME);
|
||||
if (StringUtils.isNotEmpty(dubboURLsJSON)) {
|
||||
builder = loader.getExtension(SpringCloudMetadataServiceURLBuilder.NAME);
|
||||
} else {
|
||||
builder = loader.getExtension(StandardMetadataServiceURLBuilder.NAME);
|
||||
lock.lock();
|
||||
try {
|
||||
return metadataServiceProxies.computeIfAbsent(key, k -> referProxy(k, instance));
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public static void destroyMetadataServiceProxy(ServiceInstance instance, ServiceDiscovery serviceDiscovery) {
|
||||
String key = computeKey(instance);
|
||||
Lock lock = metadataServiceLocks.computeIfAbsent(key, k -> new ReentrantLock());
|
||||
|
||||
lock.lock();
|
||||
try {
|
||||
if (metadataServiceProxies.containsKey(key)) {
|
||||
metadataServiceProxies.remove(key);
|
||||
Invoker<?> invoker = metadataServiceInvokers.remove(key);
|
||||
invoker.destroy();
|
||||
}
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
List<URL> urls = builder.build(instance);
|
||||
if (CollectionUtils.isEmpty(urls)) {
|
||||
throw new IllegalStateException("You have enabled introspection service discovery mode for instance "
|
||||
+ instance + ", but no metadata service can build from it.");
|
||||
}
|
||||
private static MetadataService referProxy(String key, ServiceInstance instance) {
|
||||
MetadataServiceURLBuilder builder = null;
|
||||
ExtensionLoader<MetadataServiceURLBuilder> loader
|
||||
= ExtensionLoader.getExtensionLoader(MetadataServiceURLBuilder.class);
|
||||
|
||||
// Simply rely on the first metadata url, as stated in MetadataServiceURLBuilder.
|
||||
Invoker<MetadataService> invoker = protocol.refer(MetadataService.class, urls.get(0));
|
||||
Map<String, String> metadata = instance.getMetadata();
|
||||
// METADATA_SERVICE_URLS_PROPERTY_NAME is a unique key exists only on instances of spring-cloud-alibaba.
|
||||
String dubboURLsJSON = metadata.get(METADATA_SERVICE_URLS_PROPERTY_NAME);
|
||||
if (metadata.isEmpty() || StringUtils.isEmpty(dubboURLsJSON)) {
|
||||
builder = loader.getExtension(StandardMetadataServiceURLBuilder.NAME);
|
||||
} else {
|
||||
builder = loader.getExtension(SpringCloudMetadataServiceURLBuilder.NAME);
|
||||
}
|
||||
|
||||
return proxyFactory.getProxy(invoker);
|
||||
});
|
||||
List<URL> urls = builder.build(instance);
|
||||
if (CollectionUtils.isEmpty(urls)) {
|
||||
throw new IllegalStateException("You have enabled introspection service discovery mode for instance "
|
||||
+ instance + ", but no metadata service can build from it.");
|
||||
}
|
||||
|
||||
// Simply rely on the first metadata url, as stated in MetadataServiceURLBuilder.
|
||||
Invoker<MetadataService> invoker = protocol.refer(MetadataService.class, urls.get(0));
|
||||
metadataServiceInvokers.put(key, invoker);
|
||||
|
||||
return proxyFactory.getProxy(invoker);
|
||||
}
|
||||
|
||||
public static void saveMetadataURL(URL url) {
|
||||
|
|
|
|||
|
|
@ -19,19 +19,25 @@ package org.apache.dubbo.registry.client.metadata;
|
|||
import org.apache.dubbo.common.URL;
|
||||
import org.apache.dubbo.common.URLBuilder;
|
||||
import org.apache.dubbo.common.config.ConfigurationUtils;
|
||||
import org.apache.dubbo.common.logger.Logger;
|
||||
import org.apache.dubbo.common.logger.LoggerFactory;
|
||||
import org.apache.dubbo.metadata.MetadataService;
|
||||
import org.apache.dubbo.registry.client.ServiceInstance;
|
||||
import org.apache.dubbo.remoting.Constants;
|
||||
import org.apache.dubbo.rpc.model.ApplicationModel;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.apache.dubbo.common.constants.CommonConstants.CONSUMER;
|
||||
import static org.apache.dubbo.common.constants.CommonConstants.DUBBO_PROTOCOL;
|
||||
import static org.apache.dubbo.common.constants.CommonConstants.GROUP_KEY;
|
||||
import static org.apache.dubbo.common.constants.CommonConstants.PORT_KEY;
|
||||
import static org.apache.dubbo.common.constants.CommonConstants.PROTOCOL_KEY;
|
||||
import static org.apache.dubbo.common.constants.CommonConstants.SIDE_KEY;
|
||||
import static org.apache.dubbo.common.constants.CommonConstants.TIMEOUT_KEY;
|
||||
import static org.apache.dubbo.common.constants.CommonConstants.VERSION_KEY;
|
||||
import static org.apache.dubbo.metadata.MetadataConstants.DEFAULT_METADATA_TIMEOUT_VALUE;
|
||||
import static org.apache.dubbo.metadata.MetadataConstants.METADATA_PROXY_TIMEOUT_KEY;
|
||||
import static org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils.getMetadataServiceURLsParams;
|
||||
|
|
@ -43,7 +49,9 @@ import static org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataU
|
|||
* @since 2.7.5
|
||||
*/
|
||||
public class StandardMetadataServiceURLBuilder implements MetadataServiceURLBuilder {
|
||||
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(getClass());
|
||||
|
||||
public static final String NAME = "standard";
|
||||
|
||||
/**
|
||||
|
|
@ -61,24 +69,66 @@ public class StandardMetadataServiceURLBuilder implements MetadataServiceURLBuil
|
|||
String serviceName = serviceInstance.getServiceName();
|
||||
|
||||
String host = serviceInstance.getHost();
|
||||
URLBuilder urlBuilder = new URLBuilder();
|
||||
for (Map.Entry<String, String> entry : paramsMap.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
String value = entry.getValue();
|
||||
if (key.equals(PORT_KEY)) {
|
||||
urlBuilder.setPort(Integer.parseInt(value));
|
||||
} else if (key.equals(PROTOCOL_KEY)) {
|
||||
urlBuilder.setProtocol(value);
|
||||
} else {
|
||||
urlBuilder.addParameter(key, value);
|
||||
}
|
||||
}
|
||||
urlBuilder.setHost(host).setPath(MetadataService.class.getName())
|
||||
.addParameter(TIMEOUT_KEY, ConfigurationUtils.get(METADATA_PROXY_TIMEOUT_KEY, DEFAULT_METADATA_TIMEOUT_VALUE))
|
||||
.addParameter(SIDE_KEY, CONSUMER)
|
||||
.addParameter(GROUP_KEY, serviceName);
|
||||
|
||||
urls.add(urlBuilder.build());
|
||||
if (paramsMap.isEmpty()) {
|
||||
// ServiceInstance Metadata is empty. Happened when registry not support metadata write.
|
||||
urls.add(generateUrlWithoutMetadata(serviceName, host, serviceInstance.getPort()));
|
||||
} else {
|
||||
urls.add(generateWithMetadata(serviceName, host, paramsMap));
|
||||
}
|
||||
|
||||
return urls;
|
||||
}
|
||||
|
||||
private URL generateWithMetadata(String serviceName, String host, Map<String, String> params) {
|
||||
String protocol = params.get(PROTOCOL_KEY);
|
||||
int port = Integer.parseInt(params.get(PORT_KEY));
|
||||
URLBuilder urlBuilder = new URLBuilder()
|
||||
.setHost(host)
|
||||
.setPort(port)
|
||||
.setProtocol(protocol)
|
||||
.setPath(MetadataService.class.getName())
|
||||
.addParameter(TIMEOUT_KEY, ConfigurationUtils.get(METADATA_PROXY_TIMEOUT_KEY, DEFAULT_METADATA_TIMEOUT_VALUE))
|
||||
.addParameter(SIDE_KEY, CONSUMER);
|
||||
|
||||
// add parameters
|
||||
params.forEach(urlBuilder::addParameter);
|
||||
|
||||
// add the default parameters
|
||||
urlBuilder.addParameter(GROUP_KEY, serviceName);
|
||||
return urlBuilder.build();
|
||||
}
|
||||
|
||||
private URL generateUrlWithoutMetadata(String serviceName, String host, Integer instancePort) {
|
||||
Integer port = ApplicationModel.getApplicationConfig().getMetadataServicePort();
|
||||
if (port == null || port < 1) {
|
||||
logger.warn("Metadata Service Port is not provided, since DNS is not able to negotiate the metadata port " +
|
||||
"between Provider and Consumer, will try to use instance port as the default metadata port.");
|
||||
port = instancePort;
|
||||
}
|
||||
|
||||
if (port == null || port < 1) {
|
||||
String message = "Metadata Service Port should be specified for consumer. " +
|
||||
"Please set dubbo.application.metadataServicePort and " +
|
||||
"make sure it has been set on provider side. " +
|
||||
"ServiceName: " + serviceName + " Host: " + host;
|
||||
throw new IllegalStateException(message);
|
||||
}
|
||||
|
||||
URLBuilder urlBuilder = new URLBuilder()
|
||||
.setHost(host)
|
||||
.setPort(port)
|
||||
.setProtocol(DUBBO_PROTOCOL)
|
||||
.setPath(MetadataService.class.getName())
|
||||
.addParameter(TIMEOUT_KEY, ConfigurationUtils.get(METADATA_PROXY_TIMEOUT_KEY, DEFAULT_METADATA_TIMEOUT_VALUE))
|
||||
.addParameter(Constants.RECONNECT_KEY, false)
|
||||
.addParameter(SIDE_KEY, CONSUMER)
|
||||
.addParameter(GROUP_KEY, serviceName)
|
||||
.addParameter(VERSION_KEY, MetadataService.VERSION);
|
||||
|
||||
// add ServiceInstance Metadata notify support
|
||||
urlBuilder.addParameter("getAndListenInstanceMetadata.1.callback", true);
|
||||
|
||||
return urlBuilder.build();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import org.apache.dubbo.common.logger.Logger;
|
|||
import org.apache.dubbo.common.logger.LoggerFactory;
|
||||
import org.apache.dubbo.common.utils.CollectionUtils;
|
||||
import org.apache.dubbo.common.utils.StringUtils;
|
||||
import org.apache.dubbo.metadata.InstanceMetadataChangedListener;
|
||||
import org.apache.dubbo.metadata.MetadataInfo;
|
||||
import org.apache.dubbo.metadata.MetadataInfo.ServiceInfo;
|
||||
import org.apache.dubbo.metadata.MetadataService;
|
||||
|
|
@ -82,9 +83,12 @@ public class InMemoryWritableMetadataService implements WritableMetadataService
|
|||
URL metadataServiceURL;
|
||||
ConcurrentMap<String, MetadataInfo> metadataInfos;
|
||||
final Semaphore metadataSemaphore = new Semaphore(0);
|
||||
|
||||
final Map<String, Set<String>> serviceToAppsMapping = new HashMap<>();
|
||||
|
||||
String instanceMetadata;
|
||||
ConcurrentMap<String, InstanceMetadataChangedListener> instanceMetadataChangedListenerMap = new ConcurrentHashMap<>();
|
||||
|
||||
|
||||
// ==================================================================================== //
|
||||
|
||||
// =================================== Subscription =================================== //
|
||||
|
|
@ -240,6 +244,22 @@ public class InMemoryWritableMetadataService implements WritableMetadataService
|
|||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exportInstanceMetadata(String metadata) {
|
||||
this.instanceMetadata = metadata;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, InstanceMetadataChangedListener> getInstanceMetadataChangedListenerMap() {
|
||||
return instanceMetadataChangedListenerMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAndListenInstanceMetadata(String consumerId, InstanceMetadataChangedListener listener) {
|
||||
instanceMetadataChangedListenerMap.put(consumerId, listener);
|
||||
return instanceMetadata;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MetadataInfo getDefaultMetadataInfo() {
|
||||
if (CollectionUtils.isEmptyMap(metadataInfos)) {
|
||||
|
|
@ -379,5 +399,4 @@ public class InMemoryWritableMetadataService implements WritableMetadataService
|
|||
return o1.toFullString().compareTo(o2.toFullString());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -47,4 +47,4 @@ public interface MigrationClusterInvoker<T> extends ClusterInvoker<T> {
|
|||
void refreshServiceDiscoveryInvokerOnMappingCallback(boolean forceMigrate);
|
||||
|
||||
void reRefer(URL newSubscribeUrl);
|
||||
}
|
||||
}
|
||||
|
|
@ -44,6 +44,8 @@ public class InMemoryServiceDiscovery implements ServiceDiscovery {
|
|||
|
||||
private ServiceInstance serviceInstance;
|
||||
|
||||
private URL registryURL;
|
||||
|
||||
@Override
|
||||
public Set<String> getServices() {
|
||||
return repository.keySet();
|
||||
|
|
@ -70,6 +72,11 @@ public class InMemoryServiceDiscovery implements ServiceDiscovery {
|
|||
return new DefaultPage<>(offset, pageSize, data, totalSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getUrl() {
|
||||
return registryURL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServiceInstance getLocalInstance() {
|
||||
return serviceInstance;
|
||||
|
|
@ -104,7 +111,7 @@ public class InMemoryServiceDiscovery implements ServiceDiscovery {
|
|||
|
||||
@Override
|
||||
public void initialize(URL registryURL) throws Exception {
|
||||
|
||||
this.registryURL = registryURL;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -0,0 +1,90 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
contributor license agreements. See the NOTICE file distributed with
|
||||
this work for additional information regarding copyright ownership.
|
||||
The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
(the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.apache.dubbo</groupId>
|
||||
<artifactId>dubbo-registry</artifactId>
|
||||
<version>${revision}</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<artifactId>dubbo-registry-dns</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>${project.artifactId}</name>
|
||||
<description>The DNS registry module of Dubbo project</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.apache.dubbo</groupId>
|
||||
<artifactId>dubbo-registry-api</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.dubbo</groupId>
|
||||
<artifactId>dubbo-common</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-all</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.dubbo</groupId>
|
||||
<artifactId>dubbo-config-api</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.dubbo</groupId>
|
||||
<artifactId>dubbo-rpc-dubbo</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.dubbo</groupId>
|
||||
<artifactId>dubbo-serialization-hessian2</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.dubbo</groupId>
|
||||
<artifactId>dubbo-remoting-netty4</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.dubbo</groupId>
|
||||
<artifactId>dubbo-registry-multicast</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.dubbo.registry.dns;
|
||||
|
||||
import org.apache.dubbo.common.URL;
|
||||
import org.apache.dubbo.registry.NotifyListener;
|
||||
import org.apache.dubbo.registry.support.FailbackRegistry;
|
||||
|
||||
/**
|
||||
* Empty implements for DNS <br/>
|
||||
* DNS only support `Service Discovery` mode register <br/>
|
||||
* Used to compat past version like 2.6.x, 2.7.x with interface level register <br/>
|
||||
* {@link DNSServiceDiscovery} is the real implementation of DNS
|
||||
*/
|
||||
public class DNSRegistry extends FailbackRegistry {
|
||||
public DNSRegistry(URL url) {
|
||||
super(url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAvailable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doRegister(URL url) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doUnregister(URL url) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doSubscribe(URL url, NotifyListener listener) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doUnsubscribe(URL url, NotifyListener listener) {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,152 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.dubbo.registry.dns;
|
||||
|
||||
import org.apache.dubbo.common.URL;
|
||||
import org.apache.dubbo.common.logger.Logger;
|
||||
import org.apache.dubbo.common.logger.LoggerFactory;
|
||||
import org.apache.dubbo.common.utils.NamedThreadFactory;
|
||||
import org.apache.dubbo.registry.client.DefaultServiceInstance;
|
||||
import org.apache.dubbo.registry.client.SelfHostMetaServiceDiscovery;
|
||||
import org.apache.dubbo.registry.client.ServiceInstance;
|
||||
import org.apache.dubbo.registry.client.event.listener.ServiceInstancesChangedListener;
|
||||
import org.apache.dubbo.registry.dns.util.DNSClientConst;
|
||||
import org.apache.dubbo.registry.dns.util.DNSResolver;
|
||||
import org.apache.dubbo.registry.dns.util.ResolveResult;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class DNSServiceDiscovery extends SelfHostMetaServiceDiscovery {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(getClass());
|
||||
|
||||
/**
|
||||
* DNS properties
|
||||
*/
|
||||
|
||||
private String addressPrefix;
|
||||
private String addressSuffix;
|
||||
private long pollingCycle;
|
||||
private DNSResolver dnsResolver;
|
||||
|
||||
/**
|
||||
* Polling task ScheduledFuture, used to stop task when destroy
|
||||
*/
|
||||
private final ConcurrentHashMap<String, ScheduledFuture<?>> pollingExecutorMap = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* Polling check provider ExecutorService
|
||||
*/
|
||||
private ScheduledExecutorService pollingExecutorService;
|
||||
|
||||
@Override
|
||||
public void doInitialize(URL registryURL) throws Exception {
|
||||
this.addressPrefix = registryURL.getParameter(DNSClientConst.ADDRESS_PREFIX, "");
|
||||
this.addressSuffix = registryURL.getParameter(DNSClientConst.ADDRESS_SUFFIX, "");
|
||||
this.pollingCycle = registryURL.getParameter(DNSClientConst.DNS_POLLING_CYCLE, DNSClientConst.DEFAULT_DNS_POLLING_CYCLE);
|
||||
|
||||
String nameserver = registryURL.getHost();
|
||||
int port = registryURL.getPort();
|
||||
int maxQueriesPerResolve = registryURL.getParameter(DNSClientConst.MAX_QUERIES_PER_RESOLVE, 10);
|
||||
this.dnsResolver = new DNSResolver(nameserver, port, maxQueriesPerResolve);
|
||||
|
||||
|
||||
int scheduledThreadPoolSize = registryURL.getParameter(DNSClientConst.DNS_POLLING_POOL_SIZE_KEY, DNSClientConst.DEFAULT_DNS_POLLING_POOL_SIZE);
|
||||
|
||||
// polling task may take a lot of time, create a new ScheduledThreadPool
|
||||
pollingExecutorService = Executors.newScheduledThreadPool(scheduledThreadPoolSize, new NamedThreadFactory("Dubbo-DNS-Poll"));
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doDestroy() throws Exception {
|
||||
dnsResolver.destroy();
|
||||
pollingExecutorMap.forEach((serviceName, scheduledFuture) -> scheduledFuture.cancel(true));
|
||||
pollingExecutorMap.clear();
|
||||
pollingExecutorService.shutdown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getServices() {
|
||||
// it is impossible for dns to discover service names
|
||||
return Collections.singleton("Unsupported Method");
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ServiceInstance> getInstances(String serviceName) throws NullPointerException {
|
||||
|
||||
String serviceAddress = addressPrefix + serviceName + addressSuffix;
|
||||
|
||||
ResolveResult resolveResult = dnsResolver.resolve(serviceAddress);
|
||||
|
||||
return toServiceInstance(serviceName, resolveResult);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addServiceInstancesChangedListener(ServiceInstancesChangedListener listener) throws NullPointerException, IllegalArgumentException {
|
||||
listener.getServiceNames().forEach(serviceName -> {
|
||||
ScheduledFuture<?> scheduledFuture = pollingExecutorService.scheduleAtFixedRate(() -> {
|
||||
List<ServiceInstance> instances = getInstances(serviceName);
|
||||
instances.sort(Comparator.comparingInt(ServiceInstance::hashCode));
|
||||
notifyListener(serviceName, listener, instances);
|
||||
},
|
||||
pollingCycle, pollingCycle, TimeUnit.MILLISECONDS);
|
||||
|
||||
pollingExecutorMap.put(serviceName, scheduledFuture);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* UT used only
|
||||
*/
|
||||
@Deprecated
|
||||
public void setDnsResolver(DNSResolver dnsResolver) {
|
||||
this.dnsResolver = dnsResolver;
|
||||
}
|
||||
|
||||
private List<ServiceInstance> toServiceInstance(String serviceName, ResolveResult resolveResult) {
|
||||
|
||||
int port;
|
||||
|
||||
if (resolveResult.getPort().size() > 0) {
|
||||
// use first as default
|
||||
port = resolveResult.getPort().get(0);
|
||||
} else {
|
||||
// not support SRV record
|
||||
port = 20880;
|
||||
}
|
||||
|
||||
List<ServiceInstance> instanceList = new LinkedList<>();
|
||||
|
||||
for (String host : resolveResult.getHostnameList()) {
|
||||
DefaultServiceInstance serviceInstance = new DefaultServiceInstance(serviceName, host, port);
|
||||
fillServiceInstance(serviceInstance);
|
||||
instanceList.add(serviceInstance);
|
||||
}
|
||||
|
||||
return instanceList;
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,120 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.dubbo.registry.dns.util;
|
||||
|
||||
import org.apache.dubbo.common.logger.Logger;
|
||||
import org.apache.dubbo.common.logger.LoggerFactory;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.AddressedEnvelope;
|
||||
import io.netty.channel.EventLoopGroup;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.nio.NioDatagramChannel;
|
||||
import io.netty.handler.codec.dns.DefaultDnsQuestion;
|
||||
import io.netty.handler.codec.dns.DnsRawRecord;
|
||||
import io.netty.handler.codec.dns.DnsRecordType;
|
||||
import io.netty.handler.codec.dns.DnsResponse;
|
||||
import io.netty.handler.codec.dns.DnsSection;
|
||||
import io.netty.resolver.ResolvedAddressTypes;
|
||||
import io.netty.resolver.dns.DnsNameResolver;
|
||||
import io.netty.resolver.dns.DnsNameResolverBuilder;
|
||||
import io.netty.util.concurrent.Future;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static io.netty.resolver.dns.DnsServerAddresses.sequential;
|
||||
|
||||
public class DNSResolver {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(getClass());
|
||||
|
||||
private final DnsNameResolver resolver;
|
||||
|
||||
private static final EventLoopGroup GROUP = new NioEventLoopGroup(1);
|
||||
|
||||
public DNSResolver(String nameserver, int port, int maxQueriesPerResolve) {
|
||||
this.resolver = newResolver(nameserver, port, maxQueriesPerResolve);
|
||||
}
|
||||
|
||||
public ResolveResult resolve(String path) {
|
||||
ResolveResult recordList = new ResolveResult();
|
||||
|
||||
try {
|
||||
Future<List<InetAddress>> hostFuture = resolver.resolveAll(path);
|
||||
Future<AddressedEnvelope<DnsResponse, InetSocketAddress>> srvFuture =
|
||||
resolver.query(new DefaultDnsQuestion(path, DnsRecordType.SRV));
|
||||
|
||||
try {
|
||||
recordList.getHostnameList()
|
||||
.addAll(hostFuture
|
||||
.sync().getNow()
|
||||
.stream()
|
||||
.map(InetAddress::getHostAddress)
|
||||
.collect(Collectors.toList()));
|
||||
|
||||
DnsResponse srvResponse = srvFuture.sync().getNow().content();
|
||||
for (int i = 0; i < srvResponse.count(DnsSection.ANSWER); i++) {
|
||||
DnsRawRecord record = srvResponse.recordAt(DnsSection.ANSWER, i);
|
||||
ByteBuf buf = record.content();
|
||||
// Priority
|
||||
buf.readUnsignedShort();
|
||||
// Weight
|
||||
buf.readUnsignedShort();
|
||||
// Port
|
||||
int port = buf.readUnsignedShort();
|
||||
recordList.getPort().add(port);
|
||||
}
|
||||
|
||||
} catch (InterruptedException e) {
|
||||
logger.warn("Waiting DNS resolve interrupted. " + e.getLocalizedMessage());
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
if (t instanceof UnknownHostException) {
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info(t.getLocalizedMessage());
|
||||
}
|
||||
} else {
|
||||
logger.error(t.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return recordList;
|
||||
}
|
||||
|
||||
public void destroy() {
|
||||
resolver.close();
|
||||
}
|
||||
|
||||
private static DnsNameResolver newResolver(String nameserver, int port, int maxQueriesPerResolve) {
|
||||
return new DnsNameResolverBuilder(GROUP.next())
|
||||
.channelType(NioDatagramChannel.class)
|
||||
.maxQueriesPerResolve(maxQueriesPerResolve)
|
||||
.decodeIdn(true)
|
||||
.optResourceEnabled(false)
|
||||
.ndots(1)
|
||||
.resolvedAddressTypes(ResolvedAddressTypes.IPV4_PREFERRED)
|
||||
// ignore cache
|
||||
.ttl(0, 1)
|
||||
.nameServerProvider((hostname) -> sequential(new InetSocketAddress(nameserver, port)).stream())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.dubbo.registry.dns.util;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public class ResolveResult {
|
||||
|
||||
private List<String> hostnameList = new LinkedList<>();
|
||||
|
||||
private List<Integer> port = new LinkedList<>();
|
||||
|
||||
public List<String> getHostnameList() {
|
||||
return hostnameList;
|
||||
}
|
||||
|
||||
public void setHostnameList(List<String> hostnameList) {
|
||||
this.hostnameList = hostnameList;
|
||||
}
|
||||
|
||||
public List<Integer> getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
public void setPort(List<Integer> port) {
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
ResolveResult that = (ResolveResult) o;
|
||||
return Objects.equals(hostnameList, that.hostnameList) &&
|
||||
Objects.equals(port, that.port);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(hostnameList, port);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ResolveResult{" +
|
||||
"hostnameList=" + hostnameList +
|
||||
", port=" + port +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
dns=org.apache.dubbo.registry.dns.DNSRegistryFactory
|
||||
|
|
@ -0,0 +1 @@
|
|||
dns=org.apache.dubbo.registry.dns.DNSServiceDiscovery
|
||||
|
|
@ -0,0 +1 @@
|
|||
dns=org.apache.dubbo.registry.dns.DNSServiceDiscoveryFactory
|
||||
|
|
@ -0,0 +1,191 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.dubbo.registry.dns;
|
||||
|
||||
import org.apache.dubbo.common.URL;
|
||||
import org.apache.dubbo.common.utils.NetUtils;
|
||||
import org.apache.dubbo.config.ApplicationConfig;
|
||||
import org.apache.dubbo.config.ArgumentConfig;
|
||||
import org.apache.dubbo.config.MethodConfig;
|
||||
import org.apache.dubbo.config.ProtocolConfig;
|
||||
import org.apache.dubbo.config.RegistryConfig;
|
||||
import org.apache.dubbo.config.ServiceConfig;
|
||||
import org.apache.dubbo.metadata.InstanceMetadataChangedListener;
|
||||
import org.apache.dubbo.metadata.MetadataService;
|
||||
import org.apache.dubbo.metadata.WritableMetadataService;
|
||||
import org.apache.dubbo.registry.Constants;
|
||||
import org.apache.dubbo.registry.client.DefaultServiceInstance;
|
||||
import org.apache.dubbo.registry.client.ServiceDiscovery;
|
||||
import org.apache.dubbo.registry.client.ServiceInstance;
|
||||
import org.apache.dubbo.registry.client.event.ServiceInstancesChangedEvent;
|
||||
import org.apache.dubbo.registry.client.event.listener.ServiceInstancesChangedListener;
|
||||
import org.apache.dubbo.registry.dns.util.DNSClientConst;
|
||||
import org.apache.dubbo.registry.dns.util.DNSResolver;
|
||||
import org.apache.dubbo.registry.dns.util.ResolveResult;
|
||||
import org.apache.dubbo.rpc.model.ApplicationModel;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.apache.dubbo.common.constants.CommonConstants.DUBBO_PROTOCOL;
|
||||
import static org.apache.dubbo.metadata.MetadataConstants.METADATA_PROXY_TIMEOUT_KEY;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class DNSServiceDiscoveryTest {
|
||||
|
||||
@BeforeEach
|
||||
public void setup() {
|
||||
ApplicationModel.reset();
|
||||
ApplicationConfig applicationConfig = new ApplicationConfig("Test");
|
||||
ApplicationModel.getConfigManager().setApplication(applicationConfig);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void destroy() {
|
||||
ApplicationModel.reset();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testProvider() throws Exception {
|
||||
ServiceDiscovery dnsServiceDiscovery = new DNSServiceDiscovery();
|
||||
|
||||
URL registryURL = URL.valueOf("dns://");
|
||||
dnsServiceDiscovery.initialize(registryURL);
|
||||
|
||||
assertEquals(registryURL, dnsServiceDiscovery.getUrl());
|
||||
|
||||
ServiceInstance serviceInstance = new DefaultServiceInstance("TestService", "localhost", 12345);
|
||||
serviceInstance.getMetadata().put("a", "b");
|
||||
|
||||
dnsServiceDiscovery.register(serviceInstance);
|
||||
|
||||
WritableMetadataService metadataService = WritableMetadataService.getDefaultExtension();
|
||||
InstanceMetadataChangedListener changeListener = Mockito.mock(InstanceMetadataChangedListener.class);
|
||||
|
||||
String metadataString = metadataService
|
||||
.getAndListenInstanceMetadata("test", changeListener);
|
||||
|
||||
assertEquals(JSONObject.toJSONString(serviceInstance.getMetadata()), metadataString);
|
||||
assertEquals(serviceInstance, dnsServiceDiscovery.getLocalInstance());
|
||||
|
||||
dnsServiceDiscovery.unregister(serviceInstance);
|
||||
|
||||
Mockito.verify(changeListener, Mockito.times(1)).onEvent(Mockito.any());
|
||||
|
||||
metadataService.getInstanceMetadataChangedListenerMap().clear();
|
||||
metadataService.exportInstanceMetadata(null);
|
||||
|
||||
dnsServiceDiscovery.destroy();
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConsumer() throws Exception {
|
||||
DNSServiceDiscovery dnsServiceDiscovery = new DNSServiceDiscovery();
|
||||
|
||||
URL registryURL = URL.valueOf("dns://")
|
||||
.addParameter(DNSClientConst.DNS_POLLING_CYCLE, 100)
|
||||
.addParameter(Constants.ECHO_POLLING_CYCLE_KEY, 100);
|
||||
ApplicationModel.getEnvironment().getAppExternalConfigurationMap()
|
||||
.put(METADATA_PROXY_TIMEOUT_KEY, String.valueOf(500));
|
||||
dnsServiceDiscovery.initialize(registryURL);
|
||||
|
||||
WritableMetadataService metadataService = WritableMetadataService.getDefaultExtension();
|
||||
ServiceInstance serviceInstance = new DefaultServiceInstance("TestService", "localhost", 12345);
|
||||
serviceInstance.getMetadata().put("a", "b");
|
||||
|
||||
dnsServiceDiscovery.register(serviceInstance);
|
||||
|
||||
int port = NetUtils.getAvailablePort();
|
||||
ApplicationModel.getApplicationConfig().setMetadataServicePort(port);
|
||||
|
||||
WritableMetadataService spiedMetadataService = Mockito.spy(metadataService);
|
||||
|
||||
ServiceConfig<MetadataService> serviceConfig = exportMockMetadataService(spiedMetadataService, port);
|
||||
|
||||
DNSResolver dnsResolver = Mockito.mock(DNSResolver.class);
|
||||
ResolveResult resolveResult = new ResolveResult();
|
||||
resolveResult.getHostnameList().add("127.0.0.1");
|
||||
Mockito.when(dnsResolver.resolve("Test.Service.")).thenReturn(resolveResult);
|
||||
dnsServiceDiscovery.setDnsResolver(dnsResolver);
|
||||
|
||||
List<ServiceInstance> serviceInstances = dnsServiceDiscovery.getInstances("Test.Service.");
|
||||
assertEquals("b", serviceInstances.get(0).getMetadata("a"));
|
||||
|
||||
Set<String> serviceNames = new HashSet<>();
|
||||
serviceNames.add("Test.Service.");
|
||||
ServiceInstancesChangedListener changedListener = Mockito.spy(new ServiceInstancesChangedListener(serviceNames, null));
|
||||
Mockito.doNothing().when(changedListener).onEvent(Mockito.any());
|
||||
|
||||
serviceInstance.getMetadata().put("a", "c");
|
||||
dnsServiceDiscovery.update(serviceInstance);
|
||||
|
||||
serviceInstances = dnsServiceDiscovery.getInstances("Test.Service.");
|
||||
assertEquals("c", serviceInstances.get(0).getMetadata("a"));
|
||||
|
||||
dnsServiceDiscovery.addServiceInstancesChangedListener(changedListener);
|
||||
ArgumentCaptor<ServiceInstancesChangedEvent> argument = ArgumentCaptor.forClass(ServiceInstancesChangedEvent.class);
|
||||
Mockito.verify(changedListener, Mockito.timeout(1000)).onEvent(argument.capture());
|
||||
assertEquals("c", argument.getValue().getServiceInstances().get(0).getMetadata("a"));
|
||||
|
||||
Mockito.when(dnsResolver.resolve("Test.Service.")).thenReturn(new ResolveResult());
|
||||
|
||||
Thread.sleep(1000);
|
||||
assertTrue(dnsServiceDiscovery.getCachedServiceInstances().get("Test.Service.").isEmpty());
|
||||
|
||||
metadataService.exportInstanceMetadata(null);
|
||||
metadataService.getInstanceMetadataChangedListenerMap().clear();
|
||||
serviceConfig.unexport();
|
||||
|
||||
dnsServiceDiscovery.destroy();
|
||||
ApplicationModel.getEnvironment().getAppExternalConfigurationMap()
|
||||
.remove(METADATA_PROXY_TIMEOUT_KEY, String.valueOf(100));
|
||||
}
|
||||
|
||||
private ServiceConfig<MetadataService> exportMockMetadataService(MetadataService metadataService, int port) {
|
||||
ServiceConfig<MetadataService> serviceConfig = new ServiceConfig<>();
|
||||
serviceConfig.setProtocol(new ProtocolConfig(DUBBO_PROTOCOL, port));
|
||||
serviceConfig.setRegistry(new RegistryConfig("239.255.255.255", "multicast"));
|
||||
serviceConfig.setInterface(MetadataService.class);
|
||||
serviceConfig.setRef(metadataService);
|
||||
serviceConfig.setGroup("Test.Service.");
|
||||
serviceConfig.setVersion(MetadataService.VERSION);
|
||||
MethodConfig methodConfig = new MethodConfig();
|
||||
methodConfig.setName("getAndListenInstanceMetadata");
|
||||
|
||||
ArgumentConfig argumentConfig = new ArgumentConfig();
|
||||
argumentConfig.setIndex(1);
|
||||
argumentConfig.setCallback(true);
|
||||
|
||||
methodConfig.setArguments(Collections.singletonList(argumentConfig));
|
||||
serviceConfig.setMethods(Collections.singletonList(methodConfig));
|
||||
|
||||
serviceConfig.export();
|
||||
|
||||
return serviceConfig;
|
||||
}
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
~ contributor license agreements. See the NOTICE file distributed with
|
||||
~ this work for additional information regarding copyright ownership.
|
||||
~ The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
~ (the "License"); you may not use this file except in compliance with
|
||||
~ the License. You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.apache.dubbo</groupId>
|
||||
<artifactId>dubbo-registry</artifactId>
|
||||
<version>${revision}</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<artifactId>dubbo-registry-kubernetes</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<name>${project.artifactId}</name>
|
||||
<description>The Kubernetes registry module of Dubbo project</description>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.apache.dubbo</groupId>
|
||||
<artifactId>dubbo-registry-api</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.dubbo</groupId>
|
||||
<artifactId>dubbo-common</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.fabric8</groupId>
|
||||
<artifactId>kubernetes-client</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.fabric8</groupId>
|
||||
<artifactId>kubernetes-server-mock</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<version>3.4.6</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-junit-jupiter</artifactId>
|
||||
<version>3.4.6</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.dubbo.registry.kubernetes;
|
||||
|
||||
import org.apache.dubbo.common.URL;
|
||||
import org.apache.dubbo.registry.NotifyListener;
|
||||
import org.apache.dubbo.registry.support.FailbackRegistry;
|
||||
|
||||
/**
|
||||
* Empty implements for Kubernetes <br/>
|
||||
* Kubernetes only support `Service Discovery` mode register <br/>
|
||||
* Used to compat past version like 2.6.x, 2.7.x with interface level register <br/>
|
||||
* {@link KubernetesServiceDiscovery} is the real implementation of Kubernetes
|
||||
*/
|
||||
public class KubernetesRegistry extends FailbackRegistry {
|
||||
public KubernetesRegistry(URL url) {
|
||||
super(url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAvailable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doRegister(URL url) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doUnregister(URL url) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doSubscribe(URL url, NotifyListener listener) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doUnsubscribe(URL url, NotifyListener listener) {
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,404 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.dubbo.registry.kubernetes;
|
||||
|
||||
import org.apache.dubbo.common.URL;
|
||||
import org.apache.dubbo.common.logger.Logger;
|
||||
import org.apache.dubbo.common.logger.LoggerFactory;
|
||||
import org.apache.dubbo.common.utils.StringUtils;
|
||||
import org.apache.dubbo.registry.client.DefaultServiceInstance;
|
||||
import org.apache.dubbo.registry.client.ServiceDiscovery;
|
||||
import org.apache.dubbo.registry.client.ServiceInstance;
|
||||
import org.apache.dubbo.registry.client.event.ServiceInstancesChangedEvent;
|
||||
import org.apache.dubbo.registry.client.event.listener.ServiceInstancesChangedListener;
|
||||
import org.apache.dubbo.registry.kubernetes.util.KubernetesClientConst;
|
||||
import org.apache.dubbo.registry.kubernetes.util.KubernetesConfigUtils;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import io.fabric8.kubernetes.api.model.EndpointAddress;
|
||||
import io.fabric8.kubernetes.api.model.EndpointPort;
|
||||
import io.fabric8.kubernetes.api.model.EndpointSubset;
|
||||
import io.fabric8.kubernetes.api.model.Endpoints;
|
||||
import io.fabric8.kubernetes.api.model.Pod;
|
||||
import io.fabric8.kubernetes.api.model.Service;
|
||||
import io.fabric8.kubernetes.client.Config;
|
||||
import io.fabric8.kubernetes.client.DefaultKubernetesClient;
|
||||
import io.fabric8.kubernetes.client.KubernetesClient;
|
||||
import io.fabric8.kubernetes.client.KubernetesClientException;
|
||||
import io.fabric8.kubernetes.client.Watch;
|
||||
import io.fabric8.kubernetes.client.Watcher;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class KubernetesServiceDiscovery implements ServiceDiscovery {
|
||||
private final Logger logger = LoggerFactory.getLogger(getClass());
|
||||
|
||||
private KubernetesClient kubernetesClient;
|
||||
|
||||
private String currentHostname;
|
||||
|
||||
private ServiceInstance localServiceInstance;
|
||||
|
||||
private URL registryURL;
|
||||
|
||||
private String namespace;
|
||||
|
||||
private boolean enableRegister;
|
||||
|
||||
public final static String KUBERNETES_PROPERTIES_KEY = "io.dubbo/metadata";
|
||||
|
||||
private final static ConcurrentHashMap<String, Watch> SERVICE_WATCHER = new ConcurrentHashMap<>(64);
|
||||
|
||||
private final static ConcurrentHashMap<String, Watch> PODS_WATCHER = new ConcurrentHashMap<>(64);
|
||||
|
||||
private final static ConcurrentHashMap<String, Watch> ENDPOINTS_WATCHER = new ConcurrentHashMap<>(64);
|
||||
|
||||
private final static ConcurrentHashMap<String, AtomicLong> SERVICE_UPDATE_TIME = new ConcurrentHashMap<>(64);
|
||||
|
||||
@Override
|
||||
public void initialize(URL registryURL) throws Exception {
|
||||
Config config = KubernetesConfigUtils.createKubernetesConfig(registryURL);
|
||||
this.kubernetesClient = new DefaultKubernetesClient(config);
|
||||
this.currentHostname = System.getenv("HOSTNAME");
|
||||
this.registryURL = registryURL;
|
||||
this.namespace = config.getNamespace();
|
||||
this.enableRegister = registryURL.getParameter(KubernetesClientConst.ENABLE_REGISTER, true);
|
||||
|
||||
boolean availableAccess;
|
||||
try {
|
||||
availableAccess = kubernetesClient.pods().withName(currentHostname).get() != null;
|
||||
} catch (Throwable e) {
|
||||
availableAccess = false;
|
||||
}
|
||||
if (!availableAccess) {
|
||||
String message = "Unable to access api server. " +
|
||||
"Please check your url config." +
|
||||
" Master URL: " + config.getMasterUrl() +
|
||||
" Hostname: " + currentHostname;
|
||||
logger.error(message);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() throws Exception {
|
||||
SERVICE_WATCHER.forEach((k, v) -> v.close());
|
||||
SERVICE_WATCHER.clear();
|
||||
|
||||
PODS_WATCHER.forEach((k, v) -> v.close());
|
||||
PODS_WATCHER.clear();
|
||||
|
||||
ENDPOINTS_WATCHER.forEach((k, v) -> v.close());
|
||||
ENDPOINTS_WATCHER.clear();
|
||||
|
||||
kubernetesClient.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void register(ServiceInstance serviceInstance) throws RuntimeException {
|
||||
localServiceInstance = serviceInstance;
|
||||
|
||||
if (enableRegister) {
|
||||
kubernetesClient
|
||||
.pods()
|
||||
.inNamespace(namespace)
|
||||
.withName(currentHostname)
|
||||
.edit()
|
||||
.editOrNewMetadata()
|
||||
.addToAnnotations(KUBERNETES_PROPERTIES_KEY, JSONObject.toJSONString(serviceInstance.getMetadata()))
|
||||
.endMetadata()
|
||||
.done();
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Write Current Service Instance Metadata to Kubernetes pod. " +
|
||||
"Current pod name: " + currentHostname);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(ServiceInstance serviceInstance) throws RuntimeException {
|
||||
register(serviceInstance);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregister(ServiceInstance serviceInstance) throws RuntimeException {
|
||||
localServiceInstance = null;
|
||||
|
||||
if (enableRegister) {
|
||||
kubernetesClient
|
||||
.pods()
|
||||
.inNamespace(namespace)
|
||||
.withName(currentHostname)
|
||||
.edit()
|
||||
.editOrNewMetadata()
|
||||
.removeFromAnnotations(KUBERNETES_PROPERTIES_KEY)
|
||||
.endMetadata()
|
||||
.done();
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Remove Current Service Instance from Kubernetes pod. Current pod name: " + currentHostname);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getServices() {
|
||||
return kubernetesClient
|
||||
.services()
|
||||
.inNamespace(namespace)
|
||||
.list()
|
||||
.getItems()
|
||||
.stream()
|
||||
.map(service -> service.getMetadata().getName())
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServiceInstance getLocalInstance() {
|
||||
return localServiceInstance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<ServiceInstance> getInstances(String serviceName) throws NullPointerException {
|
||||
Endpoints endpoints =
|
||||
kubernetesClient
|
||||
.endpoints()
|
||||
.inNamespace(namespace)
|
||||
.withName(serviceName)
|
||||
.get();
|
||||
|
||||
return toServiceInstance(endpoints, serviceName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addServiceInstancesChangedListener(ServiceInstancesChangedListener listener) throws NullPointerException, IllegalArgumentException {
|
||||
listener.getServiceNames().forEach(serviceName -> {
|
||||
SERVICE_UPDATE_TIME.put(serviceName, new AtomicLong(0L));
|
||||
|
||||
// Watch Service Endpoint Modification
|
||||
watchEndpoints(listener, serviceName);
|
||||
|
||||
// Watch Pods Modification, happens when ServiceInstance updated
|
||||
watchPods(listener, serviceName);
|
||||
|
||||
// Watch Service Modification, happens when Service Selector updated, used to update pods watcher
|
||||
watchService(listener, serviceName);
|
||||
});
|
||||
}
|
||||
|
||||
private void watchEndpoints(ServiceInstancesChangedListener listener, String serviceName) {
|
||||
Watch watch = kubernetesClient
|
||||
.endpoints()
|
||||
.inNamespace(namespace)
|
||||
.withName(serviceName)
|
||||
.watch(new Watcher<Endpoints>() {
|
||||
@Override
|
||||
public void eventReceived(Action action, Endpoints resource) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Received Endpoint Event. Event type: " + action.name() +
|
||||
". Current pod name: " + currentHostname);
|
||||
}
|
||||
|
||||
notifyServiceChanged(serviceName, listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClose(KubernetesClientException cause) {
|
||||
// ignore
|
||||
}
|
||||
});
|
||||
|
||||
ENDPOINTS_WATCHER.put(serviceName, watch);
|
||||
}
|
||||
|
||||
private void watchPods(ServiceInstancesChangedListener listener, String serviceName) {
|
||||
Map<String, String> serviceSelector = getServiceSelector(serviceName);
|
||||
if (serviceSelector == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Watch watch = kubernetesClient
|
||||
.pods()
|
||||
.inNamespace(namespace)
|
||||
.withLabels(serviceSelector)
|
||||
.watch(new Watcher<Pod>() {
|
||||
@Override
|
||||
public void eventReceived(Action action, Pod resource) {
|
||||
if (Action.MODIFIED.equals(action)) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Received Pods Update Event. Current pod name: " + currentHostname);
|
||||
}
|
||||
|
||||
notifyServiceChanged(serviceName, listener);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClose(KubernetesClientException cause) {
|
||||
// ignore
|
||||
}
|
||||
});
|
||||
|
||||
PODS_WATCHER.put(serviceName, watch);
|
||||
}
|
||||
|
||||
private void watchService(ServiceInstancesChangedListener listener, String serviceName) {
|
||||
Watch watch = kubernetesClient
|
||||
.services()
|
||||
.inNamespace(namespace)
|
||||
.withName(serviceName)
|
||||
.watch(new Watcher<Service>() {
|
||||
@Override
|
||||
public void eventReceived(Action action, Service resource) {
|
||||
if (Action.MODIFIED.equals(action)) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Received Service Update Event. Update Pods Watcher. " +
|
||||
"Current pod name: " + currentHostname);
|
||||
}
|
||||
|
||||
if (PODS_WATCHER.containsKey(serviceName)) {
|
||||
PODS_WATCHER.get(serviceName).close();
|
||||
PODS_WATCHER.remove(serviceName);
|
||||
}
|
||||
watchPods(listener, serviceName);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClose(KubernetesClientException cause) {
|
||||
// ignore
|
||||
}
|
||||
});
|
||||
|
||||
SERVICE_WATCHER.put(serviceName, watch);
|
||||
}
|
||||
|
||||
private void notifyServiceChanged(String serviceName, ServiceInstancesChangedListener listener) {
|
||||
long receivedTime = System.nanoTime();
|
||||
|
||||
ServiceInstancesChangedEvent event;
|
||||
|
||||
event = new ServiceInstancesChangedEvent(serviceName, getInstances(serviceName));
|
||||
|
||||
AtomicLong updateTime = SERVICE_UPDATE_TIME.get(serviceName);
|
||||
long lastUpdateTime = updateTime.get();
|
||||
|
||||
if (lastUpdateTime <= receivedTime) {
|
||||
if (updateTime.compareAndSet(lastUpdateTime, receivedTime)) {
|
||||
listener.onEvent(event);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Discard Service Instance Data. " +
|
||||
"Possible Cause: Newer message has been processed or Failed to update time record by CAS. " +
|
||||
"Current Data received time: " + receivedTime + ". " +
|
||||
"Newer Data received time: " + lastUpdateTime + ".");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getUrl() {
|
||||
return registryURL;
|
||||
}
|
||||
|
||||
private Map<String, String> getServiceSelector(String serviceName) {
|
||||
Service service = kubernetesClient.services().inNamespace(namespace).withName(serviceName).get();
|
||||
if (service == null) {
|
||||
return null;
|
||||
}
|
||||
return service.getSpec().getSelector();
|
||||
}
|
||||
|
||||
private List<ServiceInstance> toServiceInstance(Endpoints endpoints, String serviceName) {
|
||||
Map<String, String> serviceSelector = getServiceSelector(serviceName);
|
||||
if (serviceSelector == null) {
|
||||
return new LinkedList<>();
|
||||
}
|
||||
Map<String, Pod> pods = kubernetesClient
|
||||
.pods()
|
||||
.inNamespace(namespace)
|
||||
.withLabels(serviceSelector)
|
||||
.list()
|
||||
.getItems()
|
||||
.stream()
|
||||
.collect(
|
||||
Collectors.toMap(
|
||||
pod -> pod.getMetadata().getName(),
|
||||
pod -> pod));
|
||||
|
||||
List<ServiceInstance> instances = new LinkedList<>();
|
||||
Set<Integer> instancePorts = new HashSet<>();
|
||||
|
||||
for (EndpointSubset endpointSubset : endpoints.getSubsets()) {
|
||||
instancePorts.addAll(
|
||||
endpointSubset.getPorts()
|
||||
.stream().map(EndpointPort::getPort)
|
||||
.collect(Collectors.toSet()));
|
||||
}
|
||||
|
||||
for (EndpointSubset endpointSubset : endpoints.getSubsets()) {
|
||||
for (EndpointAddress address : endpointSubset.getAddresses()) {
|
||||
Pod pod = pods.get(address.getTargetRef().getName());
|
||||
String ip = address.getIp();
|
||||
if (pod == null) {
|
||||
logger.warn("Unable to match Kubernetes Endpoint address with Pod. " +
|
||||
"EndpointAddress Hostname: " + address.getTargetRef().getName());
|
||||
continue;
|
||||
}
|
||||
|
||||
instancePorts.forEach(port -> {
|
||||
ServiceInstance serviceInstance = new DefaultServiceInstance(serviceName, ip, port);
|
||||
|
||||
String properties = pod.getMetadata().getAnnotations().get(KUBERNETES_PROPERTIES_KEY);
|
||||
if (StringUtils.isNotEmpty(properties)) {
|
||||
serviceInstance.getMetadata().putAll(JSONObject.parseObject(properties, Map.class));
|
||||
instances.add(serviceInstance);
|
||||
} else {
|
||||
logger.warn("Unable to find Service Instance metadata in Pod Annotations. " +
|
||||
"Possibly cause: provider has not been initialized successfully. " +
|
||||
"EndpointAddress Hostname: " + address.getTargetRef().getName());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return instances;
|
||||
}
|
||||
|
||||
/**
|
||||
* UT used only
|
||||
*/
|
||||
@Deprecated
|
||||
public void setCurrentHostname(String currentHostname) {
|
||||
this.currentHostname = currentHostname;
|
||||
}
|
||||
|
||||
/**
|
||||
* UT used only
|
||||
*/
|
||||
@Deprecated
|
||||
public void setKubernetesClient(KubernetesClient kubernetesClient) {
|
||||
this.kubernetesClient = kubernetesClient;
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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";
|
||||
}
|
||||
|
|
@ -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)) :
|
||||
"";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
kubernetes=org.apache.dubbo.registry.kubernetes.KubernetesRegistryFactory
|
||||
|
|
@ -0,0 +1 @@
|
|||
kubernetes=org.apache.dubbo.registry.kubernetes.KubernetesServiceDiscovery
|
||||
|
|
@ -0,0 +1 @@
|
|||
kubernetes=org.apache.dubbo.registry.kubernetes.KubernetesServiceDiscoveryFactory
|
||||
|
|
@ -0,0 +1,191 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.dubbo.registry.kubernetes;
|
||||
|
||||
import org.apache.dubbo.common.URL;
|
||||
import org.apache.dubbo.registry.client.DefaultServiceInstance;
|
||||
import org.apache.dubbo.registry.client.ServiceInstance;
|
||||
import org.apache.dubbo.registry.client.event.ServiceInstancesChangedEvent;
|
||||
import org.apache.dubbo.registry.client.event.listener.ServiceInstancesChangedListener;
|
||||
import org.apache.dubbo.registry.kubernetes.util.KubernetesClientConst;
|
||||
|
||||
import io.fabric8.kubernetes.api.model.Endpoints;
|
||||
import io.fabric8.kubernetes.api.model.EndpointsBuilder;
|
||||
import io.fabric8.kubernetes.api.model.Pod;
|
||||
import io.fabric8.kubernetes.api.model.PodBuilder;
|
||||
import io.fabric8.kubernetes.api.model.Service;
|
||||
import io.fabric8.kubernetes.api.model.ServiceBuilder;
|
||||
import io.fabric8.kubernetes.client.Config;
|
||||
import io.fabric8.kubernetes.client.KubernetesClient;
|
||||
import io.fabric8.kubernetes.client.server.mock.KubernetesServer;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mockito;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
|
||||
@ExtendWith({MockitoExtension.class})
|
||||
public class KubernetesServiceDiscoveryTest {
|
||||
public KubernetesServer mockServer = new KubernetesServer(true, true);
|
||||
|
||||
private KubernetesClient mockClient;
|
||||
|
||||
private ServiceInstancesChangedListener mockListener = Mockito.mock(ServiceInstancesChangedListener.class);
|
||||
|
||||
private URL serverUrl;
|
||||
|
||||
private Map<String, String> selector;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
mockServer.before();
|
||||
mockClient = mockServer.getClient();
|
||||
|
||||
serverUrl = URL.valueOf(mockClient.getConfiguration().getMasterUrl())
|
||||
.setProtocol("kubernetes")
|
||||
.addParameter(KubernetesClientConst.TRUST_CERTS, "true")
|
||||
.addParameter(KubernetesClientConst.HTTP2_DISABLE, "true");
|
||||
|
||||
System.setProperty(Config.KUBERNETES_AUTH_TRYKUBECONFIG_SYSTEM_PROPERTY, "false");
|
||||
System.setProperty(Config.KUBERNETES_AUTH_TRYSERVICEACCOUNT_SYSTEM_PROPERTY, "false");
|
||||
|
||||
selector = new HashMap<>(4);
|
||||
selector.put("l", "v");
|
||||
Pod pod = new PodBuilder()
|
||||
.withNewMetadata().withName("TestServer").withLabels(selector).endMetadata()
|
||||
.build();
|
||||
|
||||
Service service = new ServiceBuilder()
|
||||
.withNewMetadata().withName("TestService").endMetadata()
|
||||
.withNewSpec().withSelector(selector).endSpec().build();
|
||||
|
||||
Endpoints endPoints = new EndpointsBuilder()
|
||||
.withNewMetadata().withName("TestService").endMetadata()
|
||||
.addNewSubset()
|
||||
.addNewAddress().withIp("ip1")
|
||||
.withNewTargetRef().withUid("uid1").withName("TestServer").endTargetRef().endAddress()
|
||||
.addNewPort("Test", "Test", 12345, "TCP").endSubset()
|
||||
.build();
|
||||
|
||||
mockClient.pods().create(pod);
|
||||
mockClient.services().create(service);
|
||||
mockClient.endpoints().create(endPoints);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void destroy() {
|
||||
mockServer.after();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEndpointsUpdate() throws Exception {
|
||||
|
||||
KubernetesServiceDiscovery serviceDiscovery = new KubernetesServiceDiscovery();
|
||||
serviceDiscovery.initialize(serverUrl);
|
||||
|
||||
serviceDiscovery.setCurrentHostname("TestServer");
|
||||
serviceDiscovery.setKubernetesClient(mockClient);
|
||||
|
||||
ServiceInstance serviceInstance = new DefaultServiceInstance("TestService", "Test", 12345);
|
||||
serviceDiscovery.register(serviceInstance);
|
||||
|
||||
HashSet<String> serviceList = new HashSet<>(4);
|
||||
serviceList.add("TestService");
|
||||
Mockito.when(mockListener.getServiceNames()).thenReturn(serviceList);
|
||||
Mockito.doNothing().when(mockListener).onEvent(Mockito.any());
|
||||
|
||||
serviceDiscovery.addServiceInstancesChangedListener(mockListener);
|
||||
mockClient.endpoints().withName("TestService").edit().editFirstSubset()
|
||||
.addNewAddress().withIp("ip2")
|
||||
.withNewTargetRef().withUid("uid2").withName("TestServer").endTargetRef().endAddress()
|
||||
.addNewPort("Test", "Test", 12345, "TCP").endSubset()
|
||||
.done();
|
||||
|
||||
Thread.sleep(5000);
|
||||
ArgumentCaptor<ServiceInstancesChangedEvent> eventArgumentCaptor =
|
||||
ArgumentCaptor.forClass(ServiceInstancesChangedEvent.class);
|
||||
Mockito.verify(mockListener).onEvent(eventArgumentCaptor.capture());
|
||||
Assertions.assertEquals(2, eventArgumentCaptor.getValue().getServiceInstances().size());
|
||||
|
||||
serviceDiscovery.unregister(serviceInstance);
|
||||
|
||||
serviceDiscovery.destroy();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPodsUpdate() throws Exception {
|
||||
|
||||
KubernetesServiceDiscovery serviceDiscovery = new KubernetesServiceDiscovery();
|
||||
serviceDiscovery.initialize(serverUrl);
|
||||
|
||||
serviceDiscovery.setCurrentHostname("TestServer");
|
||||
serviceDiscovery.setKubernetesClient(mockClient);
|
||||
|
||||
ServiceInstance serviceInstance = new DefaultServiceInstance("TestService", "Test", 12345);
|
||||
serviceDiscovery.register(serviceInstance);
|
||||
|
||||
HashSet<String> serviceList = new HashSet<>(4);
|
||||
serviceList.add("TestService");
|
||||
Mockito.when(mockListener.getServiceNames()).thenReturn(serviceList);
|
||||
Mockito.doNothing().when(mockListener).onEvent(Mockito.any());
|
||||
|
||||
serviceDiscovery.addServiceInstancesChangedListener(mockListener);
|
||||
|
||||
serviceInstance = new DefaultServiceInstance("TestService", "Test12345", 12345);
|
||||
serviceDiscovery.update(serviceInstance);
|
||||
|
||||
Thread.sleep(5000);
|
||||
ArgumentCaptor<ServiceInstancesChangedEvent> eventArgumentCaptor =
|
||||
ArgumentCaptor.forClass(ServiceInstancesChangedEvent.class);
|
||||
Mockito.verify(mockListener).onEvent(eventArgumentCaptor.capture());
|
||||
Assertions.assertEquals(1, eventArgumentCaptor.getValue().getServiceInstances().size());
|
||||
|
||||
serviceDiscovery.unregister(serviceInstance);
|
||||
|
||||
serviceDiscovery.destroy();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetInstance() throws Exception {
|
||||
KubernetesServiceDiscovery serviceDiscovery = new KubernetesServiceDiscovery();
|
||||
serviceDiscovery.initialize(serverUrl);
|
||||
|
||||
serviceDiscovery.setCurrentHostname("TestServer");
|
||||
serviceDiscovery.setKubernetesClient(mockClient);
|
||||
|
||||
ServiceInstance serviceInstance = new DefaultServiceInstance("TestService", "Test", 12345);
|
||||
serviceDiscovery.register(serviceInstance);
|
||||
|
||||
serviceDiscovery.update(serviceInstance);
|
||||
|
||||
Assertions.assertEquals(1, serviceDiscovery.getServices().size());
|
||||
Assertions.assertEquals(1, serviceDiscovery.getInstances("TestService").size());
|
||||
|
||||
Assertions.assertEquals(serviceInstance, serviceDiscovery.getLocalInstance());
|
||||
|
||||
serviceDiscovery.unregister(serviceInstance);
|
||||
|
||||
serviceDiscovery.destroy();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
mock-maker-inline
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.apache.dubbo.registry.multicast;
|
||||
|
||||
import org.apache.dubbo.common.URL;
|
||||
import org.apache.dubbo.registry.client.ServiceDiscovery;
|
||||
import org.apache.dubbo.registry.client.ServiceInstance;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* TODO: make multicast protocol support Service Discovery
|
||||
*/
|
||||
public class MulticastServiceDiscovery implements ServiceDiscovery {
|
||||
private URL registryURL;
|
||||
private ServiceInstance serviceInstance;
|
||||
|
||||
@Override
|
||||
public void initialize(URL registryURL) throws Exception {
|
||||
this.registryURL = registryURL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() throws Exception {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void register(ServiceInstance serviceInstance) throws RuntimeException {
|
||||
this.serviceInstance = serviceInstance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(ServiceInstance serviceInstance) throws RuntimeException {
|
||||
this.serviceInstance = serviceInstance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregister(ServiceInstance serviceInstance) throws RuntimeException {
|
||||
this.serviceInstance = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getServices() {
|
||||
return Collections.singleton("Unsupported Operation");
|
||||
}
|
||||
|
||||
@Override
|
||||
public URL getUrl() {
|
||||
return registryURL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServiceInstance getLocalInstance() {
|
||||
return serviceInstance;
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
multicast=org.apache.dubbo.registry.multicast.MulticastServiceDiscovery
|
||||
|
|
@ -0,0 +1 @@
|
|||
multicast=org.apache.dubbo.registry.multicast.MulticastServiceDiscoveryFactory
|
||||
|
|
@ -30,6 +30,7 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.apache.dubbo.common.constants.RegistryConstants.EMPTY_PROTOCOL;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
|
@ -39,7 +40,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
|
|||
public class MulticastRegistryTest {
|
||||
|
||||
private String service = "org.apache.dubbo.test.injvmServie";
|
||||
private URL registryUrl = URL.valueOf("multicast://239.255.255.255/");
|
||||
private URL registryUrl = URL.valueOf("multicast://239.239.239.239/");
|
||||
private URL serviceUrl = URL.valueOf("dubbo://" + NetUtils.getLocalHost() + "/" + service
|
||||
+ "?methods=test1,test2");
|
||||
private URL adminUrl = URL.valueOf("dubbo://" + NetUtils.getLocalHost() + "/*");
|
||||
|
|
@ -79,7 +80,7 @@ public class MulticastRegistryTest {
|
|||
@Test
|
||||
public void testGetCustomPort() {
|
||||
int port = NetUtils.getAvailablePort();
|
||||
URL customPortUrl = URL.valueOf("multicast://239.255.255.255:" + port);
|
||||
URL customPortUrl = URL.valueOf("multicast://239.239.239.239:" + port);
|
||||
MulticastRegistry multicastRegistry = new MulticastRegistry(customPortUrl);
|
||||
assertThat(multicastRegistry.getUrl().getPort(), is(port));
|
||||
}
|
||||
|
|
@ -132,15 +133,20 @@ public class MulticastRegistryTest {
|
|||
@Test
|
||||
public void testSubscribe() {
|
||||
// verify listener
|
||||
registry.subscribe(consumerUrl, new NotifyListener() {
|
||||
@Override
|
||||
public void notify(List<URL> urls) {
|
||||
assertEquals(serviceUrl.toFullString(), urls.get(0).toFullString());
|
||||
final URL[] notifyUrl = new URL[1];
|
||||
for (int i = 0; i < 10; i++) {
|
||||
registry.register(serviceUrl);
|
||||
registry.subscribe(consumerUrl, urls -> {
|
||||
notifyUrl[0] = urls.get(0);
|
||||
|
||||
Map<URL, Set<NotifyListener>> subscribed = registry.getSubscribed();
|
||||
assertEquals(consumerUrl, subscribed.keySet().iterator().next());
|
||||
});
|
||||
if (!EMPTY_PROTOCOL.equalsIgnoreCase(notifyUrl[0].getProtocol())) {
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
assertEquals(serviceUrl.toFullString(), notifyUrl[0].toFullString());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import java.util.Map;
|
|||
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.apache.dubbo.common.utils.NetUtils.getAvailablePort;
|
||||
import static org.apache.dubbo.registry.client.metadata.ServiceInstanceMetadataUtils.INSTANCE_REVISION_UPDATED_KEY;
|
||||
import static org.apache.dubbo.registry.zookeeper.util.CuratorFrameworkUtils.generateId;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
|
@ -73,7 +74,7 @@ public class ZookeeperServiceDiscoveryTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testRegistration() {
|
||||
public void testRegistration() throws InterruptedException {
|
||||
|
||||
DefaultServiceInstance serviceInstance = createServiceInstance(SERVICE_NAME, LOCALHOST, NetUtils.getAvailablePort());
|
||||
|
||||
|
|
@ -87,6 +88,7 @@ public class ZookeeperServiceDiscoveryTest {
|
|||
Map<String, String> metadata = new HashMap<>();
|
||||
metadata.put("message", "Hello,World");
|
||||
serviceInstance.setMetadata(metadata);
|
||||
serviceInstance.getExtendParams().put(INSTANCE_REVISION_UPDATED_KEY, "true");
|
||||
|
||||
discovery.update(serviceInstance);
|
||||
|
||||
|
|
|
|||
|
|
@ -35,5 +35,7 @@
|
|||
<module>dubbo-registry-zookeeper</module>
|
||||
<module>dubbo-registry-nacos</module>
|
||||
<module>dubbo-registry-multiple</module>
|
||||
<module>dubbo-registry-kubernetes</module>
|
||||
<module>dubbo-registry-dns</module>
|
||||
</modules>
|
||||
</project>
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ package org.apache.dubbo.remoting.exchange.support.header;
|
|||
import org.apache.dubbo.common.URL;
|
||||
import org.apache.dubbo.common.logger.Logger;
|
||||
import org.apache.dubbo.common.logger.LoggerFactory;
|
||||
import org.apache.dubbo.common.utils.NetUtils;
|
||||
import org.apache.dubbo.remoting.Channel;
|
||||
import org.apache.dubbo.remoting.Constants;
|
||||
import org.apache.dubbo.remoting.RemotingException;
|
||||
|
|
@ -35,6 +36,7 @@ import org.junit.jupiter.api.Assertions;
|
|||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
public class HeartbeatHandlerTest {
|
||||
|
||||
|
|
@ -61,9 +63,13 @@ public class HeartbeatHandlerTest {
|
|||
|
||||
@Test
|
||||
public void testServerHeartbeat() throws Exception {
|
||||
URL serverURL = URL.valueOf("header://localhost:55555?transporter=netty3");
|
||||
serverURL = serverURL.addParameter(Constants.HEARTBEAT_KEY, 1000);
|
||||
TestHeartbeatHandler handler = new TestHeartbeatHandler();
|
||||
URL serverURL = URL.valueOf("telnet://localhost:" + NetUtils.getAvailablePort(56780))
|
||||
.addParameter(Constants.EXCHANGER_KEY, HeaderExchanger.NAME)
|
||||
.addParameter(Constants.TRANSPORTER_KEY, "netty3")
|
||||
.addParameter(Constants.HEARTBEAT_KEY, 1000);
|
||||
CountDownLatch connect = new CountDownLatch(1);
|
||||
CountDownLatch disconnect = new CountDownLatch(1);
|
||||
TestHeartbeatHandler handler = new TestHeartbeatHandler(connect, disconnect);
|
||||
server = Exchangers.bind(serverURL, handler);
|
||||
System.out.println("Server bind successfully");
|
||||
|
||||
|
|
@ -75,21 +81,25 @@ public class HeartbeatHandlerTest {
|
|||
serverURL = serverURL.addParameter(Constants.RECONNECT_KEY, false);
|
||||
|
||||
client = Exchangers.connect(serverURL);
|
||||
Thread.sleep(10000);
|
||||
disconnect.await();
|
||||
Assertions.assertTrue(handler.disconnectCount > 0);
|
||||
System.out.println("disconnect count " + handler.disconnectCount);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHeartbeat() throws Exception {
|
||||
URL serverURL = URL.valueOf("header://localhost:55556?transporter=netty3");
|
||||
serverURL = serverURL.addParameter(Constants.HEARTBEAT_KEY, 1000);
|
||||
TestHeartbeatHandler handler = new TestHeartbeatHandler();
|
||||
URL serverURL = URL.valueOf("telnet://localhost:" + NetUtils.getAvailablePort(56785))
|
||||
.addParameter(Constants.EXCHANGER_KEY, HeaderExchanger.NAME)
|
||||
.addParameter(Constants.TRANSPORTER_KEY, "netty3")
|
||||
.addParameter(Constants.HEARTBEAT_KEY, 1000);
|
||||
CountDownLatch connect = new CountDownLatch(1);
|
||||
CountDownLatch disconnect = new CountDownLatch(1);
|
||||
TestHeartbeatHandler handler = new TestHeartbeatHandler(connect, disconnect);
|
||||
server = Exchangers.bind(serverURL, handler);
|
||||
System.out.println("Server bind successfully");
|
||||
|
||||
client = Exchangers.connect(serverURL);
|
||||
Thread.sleep(10000);
|
||||
connect.await();
|
||||
System.err.println("++++++++++++++ disconnect count " + handler.disconnectCount);
|
||||
System.err.println("++++++++++++++ connect count " + handler.connectCount);
|
||||
Assertions.assertEquals(0, handler.disconnectCount);
|
||||
|
|
@ -99,15 +109,19 @@ public class HeartbeatHandlerTest {
|
|||
@Test
|
||||
public void testClientHeartbeat() throws Exception {
|
||||
FakeChannelHandlers.setTestingChannelHandlers();
|
||||
URL serverURL = URL.valueOf("header://localhost:55557?transporter=netty3");
|
||||
TestHeartbeatHandler handler = new TestHeartbeatHandler();
|
||||
URL serverURL = URL.valueOf("telnet://localhost:" + NetUtils.getAvailablePort(56790))
|
||||
.addParameter(Constants.EXCHANGER_KEY, HeaderExchanger.NAME)
|
||||
.addParameter(Constants.TRANSPORTER_KEY, "netty3");
|
||||
CountDownLatch connect = new CountDownLatch(1);
|
||||
CountDownLatch disconnect = new CountDownLatch(1);
|
||||
TestHeartbeatHandler handler = new TestHeartbeatHandler(connect, disconnect);
|
||||
server = Exchangers.bind(serverURL, handler);
|
||||
System.out.println("Server bind successfully");
|
||||
|
||||
FakeChannelHandlers.resetChannelHandlers();
|
||||
serverURL = serverURL.addParameter(Constants.HEARTBEAT_KEY, 1000);
|
||||
client = Exchangers.connect(serverURL);
|
||||
Thread.sleep(10000);
|
||||
connect.await();
|
||||
Assertions.assertTrue(handler.connectCount > 0);
|
||||
System.out.println("connect count " + handler.connectCount);
|
||||
}
|
||||
|
|
@ -116,6 +130,13 @@ public class HeartbeatHandlerTest {
|
|||
|
||||
public int disconnectCount = 0;
|
||||
public int connectCount = 0;
|
||||
private CountDownLatch connectCountDownLatch;
|
||||
private CountDownLatch disconnectCountDownLatch;
|
||||
|
||||
public TestHeartbeatHandler(CountDownLatch connectCountDownLatch, CountDownLatch disconnectCountDownLatch) {
|
||||
this.connectCountDownLatch = connectCountDownLatch;
|
||||
this.disconnectCountDownLatch = disconnectCountDownLatch;
|
||||
}
|
||||
|
||||
public CompletableFuture<Object> reply(ExchangeChannel channel, Object request) throws RemotingException {
|
||||
return CompletableFuture.completedFuture(request);
|
||||
|
|
@ -124,11 +145,13 @@ public class HeartbeatHandlerTest {
|
|||
@Override
|
||||
public void connected(Channel channel) throws RemotingException {
|
||||
++connectCount;
|
||||
connectCountDownLatch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnected(Channel channel) throws RemotingException {
|
||||
++disconnectCount;
|
||||
disconnectCountDownLatch.countDown();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Reference in New Issue