diff --git a/api/src/main/java/com/alibaba/nacos/api/ability/constant/AbilityKey.java b/api/src/main/java/com/alibaba/nacos/api/ability/constant/AbilityKey.java index 6ce1937c4..fd3baef3e 100644 --- a/api/src/main/java/com/alibaba/nacos/api/ability/constant/AbilityKey.java +++ b/api/src/main/java/com/alibaba/nacos/api/ability/constant/AbilityKey.java @@ -18,8 +18,8 @@ package com.alibaba.nacos.api.ability.constant; import java.util.Collection; import java.util.Collections; -import java.util.Map; import java.util.HashMap; +import java.util.Map; import java.util.stream.Collectors; /** @@ -30,6 +30,12 @@ import java.util.stream.Collectors; * @date 2022/8/31 12:27 **/ public enum AbilityKey { + + /** + * Server support register or deregister persistent instance by grpc. + */ + SERVER_SUPPORT_PERSISTENT_INSTANCE_BY_GRPC("supportPersistentInstanceByGrpc", + "support persistent instance by grpc", AbilityMode.SERVER), /** * For Test temporarily. diff --git a/api/src/main/java/com/alibaba/nacos/api/ability/register/impl/ServerAbilities.java b/api/src/main/java/com/alibaba/nacos/api/ability/register/impl/ServerAbilities.java index 2fa8f9693..14922877f 100644 --- a/api/src/main/java/com/alibaba/nacos/api/ability/register/impl/ServerAbilities.java +++ b/api/src/main/java/com/alibaba/nacos/api/ability/register/impl/ServerAbilities.java @@ -45,6 +45,7 @@ public class ServerAbilities extends AbstractAbilityRegistry { * */ // put ability here, which you want current server supports + supportedAbilities.put(AbilityKey.SERVER_SUPPORT_PERSISTENT_INSTANCE_BY_GRPC, true); } /**. diff --git a/api/src/main/java/com/alibaba/nacos/api/naming/remote/request/PersistentInstanceRequest.java b/api/src/main/java/com/alibaba/nacos/api/naming/remote/request/PersistentInstanceRequest.java new file mode 100644 index 000000000..444c87efc --- /dev/null +++ b/api/src/main/java/com/alibaba/nacos/api/naming/remote/request/PersistentInstanceRequest.java @@ -0,0 +1,56 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.alibaba.nacos.api.naming.remote.request; + +import com.alibaba.nacos.api.naming.pojo.Instance; + +/** + * Nacos persistent instances request. + * + * @author blake.qiu + */ +public class PersistentInstanceRequest extends AbstractNamingRequest { + + private String type; + + private Instance instance; + + public PersistentInstanceRequest() { + } + + public PersistentInstanceRequest(String namespace, String serviceName, String groupName, String type, Instance instance) { + super(namespace, serviceName, groupName); + this.type = type; + this.instance = instance; + } + + public String getType() { + return this.type; + } + + public void setType(String type) { + this.type = type; + } + + public Instance getInstance() { + return instance; + } + + public void setInstance(Instance instance) { + this.instance = instance; + } +} diff --git a/api/src/main/java/com/alibaba/nacos/api/naming/utils/NamingUtils.java b/api/src/main/java/com/alibaba/nacos/api/naming/utils/NamingUtils.java index 7c88abc70..3f3e280f0 100644 --- a/api/src/main/java/com/alibaba/nacos/api/naming/utils/NamingUtils.java +++ b/api/src/main/java/com/alibaba/nacos/api/naming/utils/NamingUtils.java @@ -138,6 +138,10 @@ public class NamingUtils { * @throws NacosException if check failed, throw exception */ public static void checkInstanceIsLegal(Instance instance) throws NacosException { + if (null == instance) { + throw new NacosApiException(NacosException.INVALID_PARAM, ErrorCode.INSTANCE_ERROR, + "Instance can not be null."); + } if (instance.getInstanceHeartBeatTimeOut() < instance.getInstanceHeartBeatInterval() || instance.getIpDeleteTimeout() < instance.getInstanceHeartBeatInterval()) { throw new NacosApiException(NacosException.INVALID_PARAM, ErrorCode.INSTANCE_ERROR, diff --git a/api/src/main/resources/META-INF/services/com.alibaba.nacos.api.remote.Payload b/api/src/main/resources/META-INF/services/com.alibaba.nacos.api.remote.Payload index 5e9552afd..cbd1e8750 100644 --- a/api/src/main/resources/META-INF/services/com.alibaba.nacos.api.remote.Payload +++ b/api/src/main/resources/META-INF/services/com.alibaba.nacos.api.remote.Payload @@ -47,6 +47,7 @@ com.alibaba.nacos.api.config.remote.request.cluster.ConfigChangeClusterSyncReque com.alibaba.nacos.api.config.remote.response.cluster.ConfigChangeClusterSyncResponse com.alibaba.nacos.api.naming.remote.request.BatchInstanceRequest com.alibaba.nacos.api.naming.remote.request.InstanceRequest +com.alibaba.nacos.api.naming.remote.request.PersistentInstanceRequest com.alibaba.nacos.api.naming.remote.request.NotifySubscriberRequest com.alibaba.nacos.api.naming.remote.request.ServiceListRequest com.alibaba.nacos.api.naming.remote.request.ServiceQueryRequest diff --git a/api/src/test/java/com/alibaba/nacos/api/ability/register/impl/ServerAbilitiesTest.java b/api/src/test/java/com/alibaba/nacos/api/ability/register/impl/ServerAbilitiesTest.java index c8d15261c..2058a3531 100644 --- a/api/src/test/java/com/alibaba/nacos/api/ability/register/impl/ServerAbilitiesTest.java +++ b/api/src/test/java/com/alibaba/nacos/api/ability/register/impl/ServerAbilitiesTest.java @@ -16,15 +16,21 @@ package com.alibaba.nacos.api.ability.register.impl; +import com.alibaba.nacos.api.ability.constant.AbilityKey; import org.junit.Test; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; public class ServerAbilitiesTest { @Test public void testGetStaticAbilities() { - // TODO add the server abilities. - assertTrue(ServerAbilities.getStaticAbilities().isEmpty()); + assertFalse(ServerAbilities.getStaticAbilities().isEmpty()); + } + + @Test + public void testSupportPersistentInstanceByGrpcAbilities() { + assertTrue(ServerAbilities.getStaticAbilities().get(AbilityKey.SERVER_SUPPORT_PERSISTENT_INSTANCE_BY_GRPC)); } } \ No newline at end of file diff --git a/api/src/test/java/com/alibaba/nacos/api/naming/remote/request/PersistentInstanceRequestTest.java b/api/src/test/java/com/alibaba/nacos/api/naming/remote/request/PersistentInstanceRequestTest.java new file mode 100644 index 000000000..be358447a --- /dev/null +++ b/api/src/test/java/com/alibaba/nacos/api/naming/remote/request/PersistentInstanceRequestTest.java @@ -0,0 +1,51 @@ +/* + * Copyright 1999-2021 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.alibaba.nacos.api.naming.remote.request; + +import com.alibaba.nacos.api.naming.pojo.Instance; +import com.alibaba.nacos.api.naming.remote.NamingRemoteConstants; +import com.fasterxml.jackson.core.JsonProcessingException; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class PersistentInstanceRequestTest extends BasedNamingRequestTest { + + @Test + public void testSerialize() throws JsonProcessingException { + PersistentInstanceRequest request = new PersistentInstanceRequest(NAMESPACE, SERVICE, GROUP, + NamingRemoteConstants.REGISTER_INSTANCE, new Instance()); + String json = mapper.writeValueAsString(request); + checkSerializeBasedInfo(json); + assertTrue(json.contains("\"type\":\"" + NamingRemoteConstants.REGISTER_INSTANCE + "\"")); + assertTrue(json.contains("\"instance\":{")); + } + + @Test + public void testDeserialize() throws JsonProcessingException { + String json = "{\"headers\":{},\"namespace\":\"namespace\",\"serviceName\":\"service\",\"groupName\":\"group\"," + + "\"type\":\"deregisterInstance\",\"instance\":{\"port\":0,\"weight\":1.0,\"healthy\":true," + + "\"enabled\":true,\"ephemeral\":true,\"metadata\":{},\"instanceIdGenerator\":\"simple\"," + + "\"instanceHeartBeatInterval\":5000,\"instanceHeartBeatTimeOut\":15000,\"ipDeleteTimeout\":30000}," + + "\"module\":\"naming\"}"; + PersistentInstanceRequest actual = mapper.readValue(json, PersistentInstanceRequest.class); + checkNamingRequestBasedInfo(actual); + assertEquals(NamingRemoteConstants.DE_REGISTER_INSTANCE, actual.getType()); + assertEquals(new Instance(), actual.getInstance()); + } +} \ No newline at end of file diff --git a/api/src/test/java/com/alibaba/nacos/api/naming/utils/NamingUtilsTest.java b/api/src/test/java/com/alibaba/nacos/api/naming/utils/NamingUtilsTest.java index 159616f28..28f863910 100644 --- a/api/src/test/java/com/alibaba/nacos/api/naming/utils/NamingUtilsTest.java +++ b/api/src/test/java/com/alibaba/nacos/api/naming/utils/NamingUtilsTest.java @@ -231,6 +231,19 @@ public class NamingUtilsTest { Assert.assertEquals(e.getErrCode(), NacosException.INVALID_PARAM); } } + + @Test + public void testCheckInstanceIsNull() throws NacosException { + Instance instance = new Instance(); + instance.setIp("127.0.0.1"); + instance.setPort(9089); + NamingUtils.checkInstanceIsLegal(instance); + try { + NamingUtils.checkInstanceIsLegal(null); + } catch (NacosException e) { + Assert.assertEquals(e.getErrCode(), NacosException.INVALID_PARAM); + } + } @Test public void testIsNumber() { diff --git a/api/src/test/java/com/alibaba/nacos/api/utils/AbilityKeyTest.java b/api/src/test/java/com/alibaba/nacos/api/utils/AbilityKeyTest.java index c757d5fa0..a7a88fce3 100644 --- a/api/src/test/java/com/alibaba/nacos/api/utils/AbilityKeyTest.java +++ b/api/src/test/java/com/alibaba/nacos/api/utils/AbilityKeyTest.java @@ -44,16 +44,20 @@ public class AbilityKeyTest { enumMap.put(AbilityKey.SERVER_TEST_1, true); enumMap.put(AbilityKey.SERVER_TEST_2, false); + enumMap.put(AbilityKey.SERVER_SUPPORT_PERSISTENT_INSTANCE_BY_GRPC, false); stringBooleanMap = AbilityKey.mapStr(enumMap); - assertEquals(2, stringBooleanMap.size()); + assertEquals(3, stringBooleanMap.size()); Assert.assertTrue(stringBooleanMap.get(AbilityKey.SERVER_TEST_1.getName())); Assert.assertFalse(stringBooleanMap.get(AbilityKey.SERVER_TEST_2.getName())); + Assert.assertFalse(stringBooleanMap.get(AbilityKey.SERVER_SUPPORT_PERSISTENT_INSTANCE_BY_GRPC.getName())); enumMap.put(AbilityKey.SERVER_TEST_2, true); + enumMap.put(AbilityKey.SERVER_SUPPORT_PERSISTENT_INSTANCE_BY_GRPC, true); stringBooleanMap = AbilityKey.mapStr(enumMap); - assertEquals(2, stringBooleanMap.size()); + assertEquals(3, stringBooleanMap.size()); Assert.assertTrue(stringBooleanMap.get(AbilityKey.SERVER_TEST_1.getName())); Assert.assertTrue(stringBooleanMap.get(AbilityKey.SERVER_TEST_2.getName())); + Assert.assertTrue(stringBooleanMap.get(AbilityKey.SERVER_SUPPORT_PERSISTENT_INSTANCE_BY_GRPC.getName())); } @Test @@ -71,23 +75,27 @@ public class AbilityKeyTest { mapStr.put(AbilityKey.SERVER_TEST_2.getName(), false); mapStr.put(AbilityKey.SERVER_TEST_1.getName(), true); + mapStr.put(AbilityKey.SERVER_SUPPORT_PERSISTENT_INSTANCE_BY_GRPC.getName(), true); enumMap = AbilityKey.mapEnum(AbilityMode.SERVER, mapStr); Assert.assertFalse(enumMap.get(AbilityKey.SERVER_TEST_2)); Assert.assertTrue(enumMap.get(AbilityKey.SERVER_TEST_1)); + Assert.assertTrue(enumMap.get(AbilityKey.SERVER_SUPPORT_PERSISTENT_INSTANCE_BY_GRPC)); mapStr.clear(); mapStr.put(AbilityKey.SERVER_TEST_2.getName(), true); mapStr.put(AbilityKey.SERVER_TEST_1.getName(), true); + mapStr.put(AbilityKey.SERVER_SUPPORT_PERSISTENT_INSTANCE_BY_GRPC.getName(), true); enumMap = AbilityKey.mapEnum(AbilityMode.SERVER, mapStr); Assert.assertTrue(enumMap.get(AbilityKey.SERVER_TEST_2)); Assert.assertTrue(enumMap.get(AbilityKey.SERVER_TEST_1)); + Assert.assertTrue(enumMap.get(AbilityKey.SERVER_SUPPORT_PERSISTENT_INSTANCE_BY_GRPC)); } @Test public void testGetAllValues() { Collection actual = AbilityKey.getAllValues(AbilityMode.SERVER); - assertEquals(2, actual.size()); + assertEquals(3, actual.size()); actual = AbilityKey.getAllValues(AbilityMode.SDK_CLIENT); assertEquals(1, actual.size()); actual = AbilityKey.getAllValues(AbilityMode.CLUSTER_CLIENT); @@ -97,7 +105,7 @@ public class AbilityKeyTest { @Test public void testGetAllNames() { Collection actual = AbilityKey.getAllNames(AbilityMode.SERVER); - assertEquals(2, actual.size()); + assertEquals(3, actual.size()); actual = AbilityKey.getAllNames(AbilityMode.SDK_CLIENT); assertEquals(1, actual.size()); actual = AbilityKey.getAllNames(AbilityMode.CLUSTER_CLIENT); diff --git a/client/src/main/java/com/alibaba/nacos/client/naming/NacosNamingService.java b/client/src/main/java/com/alibaba/nacos/client/naming/NacosNamingService.java index 18583cc8f..b03c2efd3 100644 --- a/client/src/main/java/com/alibaba/nacos/client/naming/NacosNamingService.java +++ b/client/src/main/java/com/alibaba/nacos/client/naming/NacosNamingService.java @@ -194,6 +194,7 @@ public class NacosNamingService implements NamingService { @Override public void deregisterInstance(String serviceName, String groupName, Instance instance) throws NacosException { + NamingUtils.checkInstanceIsLegal(instance); checkAndStripGroupNamePrefix(instance, groupName); clientProxy.deregisterService(serviceName, groupName, instance); } diff --git a/client/src/main/java/com/alibaba/nacos/client/naming/remote/NamingClientProxyDelegate.java b/client/src/main/java/com/alibaba/nacos/client/naming/remote/NamingClientProxyDelegate.java index 65c17560d..05d66bf24 100644 --- a/client/src/main/java/com/alibaba/nacos/client/naming/remote/NamingClientProxyDelegate.java +++ b/client/src/main/java/com/alibaba/nacos/client/naming/remote/NamingClientProxyDelegate.java @@ -16,6 +16,7 @@ package com.alibaba.nacos.client.naming.remote; +import com.alibaba.nacos.api.ability.constant.AbilityKey; import com.alibaba.nacos.api.exception.NacosException; import com.alibaba.nacos.api.naming.pojo.Instance; import com.alibaba.nacos.api.naming.pojo.ListView; @@ -194,7 +195,10 @@ public class NamingClientProxyDelegate implements NamingClientProxy { } private NamingClientProxy getExecuteClientProxy(Instance instance) { - return instance.isEphemeral() ? grpcClientProxy : httpClientProxy; + if (instance.isEphemeral() || grpcClientProxy.isAbilitySupportedByServer(AbilityKey.SERVER_SUPPORT_PERSISTENT_INSTANCE_BY_GRPC)) { + return grpcClientProxy; + } + return httpClientProxy; } @Override diff --git a/client/src/main/java/com/alibaba/nacos/client/naming/remote/gprc/NamingGrpcClientProxy.java b/client/src/main/java/com/alibaba/nacos/client/naming/remote/gprc/NamingGrpcClientProxy.java index 95ade8be1..640af4ecc 100644 --- a/client/src/main/java/com/alibaba/nacos/client/naming/remote/gprc/NamingGrpcClientProxy.java +++ b/client/src/main/java/com/alibaba/nacos/client/naming/remote/gprc/NamingGrpcClientProxy.java @@ -16,6 +16,8 @@ package com.alibaba.nacos.client.naming.remote.gprc; +import com.alibaba.nacos.api.ability.constant.AbilityKey; +import com.alibaba.nacos.api.ability.constant.AbilityStatus; import com.alibaba.nacos.api.common.Constants; import com.alibaba.nacos.api.exception.NacosException; import com.alibaba.nacos.api.naming.CommonParams; @@ -27,6 +29,7 @@ import com.alibaba.nacos.api.naming.remote.NamingRemoteConstants; import com.alibaba.nacos.api.naming.remote.request.AbstractNamingRequest; import com.alibaba.nacos.api.naming.remote.request.BatchInstanceRequest; import com.alibaba.nacos.api.naming.remote.request.InstanceRequest; +import com.alibaba.nacos.api.naming.remote.request.PersistentInstanceRequest; import com.alibaba.nacos.api.naming.remote.request.ServiceListRequest; import com.alibaba.nacos.api.naming.remote.request.ServiceQueryRequest; import com.alibaba.nacos.api.naming.remote.request.SubscribeServiceRequest; @@ -129,6 +132,14 @@ public class NamingGrpcClientProxy extends AbstractNamingClientProxy { public void registerService(String serviceName, String groupName, Instance instance) throws NacosException { NAMING_LOGGER.info("[REGISTER-SERVICE] {} registering service {} with instance {}", namespaceId, serviceName, instance); + if (instance.isEphemeral()) { + registerServiceForEphemeral(serviceName, groupName, instance); + } else { + doRegisterServiceForPersistent(serviceName, groupName, instance); + } + } + + private void registerServiceForEphemeral(String serviceName, String groupName, Instance instance) throws NacosException { redoService.cacheInstanceForRedo(serviceName, groupName, instance); doRegisterService(serviceName, groupName, instance); } @@ -239,10 +250,32 @@ public class NamingGrpcClientProxy extends AbstractNamingClientProxy { redoService.instanceRegistered(serviceName, groupName); } + /** + * Execute register operation for persistent instance. + * + * @param serviceName name of service + * @param groupName group of service + * @param instance instance to register + * @throws NacosException nacos exception + */ + public void doRegisterServiceForPersistent(String serviceName, String groupName, Instance instance) throws NacosException { + PersistentInstanceRequest request = new PersistentInstanceRequest(namespaceId, serviceName, groupName, + NamingRemoteConstants.REGISTER_INSTANCE, instance); + requestToServer(request, Response.class); + } + @Override public void deregisterService(String serviceName, String groupName, Instance instance) throws NacosException { NAMING_LOGGER.info("[DEREGISTER-SERVICE] {} deregistering service {} with instance: {}", namespaceId, serviceName, instance); + if (instance.isEphemeral()) { + deregisterServiceForEphemeral(serviceName, groupName, instance); + } else { + doDeregisterServiceForPersistent(serviceName, groupName, instance); + } + } + + private void deregisterServiceForEphemeral(String serviceName, String groupName, Instance instance) throws NacosException { String key = NamingUtils.getGroupedName(serviceName, groupName); InstanceRedoData instanceRedoData = redoService.getRegisteredInstancesByKey(key); if (instanceRedoData instanceof BatchInstanceRedoData) { @@ -271,6 +304,20 @@ public class NamingGrpcClientProxy extends AbstractNamingClientProxy { requestToServer(request, Response.class); redoService.instanceDeregistered(serviceName, groupName); } + + /** + * Execute deregister operation for persistent instance. + * + * @param serviceName service name + * @param groupName group name + * @param instance instance + * @throws NacosException nacos exception + */ + public void doDeregisterServiceForPersistent(String serviceName, String groupName, Instance instance) throws NacosException { + PersistentInstanceRequest request = new PersistentInstanceRequest(namespaceId, serviceName, groupName, + NamingRemoteConstants.DE_REGISTER_INSTANCE, instance); + requestToServer(request, Response.class); + } @Override public void updateInstance(String serviceName, String groupName, Instance instance) throws NacosException { @@ -384,6 +431,16 @@ public class NamingGrpcClientProxy extends AbstractNamingClientProxy { return rpcClient.isRunning(); } + /** + * Determine whether nacos-server supports the capability. + * + * @param abilityKey ability key + * @return true if supported, otherwise false + */ + public boolean isAbilitySupportedByServer(AbilityKey abilityKey) { + return rpcClient.getConnectionAbility(abilityKey) == AbilityStatus.SUPPORTED; + } + private T requestToServer(AbstractNamingRequest request, Class responseClass) throws NacosException { Response response = null; diff --git a/client/src/main/java/com/alibaba/nacos/client/naming/remote/http/NamingHttpClientProxy.java b/client/src/main/java/com/alibaba/nacos/client/naming/remote/http/NamingHttpClientProxy.java index 3e008e3d7..e2c831d7d 100644 --- a/client/src/main/java/com/alibaba/nacos/client/naming/remote/http/NamingHttpClientProxy.java +++ b/client/src/main/java/com/alibaba/nacos/client/naming/remote/http/NamingHttpClientProxy.java @@ -268,7 +268,6 @@ public class NamingHttpClientProxy extends AbstractNamingClientProxy { @Override public boolean serverHealthy() { - try { String result = reqApi(UtilAndComs.nacosUrlBase + "/operator/metrics", new HashMap<>(8), HttpMethod.GET); JsonNode json = JacksonUtils.toObj(result); diff --git a/client/src/test/java/com/alibaba/nacos/client/naming/remote/NamingClientProxyDelegateTest.java b/client/src/test/java/com/alibaba/nacos/client/naming/remote/NamingClientProxyDelegateTest.java index 9ce7a980e..3a098ed6d 100644 --- a/client/src/test/java/com/alibaba/nacos/client/naming/remote/NamingClientProxyDelegateTest.java +++ b/client/src/test/java/com/alibaba/nacos/client/naming/remote/NamingClientProxyDelegateTest.java @@ -18,6 +18,7 @@ package com.alibaba.nacos.client.naming.remote; +import com.alibaba.nacos.api.ability.constant.AbilityKey; import com.alibaba.nacos.api.exception.NacosException; import com.alibaba.nacos.api.naming.pojo.Instance; import com.alibaba.nacos.api.naming.pojo.Service; @@ -46,7 +47,7 @@ import static org.mockito.Mockito.when; public class NamingClientProxyDelegateTest { @Test - public void testRegisterServiceByGrpc() throws NacosException, NoSuchFieldException, IllegalAccessException { + public void testRegisterEphemeralServiceByGrpc() throws NacosException, NoSuchFieldException, IllegalAccessException { String ns = "ns1"; ServiceInfoHolder holder = Mockito.mock(ServiceInfoHolder.class); Properties props = new Properties(); @@ -100,7 +101,36 @@ public class NamingClientProxyDelegateTest { } @Test - public void testRegisterServiceByHttp() throws NacosException, NoSuchFieldException, IllegalAccessException { + public void testRegisterPersistentServiceByGrpc() throws NacosException, NoSuchFieldException, IllegalAccessException { + String ns = "ns1"; + ServiceInfoHolder holder = Mockito.mock(ServiceInfoHolder.class); + Properties props = new Properties(); + props.setProperty("serverAddr", "localhost"); + final NacosClientProperties nacosClientProperties = NacosClientProperties.PROTOTYPE.derive(props); + InstancesChangeNotifier notifier = new InstancesChangeNotifier(); + NamingClientProxyDelegate delegate = new NamingClientProxyDelegate(ns, holder, nacosClientProperties, notifier); + NamingGrpcClientProxy mockGrpcClient = Mockito.mock(NamingGrpcClientProxy.class); + Field grpcClientProxyField = NamingClientProxyDelegate.class.getDeclaredField("grpcClientProxy"); + grpcClientProxyField.setAccessible(true); + grpcClientProxyField.set(delegate, mockGrpcClient); + + String serviceName = "service1"; + String groupName = "group1"; + Instance instance = new Instance(); + instance.setServiceName(serviceName); + instance.setClusterName(groupName); + instance.setIp("1.1.1.1"); + instance.setPort(1); + // persistent instance + instance.setEphemeral(false); + // when server support register persistent instance by grpc, will use grpc to register + when(mockGrpcClient.isAbilitySupportedByServer(AbilityKey.SERVER_SUPPORT_PERSISTENT_INSTANCE_BY_GRPC)).thenReturn(true); + delegate.registerService(serviceName, groupName, instance); + verify(mockGrpcClient, times(1)).registerService(serviceName, groupName, instance); + } + + @Test + public void testRegisterPersistentServiceByHttp() throws NacosException, NoSuchFieldException, IllegalAccessException { String ns = "ns1"; ServiceInfoHolder holder = Mockito.mock(ServiceInfoHolder.class); Properties props = new Properties(); @@ -121,14 +151,15 @@ public class NamingClientProxyDelegateTest { instance.setClusterName(groupName); instance.setIp("1.1.1.1"); instance.setPort(1); - // use grpc + // persistent instance instance.setEphemeral(false); + // when server do not support register persistent instance by grpc, will use http to register delegate.registerService(serviceName, groupName, instance); verify(mockHttpClient, times(1)).registerService(serviceName, groupName, instance); } @Test - public void testDeregisterServiceGrpc() throws NacosException, NoSuchFieldException, IllegalAccessException { + public void testDeregisterEphemeralServiceGrpc() throws NacosException, NoSuchFieldException, IllegalAccessException { String ns = "ns1"; ServiceInfoHolder holder = Mockito.mock(ServiceInfoHolder.class); Properties props = new Properties(); @@ -156,13 +187,43 @@ public class NamingClientProxyDelegateTest { } @Test - public void testDeregisterServiceHttp() throws NacosException, NoSuchFieldException, IllegalAccessException { + public void testDeregisterPersistentServiceGrpc() throws NacosException, NoSuchFieldException, IllegalAccessException { String ns = "ns1"; ServiceInfoHolder holder = Mockito.mock(ServiceInfoHolder.class); Properties props = new Properties(); props.setProperty("serverAddr", "localhost"); InstancesChangeNotifier notifier = new InstancesChangeNotifier(); + final NacosClientProperties nacosClientProperties = NacosClientProperties.PROTOTYPE.derive(props); + NamingClientProxyDelegate delegate = new NamingClientProxyDelegate(ns, holder, nacosClientProperties, notifier); + NamingGrpcClientProxy mockGrpcClient = Mockito.mock(NamingGrpcClientProxy.class); + Field grpcClientProxyField = NamingClientProxyDelegate.class.getDeclaredField("grpcClientProxy"); + grpcClientProxyField.setAccessible(true); + grpcClientProxyField.set(delegate, mockGrpcClient); + + String serviceName = "service1"; + String groupName = "group1"; + Instance instance = new Instance(); + instance.setServiceName(serviceName); + instance.setClusterName(groupName); + instance.setIp("1.1.1.1"); + instance.setPort(1); + // persistent instance + instance.setEphemeral(false); + // when server support deregister persistent instance by grpc, will use grpc to deregister + when(mockGrpcClient.isAbilitySupportedByServer(AbilityKey.SERVER_SUPPORT_PERSISTENT_INSTANCE_BY_GRPC)).thenReturn(true); + delegate.deregisterService(serviceName, groupName, instance); + verify(mockGrpcClient, times(1)).deregisterService(serviceName, groupName, instance); + } + + @Test + public void testDeregisterPersistentServiceHttp() throws NacosException, NoSuchFieldException, IllegalAccessException { + String ns = "ns1"; + ServiceInfoHolder holder = Mockito.mock(ServiceInfoHolder.class); + Properties props = new Properties(); + props.setProperty("serverAddr", "localhost"); + InstancesChangeNotifier notifier = new InstancesChangeNotifier(); + final NacosClientProperties nacosClientProperties = NacosClientProperties.PROTOTYPE.derive(props); NamingClientProxyDelegate delegate = new NamingClientProxyDelegate(ns, holder, nacosClientProperties, notifier); NamingHttpClientProxy mockHttpClient = Mockito.mock(NamingHttpClientProxy.class); diff --git a/client/src/test/java/com/alibaba/nacos/client/naming/remote/gprc/NamingGrpcClientProxyTest.java b/client/src/test/java/com/alibaba/nacos/client/naming/remote/gprc/NamingGrpcClientProxyTest.java index cd176d098..d96b5b4f5 100644 --- a/client/src/test/java/com/alibaba/nacos/client/naming/remote/gprc/NamingGrpcClientProxyTest.java +++ b/client/src/test/java/com/alibaba/nacos/client/naming/remote/gprc/NamingGrpcClientProxyTest.java @@ -18,6 +18,8 @@ package com.alibaba.nacos.client.naming.remote.gprc; +import com.alibaba.nacos.api.ability.constant.AbilityKey; +import com.alibaba.nacos.api.ability.constant.AbilityStatus; import com.alibaba.nacos.api.common.Constants; import com.alibaba.nacos.api.exception.NacosException; import com.alibaba.nacos.api.naming.pojo.Instance; @@ -27,6 +29,7 @@ import com.alibaba.nacos.api.naming.pojo.ServiceInfo; import com.alibaba.nacos.api.naming.remote.NamingRemoteConstants; import com.alibaba.nacos.api.naming.remote.request.BatchInstanceRequest; import com.alibaba.nacos.api.naming.remote.request.InstanceRequest; +import com.alibaba.nacos.api.naming.remote.request.PersistentInstanceRequest; import com.alibaba.nacos.api.naming.remote.request.SubscribeServiceRequest; import com.alibaba.nacos.api.naming.remote.response.BatchInstanceResponse; import com.alibaba.nacos.api.naming.remote.response.InstanceResponse; @@ -123,6 +126,8 @@ public class NamingGrpcClientProxyTest { private Instance instance; + private Instance persistentInstance; + private String uuid; @Rule @@ -143,7 +148,7 @@ public class NamingGrpcClientProxyTest { Field uuidField = NamingGrpcClientProxy.class.getDeclaredField("uuid"); uuidField.setAccessible(true); uuid = (String) uuidField.get(client); - + Assert.assertNotNull(RpcClientFactory.getClient(uuid)); Field rpcClientField = NamingGrpcClientProxy.class.getDeclaredField("rpcClient"); rpcClientField.setAccessible(true); @@ -156,6 +161,12 @@ public class NamingGrpcClientProxyTest { instance.setServiceName(SERVICE_NAME); instance.setIp("1.1.1.1"); instance.setPort(1111); + + persistentInstance = new Instance(); + persistentInstance.setServiceName(SERVICE_NAME); + persistentInstance.setIp("1.1.1.1"); + persistentInstance.setPort(1111); + persistentInstance.setEphemeral(false); } @After @@ -177,6 +188,18 @@ public class NamingGrpcClientProxyTest { })); } + @Test + public void testRegisterPersistentService() throws NacosException { + client.registerService(SERVICE_NAME, GROUP_NAME, persistentInstance); + verify(this.rpcClient, times(1)).request(argThat(request -> { + if (request instanceof PersistentInstanceRequest) { + PersistentInstanceRequest request1 = (PersistentInstanceRequest) request; + return request1.getType().equals(NamingRemoteConstants.REGISTER_INSTANCE); + } + return false; + })); + } + @Test public void testRegisterServiceThrowsNacosException() throws NacosException { expectedException.expect(NacosException.class); @@ -220,6 +243,18 @@ public class NamingGrpcClientProxyTest { return false; })); } + + @Test + public void testDeregisterPersistentService() throws NacosException { + client.deregisterService(SERVICE_NAME, GROUP_NAME, persistentInstance); + verify(this.rpcClient, times(1)).request(argThat(request -> { + if (request instanceof PersistentInstanceRequest) { + PersistentInstanceRequest request1 = (PersistentInstanceRequest) request; + return request1.getType().equals(NamingRemoteConstants.DE_REGISTER_INSTANCE); + } + return false; + })); + } @Test public void testDeregisterServiceForBatchRegistered() throws NacosException { @@ -461,6 +496,42 @@ public class NamingGrpcClientProxyTest { Assert.assertTrue(client.serverHealthy()); verify(this.rpcClient, times(1)).isRunning(); } + + @Test + public void testIsAbilitySupportedByServer1() { + when(this.rpcClient.getConnectionAbility(AbilityKey.SERVER_SUPPORT_PERSISTENT_INSTANCE_BY_GRPC)) + .thenReturn(AbilityStatus.SUPPORTED); + Assert.assertTrue(client.isAbilitySupportedByServer(AbilityKey.SERVER_SUPPORT_PERSISTENT_INSTANCE_BY_GRPC)); + verify(this.rpcClient, times(1)) + .getConnectionAbility(AbilityKey.SERVER_SUPPORT_PERSISTENT_INSTANCE_BY_GRPC); + } + + @Test + public void testIsAbilitySupportedByServer2() { + when(this.rpcClient.getConnectionAbility(AbilityKey.SERVER_SUPPORT_PERSISTENT_INSTANCE_BY_GRPC)) + .thenReturn(AbilityStatus.NOT_SUPPORTED); + Assert.assertFalse(client.isAbilitySupportedByServer(AbilityKey.SERVER_SUPPORT_PERSISTENT_INSTANCE_BY_GRPC)); + verify(this.rpcClient, times(1)) + .getConnectionAbility(AbilityKey.SERVER_SUPPORT_PERSISTENT_INSTANCE_BY_GRPC); + } + + @Test + public void testIsAbilitySupportedByServer3() { + when(this.rpcClient.getConnectionAbility(AbilityKey.SERVER_SUPPORT_PERSISTENT_INSTANCE_BY_GRPC)) + .thenReturn(AbilityStatus.UNKNOWN); + Assert.assertFalse(client.isAbilitySupportedByServer(AbilityKey.SERVER_SUPPORT_PERSISTENT_INSTANCE_BY_GRPC)); + verify(this.rpcClient, times(1)) + .getConnectionAbility(AbilityKey.SERVER_SUPPORT_PERSISTENT_INSTANCE_BY_GRPC); + } + + @Test + public void testIsAbilitySupportedByServer4() { + when(this.rpcClient.getConnectionAbility(AbilityKey.SERVER_SUPPORT_PERSISTENT_INSTANCE_BY_GRPC)) + .thenReturn(null); + Assert.assertFalse(client.isAbilitySupportedByServer(AbilityKey.SERVER_SUPPORT_PERSISTENT_INSTANCE_BY_GRPC)); + verify(this.rpcClient, times(1)) + .getConnectionAbility(AbilityKey.SERVER_SUPPORT_PERSISTENT_INSTANCE_BY_GRPC); + } @Test public void testShutdown() throws Exception { diff --git a/core/src/main/java/com/alibaba/nacos/core/paramcheck/impl/PersistentInstanceRequestParamExtractor.java b/core/src/main/java/com/alibaba/nacos/core/paramcheck/impl/PersistentInstanceRequestParamExtractor.java new file mode 100644 index 000000000..0bf60813b --- /dev/null +++ b/core/src/main/java/com/alibaba/nacos/core/paramcheck/impl/PersistentInstanceRequestParamExtractor.java @@ -0,0 +1,55 @@ +/* + * Copyright 1999-2023 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.alibaba.nacos.core.paramcheck.impl; + +import com.alibaba.nacos.api.naming.pojo.Instance; +import com.alibaba.nacos.api.naming.remote.request.PersistentInstanceRequest; +import com.alibaba.nacos.api.remote.request.Request; +import com.alibaba.nacos.common.paramcheck.ParamInfo; +import com.alibaba.nacos.core.paramcheck.AbstractRpcParamExtractor; + +import java.util.ArrayList; +import java.util.List; + +/** + * Param extractor for {@link com.alibaba.nacos.api.naming.remote.request.PersistentInstanceRequest}. + * + * @author blake.qiu + */ +public class PersistentInstanceRequestParamExtractor extends AbstractRpcParamExtractor { + + @Override + public List extractParam(Request request) { + PersistentInstanceRequest req = (PersistentInstanceRequest) request; + ParamInfo paramInfo = new ParamInfo(); + paramInfo.setNamespaceId(req.getNamespace()); + paramInfo.setServiceName(req.getServiceName()); + paramInfo.setGroup(req.getGroupName()); + Instance instance = req.getInstance(); + ArrayList paramInfos = new ArrayList<>(); + if (instance == null) { + paramInfos.add(paramInfo); + return paramInfos; + } + paramInfo.setIp(instance.getIp()); + paramInfo.setPort(String.valueOf(instance.getPort())); + paramInfo.setCluster(instance.getClusterName()); + paramInfo.setMetadata(instance.getMetadata()); + paramInfos.add(paramInfo); + return paramInfos; + } +} diff --git a/core/src/main/resources/META-INF/services/com.alibaba.nacos.core.paramcheck.AbstractRpcParamExtractor b/core/src/main/resources/META-INF/services/com.alibaba.nacos.core.paramcheck.AbstractRpcParamExtractor index 9410d1616..38b6f7262 100644 --- a/core/src/main/resources/META-INF/services/com.alibaba.nacos.core.paramcheck.AbstractRpcParamExtractor +++ b/core/src/main/resources/META-INF/services/com.alibaba.nacos.core.paramcheck.AbstractRpcParamExtractor @@ -18,6 +18,7 @@ com.alibaba.nacos.core.paramcheck.impl.SubscribeServiceRequestParamExtractor com.alibaba.nacos.core.paramcheck.impl.ServiceQueryRequestParamExtractor com.alibaba.nacos.core.paramcheck.impl.ServiceListRequestParamExtractor com.alibaba.nacos.core.paramcheck.impl.InstanceRequestParamExtractor +com.alibaba.nacos.core.paramcheck.impl.PersistentInstanceRequestParamExtractor com.alibaba.nacos.core.paramcheck.impl.ConfigRequestParamExtractor com.alibaba.nacos.core.paramcheck.impl.ConfigBatchListenRequestParamExtractor com.alibaba.nacos.core.paramcheck.impl.BatchInstanceRequestParamExtractor \ No newline at end of file diff --git a/naming/src/main/java/com/alibaba/nacos/naming/remote/rpc/handler/PersistentInstanceRequestHandler.java b/naming/src/main/java/com/alibaba/nacos/naming/remote/rpc/handler/PersistentInstanceRequestHandler.java new file mode 100644 index 000000000..3d862219a --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/remote/rpc/handler/PersistentInstanceRequestHandler.java @@ -0,0 +1,92 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.alibaba.nacos.naming.remote.rpc.handler; + +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.api.naming.pojo.Instance; +import com.alibaba.nacos.api.naming.remote.NamingRemoteConstants; +import com.alibaba.nacos.api.naming.remote.request.PersistentInstanceRequest; +import com.alibaba.nacos.api.naming.remote.response.InstanceResponse; +import com.alibaba.nacos.api.remote.request.RequestMeta; +import com.alibaba.nacos.auth.annotation.Secured; +import com.alibaba.nacos.common.notify.NotifyCenter; +import com.alibaba.nacos.common.trace.DeregisterInstanceReason; +import com.alibaba.nacos.common.trace.event.naming.DeregisterInstanceTraceEvent; +import com.alibaba.nacos.common.trace.event.naming.RegisterInstanceTraceEvent; +import com.alibaba.nacos.core.control.TpsControl; +import com.alibaba.nacos.core.paramcheck.ExtractorManager; +import com.alibaba.nacos.core.paramcheck.impl.PersistentInstanceRequestParamExtractor; +import com.alibaba.nacos.core.remote.RequestHandler; +import com.alibaba.nacos.naming.core.v2.client.impl.IpPortBasedClient; +import com.alibaba.nacos.naming.core.v2.pojo.Service; +import com.alibaba.nacos.naming.core.v2.service.impl.PersistentClientOperationServiceImpl; +import com.alibaba.nacos.naming.utils.InstanceUtil; +import com.alibaba.nacos.plugin.auth.constant.ActionTypes; +import org.springframework.stereotype.Component; + +/** + * Persistent instance request handler. + * + * @author blake.qiu + */ +@Component +public class PersistentInstanceRequestHandler extends RequestHandler { + + private final PersistentClientOperationServiceImpl clientOperationService; + + public PersistentInstanceRequestHandler(PersistentClientOperationServiceImpl clientOperationService) { + this.clientOperationService = clientOperationService; + } + + @Override + @TpsControl(pointName = "RemoteNamingInstanceRegisterDeregister", name = "RemoteNamingInstanceRegisterDeregister") + @Secured(action = ActionTypes.WRITE) + @ExtractorManager.Extractor(rpcExtractor = PersistentInstanceRequestParamExtractor.class) + public InstanceResponse handle(PersistentInstanceRequest request, RequestMeta meta) throws NacosException { + Service service = Service.newService(request.getNamespace(), request.getGroupName(), request.getServiceName(), + false); + InstanceUtil.setInstanceIdIfEmpty(request.getInstance(), service.getGroupedServiceName()); + switch (request.getType()) { + case NamingRemoteConstants.REGISTER_INSTANCE: + return registerInstance(service, request, meta); + case NamingRemoteConstants.DE_REGISTER_INSTANCE: + return deregisterInstance(service, request, meta); + default: + throw new NacosException(NacosException.INVALID_PARAM, + String.format("Unsupported request type %s", request.getType())); + } + } + + private InstanceResponse registerInstance(Service service, PersistentInstanceRequest request, RequestMeta meta) { + Instance instance = request.getInstance(); + String clientId = IpPortBasedClient.getClientId(instance.toInetAddr(), false); + clientOperationService.registerInstance(service, instance, clientId); + NotifyCenter.publishEvent(new RegisterInstanceTraceEvent(System.currentTimeMillis(), meta.getClientIp(), true, + service.getNamespace(), service.getGroup(), service.getName(), instance.getIp(), instance.getPort())); + return new InstanceResponse(NamingRemoteConstants.REGISTER_INSTANCE); + } + + private InstanceResponse deregisterInstance(Service service, PersistentInstanceRequest request, RequestMeta meta) { + Instance instance = request.getInstance(); + String clientId = IpPortBasedClient.getClientId(instance.toInetAddr(), false); + clientOperationService.deregisterInstance(service, instance, clientId); + NotifyCenter.publishEvent(new DeregisterInstanceTraceEvent(System.currentTimeMillis(), meta.getClientIp(), true, + DeregisterInstanceReason.REQUEST, service.getNamespace(), service.getGroup(), service.getName(), + instance.getIp(), instance.getPort())); + return new InstanceResponse(NamingRemoteConstants.DE_REGISTER_INSTANCE); + } +} diff --git a/naming/src/test/java/com/alibaba/nacos/naming/remote/rpc/handler/PersistentInstanceRequestHandlerTest.java b/naming/src/test/java/com/alibaba/nacos/naming/remote/rpc/handler/PersistentInstanceRequestHandlerTest.java new file mode 100644 index 000000000..215d93164 --- /dev/null +++ b/naming/src/test/java/com/alibaba/nacos/naming/remote/rpc/handler/PersistentInstanceRequestHandlerTest.java @@ -0,0 +1,71 @@ +/* + * Copyright 1999-2021 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.alibaba.nacos.naming.remote.rpc.handler; + +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.api.naming.pojo.Instance; +import com.alibaba.nacos.api.naming.remote.NamingRemoteConstants; +import com.alibaba.nacos.api.naming.remote.request.PersistentInstanceRequest; +import com.alibaba.nacos.api.remote.request.RequestMeta; +import com.alibaba.nacos.naming.core.v2.service.impl.PersistentClientOperationServiceImpl; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; + +/** + * {@link PersistentInstanceRequestHandler} unit tests. + * + * @author blake.qiu + */ +@RunWith(MockitoJUnitRunner.class) +public class PersistentInstanceRequestHandlerTest { + + @InjectMocks + private PersistentInstanceRequestHandler persistentInstanceRequestHandler; + + @Mock + private PersistentClientOperationServiceImpl clientOperationService; + + @Test + public void testHandle() throws NacosException { + PersistentInstanceRequest instanceRequest = new PersistentInstanceRequest(); + instanceRequest.setType(NamingRemoteConstants.REGISTER_INSTANCE); + instanceRequest.setServiceName("service1"); + instanceRequest.setGroupName("group1"); + Instance instance = new Instance(); + instanceRequest.setInstance(instance); + RequestMeta requestMeta = new RequestMeta(); + persistentInstanceRequestHandler.handle(instanceRequest, requestMeta); + Mockito.verify(clientOperationService).registerInstance(Mockito.any(), Mockito.any(), Mockito.anyString()); + + instanceRequest.setType(NamingRemoteConstants.DE_REGISTER_INSTANCE); + persistentInstanceRequestHandler.handle(instanceRequest, requestMeta); + Mockito.verify(clientOperationService).deregisterInstance(Mockito.any(), Mockito.any(), Mockito.anyString()); + + instanceRequest.setType("xxx"); + try { + persistentInstanceRequestHandler.handle(instanceRequest, requestMeta); + } catch (Exception e) { + Assert.assertEquals(((NacosException) e).getErrCode(), NacosException.INVALID_PARAM); + } + } +}