diff --git a/sermant-plugins/sermant-springboot-registry/config/config.yaml b/sermant-plugins/sermant-springboot-registry/config/config.yaml index a19a3b0d30..f2b0fa2444 100644 --- a/sermant-plugins/sermant-springboot-registry/config/config.yaml +++ b/sermant-plugins/sermant-springboot-registry/config/config.yaml @@ -7,6 +7,8 @@ sermant.springboot.registry: realmName: www.domain.com # Whether to enable traffic statistics, after it is enabled, the traffic entering the plug-in will be printed every time enableRequestCount: false + # The type of the registry center, currently supports NACOS and ZOOKEEPER + registryCenterType: ZOOKEEPER sermant.springboot.registry.lb: # The load balancer type currently supports RoundRobin、Random、WeightedResponseTime、 @@ -28,3 +30,21 @@ sermant.springboot.registry.lb: # For retry scenarios, for {@link java.util.concurrent.TimeoutException}, whether you need to retry, which is enabled # by default, and this timeout is mostly used in asynchronous scenarios, such as Future, MinimalHttpAsyncClient enableTimeoutExRetry: true + +nacos.config: + # Whether to enable authentication + enableAuth: false + # nacos verifies accounts + username: "" + # nacos verifies password need encrypts + password: "" + # Specify the AES privateKey that encrypts and decrypts the password + privateKey: "" + # Namespace, the nacos configuration creates the id value of the namespace + namespace: "" + # Service instance weight value + weight: 1 + # Cluster name + clusterName: DEFAULT + # Whether it is a temporary node. The value can be true or false + ephemeral: true diff --git a/sermant-plugins/sermant-springboot-registry/springboot-registry-plugin/src/main/java/io/sermant/discovery/config/DiscoveryPluginConfig.java b/sermant-plugins/sermant-springboot-registry/springboot-registry-plugin/src/main/java/io/sermant/discovery/config/DiscoveryPluginConfig.java index 42fa100df2..76d57835b9 100644 --- a/sermant-plugins/sermant-springboot-registry/springboot-registry-plugin/src/main/java/io/sermant/discovery/config/DiscoveryPluginConfig.java +++ b/sermant-plugins/sermant-springboot-registry/springboot-registry-plugin/src/main/java/io/sermant/discovery/config/DiscoveryPluginConfig.java @@ -42,6 +42,11 @@ public class DiscoveryPluginConfig implements PluginConfig { */ private boolean enableRegistry = true; + /** + * Registry type, currently supports NACOS and ZOOKEEPER; + */ + private RegisterType registerType = RegisterType.ZOOKEEPER; + public boolean isEnableRegistry() { return enableRegistry; } @@ -65,4 +70,12 @@ public boolean isEnableRequestCount() { public void setEnableRequestCount(boolean enableRequestCount) { this.enableRequestCount = enableRequestCount; } + + public RegisterType getRegisterType() { + return registerType; + } + + public void setRegisterType(RegisterType registerType) { + this.registerType = registerType; + } } diff --git a/sermant-plugins/sermant-springboot-registry/springboot-registry-plugin/src/main/java/io/sermant/discovery/config/LbConfig.java b/sermant-plugins/sermant-springboot-registry/springboot-registry-plugin/src/main/java/io/sermant/discovery/config/LbConfig.java index e76887ab83..62c7a1f7e9 100644 --- a/sermant-plugins/sermant-springboot-registry/springboot-registry-plugin/src/main/java/io/sermant/discovery/config/LbConfig.java +++ b/sermant-plugins/sermant-springboot-registry/springboot-registry-plugin/src/main/java/io/sermant/discovery/config/LbConfig.java @@ -69,11 +69,6 @@ public class LbConfig implements PluginConfig { */ private String registryAddress = "127.0.0.1:2181"; - /** - * Registry type, currently only ZK is supported - */ - private String registryCenterType = "Zookeeper"; - /** * When it is enabled, if other registries are the same as the registries of this plugin and are registered to the * registry at the same time, the registries that are not registered by the plugin will be automatically excluded @@ -309,14 +304,6 @@ public void setOnlyCurRegisterInstances(boolean onlyCurRegisterInstances) { this.onlyCurRegisterInstances = onlyCurRegisterInstances; } - public String getRegistryCenterType() { - return registryCenterType; - } - - public void setRegistryCenterType(String registryCenterType) { - this.registryCenterType = registryCenterType; - } - public String getZkServerVersion() { return zkServerVersion; } diff --git a/sermant-plugins/sermant-springboot-registry/springboot-registry-plugin/src/main/java/io/sermant/discovery/config/NacosRegisterConfig.java b/sermant-plugins/sermant-springboot-registry/springboot-registry-plugin/src/main/java/io/sermant/discovery/config/NacosRegisterConfig.java new file mode 100644 index 0000000000..f9a7c6940b --- /dev/null +++ b/sermant-plugins/sermant-springboot-registry/springboot-registry-plugin/src/main/java/io/sermant/discovery/config/NacosRegisterConfig.java @@ -0,0 +1,341 @@ +/* + * Copyright (C) 2024-2024 Sermant Authors. All rights reserved. + * + * 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 io.sermant.discovery.config; + +import io.sermant.core.common.LoggerFactory; +import io.sermant.core.config.ConfigManager; +import io.sermant.core.config.common.ConfigTypeKey; +import io.sermant.core.plugin.config.PluginConfig; +import io.sermant.core.plugin.config.PluginConfigManager; +import io.sermant.core.plugin.config.ServiceMeta; +import io.sermant.core.utils.AesUtil; +import io.sermant.core.utils.StringUtils; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Properties; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * nacos Register plug-in configuration + * + * @author xiaozhao + * @since 2024-11-16 + */ +@ConfigTypeKey(value = "nacos.config") +public class NacosRegisterConfig implements PluginConfig { + /** + * logger + */ + private static final Logger LOGGER = LoggerFactory.getLogger(); + + /** + * spring boot zone + * If you do not configure a zone that uses system environment variables by default, + */ + private String zone; + + /** + * Whether it is encrypted or not + */ + private boolean secure = false; + + /** + * Whether to enable authentication + */ + private boolean enableAuth; + + /** + * Nacos authentication Account + */ + private String username; + + /** + * Nacos authentication password + */ + private String password; + + /** + * privateKey + */ + private String privateKey; + + /** + * Typically used to specify the full URL of the Nacos server, including the protocol (such as http or https) + */ + private String endpoint = ""; + + /** + * Namespace + */ + private String namespace; + + /** + * The name of the nacos log file + */ + private String logName; + + /** + * The weight of the service instance + */ + private float weight = 1f; + + /** + * The name of the cluster + */ + private String clusterName = "DEFAULT"; + + /** + * Group + */ + private String group = "DEFAULT_GROUP"; + + /** + * Whether to load the cache at startup + */ + private String namingLoadCacheAtStart = "false"; + + /** + * Namespace AK + */ + private String accessKey; + + /** + * Namespace SK + */ + private String secretKey; + + /** + * Whether the instance is available + */ + private boolean instanceEnabled = true; + + /** + * Whether it is a temporary node + */ + private boolean ephemeral = true; + + /** + * Instance metadata + */ + private Map metadata = new HashMap<>(); + + /** + * Whether it fails to retrieve cached data quickly, if false is not retrieved, it will fail directly + */ + private boolean failureToleranceEnabled = false; + + /** + * Constructor + */ + public NacosRegisterConfig() { + final ServiceMeta serviceMeta = ConfigManager.getConfig(ServiceMeta.class); + if (serviceMeta == null) { + return; + } + zone = serviceMeta.getZone(); + group = serviceMeta.getApplication(); + } + + public String getZone() { + return zone; + } + + public void setZone(String zone) { + this.zone = zone; + } + + public boolean isSecure() { + return secure; + } + + public void setSecure(boolean secure) { + this.secure = secure; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getEndpoint() { + return endpoint; + } + + public void setEndpoint(String endpoint) { + this.endpoint = endpoint; + } + + public String getNamespace() { + return namespace; + } + + public void setNamespace(String namespace) { + this.namespace = namespace; + } + + public String getLogName() { + return logName; + } + + public void setLogName(String logName) { + this.logName = logName; + } + + public float getWeight() { + return weight; + } + + public void setWeight(float weight) { + this.weight = weight; + } + + public String getClusterName() { + return clusterName; + } + + public void setClusterName(String clusterName) { + this.clusterName = clusterName; + } + + public String getGroup() { + return group; + } + + public void setGroup(String group) { + this.group = group; + } + + public String getNamingLoadCacheAtStart() { + return namingLoadCacheAtStart; + } + + public void setNamingLoadCacheAtStart(String namingLoadCacheAtStart) { + this.namingLoadCacheAtStart = namingLoadCacheAtStart; + } + + public String getAccessKey() { + return accessKey; + } + + public void setAccessKey(String accessKey) { + this.accessKey = accessKey; + } + + public String getSecretKey() { + return secretKey; + } + + public void setSecretKey(String secretKey) { + this.secretKey = secretKey; + } + + public boolean isInstanceEnabled() { + return instanceEnabled; + } + + public void setInstanceEnabled(boolean instanceEnabled) { + this.instanceEnabled = instanceEnabled; + } + + public boolean isEphemeral() { + return ephemeral; + } + + public void setEphemeral(boolean ephemeral) { + this.ephemeral = ephemeral; + } + + public Map getMetadata() { + return metadata; + } + + public void setMetadata(Map metadata) { + this.metadata = metadata; + } + + public boolean isFailureToleranceEnabled() { + return failureToleranceEnabled; + } + + public void setFailureToleranceEnabled(boolean failureToleranceEnabled) { + this.failureToleranceEnabled = failureToleranceEnabled; + } + + public String getPrivateKey() { + return privateKey; + } + + public void setPrivateKey(String privateKey) { + this.privateKey = privateKey; + } + + public boolean isEnableAuth() { + return enableAuth; + } + + public void setEnableAuth(boolean enableAuth) { + this.enableAuth = enableAuth; + } + + /** + * Get configuration parameters + * + * @return Properties + */ + public Properties getNacosProperties() { + LbConfig lbConfig = PluginConfigManager.getPluginConfig(LbConfig.class); + Properties properties = new Properties(); + if (enableAuth) { + if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password) + || StringUtils.isEmpty(privateKey)) { + LOGGER.log(Level.SEVERE, "Nacos username, password or privateKey is Empty"); + return properties; + } + Optional pwd = AesUtil.decrypt(privateKey, password); + if (!pwd.isPresent()) { + LOGGER.log(Level.SEVERE, "Nacos password parsing failed"); + return properties; + } + properties.put(PropertyKeyConst.USERNAME, username); + properties.put(PropertyKeyConst.PASSWORD, pwd.get()); + } + properties.put(PropertyKeyConst.SERVER_ADDR, lbConfig.getRegistryAddress()); + properties.put(PropertyKeyConst.NAMESPACE, Objects.toString(namespace, "")); + properties.put(PropertyKeyConst.NACOS_NAMING_LOG_NAME, Objects.toString(logName, "")); + properties.put(PropertyKeyConst.ENDPOINT, Objects.toString(endpoint, "")); + properties.put(PropertyKeyConst.ACCESS_KEY, Objects.toString(accessKey, "")); + properties.put(PropertyKeyConst.SECRET_KEY, Objects.toString(secretKey, "")); + properties.put(PropertyKeyConst.CLUSTER_NAME, clusterName); + properties.put(PropertyKeyConst.NAMING_LOAD_CACHE_AT_START, namingLoadCacheAtStart); + return properties; + } +} diff --git a/sermant-plugins/sermant-springboot-registry/springboot-registry-plugin/src/main/java/io/sermant/discovery/config/PropertyKeyConst.java b/sermant-plugins/sermant-springboot-registry/springboot-registry-plugin/src/main/java/io/sermant/discovery/config/PropertyKeyConst.java new file mode 100644 index 0000000000..61cd2220f4 --- /dev/null +++ b/sermant-plugins/sermant-springboot-registry/springboot-registry-plugin/src/main/java/io/sermant/discovery/config/PropertyKeyConst.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2024-2024 Sermant Authors. All rights reserved. + * + * 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 io.sermant.discovery.config; + +/** + * NACOS registers static constants for parameters + * + * @author xz + * @since 2024-11-25 + */ +public class PropertyKeyConst { + /** + * Profanity + */ + public static final String HTTP_URL_COLON = ":"; + + /** + * Node + */ + public static final String ENDPOINT = "endpoint"; + + /** + * Node port + */ + public static final String ENDPOINT_PORT = "endpointPort"; + + /** + * Namespace + */ + public static final String NAMESPACE = "namespace"; + + /** + * Username + */ + public static final String USERNAME = "username"; + + /** + * User password + */ + public static final String PASSWORD = "password"; + + /** + * AK value + */ + public static final String ACCESS_KEY = "accessKey"; + + /** + * SK value + */ + public static final String SECRET_KEY = "secretKey"; + + /** + * Service address + */ + public static final String SERVER_ADDR = "serverAddr"; + + /** + * The name of the cluster + */ + public static final String CLUSTER_NAME = "clusterName"; + + /** + * Start naming whether to load the cache + */ + public static final String NAMING_LOAD_CACHE_AT_START = "namingLoadCacheAtStart"; + + /** + * The name of the nacos log file + */ + public static final String NACOS_NAMING_LOG_NAME = "com.alibaba.nacos.naming.log.filename"; + + /** + * Constructor + */ + private PropertyKeyConst() { + } +} diff --git a/sermant-plugins/sermant-springboot-registry/springboot-registry-plugin/src/main/java/io/sermant/discovery/config/RegisterType.java b/sermant-plugins/sermant-springboot-registry/springboot-registry-plugin/src/main/java/io/sermant/discovery/config/RegisterType.java new file mode 100644 index 0000000000..266ec46b52 --- /dev/null +++ b/sermant-plugins/sermant-springboot-registry/springboot-registry-plugin/src/main/java/io/sermant/discovery/config/RegisterType.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2024-2024 Sermant Authors. All rights reserved. + * + * 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 io.sermant.discovery.config; + +/** + * registry type, currently only SC is supported + * + * @author zhouss + * @since 2024-11-29 + */ +public enum RegisterType { + /** + * ZK Registry + */ + ZOOKEEPER, + + /** + * NACOS Registry + */ + NACOS +} diff --git a/sermant-plugins/sermant-springboot-registry/springboot-registry-plugin/src/main/java/io/sermant/discovery/entity/RegisterContext.java b/sermant-plugins/sermant-springboot-registry/springboot-registry-plugin/src/main/java/io/sermant/discovery/entity/RegisterContext.java index 505be6e7be..1d7a3a9d24 100644 --- a/sermant-plugins/sermant-springboot-registry/springboot-registry-plugin/src/main/java/io/sermant/discovery/entity/RegisterContext.java +++ b/sermant-plugins/sermant-springboot-registry/springboot-registry-plugin/src/main/java/io/sermant/discovery/entity/RegisterContext.java @@ -16,6 +16,8 @@ package io.sermant.discovery.entity; +import java.util.Map; + /** * Registration Information Class * @@ -23,12 +25,13 @@ * @since 2022-10-09 */ public enum RegisterContext { - /** * Instance */ INSTANCE; + private final ClientInfo clientInfo = new ClientInfo(); + private final DefaultServiceInstance serviceInstance = new DefaultServiceInstance(); /** @@ -40,4 +43,118 @@ public DefaultServiceInstance getServiceInstance() { return this.serviceInstance; } + public ClientInfo getClientInfo() { + return clientInfo; + } + + /** + * Client information + * + * @since 2022-03-01 + */ + public static class ClientInfo { + /** + * the service name is obtained by intercepting + */ + private String serviceName; + + /** + * domain name + */ + private String host; + + /** + * ip + */ + private String ip; + + /** + * port + */ + private int port; + + /** + * service id + */ + private String serviceId; + + /** + * service meta information + */ + private Map meta; + + /** + * region + */ + private String zone; + + /** + * instance state UP DOWN + */ + private String status = "UN_KNOWN"; + + public String getStatus() { + return status; + } + + public void setStatus(String status) { + this.status = status; + } + + public String getServiceName() { + return serviceName; + } + + public void setServiceName(String serviceName) { + this.serviceName = serviceName; + } + + public String getHost() { + return host; + } + + public void setHost(String host) { + this.host = host; + } + + public String getIp() { + return ip; + } + + public void setIp(String ip) { + this.ip = ip; + } + + public int getPort() { + return port; + } + + public void setPort(int port) { + this.port = port; + } + + public String getServiceId() { + return serviceId; + } + + public void setServiceId(String serviceId) { + this.serviceId = serviceId; + } + + public String getZone() { + return zone; + } + + public void setZone(String zone) { + this.zone = zone; + } + + public Map getMeta() { + return meta; + } + + public void setMeta(Map meta) { + this.meta = meta; + } + } } diff --git a/sermant-plugins/sermant-springboot-registry/springboot-registry-plugin/src/main/resources/META-INF/services/io.sermant.core.plugin.config.PluginConfig b/sermant-plugins/sermant-springboot-registry/springboot-registry-plugin/src/main/resources/META-INF/services/io.sermant.core.plugin.config.PluginConfig index f8585113e6..942ee0b062 100644 --- a/sermant-plugins/sermant-springboot-registry/springboot-registry-plugin/src/main/resources/META-INF/services/io.sermant.core.plugin.config.PluginConfig +++ b/sermant-plugins/sermant-springboot-registry/springboot-registry-plugin/src/main/resources/META-INF/services/io.sermant.core.plugin.config.PluginConfig @@ -16,3 +16,4 @@ io.sermant.discovery.config.LbConfig io.sermant.discovery.config.DiscoveryPluginConfig +io.sermant.discovery.config.NacosRegisterConfig diff --git a/sermant-plugins/sermant-springboot-registry/springboot-registry-service/pom.xml b/sermant-plugins/sermant-springboot-registry/springboot-registry-service/pom.xml index eff5848fdf..30737fec63 100644 --- a/sermant-plugins/sermant-springboot-registry/springboot-registry-service/pom.xml +++ b/sermant-plugins/sermant-springboot-registry/springboot-registry-service/pom.xml @@ -21,6 +21,7 @@ 3.1.0 2.13.4.2 31.1-jre + 2.0.4 @@ -42,6 +43,12 @@ provided + + com.alibaba.nacos + nacos-client + ${nacos.version} + + org.springframework.cloud diff --git a/sermant-plugins/sermant-springboot-registry/springboot-registry-service/src/main/java/io/sermant/discovery/service/lb/DiscoveryManager.java b/sermant-plugins/sermant-springboot-registry/springboot-registry-service/src/main/java/io/sermant/discovery/service/lb/DiscoveryManager.java index fb8a7f2c8b..de83775fb5 100644 --- a/sermant-plugins/sermant-springboot-registry/springboot-registry-service/src/main/java/io/sermant/discovery/service/lb/DiscoveryManager.java +++ b/sermant-plugins/sermant-springboot-registry/springboot-registry-service/src/main/java/io/sermant/discovery/service/lb/DiscoveryManager.java @@ -18,7 +18,9 @@ import io.sermant.core.common.LoggerFactory; import io.sermant.core.plugin.config.PluginConfigManager; +import io.sermant.discovery.config.DiscoveryPluginConfig; import io.sermant.discovery.config.LbConfig; +import io.sermant.discovery.config.RegisterType; import io.sermant.discovery.entity.ServiceInstance; import io.sermant.discovery.service.lb.cache.InstanceCacheManager; import io.sermant.discovery.service.lb.discovery.InstanceListenable; @@ -83,16 +85,16 @@ public enum DiscoveryManager { } private void initServiceDiscoveryClient() { - final String registryCenterType = lbConfig.getRegistryCenterType(); + RegisterType registerType = PluginConfigManager.getPluginConfig(DiscoveryPluginConfig.class).getRegisterType(); for (ServiceDiscoveryClient discoveryClient : ServiceLoader.load(ServiceDiscoveryClient.class, this.getClass() .getClassLoader())) { - if (discoveryClient.name().equalsIgnoreCase(lbConfig.getRegistryCenterType())) { + if (discoveryClient.registerType().equals(registerType)) { this.serviceDiscoveryClient = discoveryClient; break; } } if (this.serviceDiscoveryClient == null) { - throw new IllegalStateException("Can not support register center type: " + registryCenterType); + throw new IllegalStateException("Can not support register center type: " + registerType); } this.serviceDiscoveryClient.init(); } @@ -105,9 +107,10 @@ private void loadLb() { } private void loadListen() { + RegisterType registerType = PluginConfigManager.getPluginConfig(DiscoveryPluginConfig.class).getRegisterType(); for (InstanceListenable listenable : ServiceLoader.load(InstanceListenable.class, this.getClass() .getClassLoader())) { - if (listenable.name().equalsIgnoreCase(lbConfig.getRegistryCenterType())) { + if (listenable.registerType().equals(registerType)) { this.instanceListenable = listenable; break; } diff --git a/sermant-plugins/sermant-springboot-registry/springboot-registry-service/src/main/java/io/sermant/discovery/service/lb/cache/InstanceCacheManager.java b/sermant-plugins/sermant-springboot-registry/springboot-registry-service/src/main/java/io/sermant/discovery/service/lb/cache/InstanceCacheManager.java index a7d102da18..473c2997c6 100644 --- a/sermant-plugins/sermant-springboot-registry/springboot-registry-service/src/main/java/io/sermant/discovery/service/lb/cache/InstanceCacheManager.java +++ b/sermant-plugins/sermant-springboot-registry/springboot-registry-service/src/main/java/io/sermant/discovery/service/lb/cache/InstanceCacheManager.java @@ -263,6 +263,14 @@ public void notify(EventType eventType, ServiceInstance serviceInstance) { instanceCaches.put(serviceName, instanceCache); } + @Override + public void notify(List serviceInstances) { + String serviceName = serviceInstances.get(0).getServiceName(); + InstanceCache instanceCache = new InstanceCache(serviceName, serviceInstances); + instanceCache.setUpdateTimestamp(System.currentTimeMillis()); + instanceCaches.put(serviceName, instanceCache); + } + private void printLog(EventType eventType, ServiceInstance serviceInstance) { final Map metadata = serviceInstance.getMetadata(); if (metadata == null) { diff --git a/sermant-plugins/sermant-springboot-registry/springboot-registry-service/src/main/java/io/sermant/discovery/service/lb/discovery/InstanceChangeListener.java b/sermant-plugins/sermant-springboot-registry/springboot-registry-service/src/main/java/io/sermant/discovery/service/lb/discovery/InstanceChangeListener.java index 0749792711..c4a8b18814 100644 --- a/sermant-plugins/sermant-springboot-registry/springboot-registry-service/src/main/java/io/sermant/discovery/service/lb/discovery/InstanceChangeListener.java +++ b/sermant-plugins/sermant-springboot-registry/springboot-registry-service/src/main/java/io/sermant/discovery/service/lb/discovery/InstanceChangeListener.java @@ -18,6 +18,8 @@ import io.sermant.discovery.entity.ServiceInstance; +import java.util.List; + /** * Listeners for instance changes, and notifications are sent when the instance changes * @@ -33,6 +35,14 @@ public interface InstanceChangeListener { */ void notify(EventType eventType, ServiceInstance serviceInstance); + + /** + * Notice + * + * @param serviceInstances All instances + */ + void notify(List serviceInstances); + /** * The type of event * diff --git a/sermant-plugins/sermant-springboot-registry/springboot-registry-service/src/main/java/io/sermant/discovery/service/lb/discovery/InstanceListenable.java b/sermant-plugins/sermant-springboot-registry/springboot-registry-service/src/main/java/io/sermant/discovery/service/lb/discovery/InstanceListenable.java index b2b47e8922..2c7eaf736f 100644 --- a/sermant-plugins/sermant-springboot-registry/springboot-registry-service/src/main/java/io/sermant/discovery/service/lb/discovery/InstanceListenable.java +++ b/sermant-plugins/sermant-springboot-registry/springboot-registry-service/src/main/java/io/sermant/discovery/service/lb/discovery/InstanceListenable.java @@ -16,6 +16,7 @@ package io.sermant.discovery.service.lb.discovery; +import io.sermant.discovery.config.RegisterType; import io.sermant.discovery.service.lb.cache.InstanceCacheManager; /** @@ -45,9 +46,9 @@ public interface InstanceListenable { void close(); /** - * The name of the listener + * The RegisterType of the listener * - * @return Name + * @return RegisterType */ - String name(); + RegisterType registerType(); } diff --git a/sermant-plugins/sermant-springboot-registry/springboot-registry-service/src/main/java/io/sermant/discovery/service/lb/discovery/ServiceDiscoveryClient.java b/sermant-plugins/sermant-springboot-registry/springboot-registry-service/src/main/java/io/sermant/discovery/service/lb/discovery/ServiceDiscoveryClient.java index 8e427ae44e..694a4b0dc9 100644 --- a/sermant-plugins/sermant-springboot-registry/springboot-registry-service/src/main/java/io/sermant/discovery/service/lb/discovery/ServiceDiscoveryClient.java +++ b/sermant-plugins/sermant-springboot-registry/springboot-registry-service/src/main/java/io/sermant/discovery/service/lb/discovery/ServiceDiscoveryClient.java @@ -16,6 +16,7 @@ package io.sermant.discovery.service.lb.discovery; +import io.sermant.discovery.config.RegisterType; import io.sermant.discovery.entity.ServiceInstance; import io.sermant.discovery.service.ex.QueryInstanceException; @@ -68,7 +69,7 @@ public interface ServiceDiscoveryClient extends Closeable { /** * The name of the service discovery, associated with the registry type * - * @return Name + * @return RegisterType */ - String name(); + RegisterType registerType(); } diff --git a/sermant-plugins/sermant-springboot-registry/springboot-registry-service/src/main/java/io/sermant/discovery/service/lb/discovery/nacos/NacosDiscoveryClient.java b/sermant-plugins/sermant-springboot-registry/springboot-registry-service/src/main/java/io/sermant/discovery/service/lb/discovery/nacos/NacosDiscoveryClient.java new file mode 100644 index 0000000000..25a8422bce --- /dev/null +++ b/sermant-plugins/sermant-springboot-registry/springboot-registry-service/src/main/java/io/sermant/discovery/service/lb/discovery/nacos/NacosDiscoveryClient.java @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2024-2024 Sermant Authors. All rights reserved. + * + * 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 io.sermant.discovery.service.lb.discovery.nacos; + +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.api.naming.NamingService; +import com.alibaba.nacos.api.naming.pojo.Instance; +import com.alibaba.nacos.api.naming.pojo.ListView; + +import io.sermant.core.common.LoggerFactory; +import io.sermant.core.plugin.config.PluginConfigManager; +import io.sermant.core.utils.StringUtils; +import io.sermant.discovery.config.NacosRegisterConfig; +import io.sermant.discovery.config.RegisterType; +import io.sermant.discovery.entity.RegisterContext; +import io.sermant.discovery.entity.ServiceInstance; +import io.sermant.discovery.service.lb.discovery.ServiceDiscoveryClient; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.Optional; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * NACOS connects to the client + * + * @author xiaozhao + * @since 2024-11-16 + */ +public class NacosDiscoveryClient implements ServiceDiscoveryClient { + private static final Logger LOGGER = LoggerFactory.getLogger(); + + private final NacosServiceManager nacosServiceManager = NacosServiceManager.getInstance(); + + private Instance instance; + + private final NacosRegisterConfig nacosRegisterConfig; + + /** + * constructor + */ + public NacosDiscoveryClient() { + nacosRegisterConfig = PluginConfigManager.getPluginConfig(NacosRegisterConfig.class); + } + + @Override + public void init() { + } + + @Override + public boolean registry(ServiceInstance serviceInstance) { + String serviceId = serviceInstance.getServiceName(); + String group = nacosRegisterConfig.getGroup(); + instance = nacosServiceManager.buildNacosInstanceFromRegistration(serviceInstance); + try { + NamingService namingService = nacosServiceManager.getNamingService(); + namingService.registerInstance(serviceId, group, instance); + return true; + } catch (NacosException e) { + LOGGER.log(Level.SEVERE, String.format(Locale.ENGLISH, "failed when registry service,serviceId={%s}", + serviceId), e); + } + return false; + } + + @Override + public Collection getServices() { + try { + String group = nacosRegisterConfig.getGroup(); + NamingService namingService = nacosServiceManager.getNamingService(); + ListView services = namingService.getServicesOfServer(1, Integer.MAX_VALUE, group); + return services.getData(); + } catch (NacosException e) { + LOGGER.log(Level.SEVERE, String.format(Locale.ENGLISH, "getServices failed," + + "isFailureToleranceEnabled={%s}", nacosRegisterConfig.isFailureToleranceEnabled()), e); + } + return Collections.emptyList(); + } + + @Override + public boolean unRegistry() { + if (StringUtils.isEmpty(RegisterContext.INSTANCE.getClientInfo().getServiceId())) { + LOGGER.warning("No service to de-register for nacos client..."); + return false; + } + String serviceId = RegisterContext.INSTANCE.getClientInfo().getServiceId(); + String group = nacosRegisterConfig.getGroup(); + try { + NamingService namingService = nacosServiceManager.getNamingService(); + namingService.deregisterInstance(serviceId, group, instance); + return true; + } catch (NacosException e) { + LOGGER.log(Level.SEVERE, String.format(Locale.ENGLISH, "failed when deRegister service," + + "serviceId={%s}", serviceId), e); + } + return false; + } + + @Override + public RegisterType registerType() { + return RegisterType.NACOS; + } + + @Override + public void close() throws IOException { + } + + /** + * Obtain information about the microservice instance corresponding to the service name + * + * @param serviceId ServiceId + * @return Service information + */ + public List getInstances(String serviceId) { + String group = nacosRegisterConfig.getGroup(); + try { + NamingService namingService = nacosServiceManager.getNamingService(); + List instances = namingService.selectInstances(serviceId, group, true); + return convertServiceInstanceList(instances, serviceId); + } catch (NacosException e) { + LOGGER.log(Level.SEVERE, String.format(Locale.ENGLISH, "failed get Instances," + + "serviceId={%s}", serviceId), e); + } + return Collections.emptyList(); + } + + /** + * Convert the instance list to service instance list + * + * @param instances + * @param serviceId + * @return ServiceInstance list + */ + public List convertServiceInstanceList(List instances, String serviceId) { + List result = new ArrayList<>(instances.size()); + for (Instance nacosInstance : instances) { + Optional optional = nacosServiceManager.convertServiceInstance(nacosInstance, serviceId); + optional.ifPresent(result::add); + } + return result; + } +} diff --git a/sermant-plugins/sermant-springboot-registry/springboot-registry-service/src/main/java/io/sermant/discovery/service/lb/discovery/nacos/NacosServiceManager.java b/sermant-plugins/sermant-springboot-registry/springboot-registry-service/src/main/java/io/sermant/discovery/service/lb/discovery/nacos/NacosServiceManager.java new file mode 100644 index 0000000000..9c0b0f9055 --- /dev/null +++ b/sermant-plugins/sermant-springboot-registry/springboot-registry-service/src/main/java/io/sermant/discovery/service/lb/discovery/nacos/NacosServiceManager.java @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2024-2024 Sermant Authors. All rights reserved. + * + * 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 io.sermant.discovery.service.lb.discovery.nacos; + +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.api.naming.NamingMaintainService; +import com.alibaba.nacos.api.naming.NamingService; +import com.alibaba.nacos.api.naming.pojo.Instance; +import com.alibaba.nacos.client.naming.NacosNamingMaintainService; +import com.alibaba.nacos.client.naming.NacosNamingService; + +import io.sermant.core.plugin.config.PluginConfigManager; +import io.sermant.discovery.config.NacosRegisterConfig; +import io.sermant.discovery.entity.DefaultServiceInstance; +import io.sermant.discovery.entity.ServiceInstance; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Properties; + +/** + * NACOS Registration Service Manager + * + * @author xiaozhao + * @since 2024-11-16 + */ +public class NacosServiceManager { + private static final int DEFAULT_CAPACITY = 16; + + private static NacosServiceManager nacosServiceManager; + + private volatile NamingService namingService; + + private volatile NamingMaintainService namingMaintainService; + + private final NacosRegisterConfig nacosRegisterConfig; + + /** + * Constructor + */ + public NacosServiceManager() { + nacosRegisterConfig = PluginConfigManager.getPluginConfig(NacosRegisterConfig.class); + } + + /** + * Get the singleton instance + * + * @return NacosServiceManager + */ + public static NacosServiceManager getInstance() { + if (nacosServiceManager == null) { + synchronized (NacosServiceManager.class) { + if (nacosServiceManager == null) { + nacosServiceManager = new NacosServiceManager(); + } + } + } + return nacosServiceManager; + } + + /** + * Get registration services + * + * @return NamingService services + * @throws NacosException nacos exception + */ + public NamingService getNamingService() throws NacosException { + if (Objects.isNull(this.namingService)) { + buildNamingService(nacosRegisterConfig.getNacosProperties()); + } + return namingService; + } + + /** + * Obtain the namingMaintain service + * + * @return namingMaintain service + * @throws NacosException nacos exception + */ + public NamingMaintainService getNamingMaintainService() throws NacosException { + if (Objects.isNull(namingMaintainService)) { + buildNamingMaintainService(nacosRegisterConfig.getNacosProperties()); + } + return namingMaintainService; + } + + private void buildNamingMaintainService(Properties properties) throws NacosException { + if (Objects.isNull(namingMaintainService)) { + synchronized (NacosServiceManager.class) { + if (Objects.isNull(namingMaintainService)) { + namingMaintainService = createNamingMaintainService(properties); + } + } + } + } + + private void buildNamingService(Properties properties) throws NacosException { + if (Objects.isNull(namingService)) { + synchronized (NacosServiceManager.class) { + if (Objects.isNull(namingService)) { + namingService = createNewNamingService(properties); + } + } + } + } + + private NamingService createNewNamingService(Properties properties) throws NacosException { + return new NacosNamingService(properties); + } + + private NamingMaintainService createNamingMaintainService(Properties properties) throws NacosException { + return new NacosNamingMaintainService(properties); + } + + /** + * Build the nacos registry instance + * + * @param serviceInstance ServiceInstance + * @return instance + */ + public Instance buildNacosInstanceFromRegistration(ServiceInstance serviceInstance) { + Instance instance = new Instance(); + instance.setIp(serviceInstance.getIp()); + instance.setPort(serviceInstance.getPort()); + instance.setWeight(nacosRegisterConfig.getWeight()); + instance.setClusterName(nacosRegisterConfig.getClusterName()); + instance.setEnabled(nacosRegisterConfig.isInstanceEnabled()); + final HashMap metadata = new HashMap<>(serviceInstance.getMetadata()); + instance.setMetadata(metadata); + instance.setEphemeral(nacosRegisterConfig.isEphemeral()); + return instance; + } + + /** + * Instance information conversion + * + * @param instance Instance + * @param serviceId id + * @return Converted instance information + */ + public Optional convertServiceInstance(Instance instance, String serviceId) { + if (instance == null || !instance.isEnabled() || !instance.isHealthy()) { + return Optional.empty(); + } + DefaultServiceInstance nacosServiceInstance = new DefaultServiceInstance(); + nacosServiceInstance.setHost(instance.getIp()); + nacosServiceInstance.setIp(instance.getIp()); + nacosServiceInstance.setPort(instance.getPort()); + nacosServiceInstance.setServiceName(serviceId); + nacosServiceInstance.setId(instance.getIp() + ":" + instance.getPort()); + + Map metadata = new HashMap<>(DEFAULT_CAPACITY); + metadata.put("nacos.instanceId", instance.getInstanceId()); + metadata.put("nacos.weight", instance.getWeight() + ""); + metadata.put("nacos.healthy", instance.isHealthy() + ""); + metadata.put("nacos.cluster", instance.getClusterName() + ""); + if (instance.getMetadata() != null) { + metadata.putAll(instance.getMetadata()); + } + metadata.put("nacos.ephemeral", String.valueOf(instance.isEphemeral())); + nacosServiceInstance.setMetadata(metadata); + + return Optional.of(nacosServiceInstance); + } +} diff --git a/sermant-plugins/sermant-springboot-registry/springboot-registry-service/src/main/java/io/sermant/discovery/service/lb/discovery/nacos/listen/NacosInstanceListenable.java b/sermant-plugins/sermant-springboot-registry/springboot-registry-service/src/main/java/io/sermant/discovery/service/lb/discovery/nacos/listen/NacosInstanceListenable.java new file mode 100644 index 0000000000..678e470c29 --- /dev/null +++ b/sermant-plugins/sermant-springboot-registry/springboot-registry-service/src/main/java/io/sermant/discovery/service/lb/discovery/nacos/listen/NacosInstanceListenable.java @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2024-2024 Sermant Authors. All rights reserved. + * + * 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 io.sermant.discovery.service.lb.discovery.nacos.listen; + +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.api.naming.NamingService; +import com.alibaba.nacos.api.naming.listener.EventListener; +import com.alibaba.nacos.api.naming.pojo.Instance; +import com.alibaba.nacos.client.naming.event.InstancesChangeEvent; +import com.alibaba.nacos.common.notify.Event; +import com.alibaba.nacos.common.notify.NotifyCenter; +import com.alibaba.nacos.common.notify.listener.Subscriber; + +import io.sermant.core.common.LoggerFactory; +import io.sermant.core.utils.CollectionUtils; +import io.sermant.discovery.config.RegisterType; +import io.sermant.discovery.entity.ServiceInstance; +import io.sermant.discovery.service.lb.discovery.InstanceChangeListener; +import io.sermant.discovery.service.lb.discovery.InstanceListenable; +import io.sermant.discovery.service.lb.discovery.nacos.NacosServiceManager; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Logger; + +/** + * nacos Listen + * + * @author xz + * @since 2024-11-12 + */ +public class NacosInstanceListenable extends Subscriber implements InstanceListenable { + private static final Logger LOGGER = LoggerFactory.getLogger(); + + private final Map listenerCache = new ConcurrentHashMap<>(); + + private final NacosServiceManager nacosServiceManager = NacosServiceManager.getInstance(); + + /** + * Construct + */ + public NacosInstanceListenable() { + } + + @Override + public void onEvent(InstancesChangeEvent instancesChangeEvent) { + String serviceName = instancesChangeEvent.getServiceName(); + List instances = instancesChangeEvent.getHosts(); + if (CollectionUtils.isEmpty(instances)) { + return; + } + List serviceInstances = new ArrayList<>(instances.size()); + for (Instance instance : instances) { + if (instance.isEnabled() && instance.isHealthy()) { + Optional optional = nacosServiceManager.convertServiceInstance(instance, serviceName); + optional.ifPresent(serviceInstances::add); + } + } + listenerCache.get(serviceName).notify(serviceInstances); + } + + @Override + public Class subscribeType() { + return InstancesChangeEvent.class; + } + + @Override + public void init() { + } + + @Override + public void tryAdd(String serviceName, InstanceChangeListener listener) { + NotifyCenter.registerSubscriber(this); + try { + NamingService namingService = nacosServiceManager.getNamingService(); + namingService.subscribe(serviceName, new EventListener() { + @Override + public void onEvent(com.alibaba.nacos.api.naming.listener.Event event) { + LOGGER.info("Receive nacos instance change event: %s"); + } + }); + listenerCache.put(serviceName, listener); + } catch (NacosException e) { + throw new RuntimeException(e); + } + } + + @Override + public void close() { + listenerCache.clear(); + } + + @Override + public RegisterType registerType() { + return RegisterType.NACOS; + } +} diff --git a/sermant-plugins/sermant-springboot-registry/springboot-registry-service/src/main/java/io/sermant/discovery/service/lb/discovery/zk/ZkClient.java b/sermant-plugins/sermant-springboot-registry/springboot-registry-service/src/main/java/io/sermant/discovery/service/lb/discovery/zk/ZkClient.java index 3b243dfd8a..94aa1625e1 100644 --- a/sermant-plugins/sermant-springboot-registry/springboot-registry-service/src/main/java/io/sermant/discovery/service/lb/discovery/zk/ZkClient.java +++ b/sermant-plugins/sermant-springboot-registry/springboot-registry-service/src/main/java/io/sermant/discovery/service/lb/discovery/zk/ZkClient.java @@ -20,6 +20,7 @@ import io.sermant.core.plugin.service.PluginService; import io.sermant.discovery.config.DiscoveryPluginConfig; import io.sermant.discovery.config.LbConfig; +import io.sermant.discovery.config.RegisterType; import org.apache.curator.framework.CuratorFramework; import org.apache.curator.framework.CuratorFrameworkFactory; @@ -51,7 +52,8 @@ public ZkClient() { @Override public void start() { - if (!PluginConfigManager.getPluginConfig(DiscoveryPluginConfig.class).isEnableRegistry()) { + DiscoveryPluginConfig pluginConfig = PluginConfigManager.getPluginConfig(DiscoveryPluginConfig.class); + if (!pluginConfig.isEnableRegistry() || !RegisterType.ZOOKEEPER.equals(pluginConfig.getRegisterType())) { return; } this.client = this.buildClient(); diff --git a/sermant-plugins/sermant-springboot-registry/springboot-registry-service/src/main/java/io/sermant/discovery/service/lb/discovery/zk/ZkDiscoveryClient.java b/sermant-plugins/sermant-springboot-registry/springboot-registry-service/src/main/java/io/sermant/discovery/service/lb/discovery/zk/ZkDiscoveryClient.java index b28b3f9765..b0494b1afe 100644 --- a/sermant-plugins/sermant-springboot-registry/springboot-registry-service/src/main/java/io/sermant/discovery/service/lb/discovery/zk/ZkDiscoveryClient.java +++ b/sermant-plugins/sermant-springboot-registry/springboot-registry-service/src/main/java/io/sermant/discovery/service/lb/discovery/zk/ZkDiscoveryClient.java @@ -25,6 +25,7 @@ import io.sermant.core.plugin.config.PluginConfigManager; import io.sermant.core.plugin.service.PluginServiceManager; import io.sermant.discovery.config.LbConfig; +import io.sermant.discovery.config.RegisterType; import io.sermant.discovery.entity.ServiceInstance; import io.sermant.discovery.service.ex.QueryInstanceException; import io.sermant.discovery.service.lb.LbConstants; @@ -208,8 +209,8 @@ public boolean unRegistry() { } @Override - public String name() { - return "Zookeeper"; + public RegisterType registerType() { + return RegisterType.ZOOKEEPER; } private ServiceDiscovery build() { diff --git a/sermant-plugins/sermant-springboot-registry/springboot-registry-service/src/main/java/io/sermant/discovery/service/lb/discovery/zk/ZkDiscoveryClientProxy.java b/sermant-plugins/sermant-springboot-registry/springboot-registry-service/src/main/java/io/sermant/discovery/service/lb/discovery/zk/ZkDiscoveryClientProxy.java index 22fffe672a..aa7581989f 100644 --- a/sermant-plugins/sermant-springboot-registry/springboot-registry-service/src/main/java/io/sermant/discovery/service/lb/discovery/zk/ZkDiscoveryClientProxy.java +++ b/sermant-plugins/sermant-springboot-registry/springboot-registry-service/src/main/java/io/sermant/discovery/service/lb/discovery/zk/ZkDiscoveryClientProxy.java @@ -16,6 +16,7 @@ package io.sermant.discovery.service.lb.discovery.zk; +import io.sermant.discovery.config.RegisterType; import io.sermant.discovery.entity.ServiceInstance; import io.sermant.discovery.service.ex.QueryInstanceException; import io.sermant.discovery.service.lb.discovery.ServiceDiscoveryClient; @@ -58,8 +59,8 @@ public boolean unRegistry() { } @Override - public String name() { - return "Zookeeper"; + public RegisterType registerType() { + return RegisterType.ZOOKEEPER; } @Override diff --git a/sermant-plugins/sermant-springboot-registry/springboot-registry-service/src/main/java/io/sermant/discovery/service/lb/discovery/zk/listen/ZkInstanceListenable.java b/sermant-plugins/sermant-springboot-registry/springboot-registry-service/src/main/java/io/sermant/discovery/service/lb/discovery/zk/listen/ZkInstanceListenable.java index 0db7866de3..2817c3a3b5 100644 --- a/sermant-plugins/sermant-springboot-registry/springboot-registry-service/src/main/java/io/sermant/discovery/service/lb/discovery/zk/listen/ZkInstanceListenable.java +++ b/sermant-plugins/sermant-springboot-registry/springboot-registry-service/src/main/java/io/sermant/discovery/service/lb/discovery/zk/listen/ZkInstanceListenable.java @@ -20,6 +20,7 @@ import io.sermant.core.plugin.config.PluginConfigManager; import io.sermant.core.plugin.service.PluginServiceManager; import io.sermant.discovery.config.LbConfig; +import io.sermant.discovery.config.RegisterType; import io.sermant.discovery.service.lb.discovery.InstanceChangeListener; import io.sermant.discovery.service.lb.discovery.InstanceChangeListener.EventType; import io.sermant.discovery.service.lb.discovery.InstanceListenable; @@ -200,7 +201,7 @@ public void close() { } @Override - public String name() { - return "Zookeeper"; + public RegisterType registerType() { + return RegisterType.ZOOKEEPER; } } diff --git a/sermant-plugins/sermant-springboot-registry/springboot-registry-service/src/main/java/io/sermant/discovery/service/lb/rule/NacosWeightRandomRule.java b/sermant-plugins/sermant-springboot-registry/springboot-registry-service/src/main/java/io/sermant/discovery/service/lb/rule/NacosWeightRandomRule.java new file mode 100644 index 0000000000..2cf34fb902 --- /dev/null +++ b/sermant-plugins/sermant-springboot-registry/springboot-registry-service/src/main/java/io/sermant/discovery/service/lb/rule/NacosWeightRandomRule.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2024-2024 Sermant Authors. All rights reserved. + * + * 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 io.sermant.discovery.service.lb.rule; + +import io.sermant.discovery.entity.ServiceInstance; + +import java.util.List; +import java.util.Random; +import java.util.stream.Collectors; + +/** + * nacos Weight + * + * @author xiaozhao + * @since 2024-11-16 + */ +public class NacosWeightRandomRule extends AbstractLoadbalancer { + @Override + protected ServiceInstance doChoose(String serviceName, List instances) { + List withWeights = instances.stream() + .map(instance -> { + int weight = Integer.parseInt(instance.getMetadata().get("nacos.weight")); + return new InstanceWithWeight(instance, weight); + }).collect(Collectors.toList()); + return this.weightRandom(withWeights); + } + + @Override + public String lbType() { + return "NacosWeight"; + } + + /** + * nacos Weight + * + * @author xiaozhao + * @since 2024-11-16 + */ + public static class InstanceWithWeight { + private ServiceInstance server; + private Integer weight; + + /** + * constructor + * + * @param instance instance + * @param weight weight + */ + public InstanceWithWeight(ServiceInstance instance, int weight) { + this.server = instance; + this.weight = weight; + } + + public ServiceInstance getServer() { + return server; + } + + public Integer getWeight() { + return weight; + } + } + + /** + * Random by weight + * + * @param list instance list + * @return just a random result + */ + public ServiceInstance weightRandom(List list) { + int totalWeight = list.stream().mapToInt(InstanceWithWeight::getWeight).sum(); + int randomWeight = new Random().nextInt(totalWeight); + int currentWeight = 0; + for (InstanceWithWeight instanceWithWeight : list) { + currentWeight += instanceWithWeight.getWeight(); + if (randomWeight < currentWeight) { + return instanceWithWeight.getServer(); + } + } + return list.get(0).getServer(); + } +} diff --git a/sermant-plugins/sermant-springboot-registry/springboot-registry-service/src/main/resources/META-INF/services/io.sermant.discovery.service.lb.discovery.InstanceListenable b/sermant-plugins/sermant-springboot-registry/springboot-registry-service/src/main/resources/META-INF/services/io.sermant.discovery.service.lb.discovery.InstanceListenable index e5086a73ea..5384daa277 100644 --- a/sermant-plugins/sermant-springboot-registry/springboot-registry-service/src/main/resources/META-INF/services/io.sermant.discovery.service.lb.discovery.InstanceListenable +++ b/sermant-plugins/sermant-springboot-registry/springboot-registry-service/src/main/resources/META-INF/services/io.sermant.discovery.service.lb.discovery.InstanceListenable @@ -15,3 +15,4 @@ # io.sermant.discovery.service.lb.discovery.zk.listen.ZkInstanceListenable +io.sermant.discovery.service.lb.discovery.nacos.listen.NacosInstanceListenable diff --git a/sermant-plugins/sermant-springboot-registry/springboot-registry-service/src/main/resources/META-INF/services/io.sermant.discovery.service.lb.discovery.ServiceDiscoveryClient b/sermant-plugins/sermant-springboot-registry/springboot-registry-service/src/main/resources/META-INF/services/io.sermant.discovery.service.lb.discovery.ServiceDiscoveryClient index 48e1c03d16..4f8b254477 100644 --- a/sermant-plugins/sermant-springboot-registry/springboot-registry-service/src/main/resources/META-INF/services/io.sermant.discovery.service.lb.discovery.ServiceDiscoveryClient +++ b/sermant-plugins/sermant-springboot-registry/springboot-registry-service/src/main/resources/META-INF/services/io.sermant.discovery.service.lb.discovery.ServiceDiscoveryClient @@ -15,3 +15,4 @@ # io.sermant.discovery.service.lb.discovery.zk.ZkDiscoveryClientProxy +io.sermant.discovery.service.lb.discovery.nacos.NacosDiscoveryClient diff --git a/sermant-plugins/sermant-springboot-registry/springboot-registry-service/src/main/resources/META-INF/services/io.sermant.discovery.service.lb.rule.AbstractLoadbalancer b/sermant-plugins/sermant-springboot-registry/springboot-registry-service/src/main/resources/META-INF/services/io.sermant.discovery.service.lb.rule.AbstractLoadbalancer index e576606039..5dbcb0bb79 100644 --- a/sermant-plugins/sermant-springboot-registry/springboot-registry-service/src/main/resources/META-INF/services/io.sermant.discovery.service.lb.rule.AbstractLoadbalancer +++ b/sermant-plugins/sermant-springboot-registry/springboot-registry-service/src/main/resources/META-INF/services/io.sermant.discovery.service.lb.rule.AbstractLoadbalancer @@ -18,3 +18,4 @@ io.sermant.discovery.service.lb.rule.RoundRobinLoadbalancer io.sermant.discovery.service.lb.rule.RandomLoadbalancer io.sermant.discovery.service.lb.rule.BestAvailableLoadbalancer io.sermant.discovery.service.lb.rule.WeightedResponseTimeLoadbalancer +io.sermant.discovery.service.lb.rule.NacosWeightRandomRule diff --git a/sermant-plugins/sermant-springboot-registry/springboot-registry-service/src/test/java/io/sermant/discovery/service/lb/discovery/nacos/NacosDiscoveryClientTest.java b/sermant-plugins/sermant-springboot-registry/springboot-registry-service/src/test/java/io/sermant/discovery/service/lb/discovery/nacos/NacosDiscoveryClientTest.java new file mode 100644 index 0000000000..7b20a0ae7d --- /dev/null +++ b/sermant-plugins/sermant-springboot-registry/springboot-registry-service/src/test/java/io/sermant/discovery/service/lb/discovery/nacos/NacosDiscoveryClientTest.java @@ -0,0 +1,125 @@ +package io.sermant.discovery.service.lb.discovery.nacos; + +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.api.naming.NamingMaintainService; +import com.alibaba.nacos.api.naming.NamingService; +import com.alibaba.nacos.api.naming.pojo.Instance; +import com.alibaba.nacos.api.naming.pojo.ListView; +import io.sermant.core.plugin.config.PluginConfigManager; +import io.sermant.core.utils.ReflectUtils; +import io.sermant.discovery.config.NacosRegisterConfig; +import io.sermant.discovery.entity.DefaultServiceInstance; +import io.sermant.discovery.entity.RegisterContext; +import io.sermant.discovery.entity.ServiceInstance; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.mockito.junit.MockitoJUnitRunner; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class NacosDiscoveryClientTest { + + @Mock + private NamingService namingService; + + private NacosRegisterConfig registerConfig = new NacosRegisterConfig(); + + private NacosDiscoveryClient nacosDiscoveryClient; + + private NacosServiceManager nacosServiceManager; + + private MockedStatic pluginConfigManagerMockedStatic; + + + @Before + public void setUp() throws Exception { + MockitoAnnotations.openMocks(this); + pluginConfigManagerMockedStatic = Mockito + .mockStatic(PluginConfigManager.class); + pluginConfigManagerMockedStatic.when(() -> PluginConfigManager.getPluginConfig(NacosRegisterConfig.class)) + .thenReturn(registerConfig); + RegisterContext.INSTANCE.getClientInfo().setServiceId("test"); + nacosDiscoveryClient = new NacosDiscoveryClient(); + nacosServiceManager = new NacosServiceManager(); + Map map = new HashMap<>(); + map.put("foo", "123"); + RegisterContext.INSTANCE.getClientInfo().setMeta(map); + } + + @After + public void tearDown() throws Exception { + pluginConfigManagerMockedStatic.close(); + } + + @Test + public void testRegister() throws NacosException { + mockNamingService(); + Instance instance = new Instance(); + instance.setIp("1.1.1.1"); + instance.setPort(8080); + instance.setServiceName("test"); + + when(namingService.selectInstances(anyString(), any(Boolean.class), any(Boolean.class))) + .thenReturn(Arrays.asList(instance)); + + Map metadata = new HashMap<>(); + metadata.put("version", "1.0.0"); + metadata.put("environment", "dev"); + ServiceInstance serviceInstance = new DefaultServiceInstance("127.0.0.1:8080", "127.0.0.1", 8080, metadata + , "test"); + nacosDiscoveryClient.registry(serviceInstance); + } + + @Test + public void testDeregister() throws NacosException { + mockNamingService(); + Instance instance = new Instance(); + instance.setIp("1.1.1.1"); + instance.setPort(8080); + instance.setServiceName("test"); + + Map instanceMap = new HashMap<>(); + instanceMap.put("1.1.1.1:8080", instance); + + when(namingService.selectInstances(anyString(), any(Boolean.class), any(Boolean.class))) + .thenReturn(Arrays.asList(instance)); + + nacosDiscoveryClient.unRegistry(); + } + + + private void mockNamingService() throws NacosException { + final NamingService namingService = Mockito.mock(NamingService.class); + List list = new ArrayList<>(); + list.add("test"); + ListView services = new ListView<>(); + services.setData(list); + Mockito.when(namingService.getServicesOfServer(1, Integer.MAX_VALUE, registerConfig.getGroup())) + .thenReturn(services); + final NamingMaintainService namingMaintainService = Mockito.mock(NamingMaintainService.class); + ReflectUtils.setFieldValue(nacosServiceManager, "namingService", namingService); + ReflectUtils.setFieldValue(nacosServiceManager, "namingMaintainService", namingMaintainService); + setNacosServiceManager(); + } + + private void setNacosServiceManager() { + ReflectUtils.setFieldValue(nacosDiscoveryClient, "nacosServiceManager", nacosServiceManager); + ReflectUtils.setFieldValue(nacosDiscoveryClient, "nacosServiceDiscovery", + new NacosDiscoveryClient()); + } +} diff --git a/sermant-plugins/sermant-springboot-registry/springboot-registry-service/src/test/java/io/sermant/discovery/service/lb/discovery/nacos/NacosServiceManagerTest.java b/sermant-plugins/sermant-springboot-registry/springboot-registry-service/src/test/java/io/sermant/discovery/service/lb/discovery/nacos/NacosServiceManagerTest.java new file mode 100644 index 0000000000..21ebcd4f6c --- /dev/null +++ b/sermant-plugins/sermant-springboot-registry/springboot-registry-service/src/test/java/io/sermant/discovery/service/lb/discovery/nacos/NacosServiceManagerTest.java @@ -0,0 +1,66 @@ +package io.sermant.discovery.service.lb.discovery.nacos; + +import java.util.*; +import java.math.*; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +public class NacosServiceManagerTest { + + private NacosServiceManager nacosServiceManagerInstance; + + @Before + public void setUp() { + // Obtain an instance of NacosServiceManager before running tests + nacosServiceManagerInstance = NacosServiceManager.getInstance(); + } + + @Test + public void testGetInstance_ShouldReturnSameInstance() { + // Act + NacosServiceManager anotherInstance = NacosServiceManager.getInstance(); + + // Assert + assertNotNull("Instance should not be null", nacosServiceManagerInstance); + assertNotNull("Another instance should not be null", anotherInstance); + assertEquals("Both instances should be the same", nacosServiceManagerInstance, anotherInstance); + } + + @Test + public void testGetInstance_ShouldBeThreadSafe() { + // Arrange + final int threadCount = 100; + Thread[] threads = new Thread[threadCount]; + NacosServiceManager[] instances = new NacosServiceManager[threadCount]; + + for (int i = 0; i < threadCount; i++) { + int finalI = i; + threads[i] = new Thread(() -> { + instances[finalI] = NacosServiceManager.getInstance(); + }); + } + + // Act + for (Thread thread : threads) { + thread.start(); + } + + try { + for (Thread thread : threads) { + thread.join(); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + + // Assert + for (int i = 0; i < threadCount; i++) { + assertNotNull("Instance should not be null", instances[i]); + assertEquals("All instances should be the same", instances[0], instances[i]); + } + } +} diff --git a/sermant-plugins/sermant-springboot-registry/springboot-registry-service/src/test/java/io/sermant/discovery/service/lb/rule/NacosWeightRandomRuleTest.java b/sermant-plugins/sermant-springboot-registry/springboot-registry-service/src/test/java/io/sermant/discovery/service/lb/rule/NacosWeightRandomRuleTest.java new file mode 100644 index 0000000000..7a3a0e59ee --- /dev/null +++ b/sermant-plugins/sermant-springboot-registry/springboot-registry-service/src/test/java/io/sermant/discovery/service/lb/rule/NacosWeightRandomRuleTest.java @@ -0,0 +1,66 @@ +package io.sermant.discovery.service.lb.rule; + +import io.sermant.discovery.entity.ServiceInstance; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.junit.Assert; +import org.mockito.junit.MockitoJUnitRunner; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; + +import static org.junit.Assert.assertNotNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class NacosWeightRandomRuleTest { + + @InjectMocks + private NacosWeightRandomRule nacosWeightRandomRule; + + private List instances; + + @Before + public void setUp() { + instances = new ArrayList<>(); + for (int i = 0; i < 5; i++) { + ServiceInstance instance = mock(ServiceInstance.class); + Map metadata = new ConcurrentHashMap<>(); + metadata.put("nacos.weight", String.valueOf(i)); + when(instance.getMetadata()).thenReturn(metadata); + instances.add(instance); + } + } + + @Test + public void testDoChoose() { + + // Test the selection method of load balancing + ServiceInstance chosenInstance = nacosWeightRandomRule.doChoose("test-service", instances); + + // Here is the basic test, without doing specific probabilistic verification, assuming that every execution is valid. + assertNotNull(chosenInstance); + + } + + @Test + public void lbType() { + Assert.assertEquals(new NacosWeightRandomRule().lbType(), "NacosWeight"); + } + + @Test + public void testWeightRandom() { + List withWeights = instances.stream() + .map(instance -> { + int weight = Integer.parseInt(instance.getMetadata().get("nacos.weight")); + return new NacosWeightRandomRule.InstanceWithWeight(instance, weight); + }).collect(Collectors.toList()); + + ServiceInstance chosenInstance = nacosWeightRandomRule.weightRandom(withWeights); + assertNotNull(chosenInstance); + } +}