diff --git a/api/src/main/java/com/alibaba/nacos/api/naming/NamingService.java b/api/src/main/java/com/alibaba/nacos/api/naming/NamingService.java index e0cbb83d3..f10c25d30 100644 --- a/api/src/main/java/com/alibaba/nacos/api/naming/NamingService.java +++ b/api/src/main/java/com/alibaba/nacos/api/naming/NamingService.java @@ -21,6 +21,7 @@ import com.alibaba.nacos.api.naming.listener.EventListener; import com.alibaba.nacos.api.naming.pojo.Instance; import com.alibaba.nacos.api.naming.pojo.ListView; import com.alibaba.nacos.api.naming.pojo.ServiceInfo; +import com.alibaba.nacos.api.naming.selector.NamingSelector; import com.alibaba.nacos.api.selector.AbstractSelector; import java.util.List; @@ -491,6 +492,28 @@ public interface NamingService { void subscribe(String serviceName, String groupName, List clusters, EventListener listener) throws NacosException; + /** + * Subscribe service to receive events of instances alteration. + * + * @param serviceName name of service + * @param selector selector of instances + * @param listener event listener + * @throws NacosException nacos exception + */ + void subscribe(String serviceName, NamingSelector selector, EventListener listener) throws NacosException; + + /** + * Subscribe service to receive events of instances alteration. + * + * @param serviceName name of service + * @param groupName group of service + * @param selector selector of instances + * @param listener event listener + * @throws NacosException nacos exception + */ + void subscribe(String serviceName, String groupName, NamingSelector selector, EventListener listener) + throws NacosException; + /** * Unsubscribe event listener of service. * @@ -532,6 +555,28 @@ public interface NamingService { void unsubscribe(String serviceName, String groupName, List clusters, EventListener listener) throws NacosException; + /** + * Unsubscribe event listener of service. + * + * @param serviceName name of service + * @param selector selector of instances + * @param listener event listener + * @throws NacosException nacos exception + */ + void unsubscribe(String serviceName, NamingSelector selector, EventListener listener) throws NacosException; + + /** + * Unsubscribe event listener of service. + * + * @param serviceName name of service + * @param groupName group of service + * @param selector selector of instances + * @param listener event listener + * @throws NacosException nacos exception + */ + void unsubscribe(String serviceName, String groupName, NamingSelector selector, EventListener listener) + throws NacosException; + /** * Get all service names from server. * diff --git a/api/src/main/java/com/alibaba/nacos/api/naming/selector/NamingContext.java b/api/src/main/java/com/alibaba/nacos/api/naming/selector/NamingContext.java new file mode 100644 index 000000000..ae94a8902 --- /dev/null +++ b/api/src/main/java/com/alibaba/nacos/api/naming/selector/NamingContext.java @@ -0,0 +1,57 @@ +/* + * 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.api.naming.selector; + +import com.alibaba.nacos.api.naming.pojo.Instance; + +import java.util.List; + +/** + * Naming selector context. + * + * @author lideyou + */ +public interface NamingContext { + + /** + * Get service name. + * + * @return service name + */ + String getServiceName(); + + /** + * Get group name. + * + * @return group name + */ + String getGroupName(); + + /** + * Get clusters. + * + * @return clusters + */ + String getClusters(); + + /** + * Get current instances. + * + * @return current instances + */ + List getInstances(); +} diff --git a/api/src/main/java/com/alibaba/nacos/api/naming/selector/NamingResult.java b/api/src/main/java/com/alibaba/nacos/api/naming/selector/NamingResult.java new file mode 100644 index 000000000..370616775 --- /dev/null +++ b/api/src/main/java/com/alibaba/nacos/api/naming/selector/NamingResult.java @@ -0,0 +1,31 @@ +/* + * 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.api.naming.selector; + +import com.alibaba.nacos.api.naming.pojo.Instance; +import com.alibaba.nacos.api.selector.client.SelectResult; + +import java.util.List; + +/** + * Naming select result. + * + * @author lideyou + */ +public interface NamingResult extends SelectResult> { + +} diff --git a/api/src/main/java/com/alibaba/nacos/api/naming/selector/NamingSelector.java b/api/src/main/java/com/alibaba/nacos/api/naming/selector/NamingSelector.java new file mode 100644 index 000000000..252a56977 --- /dev/null +++ b/api/src/main/java/com/alibaba/nacos/api/naming/selector/NamingSelector.java @@ -0,0 +1,28 @@ +/* + * 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.api.naming.selector; + +import com.alibaba.nacos.api.selector.client.Selector; + +/** + * Naming selector. + * + * @author lideyou + */ +public interface NamingSelector extends Selector { + +} diff --git a/api/src/main/java/com/alibaba/nacos/api/selector/client/SelectResult.java b/api/src/main/java/com/alibaba/nacos/api/selector/client/SelectResult.java new file mode 100644 index 000000000..951f69bd6 --- /dev/null +++ b/api/src/main/java/com/alibaba/nacos/api/selector/client/SelectResult.java @@ -0,0 +1,33 @@ +/* + * 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.api.selector.client; + +/** + * Select result. + * + * @param the type of result + * @author lideyou + */ +public interface SelectResult { + + /** + * Get select result. + * + * @return select result + */ + T getResult(); +} diff --git a/api/src/main/java/com/alibaba/nacos/api/selector/client/Selector.java b/api/src/main/java/com/alibaba/nacos/api/selector/client/Selector.java new file mode 100644 index 000000000..131e777b0 --- /dev/null +++ b/api/src/main/java/com/alibaba/nacos/api/selector/client/Selector.java @@ -0,0 +1,35 @@ +/* + * 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.api.selector.client; + +/** + * Client selector. + * + * @param the type of selector context + * @param the type of select result + * @author lideyou + */ +public interface Selector { + + /** + * select the target result. + * + * @param context selector context + * @return select result + */ + E select(C context); +} 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 da7983574..fc29d7176 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 @@ -24,6 +24,7 @@ import com.alibaba.nacos.api.naming.listener.EventListener; import com.alibaba.nacos.api.naming.pojo.Instance; import com.alibaba.nacos.api.naming.pojo.ListView; import com.alibaba.nacos.api.naming.pojo.ServiceInfo; +import com.alibaba.nacos.api.naming.selector.NamingSelector; import com.alibaba.nacos.api.naming.utils.NamingUtils; import com.alibaba.nacos.api.selector.AbstractSelector; import com.alibaba.nacos.client.env.NacosClientProperties; @@ -31,12 +32,15 @@ import com.alibaba.nacos.client.naming.cache.ServiceInfoHolder; import com.alibaba.nacos.client.naming.core.Balancer; import com.alibaba.nacos.client.naming.event.InstancesChangeEvent; import com.alibaba.nacos.client.naming.event.InstancesChangeNotifier; +import com.alibaba.nacos.client.naming.event.InstancesDiff; import com.alibaba.nacos.client.naming.remote.NamingClientProxy; import com.alibaba.nacos.client.naming.remote.NamingClientProxyDelegate; +import com.alibaba.nacos.client.naming.selector.NamingSelectorWrapper; import com.alibaba.nacos.client.naming.utils.CollectionUtils; import com.alibaba.nacos.client.naming.utils.InitUtils; import com.alibaba.nacos.client.naming.utils.UtilAndComs; import com.alibaba.nacos.client.utils.PreInitUtils; +import com.alibaba.nacos.client.naming.selector.NamingSelectorFactory; import com.alibaba.nacos.client.utils.ValidatorUtils; import com.alibaba.nacos.common.notify.NotifyCenter; import com.alibaba.nacos.common.utils.JacksonUtils; @@ -49,6 +53,7 @@ import java.util.Properties; import java.util.UUID; import static com.alibaba.nacos.client.utils.LogUtils.NAMING_LOGGER; +import static com.alibaba.nacos.client.naming.selector.NamingSelectorFactory.getUniqueClusterString; /** * Nacos Naming Service. @@ -415,12 +420,30 @@ public class NacosNamingService implements NamingService { @Override public void subscribe(String serviceName, String groupName, List clusters, EventListener listener) throws NacosException { - if (null == listener) { + NamingSelector clusterSelector = NamingSelectorFactory.newClusterSelector(clusters); + doSubscribe(serviceName, groupName, getUniqueClusterString(clusters), clusterSelector, listener); + } + + @Override + public void subscribe(String serviceName, NamingSelector selector, EventListener listener) throws NacosException { + subscribe(serviceName, Constants.DEFAULT_GROUP, selector, listener); + } + + @Override + public void subscribe(String serviceName, String groupName, NamingSelector selector, EventListener listener) + throws NacosException { + doSubscribe(serviceName, groupName, Constants.NULL, selector, listener); + } + + private void doSubscribe(String serviceName, String groupName, String clusters, NamingSelector selector, + EventListener listener) throws NacosException { + if (selector == null || listener == null) { return; } - String clusterString = StringUtils.join(clusters, ","); - changeNotifier.registerListener(groupName, serviceName, clusterString, listener); - clientProxy.subscribe(serviceName, groupName, clusterString); + NamingSelectorWrapper wrapper = new NamingSelectorWrapper(serviceName, groupName, clusters, selector, listener); + notifyIfSubscribed(serviceName, groupName, wrapper); + changeNotifier.registerListener(groupName, serviceName, wrapper); + clientProxy.subscribe(serviceName, groupName, Constants.NULL); } @Override @@ -441,10 +464,30 @@ public class NacosNamingService implements NamingService { @Override public void unsubscribe(String serviceName, String groupName, List clusters, EventListener listener) throws NacosException { - String clustersString = StringUtils.join(clusters, ","); - changeNotifier.deregisterListener(groupName, serviceName, clustersString, listener); - if (!changeNotifier.isSubscribed(groupName, serviceName, clustersString)) { - clientProxy.unsubscribe(serviceName, groupName, clustersString); + NamingSelector clusterSelector = NamingSelectorFactory.newClusterSelector(clusters); + unsubscribe(serviceName, groupName, clusterSelector, listener); + } + + @Override + public void unsubscribe(String serviceName, NamingSelector selector, EventListener listener) throws NacosException { + unsubscribe(serviceName, Constants.DEFAULT_GROUP, selector, listener); + } + + @Override + public void unsubscribe(String serviceName, String groupName, NamingSelector selector, EventListener listener) + throws NacosException { + doUnsubscribe(serviceName, groupName, selector, listener); + } + + private void doUnsubscribe(String serviceName, String groupName, NamingSelector selector, EventListener listener) + throws NacosException { + if (selector == null || listener == null) { + return; + } + NamingSelectorWrapper wrapper = new NamingSelectorWrapper(selector, listener); + changeNotifier.deregisterListener(groupName, serviceName, wrapper); + if (!changeNotifier.isSubscribed(groupName, serviceName)) { + clientProxy.unsubscribe(serviceName, groupName, Constants.NULL); } } @@ -485,7 +528,6 @@ public class NacosNamingService implements NamingService { serviceInfoHolder.shutdown(); clientProxy.shutdown(); NotifyCenter.deregisterSubscriber(changeNotifier); - } private void batchCheckAndStripGroupNamePrefix(List instances, String groupName) throws NacosException { @@ -500,9 +542,28 @@ public class NacosNamingService implements NamingService { String groupNameOfInstance = NamingUtils.getGroupName(serviceName); if (!groupName.equals(groupNameOfInstance)) { throw new NacosException(NacosException.CLIENT_INVALID_PARAM, String.format( - "wrong group name prefix of instance service name! it should be: %s, Instance: %s", groupName, instance)); + "wrong group name prefix of instance service name! it should be: %s, Instance: %s", groupName, + instance)); } instance.setServiceName(NamingUtils.getServiceName(serviceName)); } } + + private void notifyIfSubscribed(String serviceName, String groupName, NamingSelectorWrapper wrapper) { + if (changeNotifier.isSubscribed(groupName, serviceName)) { + ServiceInfo serviceInfo = serviceInfoHolder.getServiceInfo(serviceName, groupName, Constants.NULL); + InstancesChangeEvent event = transferToEvent(serviceInfo); + wrapper.notifyListener(event); + } + } + + private InstancesChangeEvent transferToEvent(ServiceInfo serviceInfo) { + if (serviceInfo == null) { + return null; + } + InstancesDiff diff = new InstancesDiff(); + diff.setAddedInstances(serviceInfo.getHosts()); + return new InstancesChangeEvent(notifierEventScope, serviceInfo.getName(), serviceInfo.getGroupName(), + serviceInfo.getClusters(), serviceInfo.getHosts(), diff); + } } diff --git a/client/src/main/java/com/alibaba/nacos/client/naming/cache/ServiceInfoHolder.java b/client/src/main/java/com/alibaba/nacos/client/naming/cache/ServiceInfoHolder.java index a59222c30..3b65e678b 100644 --- a/client/src/main/java/com/alibaba/nacos/client/naming/cache/ServiceInfoHolder.java +++ b/client/src/main/java/com/alibaba/nacos/client/naming/cache/ServiceInfoHolder.java @@ -26,6 +26,7 @@ import com.alibaba.nacos.client.monitor.MetricsMonitor; import com.alibaba.nacos.client.naming.backups.FailoverReactor; import com.alibaba.nacos.client.naming.event.InstancesChangeEvent; import com.alibaba.nacos.client.naming.utils.CacheDirUtil; +import com.alibaba.nacos.client.naming.event.InstancesDiff; import com.alibaba.nacos.common.lifecycle.Closeable; import com.alibaba.nacos.common.notify.NotifyCenter; import com.alibaba.nacos.common.utils.ConvertUtils; @@ -130,23 +131,24 @@ public class ServiceInfoHolder implements Closeable { ServiceInfo oldService = serviceInfoMap.get(serviceInfo.getKey()); if (isEmptyOrErrorPush(serviceInfo)) { //empty or error push, just ignore - NAMING_LOGGER.warn("process service info but found empty or error push, serviceKey: {}, " + NAMING_LOGGER.warn("process service info but found empty or error push, serviceKey: {}, " + "pushEmptyProtection: {}, hosts: {}", serviceKey, pushEmptyProtection, serviceInfo.getHosts()); return oldService; } serviceInfoMap.put(serviceInfo.getKey(), serviceInfo); - boolean changed = isChangedServiceInfo(oldService, serviceInfo); + InstancesDiff diff = getServiceInfoDiff(oldService, serviceInfo); if (StringUtils.isBlank(serviceInfo.getJsonFromServer())) { serviceInfo.setJsonFromServer(JacksonUtils.toJson(serviceInfo)); } MetricsMonitor.getServiceInfoMapSizeMonitor().set(serviceInfoMap.size()); - if (changed) { + if (diff.hasDifferent()) { NAMING_LOGGER.info("current ips:({}) service: {} -> {}", serviceInfo.ipCount(), serviceInfo.getKey(), JacksonUtils.toJson(serviceInfo.getHosts())); + if (!failoverReactor.isFailoverSwitch(serviceKey)) { NotifyCenter.publishEvent( new InstancesChangeEvent(notifierEventScope, serviceInfo.getName(), serviceInfo.getGroupName(), - serviceInfo.getClusters(), serviceInfo.getHosts())); + serviceInfo.getClusters(), serviceInfo.getHosts(), diff)); } DiskCache.write(serviceInfo, cacheDir); } @@ -162,20 +164,26 @@ public class ServiceInfoHolder implements Closeable { * * @param oldService old service data * @param newService new service data - * @return + * @return {@code true} if oldService is not equal newService, {@code false} otherwise. */ public boolean isChangedServiceInfo(ServiceInfo oldService, ServiceInfo newService) { + return getServiceInfoDiff(oldService, newService).hasDifferent(); + } + + private InstancesDiff getServiceInfoDiff(ServiceInfo oldService, ServiceInfo newService) { + InstancesDiff instancesDiff = new InstancesDiff(); if (null == oldService) { NAMING_LOGGER.info("init new ips({}) service: {} -> {}", newService.ipCount(), newService.getKey(), JacksonUtils.toJson(newService.getHosts())); - return true; + instancesDiff.setAddedInstances(newService.getHosts()); + return instancesDiff; } if (oldService.getLastRefTime() > newService.getLastRefTime()) { NAMING_LOGGER.warn("out of date data received, old-t: {}, new-t: {}", oldService.getLastRefTime(), newService.getLastRefTime()); - return false; + return instancesDiff; } - boolean changed = false; + Map oldHostMap = new HashMap<>(oldService.getHosts().size()); for (Instance host : oldService.getHosts()) { oldHostMap.put(host.toInetAddr(), host); @@ -215,23 +223,23 @@ public class ServiceInfoHolder implements Closeable { } if (newHosts.size() > 0) { - changed = true; NAMING_LOGGER.info("new ips({}) service: {} -> {}", newHosts.size(), newService.getKey(), JacksonUtils.toJson(newHosts)); + instancesDiff.setAddedInstances(newHosts); } if (remvHosts.size() > 0) { - changed = true; NAMING_LOGGER.info("removed ips({}) service: {} -> {}", remvHosts.size(), newService.getKey(), JacksonUtils.toJson(remvHosts)); + instancesDiff.setRemovedInstances(remvHosts); } if (modHosts.size() > 0) { - changed = true; NAMING_LOGGER.info("modified ips({}) service: {} -> {}", modHosts.size(), newService.getKey(), JacksonUtils.toJson(modHosts)); + instancesDiff.setModifiedInstances(modHosts); } - return changed; + return instancesDiff; } public String getCacheDir() { diff --git a/client/src/main/java/com/alibaba/nacos/client/naming/core/ServiceInfoUpdateService.java b/client/src/main/java/com/alibaba/nacos/client/naming/core/ServiceInfoUpdateService.java index daebbacf7..909bfdbd6 100644 --- a/client/src/main/java/com/alibaba/nacos/client/naming/core/ServiceInfoUpdateService.java +++ b/client/src/main/java/com/alibaba/nacos/client/naming/core/ServiceInfoUpdateService.java @@ -183,7 +183,7 @@ public class ServiceInfoUpdateService implements Closeable { long delayTime = DEFAULT_DELAY; try { - if (!changeNotifier.isSubscribed(groupName, serviceName, clusters) && !futureMap.containsKey( + if (!changeNotifier.isSubscribed(groupName, serviceName) && !futureMap.containsKey( serviceKey)) { NAMING_LOGGER.info("update task is stopped, service:{}, clusters:{}", groupedServiceName, clusters); isCancel = true; diff --git a/client/src/main/java/com/alibaba/nacos/client/naming/event/InstancesChangeEvent.java b/client/src/main/java/com/alibaba/nacos/client/naming/event/InstancesChangeEvent.java index 87cd89538..f0bde6682 100644 --- a/client/src/main/java/com/alibaba/nacos/client/naming/event/InstancesChangeEvent.java +++ b/client/src/main/java/com/alibaba/nacos/client/naming/event/InstancesChangeEvent.java @@ -40,13 +40,20 @@ public class InstancesChangeEvent extends Event { private final String clusters; private final List hosts; + + private InstancesDiff instancesDiff; public InstancesChangeEvent(String eventScope, String serviceName, String groupName, String clusters, List hosts) { + this(eventScope, serviceName, groupName, clusters, hosts, null); + } + + public InstancesChangeEvent(String eventScope, String serviceName, String groupName, String clusters, List hosts, InstancesDiff diff) { this.eventScope = eventScope; this.serviceName = serviceName; this.groupName = groupName; this.clusters = clusters; this.hosts = hosts; + this.instancesDiff = diff; } public String getServiceName() { @@ -64,7 +71,11 @@ public class InstancesChangeEvent extends Event { public List getHosts() { return hosts; } - + + public InstancesDiff getInstancesDiff() { + return instancesDiff; + } + @Override public String scope() { return this.eventScope; diff --git a/client/src/main/java/com/alibaba/nacos/client/naming/event/InstancesChangeNotifier.java b/client/src/main/java/com/alibaba/nacos/client/naming/event/InstancesChangeNotifier.java index da1b743ed..8c9c7500c 100644 --- a/client/src/main/java/com/alibaba/nacos/client/naming/event/InstancesChangeNotifier.java +++ b/client/src/main/java/com/alibaba/nacos/client/naming/event/InstancesChangeNotifier.java @@ -16,22 +16,18 @@ package com.alibaba.nacos.client.naming.event; -import com.alibaba.nacos.api.naming.listener.AbstractEventListener; -import com.alibaba.nacos.api.naming.listener.EventListener; -import com.alibaba.nacos.api.naming.listener.NamingEvent; import com.alibaba.nacos.api.naming.pojo.ServiceInfo; import com.alibaba.nacos.api.naming.utils.NamingUtils; +import com.alibaba.nacos.client.naming.selector.NamingSelectorWrapper; +import com.alibaba.nacos.client.selector.SelectorManager; import com.alibaba.nacos.common.JustForTest; import com.alibaba.nacos.common.notify.Event; import com.alibaba.nacos.common.notify.listener.Subscriber; -import com.alibaba.nacos.common.utils.CollectionUtils; -import com.alibaba.nacos.common.utils.ConcurrentHashSet; import java.util.ArrayList; +import java.util.Collection; import java.util.List; -import java.util.Map; import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; /** * A subscriber to notify eventListener callback. @@ -43,7 +39,7 @@ public class InstancesChangeNotifier extends Subscriber { private final String eventScope; - private final Map> listenerMap = new ConcurrentHashMap<>(); + private final SelectorManager selectorManager = new SelectorManager<>(); @JustForTest public InstancesChangeNotifier() { @@ -59,13 +55,14 @@ public class InstancesChangeNotifier extends Subscriber { * * @param groupName group name * @param serviceName serviceName - * @param clusters clusters, concat by ','. such as 'xxx,yyy' - * @param listener custom listener + * @param wrapper selectorWrapper */ - public void registerListener(String groupName, String serviceName, String clusters, EventListener listener) { - String key = ServiceInfo.getKey(NamingUtils.getGroupedName(serviceName, groupName), clusters); - ConcurrentHashSet eventListeners = listenerMap.computeIfAbsent(key, keyInner -> new ConcurrentHashSet<>()); - eventListeners.add(listener); + public void registerListener(String groupName, String serviceName, NamingSelectorWrapper wrapper) { + if (wrapper == null) { + return; + } + String subId = NamingUtils.getGroupedName(serviceName, groupName); + selectorManager.addSelectorWrapper(subId, wrapper); } /** @@ -73,38 +70,31 @@ public class InstancesChangeNotifier extends Subscriber { * * @param groupName group name * @param serviceName serviceName - * @param clusters clusters, concat by ','. such as 'xxx,yyy' - * @param listener custom listener + * @param wrapper selectorWrapper */ - public void deregisterListener(String groupName, String serviceName, String clusters, EventListener listener) { - String key = ServiceInfo.getKey(NamingUtils.getGroupedName(serviceName, groupName), clusters); - ConcurrentHashSet eventListeners = listenerMap.get(key); - if (eventListeners == null) { + public void deregisterListener(String groupName, String serviceName, NamingSelectorWrapper wrapper) { + if (wrapper == null) { return; } - eventListeners.remove(listener); - if (CollectionUtils.isEmpty(eventListeners)) { - listenerMap.remove(key); - } + String subId = NamingUtils.getGroupedName(serviceName, groupName); + selectorManager.removeSelectorWrapper(subId, wrapper); } /** - * check serviceName,clusters is subscribed. + * check serviceName,groupName is subscribed. * * @param groupName group name * @param serviceName serviceName - * @param clusters clusters, concat by ','. such as 'xxx,yyy' * @return is serviceName,clusters subscribed */ - public boolean isSubscribed(String groupName, String serviceName, String clusters) { - String key = ServiceInfo.getKey(NamingUtils.getGroupedName(serviceName, groupName), clusters); - ConcurrentHashSet eventListeners = listenerMap.get(key); - return CollectionUtils.isNotEmpty(eventListeners); + public boolean isSubscribed(String groupName, String serviceName) { + String subId = NamingUtils.getGroupedName(serviceName, groupName); + return selectorManager.isSubscribed(subId); } public List getSubscribeServices() { List serviceInfos = new ArrayList<>(); - for (String key : listenerMap.keySet()) { + for (String key : selectorManager.getSubscriptions()) { serviceInfos.add(ServiceInfo.fromKey(key)); } return serviceInfos; @@ -112,26 +102,11 @@ public class InstancesChangeNotifier extends Subscriber { @Override public void onEvent(InstancesChangeEvent event) { - String key = ServiceInfo - .getKey(NamingUtils.getGroupedName(event.getServiceName(), event.getGroupName()), event.getClusters()); - ConcurrentHashSet eventListeners = listenerMap.get(key); - if (CollectionUtils.isEmpty(eventListeners)) { - return; + String subId = NamingUtils.getGroupedName(event.getServiceName(), event.getGroupName()); + Collection selectorWrappers = selectorManager.getSelectorWrappers(subId); + for (NamingSelectorWrapper selectorWrapper : selectorWrappers) { + selectorWrapper.notifyListener(event); } - for (final EventListener listener : eventListeners) { - final com.alibaba.nacos.api.naming.listener.Event namingEvent = transferToNamingEvent(event); - if (listener instanceof AbstractEventListener && ((AbstractEventListener) listener).getExecutor() != null) { - ((AbstractEventListener) listener).getExecutor().execute(() -> listener.onEvent(namingEvent)); - } else { - listener.onEvent(namingEvent); - } - } - } - - private com.alibaba.nacos.api.naming.listener.Event transferToNamingEvent( - InstancesChangeEvent instancesChangeEvent) { - return new NamingEvent(instancesChangeEvent.getServiceName(), instancesChangeEvent.getGroupName(), - instancesChangeEvent.getClusters(), instancesChangeEvent.getHosts()); } @Override diff --git a/client/src/main/java/com/alibaba/nacos/client/naming/event/InstancesDiff.java b/client/src/main/java/com/alibaba/nacos/client/naming/event/InstancesDiff.java new file mode 100644 index 000000000..5026499f6 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/naming/event/InstancesDiff.java @@ -0,0 +1,117 @@ +/* + * 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.client.naming.event; + +import com.alibaba.nacos.api.naming.pojo.Instance; +import com.alibaba.nacos.common.utils.CollectionUtils; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * The differences in instances compared to the last callback. + * + * @author lideyou + */ +public class InstancesDiff { + + private final List addedInstances = new ArrayList<>(); + + private final List removedInstances = new ArrayList<>(); + + private final List modifiedInstances = new ArrayList<>(); + + public InstancesDiff() { + } + + public InstancesDiff(List addedInstances, List removedInstances, + List modifiedInstances) { + setAddedInstances(addedInstances); + setRemovedInstances(removedInstances); + setModifiedInstances(modifiedInstances); + } + + public List getAddedInstances() { + return addedInstances; + } + + public void setAddedInstances(Collection addedInstances) { + this.addedInstances.clear(); + if (CollectionUtils.isNotEmpty(addedInstances)) { + this.addedInstances.addAll(addedInstances); + } + } + + public List getRemovedInstances() { + return removedInstances; + } + + public void setRemovedInstances(Collection removedInstances) { + this.removedInstances.clear(); + if (CollectionUtils.isNotEmpty(removedInstances)) { + this.removedInstances.addAll(removedInstances); + } + } + + public List getModifiedInstances() { + return modifiedInstances; + } + + public void setModifiedInstances(Collection modifiedInstances) { + this.modifiedInstances.clear(); + if (CollectionUtils.isNotEmpty(modifiedInstances)) { + this.modifiedInstances.addAll(modifiedInstances); + } + } + + /** + * Check if any instances have changed. + * + * @return true if there are instances that have changed + */ + public boolean hasDifferent() { + return isAdded() || isRemoved() || isModified(); + } + + /** + * Check if any instances have been added. + * + * @return true if there are instances that have been added. + */ + public boolean isAdded() { + return CollectionUtils.isNotEmpty(this.addedInstances); + } + + /** + * Check if any instances have been added. + * + * @return true if there are instances that have been added. + */ + public boolean isRemoved() { + return CollectionUtils.isNotEmpty(this.removedInstances); + } + + /** + * Check if any instances have been added. + * + * @return true if there are instances that have been added. + */ + public boolean isModified() { + return CollectionUtils.isNotEmpty(this.modifiedInstances); + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/naming/listener/AbstractNamingChangeListener.java b/client/src/main/java/com/alibaba/nacos/client/naming/listener/AbstractNamingChangeListener.java new file mode 100644 index 000000000..932147359 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/naming/listener/AbstractNamingChangeListener.java @@ -0,0 +1,42 @@ +/* + * 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.client.naming.listener; + +import com.alibaba.nacos.api.naming.listener.AbstractEventListener; +import com.alibaba.nacos.api.naming.listener.Event; + +/** + * Listener for NamingChangeEvent. + * + * @author lideyou + */ +public abstract class AbstractNamingChangeListener extends AbstractEventListener { + + @Override + public final void onEvent(Event event) { + if (event instanceof NamingChangeEvent) { + onChange((NamingChangeEvent) event); + } + } + + /** + * Callback when instances have changed. + * + * @param event NamingChangeEvent + */ + public abstract void onChange(NamingChangeEvent event); +} diff --git a/client/src/main/java/com/alibaba/nacos/client/naming/listener/NamingChangeEvent.java b/client/src/main/java/com/alibaba/nacos/client/naming/listener/NamingChangeEvent.java new file mode 100644 index 000000000..62b4dbf33 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/naming/listener/NamingChangeEvent.java @@ -0,0 +1,68 @@ +/* + * 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.client.naming.listener; + +import com.alibaba.nacos.api.naming.listener.NamingEvent; +import com.alibaba.nacos.api.naming.pojo.Instance; +import com.alibaba.nacos.client.naming.event.InstancesDiff; + +import java.util.List; + +/** + * Naming Event with instance change information. + * + * @author lideyou + */ +public class NamingChangeEvent extends NamingEvent { + + private final InstancesDiff instancesDiff; + + public NamingChangeEvent(String serviceName, List instances, InstancesDiff instancesDiff) { + super(serviceName, instances); + this.instancesDiff = instancesDiff; + } + + public NamingChangeEvent(String serviceName, String groupName, String clusters, List instances, + InstancesDiff instancesDiff) { + super(serviceName, groupName, clusters, instances); + this.instancesDiff = instancesDiff; + } + + public boolean isAdded() { + return this.instancesDiff.isAdded(); + } + + public boolean isRemoved() { + return this.instancesDiff.isRemoved(); + } + + public boolean isModified() { + return this.instancesDiff.isModified(); + } + + public List getAddedInstances() { + return this.instancesDiff.getAddedInstances(); + } + + public List getRemovedInstances() { + return this.instancesDiff.getRemovedInstances(); + } + + public List getModifiedInstances() { + return this.instancesDiff.getModifiedInstances(); + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/naming/selector/DefaultNamingSelector.java b/client/src/main/java/com/alibaba/nacos/client/naming/selector/DefaultNamingSelector.java new file mode 100644 index 000000000..ad15508df --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/naming/selector/DefaultNamingSelector.java @@ -0,0 +1,52 @@ +/* + * 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.client.naming.selector; + +import com.alibaba.nacos.api.naming.pojo.Instance; +import com.alibaba.nacos.api.naming.selector.NamingContext; +import com.alibaba.nacos.api.naming.selector.NamingResult; +import com.alibaba.nacos.api.naming.selector.NamingSelector; + +import java.util.Collections; +import java.util.List; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +/** + * Default naming selector. + * + * @author lideyou + */ +public class DefaultNamingSelector implements NamingSelector { + + private final Predicate filter; + + public DefaultNamingSelector(Predicate filter) { + this.filter = filter; + } + + @Override + public NamingResult select(NamingContext context) { + List instances = doFilter(context.getInstances()); + return () -> instances; + } + + private List doFilter(List instances) { + return instances == null ? Collections.emptyList() + : instances.stream().filter(filter).collect(Collectors.toList()); + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/naming/selector/NamingListenerInvoker.java b/client/src/main/java/com/alibaba/nacos/client/naming/selector/NamingListenerInvoker.java new file mode 100644 index 000000000..88279bf4e --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/naming/selector/NamingListenerInvoker.java @@ -0,0 +1,66 @@ +/* + * 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.client.naming.selector; + +import com.alibaba.nacos.api.naming.listener.AbstractEventListener; +import com.alibaba.nacos.api.naming.listener.EventListener; +import com.alibaba.nacos.api.naming.listener.NamingEvent; +import com.alibaba.nacos.client.selector.ListenerInvoker; + +import java.util.Objects; + +/** + * Naming listener invoker. + * + * @author lideyou + */ +public class NamingListenerInvoker implements ListenerInvoker { + + private final EventListener listener; + + public NamingListenerInvoker(EventListener listener) { + this.listener = listener; + } + + @Override + public void invoke(NamingEvent event) { + if (listener instanceof AbstractEventListener && ((AbstractEventListener) listener).getExecutor() != null) { + ((AbstractEventListener) listener).getExecutor().execute(() -> listener.onEvent(event)); + } else { + listener.onEvent(event); + } + } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) { + return false; + } + + if (this == o) { + return true; + } + + NamingListenerInvoker that = (NamingListenerInvoker) o; + return Objects.equals(listener, that.listener); + } + + @Override + public int hashCode() { + return Objects.hashCode(listener); + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/naming/selector/NamingSelectorFactory.java b/client/src/main/java/com/alibaba/nacos/client/naming/selector/NamingSelectorFactory.java new file mode 100644 index 000000000..6f93447d0 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/naming/selector/NamingSelectorFactory.java @@ -0,0 +1,150 @@ +/* + * 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.client.naming.selector; + +import com.alibaba.nacos.api.naming.pojo.Instance; +import com.alibaba.nacos.api.naming.selector.NamingSelector; +import com.alibaba.nacos.common.utils.CollectionUtils; +import com.alibaba.nacos.common.utils.StringUtils; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.TreeSet; +import java.util.function.Predicate; +import java.util.regex.Pattern; + +/** + * Selectors factory. + * + * @author lideyou + */ +public final class NamingSelectorFactory { + + public static final NamingSelector EMPTY_SELECTOR = context -> context::getInstances; + + public static final NamingSelector HEALTHY_SELECTOR = new DefaultNamingSelector(Instance::isHealthy); + + /** + * Cluster selector. + */ + private static class ClusterSelector extends DefaultNamingSelector { + + private final String clusterString; + + public ClusterSelector(Predicate filter, String clusterString) { + super(filter); + this.clusterString = clusterString; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ClusterSelector that = (ClusterSelector) o; + return Objects.equals(this.clusterString, that.clusterString); + } + + @Override + public int hashCode() { + return Objects.hashCode(this.clusterString); + } + } + + private NamingSelectorFactory() { + } + + /** + * Create a cluster selector. + * + * @param clusters target cluster + * @return cluster selector + */ + public static NamingSelector newClusterSelector(Collection clusters) { + if (CollectionUtils.isNotEmpty(clusters)) { + final Set set = new HashSet<>(clusters); + Predicate filter = instance -> set.contains(instance.getClusterName()); + String clusterString = getUniqueClusterString(clusters); + return new ClusterSelector(filter, clusterString); + } else { + return EMPTY_SELECTOR; + } + } + + /** + * Create a IP selector. + * + * @param regex regular expression of IP + * @return IP selector + */ + public static NamingSelector newIpSelector(String regex) { + if (regex == null) { + throw new IllegalArgumentException("The parameter 'regex' cannot be null."); + } + return new DefaultNamingSelector(instance -> Pattern.matches(regex, instance.getIp())); + } + + /** + * Create a metadata selector. + * + * @param metadata metadata that needs to be matched + * @return metadata selector + */ + public static NamingSelector newMetadataSelector(Map metadata) { + return newMetadataSelector(metadata, false); + } + + /** + * Create a metadata selector. + * + * @param metadata target metadata + * @param isAny true if any of the metadata needs to be matched, false if all the metadata need to be matched. + * @return metadata selector + */ + public static NamingSelector newMetadataSelector(Map metadata, boolean isAny) { + if (metadata == null) { + throw new IllegalArgumentException("The parameter 'metadata' cannot be null."); + } + + Predicate filter = instance -> instance.getMetadata().size() >= metadata.size(); + + for (Map.Entry entry : metadata.entrySet()) { + Predicate nextFilter = instance -> { + Map map = instance.getMetadata(); + return Objects.equals(map.get(entry.getKey()), entry.getValue()); + }; + if (isAny) { + filter = filter.or(nextFilter); + } else { + filter = filter.and(nextFilter); + } + } + return new DefaultNamingSelector(filter); + } + + public static String getUniqueClusterString(Collection cluster) { + TreeSet treeSet = new TreeSet<>(cluster); + return StringUtils.join(treeSet, ","); + } + +} diff --git a/client/src/main/java/com/alibaba/nacos/client/naming/selector/NamingSelectorWrapper.java b/client/src/main/java/com/alibaba/nacos/client/naming/selector/NamingSelectorWrapper.java new file mode 100644 index 000000000..412b120f0 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/naming/selector/NamingSelectorWrapper.java @@ -0,0 +1,134 @@ +/* + * 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.client.naming.selector; + +import com.alibaba.nacos.api.naming.listener.EventListener; +import com.alibaba.nacos.api.naming.listener.NamingEvent; +import com.alibaba.nacos.api.naming.pojo.Instance; +import com.alibaba.nacos.api.naming.selector.NamingContext; +import com.alibaba.nacos.api.naming.selector.NamingSelector; +import com.alibaba.nacos.client.naming.event.InstancesChangeEvent; +import com.alibaba.nacos.client.naming.event.InstancesDiff; +import com.alibaba.nacos.client.naming.listener.NamingChangeEvent; +import com.alibaba.nacos.client.selector.AbstractSelectorWrapper; +import com.alibaba.nacos.common.utils.CollectionUtils; + +import java.util.Collections; +import java.util.List; + +/** + * Naming selector wrapper. + * + * @author lideyou + */ +public class NamingSelectorWrapper extends AbstractSelectorWrapper { + + private String serviceName; + + private String groupName; + + private String clusters; + + private final InnerNamingContext namingContext = new InnerNamingContext(); + + private class InnerNamingContext implements NamingContext { + + private List instances; + + @Override + public String getServiceName() { + return serviceName; + } + + @Override + public String getGroupName() { + return groupName; + } + + @Override + public String getClusters() { + return clusters; + } + + @Override + public List getInstances() { + return instances; + } + + private void setInstances(List instances) { + this.instances = instances; + } + } + + public NamingSelectorWrapper(NamingSelector selector, EventListener listener) { + super(selector, new NamingListenerInvoker(listener)); + } + + public NamingSelectorWrapper(String serviceName, String groupName, String clusters, NamingSelector selector, + EventListener listener) { + this(selector, listener); + this.serviceName = serviceName; + this.groupName = groupName; + this.clusters = clusters; + } + + @Override + protected boolean isSelectable(InstancesChangeEvent event) { + return event != null && event.getHosts() != null && event.getInstancesDiff() != null; + } + + @Override + public boolean isCallable(NamingEvent event) { + if (event == null) { + return false; + } + NamingChangeEvent changeEvent = (NamingChangeEvent) event; + return changeEvent.isAdded() || changeEvent.isRemoved() || changeEvent.isModified(); + } + + @Override + protected NamingEvent buildListenerEvent(InstancesChangeEvent event) { + List currentIns = Collections.emptyList(); + if (CollectionUtils.isNotEmpty(event.getHosts())) { + currentIns = doSelect(event.getHosts()); + } + + InstancesDiff diff = event.getInstancesDiff(); + InstancesDiff newDiff = new InstancesDiff(); + if (diff.isAdded()) { + newDiff.setAddedInstances(doSelect(diff.getAddedInstances())); + } + if (diff.isRemoved()) { + newDiff.setRemovedInstances(doSelect(diff.getRemovedInstances())); + } + if (diff.isModified()) { + newDiff.setModifiedInstances(doSelect(diff.getModifiedInstances())); + } + + return new NamingChangeEvent(serviceName, groupName, clusters, currentIns, newDiff); + } + + private List doSelect(List instances) { + NamingContext context = getNamingContext(instances); + return this.getSelector().select(context).getResult(); + } + + private NamingContext getNamingContext(final List instances) { + namingContext.setInstances(instances); + return namingContext; + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/selector/AbstractSelectorWrapper.java b/client/src/main/java/com/alibaba/nacos/client/selector/AbstractSelectorWrapper.java new file mode 100644 index 000000000..edbab0810 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/selector/AbstractSelectorWrapper.java @@ -0,0 +1,106 @@ +/* + * 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.client.selector; + +import com.alibaba.nacos.api.selector.client.Selector; +import com.alibaba.nacos.common.notify.Event; + +import java.util.Objects; + +/** + * Selector Wrapper. + * + * @param the type of selector + * @param the type of original event + * @param the type of listener callback event + * @author lideyou + */ +public abstract class AbstractSelectorWrapper, E, T extends Event> { + + private final S selector; + + private final ListenerInvoker listener; + + public AbstractSelectorWrapper(S selector, ListenerInvoker listener) { + this.selector = selector; + this.listener = listener; + } + + /** + * Check whether the event can be callback. + * + * @param event original event + * @return true if the event can be callback + */ + protected abstract boolean isSelectable(T event); + + /** + * Check whether the result can be callback. + * + * @param event select result + * @return true if the result can be callback + */ + protected abstract boolean isCallable(E event); + + /** + * Build an event received by the listener. + * + * @param event original event + * @return listener event + */ + protected abstract E buildListenerEvent(T event); + + /** + * Notify listener. + * + * @param event original event + */ + public void notifyListener(T event) { + if (!isSelectable(event)) { + return; + } + E newEvent = buildListenerEvent(event); + if (isCallable(newEvent)) { + listener.invoke(newEvent); + } + } + + public ListenerInvoker getListener() { + return this.listener; + } + + public S getSelector() { + return this.selector; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + AbstractSelectorWrapper that = (AbstractSelectorWrapper) o; + return Objects.equals(selector, that.selector) && Objects.equals(listener, that.listener); + } + + @Override + public int hashCode() { + return Objects.hash(selector, listener); + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/selector/ListenerInvoker.java b/client/src/main/java/com/alibaba/nacos/client/selector/ListenerInvoker.java new file mode 100644 index 000000000..81996eea7 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/selector/ListenerInvoker.java @@ -0,0 +1,33 @@ +/* + * 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.client.selector; + +/** + * Listener invoker. + * + * @param the type of event received by the listener + * @author lideyou + */ +public interface ListenerInvoker { + + /** + * Invoke inner listener. + * + * @param event event + */ + void invoke(E event); +} diff --git a/client/src/main/java/com/alibaba/nacos/client/selector/SelectorManager.java b/client/src/main/java/com/alibaba/nacos/client/selector/SelectorManager.java new file mode 100644 index 000000000..deda8f62e --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/selector/SelectorManager.java @@ -0,0 +1,103 @@ +/* + * 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.client.selector; + +import com.alibaba.nacos.common.utils.CollectionUtils; +import com.alibaba.nacos.common.utils.ConcurrentHashSet; + +import java.util.Collections; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Selector Manager. + * + * @param the type of selector wrapper + * @author lideyou + */ +public class SelectorManager> { + + Map> selectorMap = new ConcurrentHashMap<>(); + + /** + * Add a selectorWrapper to subId. + * + * @param subId subscription id + * @param wrapper selector wrapper + */ + public void addSelectorWrapper(String subId, S wrapper) { + selectorMap.compute(subId, (k, v) -> { + if (v == null) { + v = new ConcurrentHashSet<>(); + } + v.add(wrapper); + return v; + }); + } + + /** + * Get all SelectorWrappers by id. + * + * @param subId subscription id + * @return the set of SelectorWrappers + */ + public Set getSelectorWrappers(String subId) { + return selectorMap.getOrDefault(subId, Collections.emptySet()); + } + + /** + * Remove a SelectorWrapper by id. + * + * @param subId subscription id + * @param wrapper selector wrapper + */ + public void removeSelectorWrapper(String subId, S wrapper) { + selectorMap.computeIfPresent(subId, (k, v) -> { + v.remove(wrapper); + return v.isEmpty() ? null : v; + }); + } + + /** + * Remove a subscription by id. + * + * @param subId subscription id + */ + public void removeSubscription(String subId) { + selectorMap.remove(subId); + } + + /** + * Get all subscriptions. + * + * @return all subscriptions + */ + public Set getSubscriptions() { + return selectorMap.keySet(); + } + + /** + * Determine whether subId is subscribed. + * + * @param subId subscription id + * @return true if is subscribed + */ + public boolean isSubscribed(String subId) { + return CollectionUtils.isNotEmpty(this.getSelectorWrappers(subId)); + } +} diff --git a/client/src/test/java/com/alibaba/nacos/client/naming/NacosNamingServiceTest.java b/client/src/test/java/com/alibaba/nacos/client/naming/NacosNamingServiceTest.java index 02fa6ce13..7b1b0275b 100644 --- a/client/src/test/java/com/alibaba/nacos/client/naming/NacosNamingServiceTest.java +++ b/client/src/test/java/com/alibaba/nacos/client/naming/NacosNamingServiceTest.java @@ -30,6 +30,8 @@ import com.alibaba.nacos.client.naming.event.InstancesChangeEvent; import com.alibaba.nacos.client.naming.event.InstancesChangeNotifier; import com.alibaba.nacos.client.naming.remote.NamingClientProxy; import com.alibaba.nacos.client.naming.remote.http.NamingHttpClientProxy; +import com.alibaba.nacos.client.naming.selector.NamingSelectorFactory; +import com.alibaba.nacos.client.naming.selector.NamingSelectorWrapper; import com.alibaba.nacos.client.naming.utils.CollectionUtils; import com.alibaba.nacos.client.naming.utils.UtilAndComs; import org.junit.jupiter.api.AfterEach; @@ -46,6 +48,7 @@ import java.util.Collections; import java.util.List; import java.util.Properties; +import static com.alibaba.nacos.client.naming.selector.NamingSelectorFactory.getUniqueClusterString; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -800,8 +803,10 @@ class NacosNamingServiceTest { }; //when client.subscribe(serviceName, listener); + NamingSelectorWrapper wrapper = new NamingSelectorWrapper(serviceName, Constants.DEFAULT_GROUP, Constants.NULL, + NamingSelectorFactory.newClusterSelector(Collections.emptyList()), listener); //then - verify(changeNotifier, times(1)).registerListener(Constants.DEFAULT_GROUP, serviceName, "", listener); + verify(changeNotifier, times(1)).registerListener(Constants.DEFAULT_GROUP, serviceName, wrapper); verify(proxy, times(1)).subscribe(serviceName, Constants.DEFAULT_GROUP, ""); } @@ -815,8 +820,10 @@ class NacosNamingServiceTest { }; //when client.subscribe(serviceName, groupName, listener); + NamingSelectorWrapper wrapper = new NamingSelectorWrapper(serviceName, groupName, Constants.NULL, + NamingSelectorFactory.newClusterSelector(Collections.emptyList()), listener); //then - verify(changeNotifier, times(1)).registerListener(groupName, serviceName, "", listener); + verify(changeNotifier, times(1)).registerListener(groupName, serviceName, wrapper); verify(proxy, times(1)).subscribe(serviceName, groupName, ""); } @@ -830,10 +837,11 @@ class NacosNamingServiceTest { }; //when client.subscribe(serviceName, clusterList, listener); + NamingSelectorWrapper wrapper = new NamingSelectorWrapper(serviceName, Constants.DEFAULT_GROUP, Constants.NULL, + NamingSelectorFactory.newClusterSelector(clusterList), listener); //then - verify(changeNotifier, times(1)).registerListener(Constants.DEFAULT_GROUP, serviceName, "cluster1,cluster2", - listener); - verify(proxy, times(1)).subscribe(serviceName, Constants.DEFAULT_GROUP, "cluster1,cluster2"); + verify(changeNotifier, times(1)).registerListener(Constants.DEFAULT_GROUP, serviceName, wrapper); + verify(proxy, times(1)).subscribe(serviceName, Constants.DEFAULT_GROUP, Constants.NULL); } @Test @@ -847,9 +855,27 @@ class NacosNamingServiceTest { }; //when client.subscribe(serviceName, groupName, clusterList, listener); + NamingSelectorWrapper wrapper = new NamingSelectorWrapper(serviceName, groupName, + getUniqueClusterString(clusterList), NamingSelectorFactory.newClusterSelector(clusterList), listener); //then - verify(changeNotifier, times(1)).registerListener(groupName, serviceName, "cluster1,cluster2", listener); - verify(proxy, times(1)).subscribe(serviceName, groupName, "cluster1,cluster2"); + verify(changeNotifier, times(1)).registerListener(groupName, serviceName, wrapper); + verify(proxy, times(1)).subscribe(serviceName, groupName, Constants.NULL); + } + + @Test + public void testSubscribe5() throws NacosException { + String serviceName = "service1"; + String groupName = "group1"; + EventListener listener = event -> { + + }; + //when + client.subscribe(serviceName, groupName, NamingSelectorFactory.HEALTHY_SELECTOR, listener); + NamingSelectorWrapper wrapper = new NamingSelectorWrapper(serviceName, groupName, Constants.NULL, + NamingSelectorFactory.HEALTHY_SELECTOR, listener); + //then + verify(changeNotifier, times(1)).registerListener(groupName, serviceName, wrapper); + verify(proxy, times(1)).subscribe(serviceName, groupName, Constants.NULL); } @Test @@ -859,7 +885,8 @@ class NacosNamingServiceTest { //when client.subscribe(serviceName, groupName, null); //then - verify(changeNotifier, never()).registerListener(groupName, serviceName, "", null); + verify(changeNotifier, never()).registerListener(groupName, serviceName, + new NamingSelectorWrapper(NamingSelectorFactory.newIpSelector(""), null)); verify(proxy, never()).subscribe(serviceName, groupName, ""); } @@ -871,10 +898,14 @@ class NacosNamingServiceTest { EventListener listener = event -> { }; + when(changeNotifier.isSubscribed(Constants.DEFAULT_GROUP, serviceName)).thenReturn(false); + //when client.unsubscribe(serviceName, listener); //then - verify(changeNotifier, times(1)).deregisterListener(Constants.DEFAULT_GROUP, serviceName, "", listener); - verify(proxy, times(1)).unsubscribe(serviceName, Constants.DEFAULT_GROUP, ""); + NamingSelectorWrapper wrapper = new NamingSelectorWrapper( + NamingSelectorFactory.newClusterSelector(Collections.emptyList()), listener); + verify(changeNotifier, times(1)).deregisterListener(Constants.DEFAULT_GROUP, serviceName, wrapper); + verify(proxy, times(1)).unsubscribe(serviceName, Constants.DEFAULT_GROUP, Constants.NULL); } @Test @@ -885,10 +916,15 @@ class NacosNamingServiceTest { EventListener listener = event -> { }; + when(changeNotifier.isSubscribed(groupName, serviceName)).thenReturn(false); + + //when client.unsubscribe(serviceName, groupName, listener); + NamingSelectorWrapper wrapper = new NamingSelectorWrapper( + NamingSelectorFactory.newClusterSelector(Collections.emptyList()), listener); //then - verify(changeNotifier, times(1)).deregisterListener(groupName, serviceName, "", listener); - verify(proxy, times(1)).unsubscribe(serviceName, groupName, ""); + verify(changeNotifier, times(1)).deregisterListener(groupName, serviceName, wrapper); + verify(proxy, times(1)).unsubscribe(serviceName, groupName, Constants.NULL); } @Test @@ -899,11 +935,15 @@ class NacosNamingServiceTest { EventListener listener = event -> { }; + when(changeNotifier.isSubscribed(Constants.DEFAULT_GROUP, serviceName)).thenReturn(false); + + //when client.unsubscribe(serviceName, clusterList, listener); - //then - verify(changeNotifier, times(1)).deregisterListener(Constants.DEFAULT_GROUP, serviceName, "cluster1,cluster2", + NamingSelectorWrapper wrapper = new NamingSelectorWrapper(NamingSelectorFactory.newClusterSelector(clusterList), listener); - verify(proxy, times(1)).unsubscribe(serviceName, Constants.DEFAULT_GROUP, "cluster1,cluster2"); + //then + verify(changeNotifier, times(1)).deregisterListener(Constants.DEFAULT_GROUP, serviceName, wrapper); + verify(proxy, times(1)).unsubscribe(serviceName, Constants.DEFAULT_GROUP, Constants.NULL); } @Test @@ -915,10 +955,33 @@ class NacosNamingServiceTest { EventListener listener = event -> { }; + when(changeNotifier.isSubscribed(groupName, serviceName)).thenReturn(false); + + //when client.unsubscribe(serviceName, groupName, clusterList, listener); + NamingSelectorWrapper wrapper = new NamingSelectorWrapper(NamingSelectorFactory.newClusterSelector(clusterList), + listener); //then - verify(changeNotifier, times(1)).deregisterListener(groupName, serviceName, "cluster1,cluster2", listener); - verify(proxy, times(1)).unsubscribe(serviceName, groupName, "cluster1,cluster2"); + verify(changeNotifier, times(1)).deregisterListener(groupName, serviceName, wrapper); + verify(proxy, times(1)).unsubscribe(serviceName, groupName, Constants.NULL); + } + + @Test + public void testUnSubscribe5() throws NacosException { + //given + String serviceName = "service1"; + String groupName = "group1"; + EventListener listener = event -> { + + }; + when(changeNotifier.isSubscribed(groupName, serviceName)).thenReturn(false); + + //when + client.unsubscribe(serviceName, groupName, NamingSelectorFactory.HEALTHY_SELECTOR, listener); + NamingSelectorWrapper wrapper = new NamingSelectorWrapper(NamingSelectorFactory.HEALTHY_SELECTOR, listener); + //then + verify(changeNotifier, times(1)).deregisterListener(groupName, serviceName, wrapper); + verify(proxy, times(1)).unsubscribe(serviceName, groupName, Constants.NULL); } @Test diff --git a/client/src/test/java/com/alibaba/nacos/client/naming/event/InstancesChangeEventTest.java b/client/src/test/java/com/alibaba/nacos/client/naming/event/InstancesChangeEventTest.java index 4f22b4af6..e6e7cd3bd 100644 --- a/client/src/test/java/com/alibaba/nacos/client/naming/event/InstancesChangeEventTest.java +++ b/client/src/test/java/com/alibaba/nacos/client/naming/event/InstancesChangeEventTest.java @@ -23,6 +23,7 @@ import java.util.ArrayList; import java.util.List; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; class InstancesChangeEventTest { @@ -35,7 +36,10 @@ class InstancesChangeEventTest { List hosts = new ArrayList<>(); Instance ins = new Instance(); hosts.add(ins); - InstancesChangeEvent event = new InstancesChangeEvent(eventScope, serviceName, groupName, clusters, hosts); + InstancesDiff diff = new InstancesDiff(); + diff.setAddedInstances(hosts); + InstancesChangeEvent event = new InstancesChangeEvent(eventScope, serviceName, groupName, clusters, hosts, + diff); assertEquals(eventScope, event.scope()); assertEquals(serviceName, event.getServiceName()); assertEquals(clusters, event.getClusters()); @@ -43,5 +47,11 @@ class InstancesChangeEventTest { List hosts1 = event.getHosts(); assertEquals(hosts.size(), hosts1.size()); assertEquals(hosts.get(0), hosts1.get(0)); + InstancesDiff diff1 = event.getInstancesDiff(); + assertTrue(diff1.hasDifferent()); + assertEquals(diff.getAddedInstances().size(), diff1.getAddedInstances().size()); + assertEquals(diff.getAddedInstances().get(0), diff.getAddedInstances().get(0)); + assertEquals(diff.getRemovedInstances().size(), diff1.getRemovedInstances().size()); + assertEquals(diff.getModifiedInstances().size(), diff1.getModifiedInstances().size()); } } \ No newline at end of file diff --git a/client/src/test/java/com/alibaba/nacos/client/naming/event/InstancesChangeNotifierTest.java b/client/src/test/java/com/alibaba/nacos/client/naming/event/InstancesChangeNotifierTest.java index b8eeb9496..4509e71e9 100644 --- a/client/src/test/java/com/alibaba/nacos/client/naming/event/InstancesChangeNotifierTest.java +++ b/client/src/test/java/com/alibaba/nacos/client/naming/event/InstancesChangeNotifierTest.java @@ -20,15 +20,22 @@ import com.alibaba.nacos.api.naming.listener.AbstractEventListener; import com.alibaba.nacos.api.naming.listener.EventListener; import com.alibaba.nacos.api.naming.pojo.Instance; import com.alibaba.nacos.api.naming.pojo.ServiceInfo; +import com.alibaba.nacos.api.naming.selector.NamingSelector; +import com.alibaba.nacos.client.naming.selector.DefaultNamingSelector; +import com.alibaba.nacos.client.naming.selector.NamingSelectorFactory; +import com.alibaba.nacos.client.naming.selector.NamingSelectorWrapper; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mockito; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.concurrent.Executor; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; @@ -38,124 +45,123 @@ import static org.mockito.Mockito.when; class InstancesChangeNotifierTest { + private static final String EVENT_SCOPE_CASE = "scope-001"; + + private static final String GROUP_CASE = "a"; + + private static final String SERVICE_NAME_CASE = "b"; + + private static final String CLUSTER_STR_CASE = "c"; + + InstancesChangeNotifier instancesChangeNotifier; + + @BeforeEach + public void setUp() { + instancesChangeNotifier = new InstancesChangeNotifier(EVENT_SCOPE_CASE); + } + @Test void testRegisterListener() { - String eventScope = "scope-001"; - String group = "a"; - String name = "b"; - String clusters = "c"; - InstancesChangeNotifier instancesChangeNotifier = new InstancesChangeNotifier(eventScope); + List clusters = Collections.singletonList(CLUSTER_STR_CASE); EventListener listener = Mockito.mock(EventListener.class); - instancesChangeNotifier.registerListener(group, name, clusters, listener); + NamingSelector selector = NamingSelectorFactory.newClusterSelector(clusters); + NamingSelectorWrapper wrapper = new NamingSelectorWrapper(SERVICE_NAME_CASE, GROUP_CASE, CLUSTER_STR_CASE, + selector, listener); + instancesChangeNotifier.registerListener(GROUP_CASE, SERVICE_NAME_CASE, wrapper); List subscribeServices = instancesChangeNotifier.getSubscribeServices(); assertEquals(1, subscribeServices.size()); - assertEquals(group, subscribeServices.get(0).getGroupName()); - assertEquals(name, subscribeServices.get(0).getName()); - assertEquals(clusters, subscribeServices.get(0).getClusters()); + assertEquals(GROUP_CASE, subscribeServices.get(0).getGroupName()); + assertEquals(SERVICE_NAME_CASE, subscribeServices.get(0).getName()); + assertNull(subscribeServices.get(0).getClusters()); List hosts = new ArrayList<>(); Instance ins = new Instance(); hosts.add(ins); - InstancesChangeEvent event = new InstancesChangeEvent(eventScope, name, group, clusters, hosts); + InstancesDiff diff = new InstancesDiff(); + diff.setAddedInstances(hosts); + InstancesChangeEvent event = new InstancesChangeEvent(EVENT_SCOPE_CASE, SERVICE_NAME_CASE, GROUP_CASE, + CLUSTER_STR_CASE, hosts, diff); assertTrue(instancesChangeNotifier.scopeMatches(event)); } @Test void testDeregisterListener() { - String eventScope = "scope-001"; - String group = "a"; - String name = "b"; - String clusters = "c"; - InstancesChangeNotifier instancesChangeNotifier = new InstancesChangeNotifier(eventScope); + List clusters = Collections.singletonList(CLUSTER_STR_CASE); EventListener listener = Mockito.mock(EventListener.class); - instancesChangeNotifier.registerListener(group, name, clusters, listener); + NamingSelector selector = NamingSelectorFactory.newClusterSelector(clusters); + NamingSelectorWrapper wrapper = new NamingSelectorWrapper(selector, listener); + instancesChangeNotifier.registerListener(GROUP_CASE, SERVICE_NAME_CASE, wrapper); List subscribeServices = instancesChangeNotifier.getSubscribeServices(); assertEquals(1, subscribeServices.size()); - instancesChangeNotifier.deregisterListener(group, name, clusters, listener); + instancesChangeNotifier.deregisterListener(GROUP_CASE, SERVICE_NAME_CASE, wrapper); List subscribeServices2 = instancesChangeNotifier.getSubscribeServices(); assertEquals(0, subscribeServices2.size()); - - instancesChangeNotifier.deregisterListener(group, name, clusters, listener); - assertEquals(0, subscribeServices2.size()); } @Test void testIsSubscribed() { - String eventScope = "scope-001"; - String group = "a"; - String name = "b"; - String clusters = "c"; - InstancesChangeNotifier instancesChangeNotifier = new InstancesChangeNotifier(eventScope); + List clusters = Collections.singletonList(CLUSTER_STR_CASE); EventListener listener = Mockito.mock(EventListener.class); - assertFalse(instancesChangeNotifier.isSubscribed(group, name, clusters)); + NamingSelector selector = NamingSelectorFactory.newClusterSelector(clusters); + assertFalse(instancesChangeNotifier.isSubscribed(GROUP_CASE, SERVICE_NAME_CASE)); - instancesChangeNotifier.registerListener(group, name, clusters, listener); - assertTrue(instancesChangeNotifier.isSubscribed(group, name, clusters)); + NamingSelectorWrapper wrapper = new NamingSelectorWrapper(SERVICE_NAME_CASE, GROUP_CASE, CLUSTER_STR_CASE, + selector, listener); + instancesChangeNotifier.registerListener(GROUP_CASE, SERVICE_NAME_CASE, wrapper); + assertTrue(instancesChangeNotifier.isSubscribed(GROUP_CASE, SERVICE_NAME_CASE)); } @Test void testOnEvent() { - String eventScope = "scope-001"; - String group = "a"; - String name = "b"; - String clusters = "c"; - InstancesChangeNotifier instancesChangeNotifier = new InstancesChangeNotifier(eventScope); + List clusters = Collections.singletonList(CLUSTER_STR_CASE); + NamingSelector selector = NamingSelectorFactory.newClusterSelector(clusters); EventListener listener = Mockito.mock(EventListener.class); - instancesChangeNotifier.registerListener(group, name, clusters, listener); - InstancesChangeEvent event1 = Mockito.mock(InstancesChangeEvent.class); - when(event1.getClusters()).thenReturn(clusters); - when(event1.getGroupName()).thenReturn(group); - when(event1.getServiceName()).thenReturn(name); - + NamingSelectorWrapper wrapper = new NamingSelectorWrapper(SERVICE_NAME_CASE, GROUP_CASE, CLUSTER_STR_CASE, + selector, listener); + instancesChangeNotifier.registerListener(GROUP_CASE, SERVICE_NAME_CASE, wrapper); + Instance instance = new Instance(); + InstancesDiff diff = new InstancesDiff(null, Collections.singletonList(instance), null); + instance.setClusterName(CLUSTER_STR_CASE); + InstancesChangeEvent event1 = new InstancesChangeEvent(null, SERVICE_NAME_CASE, GROUP_CASE, CLUSTER_STR_CASE, + Collections.emptyList(), diff); instancesChangeNotifier.onEvent(event1); Mockito.verify(listener, times(1)).onEvent(any()); } @Test void testOnEventWithoutListener() { - String eventScope = "scope-001"; - String group = "a"; - String name = "b"; - String clusters = "c"; InstancesChangeEvent event1 = Mockito.mock(InstancesChangeEvent.class); - when(event1.getClusters()).thenReturn(clusters); - when(event1.getGroupName()).thenReturn(group); - when(event1.getServiceName()).thenReturn(name); + when(event1.getClusters()).thenReturn(CLUSTER_STR_CASE); + when(event1.getGroupName()).thenReturn(GROUP_CASE); + when(event1.getServiceName()).thenReturn(SERVICE_NAME_CASE); EventListener listener = Mockito.mock(EventListener.class); - InstancesChangeNotifier instancesChangeNotifier = new InstancesChangeNotifier(eventScope); - instancesChangeNotifier.registerListener(group, name + "c", clusters, listener); + instancesChangeNotifier.registerListener(GROUP_CASE, SERVICE_NAME_CASE + "c", new NamingSelectorWrapper( + NamingSelectorFactory.newClusterSelector(Collections.singletonList(CLUSTER_STR_CASE)), listener)); instancesChangeNotifier.onEvent(event1); Mockito.verify(listener, never()).onEvent(any()); } @Test void testOnEventByExecutor() { - String eventScope = "scope-001"; - String group = "a"; - String name = "b"; - String clusters = "c"; - InstancesChangeNotifier instancesChangeNotifier = new InstancesChangeNotifier(eventScope); AbstractEventListener listener = Mockito.mock(AbstractEventListener.class); Executor executor = mock(Executor.class); when(listener.getExecutor()).thenReturn(executor); - instancesChangeNotifier.registerListener(group, name, clusters, listener); - InstancesChangeEvent event1 = Mockito.mock(InstancesChangeEvent.class); - when(event1.getClusters()).thenReturn(clusters); - when(event1.getGroupName()).thenReturn(group); - when(event1.getServiceName()).thenReturn(name); - - instancesChangeNotifier.onEvent(event1); + instancesChangeNotifier.registerListener(GROUP_CASE, SERVICE_NAME_CASE, + new NamingSelectorWrapper(new DefaultNamingSelector(instance -> true), listener)); + InstancesDiff instancesDiff = new InstancesDiff(); + instancesDiff.setRemovedInstances(Collections.singletonList(new Instance())); + InstancesChangeEvent event = new InstancesChangeEvent(EVENT_SCOPE_CASE, SERVICE_NAME_CASE, GROUP_CASE, + CLUSTER_STR_CASE, new ArrayList<>(), instancesDiff); + instancesChangeNotifier.onEvent(event); Mockito.verify(executor).execute(any()); } @Test void testSubscribeType() { - String eventScope = "scope-001"; - InstancesChangeNotifier instancesChangeNotifier = new InstancesChangeNotifier(eventScope); assertEquals(InstancesChangeEvent.class, instancesChangeNotifier.subscribeType()); } } \ No newline at end of file diff --git a/client/src/test/java/com/alibaba/nacos/client/naming/event/InstancesDiffTest.java b/client/src/test/java/com/alibaba/nacos/client/naming/event/InstancesDiffTest.java new file mode 100644 index 000000000..0842558a8 --- /dev/null +++ b/client/src/test/java/com/alibaba/nacos/client/naming/event/InstancesDiffTest.java @@ -0,0 +1,109 @@ +/* + * 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.client.naming.event; + +import com.alibaba.nacos.api.naming.pojo.Instance; +import com.alibaba.nacos.api.naming.pojo.builder.InstanceBuilder; +import org.junit.Assert; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Random; + +public class InstancesDiffTest { + + @Test + public void testGetDiff() { + String serviceName = "testService"; + Instance addedIns = InstanceBuilder.newBuilder().setServiceName(serviceName).setClusterName("a").build(); + Instance removedIns = InstanceBuilder.newBuilder().setServiceName(serviceName).setClusterName("b").build(); + Instance modifiedIns = InstanceBuilder.newBuilder().setServiceName(serviceName).setClusterName("c").build(); + + InstancesDiff instancesDiff = new InstancesDiff(); + instancesDiff.setAddedInstances(Collections.singletonList(addedIns)); + instancesDiff.setRemovedInstances(Collections.singletonList(removedIns)); + instancesDiff.setModifiedInstances(Collections.singletonList(modifiedIns)); + + Assert.assertTrue(instancesDiff.hasDifferent()); + Assert.assertTrue(instancesDiff.isAdded()); + Assert.assertTrue(instancesDiff.isRemoved()); + Assert.assertTrue(instancesDiff.isModified()); + Assert.assertEquals(addedIns, instancesDiff.getAddedInstances().get(0)); + Assert.assertEquals(removedIns, instancesDiff.getRemovedInstances().get(0)); + Assert.assertEquals(modifiedIns, instancesDiff.getModifiedInstances().get(0)); + } + + @Test + public void testWithFullConstructor() { + Random random = new Random(); + int addedCount = random.nextInt(32) + 1; + int removedCount = random.nextInt(32) + 1; + int modifiedCount = random.nextInt(32) + 1; + InstancesDiff instancesDiff = new InstancesDiff(getInstanceList(addedCount), getInstanceList(removedCount), + getInstanceList(modifiedCount)); + + Assert.assertTrue(instancesDiff.hasDifferent()); + Assert.assertTrue(instancesDiff.isAdded()); + Assert.assertTrue(instancesDiff.isRemoved()); + Assert.assertTrue(instancesDiff.isModified()); + Assert.assertEquals(addedCount, instancesDiff.getAddedInstances().size()); + Assert.assertEquals(removedCount, instancesDiff.getRemovedInstances().size()); + Assert.assertEquals(modifiedCount, instancesDiff.getModifiedInstances().size()); + instancesDiff.getAddedInstances().clear(); + instancesDiff.getRemovedInstances().clear(); + instancesDiff.getModifiedInstances().clear(); + Assert.assertFalse(instancesDiff.hasDifferent()); + Assert.assertFalse(instancesDiff.hasDifferent()); + Assert.assertFalse(instancesDiff.isAdded()); + Assert.assertFalse(instancesDiff.isRemoved()); + Assert.assertFalse(instancesDiff.isModified()); + } + + @Test + public void testWithNoConstructor() { + Random random = new Random(); + int addedCount = random.nextInt(32) + 1; + int removedCount = random.nextInt(32) + 1; + int modifiedCount = random.nextInt(32) + 1; + InstancesDiff instancesDiff = new InstancesDiff(); + instancesDiff.setAddedInstances(getInstanceList(addedCount)); + instancesDiff.setRemovedInstances(getInstanceList(removedCount)); + instancesDiff.setModifiedInstances(getInstanceList(modifiedCount)); + + Assert.assertTrue(instancesDiff.hasDifferent()); + Assert.assertEquals(addedCount, instancesDiff.getAddedInstances().size()); + Assert.assertEquals(removedCount, instancesDiff.getRemovedInstances().size()); + Assert.assertEquals(modifiedCount, instancesDiff.getModifiedInstances().size()); + instancesDiff.getAddedInstances().clear(); + instancesDiff.getRemovedInstances().clear(); + instancesDiff.getModifiedInstances().clear(); + Assert.assertFalse(instancesDiff.hasDifferent()); + Assert.assertFalse(instancesDiff.isAdded()); + Assert.assertFalse(instancesDiff.isRemoved()); + Assert.assertFalse(instancesDiff.isModified()); + } + + private static List getInstanceList(int count) { + ArrayList list = new ArrayList<>(count); + for (int i = 0; i < count; i++) { + list.add(new Instance()); + } + return list; + } +} diff --git a/client/src/test/java/com/alibaba/nacos/client/naming/listener/NamingChangeEventTest.java b/client/src/test/java/com/alibaba/nacos/client/naming/listener/NamingChangeEventTest.java new file mode 100644 index 000000000..66278c13a --- /dev/null +++ b/client/src/test/java/com/alibaba/nacos/client/naming/listener/NamingChangeEventTest.java @@ -0,0 +1,136 @@ +/* + * 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.client.naming.listener; + +import com.alibaba.nacos.api.naming.pojo.Instance; +import com.alibaba.nacos.client.naming.event.InstancesDiff; +import org.junit.Before; +import org.junit.Test; + +import java.util.Arrays; +import java.util.Collections; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertFalse; + +public class NamingChangeEventTest { + + private MockNamingEventListener eventListener; + + private InstancesDiff instancesDiff; + + @Before + public void setUp() throws Exception { + eventListener = new MockNamingEventListener(); + instancesDiff = new InstancesDiff(); + instancesDiff.setAddedInstances(Arrays.asList(new Instance(), new Instance(), new Instance())); + instancesDiff.setRemovedInstances(Arrays.asList(new Instance(), new Instance())); + instancesDiff.setModifiedInstances(Arrays.asList(new Instance())); + } + + @Test + public void testNamingChangeEventWithSimpleConstructor() { + NamingChangeEvent event = new NamingChangeEvent("serviceName", Collections.EMPTY_LIST, instancesDiff); + assertEquals("serviceName", event.getServiceName()); + assertNull(event.getGroupName()); + assertNull(event.getClusters()); + assertTrue(event.getInstances().isEmpty()); + assertTrue(event.isAdded()); + assertEquals(3, event.getAddedInstances().size()); + assertTrue(event.isRemoved()); + assertEquals(2, event.getRemovedInstances().size()); + assertTrue(event.isModified()); + assertEquals(1, event.getModifiedInstances().size()); + eventListener.onEvent(event); + assertNull(event.getServiceName()); + assertNull(event.getGroupName()); + assertNull(event.getClusters()); + assertNull(event.getInstances()); + assertFalse(event.isAdded()); + assertEquals(0, event.getAddedInstances().size()); + assertFalse(event.isRemoved()); + assertEquals(0, event.getRemovedInstances().size()); + assertFalse(event.isModified()); + assertEquals(0, event.getRemovedInstances().size()); + } + + @Test + public void testNamingChangeEventWithFullConstructor() { + NamingChangeEvent event = new NamingChangeEvent("serviceName", "group", "clusters", Collections.EMPTY_LIST, + instancesDiff); + assertEquals("serviceName", event.getServiceName()); + assertEquals("group", event.getGroupName()); + assertEquals("clusters", event.getClusters()); + assertTrue(event.getInstances().isEmpty()); + assertTrue(event.isAdded()); + assertEquals(3, event.getAddedInstances().size()); + assertTrue(event.isRemoved()); + assertEquals(2, event.getRemovedInstances().size()); + assertTrue(event.isModified()); + assertEquals(1, event.getModifiedInstances().size()); + eventListener.onEvent(event); + assertNull(event.getServiceName()); + assertNull(event.getGroupName()); + assertNull(event.getClusters()); + assertNull(event.getInstances()); + assertFalse(event.isAdded()); + assertEquals(0, event.getAddedInstances().size()); + assertFalse(event.isRemoved()); + assertEquals(0, event.getRemovedInstances().size()); + assertFalse(event.isModified()); + assertEquals(0, event.getRemovedInstances().size()); + } + + @Test + public void testGetChanges() { + NamingChangeEvent event = new NamingChangeEvent("serviceName", Collections.EMPTY_LIST, instancesDiff); + assertTrue(event.isAdded()); + assertEquals(3, event.getAddedInstances().size()); + event.getAddedInstances().clear(); + assertFalse(event.isAdded()); + assertEquals(0, event.getAddedInstances().size()); + + assertTrue(event.isRemoved()); + assertEquals(2, event.getRemovedInstances().size()); + event.getRemovedInstances().clear(); + assertFalse(event.isRemoved()); + assertEquals(0, event.getRemovedInstances().size()); + + assertTrue(event.isModified()); + assertEquals(1, event.getModifiedInstances().size()); + event.getModifiedInstances().clear(); + assertFalse(event.isModified()); + assertEquals(0, event.getRemovedInstances().size()); + } + + private static class MockNamingEventListener extends AbstractNamingChangeListener { + + @Override + public void onChange(NamingChangeEvent event) { + assertNull(getExecutor()); + event.setServiceName(null); + event.setGroupName(null); + event.setClusters(null); + event.setInstances(null); + event.getAddedInstances().clear(); + event.getRemovedInstances().clear(); + event.getModifiedInstances().clear(); + } + } +} diff --git a/client/src/test/java/com/alibaba/nacos/client/naming/selector/DefaultNamingSelectorTest.java b/client/src/test/java/com/alibaba/nacos/client/naming/selector/DefaultNamingSelectorTest.java new file mode 100644 index 000000000..7860d5112 --- /dev/null +++ b/client/src/test/java/com/alibaba/nacos/client/naming/selector/DefaultNamingSelectorTest.java @@ -0,0 +1,69 @@ +/* + * 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.client.naming.selector; + +import com.alibaba.nacos.api.naming.pojo.Instance; +import com.alibaba.nacos.api.naming.selector.NamingContext; +import com.alibaba.nacos.api.naming.selector.NamingResult; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class DefaultNamingSelectorTest { + + @Test + public void testSelect() { + DefaultNamingSelector namingSelector = new DefaultNamingSelector(Instance::isHealthy); + Random random = new Random(); + int total = random.nextInt(32) + 1; + int health = random.nextInt(total); + + NamingContext namingContext = getMockNamingContext(total, health); + NamingResult result = namingSelector.select(namingContext); + + assertEquals(health, result.getResult().size()); + result.getResult().forEach(ins -> assertTrue(ins.isHealthy())); + } + + private NamingContext getMockNamingContext(int total, int health) { + NamingContext namingContext = mock(NamingContext.class); + when(namingContext.getInstances()).thenReturn(getInstance(total, health)); + return namingContext; + } + + private List getInstance(int total, int health) { + List list = new ArrayList<>(total); + for (int i = 0; i < total; i++) { + Instance instance = new Instance(); + instance.setHealthy(false); + list.add(instance); + } + + for (int i = 0; i < health; i++) { + list.get(i).setHealthy(true); + } + + return list; + } +} diff --git a/client/src/test/java/com/alibaba/nacos/client/naming/selector/NamingListenerInvokerTest.java b/client/src/test/java/com/alibaba/nacos/client/naming/selector/NamingListenerInvokerTest.java new file mode 100644 index 000000000..0614bca0c --- /dev/null +++ b/client/src/test/java/com/alibaba/nacos/client/naming/selector/NamingListenerInvokerTest.java @@ -0,0 +1,76 @@ +/* + * 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.client.naming.selector; + +import com.alibaba.nacos.api.naming.listener.AbstractEventListener; +import com.alibaba.nacos.api.naming.listener.EventListener; +import com.alibaba.nacos.api.naming.listener.NamingEvent; +import com.alibaba.nacos.client.naming.event.InstancesDiff; +import com.alibaba.nacos.client.naming.listener.AbstractNamingChangeListener; +import com.alibaba.nacos.client.naming.listener.NamingChangeEvent; +import org.junit.Test; + +import java.util.Collections; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +public class NamingListenerInvokerTest { + + @Test + public void testEventListener() { + EventListener listener = mock(EventListener.class); + NamingListenerInvoker listenerInvoker = new NamingListenerInvoker(listener); + NamingEvent event = new NamingEvent("serviceName", Collections.emptyList()); + listenerInvoker.invoke(event); + verify(listener).onEvent(event); + } + + @Test + public void testAbstractEventListener() { + AbstractEventListener listener = mock(AbstractEventListener.class); + NamingListenerInvoker listenerInvoker = new NamingListenerInvoker(listener); + NamingEvent event = new NamingEvent("serviceName", Collections.emptyList()); + listenerInvoker.invoke(event); + verify(listener).getExecutor(); + } + + @Test + public void testAbstractNamingChaneEventListener() { + AbstractNamingChangeListener listener = spy(AbstractNamingChangeListener.class); + NamingListenerInvoker listenerInvoker = new NamingListenerInvoker(listener); + NamingChangeEvent event = new NamingChangeEvent("serviceName", Collections.emptyList(), new InstancesDiff()); + listenerInvoker.invoke(event); + verify(listener).onChange(event); + } + + @Test + public void testEquals() { + EventListener listener1 = mock(EventListener.class); + EventListener listener2 = mock(EventListener.class); + NamingListenerInvoker invoker1 = new NamingListenerInvoker(listener1); + NamingListenerInvoker invoker2 = new NamingListenerInvoker(listener1); + NamingListenerInvoker invoker3 = new NamingListenerInvoker(listener2); + assertEquals(invoker1.hashCode(), invoker2.hashCode()); + assertEquals(invoker1, invoker2); + assertNotEquals(invoker1.hashCode(), invoker3.hashCode()); + assertNotEquals(invoker1, invoker3); + } +} diff --git a/client/src/test/java/com/alibaba/nacos/client/naming/selector/NamingSelectorFactoryTest.java b/client/src/test/java/com/alibaba/nacos/client/naming/selector/NamingSelectorFactoryTest.java new file mode 100644 index 000000000..af4290d64 --- /dev/null +++ b/client/src/test/java/com/alibaba/nacos/client/naming/selector/NamingSelectorFactoryTest.java @@ -0,0 +1,178 @@ +/* + * 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.client.naming.selector; + +import com.alibaba.nacos.api.naming.pojo.Instance; +import com.alibaba.nacos.api.naming.selector.NamingContext; +import com.alibaba.nacos.api.naming.selector.NamingResult; +import com.alibaba.nacos.api.naming.selector.NamingSelector; +import org.junit.Test; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class NamingSelectorFactoryTest { + + @Test + public void testNewClusterSelector1() { + Instance ins1 = new Instance(); + ins1.setClusterName("a"); + Instance ins2 = new Instance(); + ins2.setClusterName("b"); + Instance ins3 = new Instance(); + ins3.setClusterName("c"); + + NamingContext namingContext = mock(NamingContext.class); + when(namingContext.getInstances()).thenReturn(Arrays.asList(ins1, ins2, ins3)); + + NamingSelector namingSelector1 = NamingSelectorFactory.newClusterSelector(Collections.singletonList("a")); + NamingResult result1 = namingSelector1.select(namingContext); + assertEquals("a", result1.getResult().get(0).getClusterName()); + + NamingSelector namingSelector2 = NamingSelectorFactory.newClusterSelector(Collections.emptyList()); + NamingResult result2 = namingSelector2.select(namingContext); + assertEquals(3, result2.getResult().size()); + } + + @Test + public void testNewClusterSelector2() { + NamingSelector namingSelector1 = NamingSelectorFactory.newClusterSelector(Arrays.asList("a", "b", "c")); + NamingSelector namingSelector2 = NamingSelectorFactory.newClusterSelector(Arrays.asList("c", "b", "a")); + NamingSelector namingSelector3 = NamingSelectorFactory.newClusterSelector(Arrays.asList("a", "b", "c", "c")); + NamingSelector namingSelector4 = NamingSelectorFactory.newClusterSelector(Arrays.asList("d", "e")); + + assertEquals(namingSelector1, namingSelector2); + assertEquals(namingSelector1, namingSelector3); + assertNotEquals(namingSelector1, namingSelector4); + } + + @Test + public void testNewIpSelector() { + Instance ins1 = new Instance(); + ins1.setIp("172.18.137.120"); + Instance ins2 = new Instance(); + ins2.setIp("172.18.137.121"); + Instance ins3 = new Instance(); + ins3.setIp("172.18.136.111"); + + NamingContext namingContext = mock(NamingContext.class); + when(namingContext.getInstances()).thenReturn(Arrays.asList(ins1, ins2, ins3)); + + NamingSelector ipSelector = NamingSelectorFactory.newIpSelector("^172\\.18\\.137.*"); + NamingResult result = ipSelector.select(namingContext); + List list = result.getResult(); + + assertEquals(2, list.size()); + assertEquals(ins1.getIp(), list.get(0).getIp()); + assertEquals(ins2.getIp(), list.get(1).getIp()); + } + + @Test + public void testNewMetadataSelector() { + Instance ins1 = new Instance(); + ins1.addMetadata("a", "1"); + ins1.addMetadata("b", "2"); + Instance ins2 = new Instance(); + ins2.addMetadata("a", "1"); + Instance ins3 = new Instance(); + ins3.addMetadata("b", "2"); + + NamingContext namingContext = mock(NamingContext.class); + when(namingContext.getInstances()).thenReturn(Arrays.asList(ins1, ins2, ins3)); + + NamingSelector metadataSelector = NamingSelectorFactory.newMetadataSelector(new HashMap() { + { + put("a", "1"); + put("b", "2"); + } + }); + List result = metadataSelector.select(namingContext).getResult(); + + assertEquals(1, result.size()); + assertEquals(ins1, result.get(0)); + } + + @Test + public void testNewMetadataSelector2() { + Instance ins1 = new Instance(); + ins1.addMetadata("a", "1"); + ins1.addMetadata("c", "3"); + Instance ins2 = new Instance(); + ins2.addMetadata("b", "2"); + Instance ins3 = new Instance(); + ins3.addMetadata("c", "3"); + + NamingContext namingContext = mock(NamingContext.class); + when(namingContext.getInstances()).thenReturn(Arrays.asList(ins1, ins2, ins3)); + + NamingSelector metadataSelector = NamingSelectorFactory.newMetadataSelector(new HashMap() { + { + put("a", "1"); + put("b", "2"); + } + }, true); + List result = metadataSelector.select(namingContext).getResult(); + + assertEquals(2, result.size()); + assertEquals(ins1, result.get(0)); + assertEquals(ins2, result.get(1)); + } + + @Test + public void testHealthSelector() { + Instance ins1 = new Instance(); + Instance ins2 = new Instance(); + Instance ins3 = new Instance(); + ins3.setHealthy(false); + + NamingContext namingContext = mock(NamingContext.class); + when(namingContext.getInstances()).thenReturn(Arrays.asList(ins1, ins2, ins3)); + + List result = NamingSelectorFactory.HEALTHY_SELECTOR.select(namingContext).getResult(); + + assertEquals(2, result.size()); + assertTrue(result.contains(ins1)); + assertTrue(result.contains(ins2)); + assertTrue(result.get(0).isHealthy()); + assertTrue(result.get(1).isHealthy()); + } + + @Test + public void testEmptySelector() { + Instance ins1 = new Instance(); + Instance ins2 = new Instance(); + Instance ins3 = new Instance(); + + NamingContext namingContext = mock(NamingContext.class); + when(namingContext.getInstances()).thenReturn(Arrays.asList(ins1, ins2, ins3)); + + List result = NamingSelectorFactory.EMPTY_SELECTOR.select(namingContext).getResult(); + + assertEquals(3, result.size()); + assertTrue(result.contains(ins1)); + assertTrue(result.contains(ins2)); + assertTrue(result.contains(ins3)); + } +} diff --git a/client/src/test/java/com/alibaba/nacos/client/naming/selector/NamingSelectorWrapperTest.java b/client/src/test/java/com/alibaba/nacos/client/naming/selector/NamingSelectorWrapperTest.java new file mode 100644 index 000000000..205a662af --- /dev/null +++ b/client/src/test/java/com/alibaba/nacos/client/naming/selector/NamingSelectorWrapperTest.java @@ -0,0 +1,103 @@ +/* + * 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.client.naming.selector; + +import com.alibaba.nacos.api.naming.listener.EventListener; +import com.alibaba.nacos.api.naming.pojo.Instance; +import com.alibaba.nacos.api.naming.selector.NamingSelector; +import com.alibaba.nacos.client.naming.event.InstancesChangeEvent; +import com.alibaba.nacos.client.naming.event.InstancesDiff; +import com.alibaba.nacos.client.naming.listener.NamingChangeEvent; +import org.junit.Test; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +public class NamingSelectorWrapperTest { + + @Test + public void testEquals() { + EventListener listener = mock(EventListener.class); + NamingSelector selector1 = mock(NamingSelector.class); + NamingSelector selector2 = mock(NamingSelector.class); + NamingSelectorWrapper sw1 = new NamingSelectorWrapper(selector1, listener); + NamingSelectorWrapper sw2 = new NamingSelectorWrapper(selector2, listener); + NamingSelectorWrapper sw3 = new NamingSelectorWrapper(selector1, listener); + + assertNotEquals(sw1.hashCode(), sw2.hashCode()); + assertEquals(sw1.hashCode(), sw3.hashCode()); + assertNotEquals(sw1, sw2); + assertEquals(sw1, sw3); + + Set set = new HashSet<>(); + set.add(sw1); + assertFalse(set.contains(sw2)); + assertTrue(set.contains(sw3)); + assertTrue(set.add(sw2)); + assertFalse(set.add(sw3)); + assertTrue(set.remove(sw3)); + + assertEquals(sw1, new NamingSelectorWrapper("a", "b", "c", selector1, listener)); + } + + @Test + public void testSelectable() { + NamingSelectorWrapper selectorWrapper = new NamingSelectorWrapper(null, null); + assertFalse(selectorWrapper.isSelectable(null)); + InstancesChangeEvent event1 = new InstancesChangeEvent(null, null, null, null, null, null); + assertFalse(selectorWrapper.isSelectable(event1)); + InstancesChangeEvent event2 = new InstancesChangeEvent(null, null, null, null, null, new InstancesDiff()); + assertFalse(selectorWrapper.isSelectable(event2)); + InstancesChangeEvent event3 = new InstancesChangeEvent(null, null, null, null, Collections.emptyList(), null); + assertFalse(selectorWrapper.isSelectable(event3)); + InstancesChangeEvent event4 = new InstancesChangeEvent(null, null, null, null, Collections.emptyList(), + new InstancesDiff()); + assertTrue(selectorWrapper.isSelectable(event4)); + } + + @Test + public void testCallable() { + NamingSelectorWrapper selectorWrapper = new NamingSelectorWrapper(null, null); + InstancesDiff instancesDiff = new InstancesDiff(null, Collections.singletonList(new Instance()), null); + NamingChangeEvent changeEvent = new NamingChangeEvent("serviceName", Collections.emptyList(), instancesDiff); + assertTrue(selectorWrapper.isCallable(changeEvent)); + changeEvent.getRemovedInstances().clear(); + assertFalse(selectorWrapper.isCallable(changeEvent)); + } + + @Test + public void testNotifyListener() { + EventListener listener = mock(EventListener.class); + NamingSelectorWrapper selectorWrapper = new NamingSelectorWrapper( + new DefaultNamingSelector(Instance::isHealthy), listener); + InstancesDiff diff = new InstancesDiff(null, Collections.singletonList(new Instance()), null); + InstancesChangeEvent event = new InstancesChangeEvent(null, "serviceName", "groupName", "clusters", + Collections.emptyList(), diff); + selectorWrapper.notifyListener(event); + verify(listener).onEvent(argThat(Objects::nonNull)); + } +} diff --git a/client/src/test/java/com/alibaba/nacos/client/selector/SelectorManagerTest.java b/client/src/test/java/com/alibaba/nacos/client/selector/SelectorManagerTest.java new file mode 100644 index 000000000..dd15a7ca7 --- /dev/null +++ b/client/src/test/java/com/alibaba/nacos/client/selector/SelectorManagerTest.java @@ -0,0 +1,83 @@ +/* + * 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.client.selector; + +import com.alibaba.nacos.client.naming.selector.NamingSelectorWrapper; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import java.util.Set; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; + +public class SelectorManagerTest { + + @Test + public void testCurd() { + SelectorManager selectorManager = new SelectorManager<>(); + String subId = "subId"; + NamingSelectorWrapper sw = mock(NamingSelectorWrapper.class); + selectorManager.addSelectorWrapper(subId, sw); + assertTrue(selectorManager.getSelectorWrappers(subId).contains(sw)); + selectorManager.removeSelectorWrapper(subId, sw); + assertTrue(selectorManager.getSelectorWrappers(subId).isEmpty()); + } + + @Test + public void testSubInfo() { + SelectorManager selectorManager = new SelectorManager<>(); + List list = new ArrayList<>(); + for (int i = 0; i < 64; i++) { + list.add(generateRandomString(2, 32)); + } + + for (String subId : list) { + selectorManager.addSelectorWrapper(subId, mock(NamingSelectorWrapper.class)); + assertTrue(selectorManager.isSubscribed(subId)); + } + + Set subsSet = selectorManager.getSubscriptions(); + for (String subId : subsSet) { + assertTrue(list.contains(subId)); + } + + for (String subId : list) { + selectorManager.removeSubscription(subId); + assertFalse(selectorManager.isSubscribed(subId)); + } + } + + private static String generateRandomString(int minLength, int maxLength) { + String characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + + Random random = new Random(); + int length = random.nextInt(maxLength - minLength + 1) + minLength; + StringBuilder sb = new StringBuilder(); + + for (int i = 0; i < length; i++) { + int index = random.nextInt(characters.length()); + char randomChar = characters.charAt(index); + sb.append(randomChar); + } + + return sb.toString(); + } +} diff --git a/test/naming-test/src/test/java/com/alibaba/nacos/test/naming/SubscribeSelector_ITCase.java b/test/naming-test/src/test/java/com/alibaba/nacos/test/naming/SubscribeSelector_ITCase.java new file mode 100644 index 000000000..0cc641c2f --- /dev/null +++ b/test/naming-test/src/test/java/com/alibaba/nacos/test/naming/SubscribeSelector_ITCase.java @@ -0,0 +1,167 @@ +/* + * 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.test.naming; + +import com.alibaba.nacos.Nacos; +import com.alibaba.nacos.api.naming.NamingFactory; +import com.alibaba.nacos.api.naming.NamingService; +import com.alibaba.nacos.api.naming.listener.Event; +import com.alibaba.nacos.api.naming.listener.EventListener; +import com.alibaba.nacos.api.naming.listener.NamingEvent; +import com.alibaba.nacos.api.naming.pojo.Instance; +import com.alibaba.nacos.api.naming.selector.NamingSelector; +import com.alibaba.nacos.client.naming.selector.DefaultNamingSelector; +import com.alibaba.nacos.client.naming.selector.NamingSelectorFactory; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.test.context.junit4.SpringRunner; + +import java.util.Collections; +import java.util.List; +import java.util.concurrent.TimeUnit; + +/** + * @author lideyou + */ +@RunWith(SpringRunner.class) +@SpringBootTest(classes = Nacos.class, properties = { + "server.servlet.context-path=/nacos"}, webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) +public class SubscribeSelector_ITCase extends NamingBase { + + private NamingService naming; + + private NamingSelector selector = new DefaultNamingSelector(instance -> instance.getIp().startsWith("172.18.137")); + + @LocalServerPort + private int port; + + @Before + public void init() throws Exception { + instances.clear(); + if (naming == null) { + naming = NamingFactory.createNamingService("127.0.0.1" + ":" + port); + } + } + + private volatile List instances = Collections.emptyList(); + + /** + * Add IP and receive notification. + * + * @throws Exception + */ + @Test(timeout = 10000L) + public void subscribeAdd() throws Exception { + String serviceName = randomDomainName(); + + naming.subscribe(serviceName, selector, new EventListener() { + @Override + public void onEvent(Event event) { + System.out.println(((NamingEvent) event).getServiceName()); + System.out.println(((NamingEvent) event).getInstances()); + instances = ((NamingEvent) event).getInstances(); + } + }); + + naming.registerInstance(serviceName, "172.18.137.1", TEST_PORT); + + while (instances.isEmpty()) { + Thread.sleep(1000L); + } + + Assert.assertTrue(verifyInstanceList(instances, naming.getAllInstances(serviceName))); + } + + /** + * Delete IP and receive notification. + * + * @throws Exception + */ + @Test(timeout = 10000L) + public void subscribeDelete() throws Exception { + String serviceName = randomDomainName(); + naming.registerInstance(serviceName, "172.18.137.1", TEST_PORT, "c1"); + + TimeUnit.SECONDS.sleep(3); + + naming.subscribe(serviceName, selector, new EventListener() { + int index = 0; + + @Override + public void onEvent(Event event) { + instances = ((NamingEvent) event).getInstances(); + if (index == 0) { + index++; + return; + } + System.out.println(((NamingEvent) event).getServiceName()); + System.out.println(((NamingEvent) event).getInstances()); + } + }); + + TimeUnit.SECONDS.sleep(1); + + naming.deregisterInstance(serviceName, "172.18.137.1", TEST_PORT, "c1"); + + while (!instances.isEmpty()) { + Thread.sleep(1000L); + } + + Assert.assertTrue(instances.isEmpty()); + } + + /** + * Add non target IP and do not receive notification. + * + * @throws Exception + */ + @Test + public void subscribeOtherIp() throws Exception { + String serviceName = randomDomainName(); + + naming.subscribe(serviceName, selector, new EventListener() { + int index = 0; + + @Override + public void onEvent(Event event) { + instances = ((NamingEvent) event).getInstances(); + if (index == 0) { + index++; + return; + } + System.out.println(((NamingEvent) event).getServiceName()); + System.out.println(((NamingEvent) event).getInstances()); + } + }); + + naming.registerInstance(serviceName, "1.1.1.1", TEST_PORT, "c1"); + + int i = 0; + while (instances.isEmpty()) { + Thread.sleep(1000L); + if (i++ > 10) { + return; + } + } + + Assert.fail(); + } +} diff --git a/test/naming-test/src/test/java/com/alibaba/nacos/test/naming/Subscribe_ITCase.java b/test/naming-test/src/test/java/com/alibaba/nacos/test/naming/Subscribe_ITCase.java index e97eb66f0..7da0c094c 100644 --- a/test/naming-test/src/test/java/com/alibaba/nacos/test/naming/Subscribe_ITCase.java +++ b/test/naming-test/src/test/java/com/alibaba/nacos/test/naming/Subscribe_ITCase.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package com.alibaba.nacos.test.naming; import com.alibaba.nacos.Nacos; @@ -22,11 +23,12 @@ import com.alibaba.nacos.api.naming.listener.Event; import com.alibaba.nacos.api.naming.listener.EventListener; import com.alibaba.nacos.api.naming.listener.NamingEvent; import com.alibaba.nacos.api.naming.pojo.Instance; +import com.alibaba.nacos.client.naming.listener.AbstractNamingChangeListener; +import com.alibaba.nacos.client.naming.listener.NamingChangeEvent; import com.alibaba.nacos.common.utils.ConcurrentHashSet; import com.alibaba.nacos.common.utils.JacksonUtils; import com.alibaba.nacos.test.base.Params; import com.fasterxml.jackson.databind.JsonNode; - import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -42,6 +44,9 @@ import java.util.Collections; import java.util.List; import java.util.Properties; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.junit.Assert.assertTrue; /** * Created by wangtong.wt on 2018/6/20. @@ -50,14 +55,15 @@ import java.util.concurrent.TimeUnit; * @date 2018/6/20 */ @RunWith(SpringRunner.class) -@SpringBootTest(classes = Nacos.class, properties = {"server.servlet.context-path=/nacos"}, - webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) +@SpringBootTest(classes = Nacos.class, properties = { + "server.servlet.context-path=/nacos"}, webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) public class Subscribe_ITCase extends NamingBase { - + private NamingService naming; + @LocalServerPort private int port; - + @Before public void init() throws Exception { instances.clear(); @@ -71,9 +77,9 @@ public class Subscribe_ITCase extends NamingBase { String url = String.format("http://localhost:%d/", port); this.base = new URL(url); } - + private volatile List instances = Collections.emptyList(); - + /** * 添加IP,收到通知 * @@ -82,7 +88,7 @@ public class Subscribe_ITCase extends NamingBase { @Test(timeout = 4 * TIME_OUT) public void subscribeAdd() throws Exception { String serviceName = randomDomainName(); - + naming.subscribe(serviceName, new EventListener() { @Override public void onEvent(Event event) { @@ -91,16 +97,16 @@ public class Subscribe_ITCase extends NamingBase { instances = ((NamingEvent) event).getInstances(); } }); - + naming.registerInstance(serviceName, "127.0.0.1", TEST_PORT, "c1"); - + while (instances.isEmpty()) { Thread.sleep(1000L); } - - Assert.assertTrue(verifyInstanceList(instances, naming.getAllInstances(serviceName))); + + assertTrue(verifyInstanceList(instances, naming.getAllInstances(serviceName))); } - + /** * 删除IP,收到通知 * @@ -110,12 +116,12 @@ public class Subscribe_ITCase extends NamingBase { public void subscribeDelete() throws Exception { String serviceName = randomDomainName(); naming.registerInstance(serviceName, "127.0.0.1", TEST_PORT, "c1"); - + TimeUnit.SECONDS.sleep(3); - + naming.subscribe(serviceName, new EventListener() { int index = 0; - + @Override public void onEvent(Event event) { if (index == 0) { @@ -127,18 +133,18 @@ public class Subscribe_ITCase extends NamingBase { instances = ((NamingEvent) event).getInstances(); } }); - + TimeUnit.SECONDS.sleep(1); - + naming.deregisterInstance(serviceName, "127.0.0.1", TEST_PORT, "c1"); - + while (!instances.isEmpty()) { Thread.sleep(1000L); } - - Assert.assertTrue(instances.isEmpty()); + + assertTrue(instances.isEmpty()); } - + /** * 添加不可用IP,收到通知 * @@ -147,7 +153,7 @@ public class Subscribe_ITCase extends NamingBase { @Test(timeout = 4 * TIME_OUT) public void subscribeUnhealthy() throws Exception { String serviceName = randomDomainName(); - + naming.subscribe(serviceName, new EventListener() { @Override public void onEvent(Event event) { @@ -156,21 +162,21 @@ public class Subscribe_ITCase extends NamingBase { instances = ((NamingEvent) event).getInstances(); } }); - + naming.registerInstance(serviceName, "1.1.1.1", TEST_PORT, "c1"); - + while (instances.isEmpty()) { Thread.sleep(1000L); } - - Assert.assertTrue(verifyInstanceList(instances, naming.getAllInstances(serviceName))); + + assertTrue(verifyInstanceList(instances, naming.getAllInstances(serviceName))); } @Test(timeout = 4 * TIME_OUT) public void subscribeEmpty() throws Exception { - + String serviceName = randomDomainName(); - + naming.subscribe(serviceName, new EventListener() { @Override public void onEvent(Event event) { @@ -179,32 +185,32 @@ public class Subscribe_ITCase extends NamingBase { instances = ((NamingEvent) event).getInstances(); } }); - + naming.registerInstance(serviceName, "1.1.1.1", TEST_PORT, "c1"); - + while (instances.isEmpty()) { Thread.sleep(1000L); } - - Assert.assertTrue(verifyInstanceList(instances, naming.getAllInstances(serviceName))); - + + assertTrue(verifyInstanceList(instances, naming.getAllInstances(serviceName))); + naming.deregisterInstance(serviceName, "1.1.1.1", TEST_PORT, "c1"); - + while (!instances.isEmpty()) { Thread.sleep(1000L); } - + Assert.assertEquals(0, instances.size()); Assert.assertEquals(0, naming.getAllInstances(serviceName).size()); } - + @Test public void querySubscribers() throws Exception { - + String serviceName = randomDomainName(); - + naming.registerInstance(serviceName, "1.1.1.1", TEST_PORT, "c1"); - + EventListener listener = new EventListener() { @Override public void onEvent(Event event) { @@ -213,30 +219,25 @@ public class Subscribe_ITCase extends NamingBase { instances = ((NamingEvent) event).getInstances(); } }; - + naming.subscribe(serviceName, listener); - + TimeUnit.SECONDS.sleep(3); - + ResponseEntity response = request(NamingBase.NAMING_CONTROLLER_PATH + "/service/subscribers", - Params.newParams() - .appendParam("serviceName", serviceName) - .appendParam("pageNo", "1") - .appendParam("pageSize", "10") - .done(), - String.class, - HttpMethod.GET); - Assert.assertTrue(response.getStatusCode().is2xxSuccessful()); - + Params.newParams().appendParam("serviceName", serviceName).appendParam("pageNo", "1") + .appendParam("pageSize", "10").done(), String.class, HttpMethod.GET); + assertTrue(response.getStatusCode().is2xxSuccessful()); + JsonNode body = JacksonUtils.toObj(response.getBody()); - + Assert.assertEquals(1, body.get("subscribers").size()); - + Properties properties = new Properties(); properties.setProperty("namingRequestTimeout", "300000"); properties.setProperty("serverAddr", "127.0.0.1" + ":" + port); NamingService naming2 = NamingFactory.createNamingService(properties); - + naming2.subscribe(serviceName, new EventListener() { @Override public void onEvent(Event event) { @@ -245,21 +246,16 @@ public class Subscribe_ITCase extends NamingBase { instances = ((NamingEvent) event).getInstances(); } }); - + TimeUnit.SECONDS.sleep(3); - + response = request(NamingBase.NAMING_CONTROLLER_PATH + "/service/subscribers", - Params.newParams() - .appendParam("serviceName", serviceName) - .appendParam("pageNo", "1") - .appendParam("pageSize", "10") - .done(), - String.class, - HttpMethod.GET); - Assert.assertTrue(response.getStatusCode().is2xxSuccessful()); - + Params.newParams().appendParam("serviceName", serviceName).appendParam("pageNo", "1") + .appendParam("pageSize", "10").done(), String.class, HttpMethod.GET); + assertTrue(response.getStatusCode().is2xxSuccessful()); + body = JacksonUtils.toObj(response.getBody()); - + // server will remove duplicate subscriber by ip port service app and so on Assert.assertEquals(1, body.get("subscribers").size()); } @@ -294,7 +290,7 @@ public class Subscribe_ITCase extends NamingBase { concurrentHashSet1.addAll(((NamingEvent) event).getInstances()); } }); - + naming1.registerInstance(serviceName, "1.1.1.1", TEST_PORT, "c1"); while (instances.isEmpty()) { @@ -302,11 +298,73 @@ public class Subscribe_ITCase extends NamingBase { } try { - Assert.assertTrue(verifyInstanceList(instances, naming1.getAllInstances(serviceName))); + assertTrue(verifyInstanceList(instances, naming1.getAllInstances(serviceName))); Assert.assertEquals(0, concurrentHashSet1.size()); } finally { naming1.shutDown(); naming2.shutDown(); } } + + @Test + public void subscribeUsingAbstractNamingChangeListener() throws Exception { + String serviceName = randomDomainName(); + + naming.subscribe(serviceName, new AbstractNamingChangeListener() { + @Override + public void onChange(NamingChangeEvent event) { + System.out.println(event.getServiceName()); + System.out.println(event.getInstances()); + instances = event.getInstances(); + assertTrue(event.isAdded()); + } + }); + + naming.registerInstance(serviceName, "127.0.0.1", TEST_PORT, "c1"); + + while (instances.isEmpty()) { + Thread.sleep(1000L); + } + + assertTrue(verifyInstanceList(instances, naming.getAllInstances(serviceName))); + } + + @Test + public void testListenerFirstCallback() throws Exception { + String serviceName = randomDomainName(); + AtomicInteger count = new AtomicInteger(0); + naming.subscribe(serviceName, new EventListener() { + @Override + public void onEvent(Event event) { + System.out.println(((NamingEvent) event).getServiceName()); + System.out.println(((NamingEvent) event).getInstances()); + instances = ((NamingEvent) event).getInstances(); + count.incrementAndGet(); + } + }); + + naming.registerInstance(serviceName, "127.0.0.1", TEST_PORT, "c1"); + + while (instances.isEmpty()) { + Thread.sleep(1000L); + } + + naming.subscribe(serviceName, new EventListener() { + @Override + public void onEvent(Event event) { + System.out.println(((NamingEvent) event).getServiceName()); + System.out.println(((NamingEvent) event).getInstances()); + instances = ((NamingEvent) event).getInstances(); + count.incrementAndGet(); + } + }); + + int i = 0; + while (count.get() < 2) { + Thread.sleep(1000L); + if (i++ > 10) { + Assert.fail(); + } + } + } } diff --git a/test/naming-test/src/test/java/com/alibaba/nacos/test/naming/Unsubscribe_ITCase.java b/test/naming-test/src/test/java/com/alibaba/nacos/test/naming/Unsubscribe_ITCase.java index 993e2d21a..5d80619bc 100644 --- a/test/naming-test/src/test/java/com/alibaba/nacos/test/naming/Unsubscribe_ITCase.java +++ b/test/naming-test/src/test/java/com/alibaba/nacos/test/naming/Unsubscribe_ITCase.java @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package com.alibaba.nacos.test.naming; import com.alibaba.nacos.Nacos; @@ -22,7 +23,8 @@ import com.alibaba.nacos.api.naming.listener.Event; import com.alibaba.nacos.api.naming.listener.EventListener; import com.alibaba.nacos.api.naming.listener.NamingEvent; import com.alibaba.nacos.api.naming.pojo.Instance; -import com.alibaba.nacos.sys.utils.ApplicationUtils; +import com.alibaba.nacos.api.naming.selector.NamingSelector; +import com.alibaba.nacos.client.naming.selector.DefaultNamingSelector; import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -35,7 +37,9 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; -import static com.alibaba.nacos.test.naming.NamingBase.*; +import static com.alibaba.nacos.test.naming.NamingBase.TEST_PORT; +import static com.alibaba.nacos.test.naming.NamingBase.randomDomainName; +import static com.alibaba.nacos.test.naming.NamingBase.verifyInstanceList; /** * Created by wangtong.wt on 2018/6/20. @@ -44,57 +48,59 @@ import static com.alibaba.nacos.test.naming.NamingBase.*; * @date 2018/6/20 */ @RunWith(SpringRunner.class) -@SpringBootTest(classes = Nacos.class, properties = {"server.servlet.context-path=/nacos"}, - webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) +@SpringBootTest(classes = Nacos.class, properties = { + "server.servlet.context-path=/nacos"}, webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT) public class Unsubscribe_ITCase { - + private NamingService naming; + @LocalServerPort private int port; - + @Before public void init() throws Exception { instances = Collections.emptyList(); if (naming == null) { //TimeUnit.SECONDS.sleep(10); - naming = NamingFactory.createNamingService("127.0.0.1"+":"+port); + naming = NamingFactory.createNamingService("127.0.0.1" + ":" + port); } } - + private volatile List instances = Collections.emptyList(); - + /** * 取消订阅,添加IP,不会收到通知 + * * @throws Exception */ @Test public void unsubscribe() throws Exception { String serviceName = randomDomainName(); - + EventListener listener = new EventListener() { @Override public void onEvent(Event event) { - System.out.println(((NamingEvent)event).getServiceName()); - System.out.println(((NamingEvent)event).getInstances()); - instances = ((NamingEvent)event).getInstances(); + System.out.println(((NamingEvent) event).getServiceName()); + System.out.println(((NamingEvent) event).getInstances()); + instances = ((NamingEvent) event).getInstances(); } }; - + naming.subscribe(serviceName, listener); - + naming.registerInstance(serviceName, "127.0.0.1", TEST_PORT, "c1"); - + while (instances.isEmpty()) { Thread.sleep(1000L); } - + Assert.assertTrue(verifyInstanceList(instances, naming.getAllInstances(serviceName))); - + naming.unsubscribe(serviceName, listener); - + instances = Collections.emptyList(); naming.registerInstance(serviceName, "127.0.0.2", TEST_PORT, "c1"); - + int i = 0; while (instances.isEmpty()) { Thread.sleep(1000L); @@ -102,42 +108,43 @@ public class Unsubscribe_ITCase { return; } } - + Assert.fail(); } - + /** * 取消订阅,在指定cluster添加IP,不会收到通知 + * * @throws Exception */ @Test public void unsubscribeCluster() throws Exception { String serviceName = randomDomainName(); - + EventListener listener = new EventListener() { @Override public void onEvent(Event event) { - System.out.println(((NamingEvent)event).getServiceName()); - System.out.println(((NamingEvent)event).getInstances()); - instances = ((NamingEvent)event).getInstances(); + System.out.println(((NamingEvent) event).getServiceName()); + System.out.println(((NamingEvent) event).getInstances()); + instances = ((NamingEvent) event).getInstances(); } }; - + naming.subscribe(serviceName, Arrays.asList("c1"), listener); - + naming.registerInstance(serviceName, "127.0.0.1", TEST_PORT, "c1"); - + while (instances.isEmpty()) { Thread.sleep(1000L); } - + Assert.assertTrue(verifyInstanceList(instances, naming.getAllInstances(serviceName))); - + naming.unsubscribe(serviceName, Arrays.asList("c1"), listener); - + instances = Collections.emptyList(); naming.registerInstance(serviceName, "127.0.0.2", TEST_PORT, "c1"); - + int i = 0; while (instances.isEmpty()) { Thread.sleep(1000L); @@ -145,8 +152,54 @@ public class Unsubscribe_ITCase { return; } } - + Assert.fail(); } - + + /** + * 取消订阅,添加选择器范围 IP,不会收到通知 + * + * @throws Exception + */ + @Test + public void unsubscribeSelector() throws Exception { + String serviceName = randomDomainName(); + + EventListener listener = new EventListener() { + @Override + public void onEvent(Event event) { + System.out.println(((NamingEvent) event).getServiceName()); + System.out.println(((NamingEvent) event).getInstances()); + instances = ((NamingEvent) event).getInstances(); + } + }; + + NamingSelector selector = new DefaultNamingSelector(instance -> instance.getIp().startsWith("127.0.0")); + + naming.subscribe(serviceName, selector, listener); + + naming.registerInstance(serviceName, "127.0.0.1", TEST_PORT); + + while (instances.isEmpty()) { + Thread.sleep(1000L); + } + + Assert.assertTrue(verifyInstanceList(instances, naming.getAllInstances(serviceName))); + + naming.unsubscribe(serviceName, selector, listener); + + instances = Collections.emptyList(); + naming.registerInstance(serviceName, "127.0.0.2", TEST_PORT); + + int i = 0; + while (instances.isEmpty()) { + Thread.sleep(1000L); + if (i++ > 10) { + return; + } + } + + Assert.fail(); + } + }