Remove Kubernetes and xDS implementations (#13469)

* Remove Kubernetes and xDS implementations

* Fix transform
This commit is contained in:
Albumen Kevin 2023-12-12 10:24:43 +08:00 committed by GitHub
parent f19e2c7627
commit 1577a31046
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
87 changed files with 0 additions and 7611 deletions

View File

@ -129,15 +129,6 @@
<optional>true</optional>
</dependency>
<!-- kubernetes -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-kubernetes</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<!-- metadata -->
<dependency>
<groupId>org.apache.dubbo</groupId>
@ -429,15 +420,6 @@
<optional>true</optional>
</dependency>
<!-- xds -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-xds</artifactId>
<version>${project.version}</version>
<scope>compile</scope>
<optional>true</optional>
</dependency>
<!-- Transitive dependencies -->
<dependency>
<groupId>org.springframework</groupId>
@ -557,8 +539,6 @@
<include>org.apache.dubbo:dubbo-serialization-hessian2</include>
<include>org.apache.dubbo:dubbo-serialization-fastjson2</include>
<include>org.apache.dubbo:dubbo-serialization-jdk</include>
<include>org.apache.dubbo:dubbo-kubernetes</include>
<include>org.apache.dubbo:dubbo-xds</include>
</includes>
</artifactSet>
<transformers>
@ -753,9 +733,6 @@
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/dubbo/internal/org.apache.dubbo.registry.integration.RegistryProtocolListener</resource>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/dubbo/internal/org.apache.dubbo.registry.xds.XdsCertificateSigner</resource>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/dubbo/internal/org.apache.dubbo.rpc.protocol.rest.filter.RestResponseInterceptor</resource>
</transformer>

View File

@ -175,13 +175,6 @@
<version>${project.version}</version>
</dependency>
<!-- kubernetes -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-kubernetes</artifactId>
<version>${project.version}</version>
</dependency>
<!-- metadata -->
<dependency>
<groupId>org.apache.dubbo</groupId>
@ -595,12 +588,6 @@
<version>${project.version}</version>
</dependency>
<!-- xds -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-xds</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</dependencyManagement>

View File

@ -515,12 +515,6 @@
META-INF/dubbo/internal/org.apache.dubbo.registry.integration.RegistryProtocolListener
</resource>
</transformer>
<transformer
implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>
META-INF/dubbo/internal/org.apache.dubbo.registry.xds.XdsCertificateSigner
</resource>
</transformer>
<transformer
implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>

View File

@ -1,73 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-parent</artifactId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>dubbo-kubernetes</artifactId>
<name>${project.artifactId}</name>
<description>The Kubernetes Integration</description>
<properties>
<skip_maven_deploy>false</skip_maven_deploy>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-registry-api</artifactId>
<version>${project.parent.version}</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-common</artifactId>
<version>${project.parent.version}</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-metadata-api</artifactId>
<version>${project.parent.version}</version>
</dependency>
<dependency>
<groupId>io.fabric8</groupId>
<artifactId>kubernetes-client</artifactId>
</dependency>
<dependency>
<groupId>io.fabric8</groupId>
<artifactId>kubernetes-server-mock</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>3.12.4</version>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</project>

View File

@ -1,207 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.registry.kubernetes;
import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
import org.apache.dubbo.common.logger.LoggerFactory;
import org.apache.dubbo.rpc.cluster.router.mesh.route.MeshAppRuleListener;
import org.apache.dubbo.rpc.cluster.router.mesh.route.MeshEnvListener;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import io.fabric8.kubernetes.api.model.GenericKubernetesResource;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.Watch;
import io.fabric8.kubernetes.client.Watcher;
import io.fabric8.kubernetes.client.WatcherException;
import org.yaml.snakeyaml.LoaderOptions;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.SafeConstructor;
import static org.apache.dubbo.common.constants.LoggerCodeConstants.REGISTRY_ERROR_LISTEN_KUBERNETES;
public class KubernetesMeshEnvListener implements MeshEnvListener {
public static final ErrorTypeAwareLogger logger =
LoggerFactory.getErrorTypeAwareLogger(KubernetesMeshEnvListener.class);
private static volatile boolean usingApiServer = false;
private static volatile KubernetesClient kubernetesClient;
private static volatile String namespace;
private final Map<String, MeshAppRuleListener> appRuleListenerMap = new ConcurrentHashMap<>();
private final Map<String, Watch> vsAppWatch = new ConcurrentHashMap<>();
private final Map<String, Watch> drAppWatch = new ConcurrentHashMap<>();
private final Map<String, String> vsAppCache = new ConcurrentHashMap<>();
private final Map<String, String> drAppCache = new ConcurrentHashMap<>();
public static void injectKubernetesEnv(KubernetesClient client, String configuredNamespace) {
usingApiServer = true;
kubernetesClient = client;
namespace = configuredNamespace;
}
@Override
public boolean isEnable() {
return usingApiServer;
}
@Override
public void onSubscribe(String appName, MeshAppRuleListener listener) {
appRuleListenerMap.put(appName, listener);
logger.info("Subscribe Mesh Rule in Kubernetes. AppName: " + appName);
// subscribe VisualService
subscribeVs(appName);
// subscribe DestinationRule
subscribeDr(appName);
// notify for start
notifyOnce(appName);
}
private void subscribeVs(String appName) {
if (vsAppWatch.containsKey(appName)) {
return;
}
try {
Watch watch = kubernetesClient
.genericKubernetesResources(MeshConstant.getVsDefinition())
.inNamespace(namespace)
.withName(appName)
.watch(new Watcher<GenericKubernetesResource>() {
@Override
public void eventReceived(Action action, GenericKubernetesResource resource) {
if (logger.isInfoEnabled()) {
logger.info("Received VS Rule notification. AppName: " + appName + " Action:" + action
+ " Resource:" + resource);
}
if (action == Action.ADDED || action == Action.MODIFIED) {
String vsRule = new Yaml(new SafeConstructor(new LoaderOptions())).dump(resource);
vsAppCache.put(appName, vsRule);
if (drAppCache.containsKey(appName)) {
notifyListener(vsRule, appName, drAppCache.get(appName));
}
} else {
appRuleListenerMap.get(appName).receiveConfigInfo("");
}
}
@Override
public void onClose(WatcherException cause) {
// ignore
}
});
vsAppWatch.put(appName, watch);
try {
GenericKubernetesResource vsRule = kubernetesClient
.genericKubernetesResources(MeshConstant.getVsDefinition())
.inNamespace(namespace)
.withName(appName)
.get();
vsAppCache.put(appName, new Yaml(new SafeConstructor(new LoaderOptions())).dump(vsRule));
} catch (Throwable ignore) {
}
} catch (Exception e) {
logger.error(REGISTRY_ERROR_LISTEN_KUBERNETES, "", "", "Error occurred when listen kubernetes crd.", e);
}
}
private void notifyListener(String vsRule, String appName, String drRule) {
String rule = vsRule + "\n---\n" + drRule;
logger.info("Notify App Rule Listener. AppName: " + appName + " Rule:" + rule);
appRuleListenerMap.get(appName).receiveConfigInfo(rule);
}
private void subscribeDr(String appName) {
if (drAppWatch.containsKey(appName)) {
return;
}
try {
Watch watch = kubernetesClient
.genericKubernetesResources(MeshConstant.getDrDefinition())
.inNamespace(namespace)
.withName(appName)
.watch(new Watcher<GenericKubernetesResource>() {
@Override
public void eventReceived(Action action, GenericKubernetesResource resource) {
if (logger.isInfoEnabled()) {
logger.info("Received VS Rule notification. AppName: " + appName + " Action:" + action
+ " Resource:" + resource);
}
if (action == Action.ADDED || action == Action.MODIFIED) {
String drRule = new Yaml(new SafeConstructor(new LoaderOptions())).dump(resource);
drAppCache.put(appName, drRule);
if (vsAppCache.containsKey(appName)) {
notifyListener(vsAppCache.get(appName), appName, drRule);
}
} else {
appRuleListenerMap.get(appName).receiveConfigInfo("");
}
}
@Override
public void onClose(WatcherException cause) {
// ignore
}
});
drAppWatch.put(appName, watch);
try {
GenericKubernetesResource drRule = kubernetesClient
.genericKubernetesResources(MeshConstant.getDrDefinition())
.inNamespace(namespace)
.withName(appName)
.get();
drAppCache.put(appName, new Yaml(new SafeConstructor(new LoaderOptions())).dump(drRule));
} catch (Throwable ignore) {
}
} catch (Exception e) {
logger.error(REGISTRY_ERROR_LISTEN_KUBERNETES, "", "", "Error occurred when listen kubernetes crd.", e);
}
}
private void notifyOnce(String appName) {
if (vsAppCache.containsKey(appName) && drAppCache.containsKey(appName)) {
notifyListener(vsAppCache.get(appName), appName, drAppCache.get(appName));
}
}
@Override
public void onUnSubscribe(String appName) {
appRuleListenerMap.remove(appName);
if (vsAppWatch.containsKey(appName)) {
vsAppWatch.remove(appName).close();
}
vsAppCache.remove(appName);
if (drAppWatch.containsKey(appName)) {
drAppWatch.remove(appName).close();
}
drAppCache.remove(appName);
}
}

View File

@ -1,42 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.registry.kubernetes;
import org.apache.dubbo.common.logger.Logger;
import org.apache.dubbo.common.logger.LoggerFactory;
import org.apache.dubbo.rpc.cluster.router.mesh.route.MeshEnvListener;
import org.apache.dubbo.rpc.cluster.router.mesh.route.MeshEnvListenerFactory;
import java.util.concurrent.atomic.AtomicBoolean;
public class KubernetesMeshEnvListenerFactory implements MeshEnvListenerFactory {
public static final Logger logger = LoggerFactory.getLogger(KubernetesMeshEnvListenerFactory.class);
private final AtomicBoolean initialized = new AtomicBoolean(false);
private MeshEnvListener listener = null;
@Override
public MeshEnvListener getListener() {
try {
if (initialized.compareAndSet(false, true)) {
listener = new NopKubernetesMeshEnvListener();
}
} catch (Throwable t) {
logger.info("Current Env not support Kubernetes.");
}
return listener;
}
}

View File

@ -1,50 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.registry.kubernetes;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.registry.NotifyListener;
import org.apache.dubbo.registry.support.FailbackRegistry;
/**
* Empty implements for Kubernetes <br/>
* Kubernetes only support `Service Discovery` mode register <br/>
* Used to compat past version like 2.6.x, 2.7.x with interface level register <br/>
* {@link KubernetesServiceDiscovery} is the real implementation of Kubernetes
*/
public class KubernetesRegistry extends FailbackRegistry {
public KubernetesRegistry(URL url) {
super(url);
}
@Override
public boolean isAvailable() {
return true;
}
@Override
public void doRegister(URL url) {}
@Override
public void doUnregister(URL url) {}
@Override
public void doSubscribe(URL url, NotifyListener listener) {}
@Override
public void doUnsubscribe(URL url, NotifyListener listener) {}
}

View File

@ -1,34 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.registry.kubernetes;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.registry.Registry;
import org.apache.dubbo.registry.support.AbstractRegistryFactory;
public class KubernetesRegistryFactory extends AbstractRegistryFactory {
@Override
protected String createRegistryCacheKey(URL url) {
return url.toFullString();
}
@Override
protected Registry createRegistry(URL url) {
return new KubernetesRegistry(url);
}
}

View File

@ -1,451 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.registry.kubernetes;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
import org.apache.dubbo.common.logger.LoggerFactory;
import org.apache.dubbo.common.utils.JsonUtils;
import org.apache.dubbo.common.utils.StringUtils;
import org.apache.dubbo.registry.client.AbstractServiceDiscovery;
import org.apache.dubbo.registry.client.DefaultServiceInstance;
import org.apache.dubbo.registry.client.ServiceInstance;
import org.apache.dubbo.registry.client.event.ServiceInstancesChangedEvent;
import org.apache.dubbo.registry.client.event.listener.ServiceInstancesChangedListener;
import org.apache.dubbo.registry.kubernetes.util.KubernetesClientConst;
import org.apache.dubbo.registry.kubernetes.util.KubernetesConfigUtils;
import org.apache.dubbo.rpc.model.ApplicationModel;
import org.apache.dubbo.rpc.model.ScopeModelUtil;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;
import io.fabric8.kubernetes.api.model.EndpointAddress;
import io.fabric8.kubernetes.api.model.EndpointPort;
import io.fabric8.kubernetes.api.model.EndpointSubset;
import io.fabric8.kubernetes.api.model.Endpoints;
import io.fabric8.kubernetes.api.model.Pod;
import io.fabric8.kubernetes.api.model.PodBuilder;
import io.fabric8.kubernetes.api.model.Service;
import io.fabric8.kubernetes.client.Config;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.KubernetesClientBuilder;
import io.fabric8.kubernetes.client.informers.ResourceEventHandler;
import io.fabric8.kubernetes.client.informers.SharedIndexInformer;
import static org.apache.dubbo.common.constants.LoggerCodeConstants.REGISTRY_UNABLE_ACCESS_KUBERNETES;
import static org.apache.dubbo.common.constants.LoggerCodeConstants.REGISTRY_UNABLE_FIND_SERVICE_KUBERNETES;
import static org.apache.dubbo.common.constants.LoggerCodeConstants.REGISTRY_UNABLE_MATCH_KUBERNETES;
public class KubernetesServiceDiscovery extends AbstractServiceDiscovery {
private final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(getClass());
private KubernetesClient kubernetesClient;
private String currentHostname;
private final URL registryURL;
private final String namespace;
private final boolean enableRegister;
public static final String KUBERNETES_PROPERTIES_KEY = "io.dubbo/metadata";
private static final ConcurrentHashMap<String, AtomicLong> SERVICE_UPDATE_TIME = new ConcurrentHashMap<>(64);
private static final ConcurrentHashMap<String, SharedIndexInformer<Service>> SERVICE_INFORMER =
new ConcurrentHashMap<>(64);
private static final ConcurrentHashMap<String, SharedIndexInformer<Pod>> PODS_INFORMER =
new ConcurrentHashMap<>(64);
private static final ConcurrentHashMap<String, SharedIndexInformer<Endpoints>> ENDPOINTS_INFORMER =
new ConcurrentHashMap<>(64);
public KubernetesServiceDiscovery(ApplicationModel applicationModel, URL registryURL) {
super(applicationModel, registryURL);
Config config = KubernetesConfigUtils.createKubernetesConfig(registryURL);
this.kubernetesClient = new KubernetesClientBuilder().withConfig(config).build();
this.currentHostname = System.getenv("HOSTNAME");
this.registryURL = registryURL;
this.namespace = config.getNamespace();
this.enableRegister = registryURL.getParameter(KubernetesClientConst.ENABLE_REGISTER, true);
boolean availableAccess;
try {
availableAccess = kubernetesClient.pods().withName(currentHostname).get() != null;
} catch (Throwable e) {
availableAccess = false;
}
if (!availableAccess) {
String message = "Unable to access api server. " + "Please check your url config."
+ " Master URL: "
+ config.getMasterUrl() + " Hostname: "
+ currentHostname;
logger.error(REGISTRY_UNABLE_ACCESS_KUBERNETES, "", "", message);
} else {
KubernetesMeshEnvListener.injectKubernetesEnv(kubernetesClient, namespace);
}
}
@Override
public void doDestroy() {
SERVICE_INFORMER.forEach((k, v) -> v.close());
SERVICE_INFORMER.clear();
PODS_INFORMER.forEach((k, v) -> v.close());
PODS_INFORMER.clear();
ENDPOINTS_INFORMER.forEach((k, v) -> v.close());
ENDPOINTS_INFORMER.clear();
kubernetesClient.close();
}
@Override
public void doRegister(ServiceInstance serviceInstance) throws RuntimeException {
if (enableRegister) {
kubernetesClient
.pods()
.inNamespace(namespace)
.withName(currentHostname)
.edit(pod -> new PodBuilder(pod)
.editOrNewMetadata()
.addToAnnotations(
KUBERNETES_PROPERTIES_KEY, JsonUtils.toJson(serviceInstance.getMetadata()))
.endMetadata()
.build());
if (logger.isInfoEnabled()) {
logger.info("Write Current Service Instance Metadata to Kubernetes pod. " + "Current pod name: "
+ currentHostname);
}
}
}
/**
* Comparing to {@link AbstractServiceDiscovery#doUpdate(ServiceInstance, ServiceInstance)}, unregister() is unnecessary here.
*/
@Override
public void doUpdate(ServiceInstance oldServiceInstance, ServiceInstance newServiceInstance)
throws RuntimeException {
reportMetadata(newServiceInstance.getServiceMetadata());
this.doRegister(newServiceInstance);
}
@Override
public void doUnregister(ServiceInstance serviceInstance) throws RuntimeException {
if (enableRegister) {
kubernetesClient
.pods()
.inNamespace(namespace)
.withName(currentHostname)
.edit(pod -> new PodBuilder(pod)
.editOrNewMetadata()
.removeFromAnnotations(KUBERNETES_PROPERTIES_KEY)
.endMetadata()
.build());
if (logger.isInfoEnabled()) {
logger.info(
"Remove Current Service Instance from Kubernetes pod. Current pod name: " + currentHostname);
}
}
}
@Override
public Set<String> getServices() {
return kubernetesClient.services().inNamespace(namespace).list().getItems().stream()
.map(service -> service.getMetadata().getName())
.collect(Collectors.toSet());
}
@Override
public List<ServiceInstance> getInstances(String serviceName) throws NullPointerException {
Endpoints endpoints = null;
SharedIndexInformer<Endpoints> endInformer = ENDPOINTS_INFORMER.get(serviceName);
if (endInformer != null) {
// get endpoints directly from informer local store
List<Endpoints> endpointsList = endInformer.getStore().list();
if (endpointsList.size() > 0) {
endpoints = endpointsList.get(0);
}
}
if (endpoints == null) {
endpoints = kubernetesClient
.endpoints()
.inNamespace(namespace)
.withName(serviceName)
.get();
}
return toServiceInstance(endpoints, serviceName);
}
@Override
public void addServiceInstancesChangedListener(ServiceInstancesChangedListener listener)
throws NullPointerException, IllegalArgumentException {
listener.getServiceNames().forEach(serviceName -> {
SERVICE_UPDATE_TIME.put(serviceName, new AtomicLong(0L));
// Watch Service Endpoint Modification
watchEndpoints(listener, serviceName);
// Watch Pods Modification, happens when ServiceInstance updated
watchPods(listener, serviceName);
// Watch Service Modification, happens when Service Selector updated, used to update pods watcher
watchService(listener, serviceName);
});
}
private void watchEndpoints(ServiceInstancesChangedListener listener, String serviceName) {
SharedIndexInformer<Endpoints> endInformer = kubernetesClient
.endpoints()
.inNamespace(namespace)
.withName(serviceName)
.inform(new ResourceEventHandler<Endpoints>() {
@Override
public void onAdd(Endpoints endpoints) {
if (logger.isDebugEnabled()) {
logger.debug("Received Endpoint Event. Event type: added. Current pod name: "
+ currentHostname + ". Endpoints is: " + endpoints);
}
notifyServiceChanged(serviceName, listener, toServiceInstance(endpoints, serviceName));
}
@Override
public void onUpdate(Endpoints oldEndpoints, Endpoints newEndpoints) {
if (logger.isDebugEnabled()) {
logger.debug("Received Endpoint Event. Event type: updated. Current pod name: "
+ currentHostname + ". The new Endpoints is: " + newEndpoints);
}
notifyServiceChanged(serviceName, listener, toServiceInstance(newEndpoints, serviceName));
}
@Override
public void onDelete(Endpoints endpoints, boolean deletedFinalStateUnknown) {
if (logger.isDebugEnabled()) {
logger.debug("Received Endpoint Event. Event type: deleted. Current pod name: "
+ currentHostname + ". Endpoints is: " + endpoints);
}
notifyServiceChanged(serviceName, listener, toServiceInstance(endpoints, serviceName));
}
});
ENDPOINTS_INFORMER.put(serviceName, endInformer);
}
private void watchPods(ServiceInstancesChangedListener listener, String serviceName) {
Map<String, String> serviceSelector = getServiceSelector(serviceName);
if (serviceSelector == null) {
return;
}
SharedIndexInformer<Pod> podInformer = kubernetesClient
.pods()
.inNamespace(namespace)
.withLabels(serviceSelector)
.inform(new ResourceEventHandler<Pod>() {
@Override
public void onAdd(Pod pod) {
if (logger.isDebugEnabled()) {
logger.debug("Received Pods Event. Event type: added. Current pod name: " + currentHostname
+ ". Pod is: " + pod);
}
}
@Override
public void onUpdate(Pod oldPod, Pod newPod) {
if (logger.isDebugEnabled()) {
logger.debug("Received Pods Event. Event type: updated. Current pod name: "
+ currentHostname + ". new Pod is: " + newPod);
}
notifyServiceChanged(serviceName, listener, getInstances(serviceName));
}
@Override
public void onDelete(Pod pod, boolean deletedFinalStateUnknown) {
if (logger.isDebugEnabled()) {
logger.debug("Received Pods Event. Event type: deleted. Current pod name: "
+ currentHostname + ". Pod is: " + pod);
}
}
});
PODS_INFORMER.put(serviceName, podInformer);
}
private void watchService(ServiceInstancesChangedListener listener, String serviceName) {
SharedIndexInformer<Service> serviceInformer = kubernetesClient
.services()
.inNamespace(namespace)
.withName(serviceName)
.inform(new ResourceEventHandler<Service>() {
@Override
public void onAdd(Service service) {
if (logger.isDebugEnabled()) {
logger.debug("Received Service Added Event. " + "Current pod name: " + currentHostname);
}
}
@Override
public void onUpdate(Service oldService, Service newService) {
if (logger.isDebugEnabled()) {
logger.debug("Received Service Update Event. Update Pods Watcher. Current pod name: "
+ currentHostname + ". The new Service is: " + newService);
}
if (PODS_INFORMER.containsKey(serviceName)) {
PODS_INFORMER.get(serviceName).close();
PODS_INFORMER.remove(serviceName);
}
watchPods(listener, serviceName);
}
@Override
public void onDelete(Service service, boolean deletedFinalStateUnknown) {
if (logger.isDebugEnabled()) {
logger.debug("Received Service Delete Event. " + "Current pod name: " + currentHostname);
}
}
});
SERVICE_INFORMER.put(serviceName, serviceInformer);
}
private void notifyServiceChanged(
String serviceName, ServiceInstancesChangedListener listener, List<ServiceInstance> serviceInstanceList) {
long receivedTime = System.nanoTime();
ServiceInstancesChangedEvent event;
event = new ServiceInstancesChangedEvent(serviceName, serviceInstanceList);
AtomicLong updateTime = SERVICE_UPDATE_TIME.get(serviceName);
long lastUpdateTime = updateTime.get();
if (lastUpdateTime <= receivedTime) {
if (updateTime.compareAndSet(lastUpdateTime, receivedTime)) {
listener.onEvent(event);
return;
}
}
if (logger.isInfoEnabled()) {
logger.info("Discard Service Instance Data. "
+ "Possible Cause: Newer message has been processed or Failed to update time record by CAS. "
+ "Current Data received time: "
+ receivedTime + ". " + "Newer Data received time: "
+ lastUpdateTime + ".");
}
}
@Override
public URL getUrl() {
return registryURL;
}
private Map<String, String> getServiceSelector(String serviceName) {
Service service = kubernetesClient
.services()
.inNamespace(namespace)
.withName(serviceName)
.get();
if (service == null) {
return null;
}
return service.getSpec().getSelector();
}
private List<ServiceInstance> toServiceInstance(Endpoints endpoints, String serviceName) {
Map<String, String> serviceSelector = getServiceSelector(serviceName);
if (serviceSelector == null) {
return new LinkedList<>();
}
Map<String, Pod> pods =
kubernetesClient.pods().inNamespace(namespace).withLabels(serviceSelector).list().getItems().stream()
.collect(Collectors.toMap(pod -> pod.getMetadata().getName(), pod -> pod));
List<ServiceInstance> instances = new LinkedList<>();
Set<Integer> instancePorts = new HashSet<>();
for (EndpointSubset endpointSubset : endpoints.getSubsets()) {
instancePorts.addAll(endpointSubset.getPorts().stream()
.map(EndpointPort::getPort)
.collect(Collectors.toSet()));
}
for (EndpointSubset endpointSubset : endpoints.getSubsets()) {
for (EndpointAddress address : endpointSubset.getAddresses()) {
Pod pod = pods.get(address.getTargetRef().getName());
String ip = address.getIp();
if (pod == null) {
logger.warn(
REGISTRY_UNABLE_MATCH_KUBERNETES,
"",
"",
"Unable to match Kubernetes Endpoint address with Pod. " + "EndpointAddress Hostname: "
+ address.getTargetRef().getName());
continue;
}
instancePorts.forEach(port -> {
ServiceInstance serviceInstance = new DefaultServiceInstance(
serviceName, ip, port, ScopeModelUtil.getApplicationModel(getUrl().getScopeModel()));
String properties = pod.getMetadata().getAnnotations().get(KUBERNETES_PROPERTIES_KEY);
if (StringUtils.isNotEmpty(properties)) {
serviceInstance.getMetadata().putAll(JsonUtils.toJavaObject(properties, Map.class));
instances.add(serviceInstance);
} else {
logger.warn(
REGISTRY_UNABLE_FIND_SERVICE_KUBERNETES,
"",
"",
"Unable to find Service Instance metadata in Pod Annotations. "
+ "Possibly cause: provider has not been initialized successfully. "
+ "EndpointAddress Hostname: "
+ address.getTargetRef().getName());
}
});
}
}
return instances;
}
/**
* UT used only
*/
@Deprecated
public void setCurrentHostname(String currentHostname) {
this.currentHostname = currentHostname;
}
/**
* UT used only
*/
@Deprecated
public void setKubernetesClient(KubernetesClient kubernetesClient) {
this.kubernetesClient = kubernetesClient;
}
}

View File

@ -1,28 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.registry.kubernetes;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.registry.client.AbstractServiceDiscoveryFactory;
import org.apache.dubbo.registry.client.ServiceDiscovery;
public class KubernetesServiceDiscoveryFactory extends AbstractServiceDiscoveryFactory {
@Override
protected ServiceDiscovery createDiscovery(URL registryURL) {
return new KubernetesServiceDiscovery(applicationModel, registryURL);
}
}

View File

@ -1,45 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.registry.kubernetes;
import io.fabric8.kubernetes.client.dsl.base.CustomResourceDefinitionContext;
public class MeshConstant {
public static CustomResourceDefinitionContext getVsDefinition() {
// TODO cache
return new CustomResourceDefinitionContext.Builder()
.withGroup("service.dubbo.apache.org")
.withVersion("v1alpha1")
.withScope("Namespaced")
.withName("virtualservices.service.dubbo.apache.org")
.withPlural("virtualservices")
.withKind("VirtualService")
.build();
}
public static CustomResourceDefinitionContext getDrDefinition() {
// TODO cache
return new CustomResourceDefinitionContext.Builder()
.withGroup("service.dubbo.apache.org")
.withVersion("v1alpha1")
.withScope("Namespaced")
.withName("destinationrules.service.dubbo.apache.org")
.withPlural("destinationrules")
.withKind("DestinationRule")
.build();
}
}

View File

@ -1,34 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.registry.kubernetes;
import org.apache.dubbo.rpc.cluster.router.mesh.route.MeshAppRuleListener;
import org.apache.dubbo.rpc.cluster.router.mesh.route.MeshEnvListener;
public class NopKubernetesMeshEnvListener implements MeshEnvListener {
@Override
public boolean isEnable() {
return false;
}
@Override
public void onSubscribe(String appName, MeshAppRuleListener listener) {}
@Override
public void onUnSubscribe(String appName) {}
}

View File

@ -1,78 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.registry.kubernetes.util;
public class KubernetesClientConst {
public static final String DEFAULT_MASTER_PLACEHOLDER = "DEFAULT_MASTER_HOST";
public static final String DEFAULT_MASTER_URL = "https://kubernetes.default.svc";
public static final String ENABLE_REGISTER = "enableRegister";
public static final String TRUST_CERTS = "trustCerts";
public static final String USE_HTTPS = "useHttps";
public static final String HTTP2_DISABLE = "http2Disable";
public static final String NAMESPACE = "namespace";
public static final String API_VERSION = "apiVersion";
public static final String CA_CERT_FILE = "caCertFile";
public static final String CA_CERT_DATA = "caCertData";
public static final String CLIENT_CERT_FILE = "clientCertFile";
public static final String CLIENT_CERT_DATA = "clientCertData";
public static final String CLIENT_KEY_FILE = "clientKeyFile";
public static final String CLIENT_KEY_DATA = "clientKeyData";
public static final String CLIENT_KEY_ALGO = "clientKeyAlgo";
public static final String CLIENT_KEY_PASSPHRASE = "clientKeyPassphrase";
public static final String OAUTH_TOKEN = "oauthToken";
public static final String USERNAME = "username";
public static final String PASSWORD = "password";
public static final String WATCH_RECONNECT_INTERVAL = "watchReconnectInterval";
public static final String WATCH_RECONNECT_LIMIT = "watchReconnectLimit";
public static final String CONNECTION_TIMEOUT = "connectionTimeout";
public static final String REQUEST_TIMEOUT = "requestTimeout";
public static final String ROLLING_TIMEOUT = "rollingTimeout";
public static final String LOGGING_INTERVAL = "loggingInterval";
public static final String HTTP_PROXY = "httpProxy";
public static final String HTTPS_PROXY = "httpsProxy";
public static final String PROXY_USERNAME = "proxyUsername";
public static final String PROXY_PASSWORD = "proxyPassword";
public static final String NO_PROXY = "noProxy";
}

View File

@ -1,104 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.registry.kubernetes.util;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.utils.StringUtils;
import java.util.Base64;
import io.fabric8.kubernetes.client.Config;
import io.fabric8.kubernetes.client.ConfigBuilder;
import static org.apache.dubbo.registry.kubernetes.util.KubernetesClientConst.API_VERSION;
import static org.apache.dubbo.registry.kubernetes.util.KubernetesClientConst.CA_CERT_DATA;
import static org.apache.dubbo.registry.kubernetes.util.KubernetesClientConst.CA_CERT_FILE;
import static org.apache.dubbo.registry.kubernetes.util.KubernetesClientConst.CLIENT_CERT_DATA;
import static org.apache.dubbo.registry.kubernetes.util.KubernetesClientConst.CLIENT_CERT_FILE;
import static org.apache.dubbo.registry.kubernetes.util.KubernetesClientConst.CLIENT_KEY_ALGO;
import static org.apache.dubbo.registry.kubernetes.util.KubernetesClientConst.CLIENT_KEY_DATA;
import static org.apache.dubbo.registry.kubernetes.util.KubernetesClientConst.CLIENT_KEY_FILE;
import static org.apache.dubbo.registry.kubernetes.util.KubernetesClientConst.CLIENT_KEY_PASSPHRASE;
import static org.apache.dubbo.registry.kubernetes.util.KubernetesClientConst.CONNECTION_TIMEOUT;
import static org.apache.dubbo.registry.kubernetes.util.KubernetesClientConst.DEFAULT_MASTER_PLACEHOLDER;
import static org.apache.dubbo.registry.kubernetes.util.KubernetesClientConst.DEFAULT_MASTER_URL;
import static org.apache.dubbo.registry.kubernetes.util.KubernetesClientConst.HTTP2_DISABLE;
import static org.apache.dubbo.registry.kubernetes.util.KubernetesClientConst.HTTPS_PROXY;
import static org.apache.dubbo.registry.kubernetes.util.KubernetesClientConst.HTTP_PROXY;
import static org.apache.dubbo.registry.kubernetes.util.KubernetesClientConst.LOGGING_INTERVAL;
import static org.apache.dubbo.registry.kubernetes.util.KubernetesClientConst.NAMESPACE;
import static org.apache.dubbo.registry.kubernetes.util.KubernetesClientConst.NO_PROXY;
import static org.apache.dubbo.registry.kubernetes.util.KubernetesClientConst.OAUTH_TOKEN;
import static org.apache.dubbo.registry.kubernetes.util.KubernetesClientConst.PASSWORD;
import static org.apache.dubbo.registry.kubernetes.util.KubernetesClientConst.PROXY_PASSWORD;
import static org.apache.dubbo.registry.kubernetes.util.KubernetesClientConst.PROXY_USERNAME;
import static org.apache.dubbo.registry.kubernetes.util.KubernetesClientConst.REQUEST_TIMEOUT;
import static org.apache.dubbo.registry.kubernetes.util.KubernetesClientConst.TRUST_CERTS;
import static org.apache.dubbo.registry.kubernetes.util.KubernetesClientConst.USERNAME;
import static org.apache.dubbo.registry.kubernetes.util.KubernetesClientConst.USE_HTTPS;
import static org.apache.dubbo.registry.kubernetes.util.KubernetesClientConst.WATCH_RECONNECT_INTERVAL;
import static org.apache.dubbo.registry.kubernetes.util.KubernetesClientConst.WATCH_RECONNECT_LIMIT;
public class KubernetesConfigUtils {
public static Config createKubernetesConfig(URL url) {
// Init default config
Config base = Config.autoConfigure(null);
// replace config with parameters if presents
return new ConfigBuilder(base) //
.withMasterUrl(buildMasterUrl(url)) //
.withApiVersion(url.getParameter(API_VERSION, base.getApiVersion())) //
.withNamespace(url.getParameter(NAMESPACE, base.getNamespace())) //
.withUsername(url.getParameter(USERNAME, base.getUsername())) //
.withPassword(url.getParameter(PASSWORD, base.getPassword())) //
.withOauthToken(url.getParameter(OAUTH_TOKEN, base.getOauthToken())) //
.withCaCertFile(url.getParameter(CA_CERT_FILE, base.getCaCertFile())) //
.withCaCertData(url.getParameter(CA_CERT_DATA, decodeBase64(base.getCaCertData()))) //
.withClientKeyFile(url.getParameter(CLIENT_KEY_FILE, base.getClientKeyFile())) //
.withClientKeyData(url.getParameter(CLIENT_KEY_DATA, decodeBase64(base.getClientKeyData()))) //
.withClientCertFile(url.getParameter(CLIENT_CERT_FILE, base.getClientCertFile())) //
.withClientCertData(url.getParameter(CLIENT_CERT_DATA, decodeBase64(base.getClientCertData()))) //
.withClientKeyAlgo(url.getParameter(CLIENT_KEY_ALGO, base.getClientKeyAlgo())) //
.withClientKeyPassphrase(url.getParameter(CLIENT_KEY_PASSPHRASE, base.getClientKeyPassphrase())) //
.withConnectionTimeout(url.getParameter(CONNECTION_TIMEOUT, base.getConnectionTimeout())) //
.withRequestTimeout(url.getParameter(REQUEST_TIMEOUT, base.getRequestTimeout())) //
.withWatchReconnectInterval(
url.getParameter(WATCH_RECONNECT_INTERVAL, base.getWatchReconnectInterval())) //
.withWatchReconnectLimit(url.getParameter(WATCH_RECONNECT_LIMIT, base.getWatchReconnectLimit())) //
.withLoggingInterval(url.getParameter(LOGGING_INTERVAL, base.getLoggingInterval())) //
.withTrustCerts(url.getParameter(TRUST_CERTS, base.isTrustCerts())) //
.withHttp2Disable(url.getParameter(HTTP2_DISABLE, base.isHttp2Disable())) //
.withHttpProxy(url.getParameter(HTTP_PROXY, base.getHttpProxy())) //
.withHttpsProxy(url.getParameter(HTTPS_PROXY, base.getHttpsProxy())) //
.withProxyUsername(url.getParameter(PROXY_USERNAME, base.getProxyUsername())) //
.withProxyPassword(url.getParameter(PROXY_PASSWORD, base.getProxyPassword())) //
.withNoProxy(url.getParameter(NO_PROXY, base.getNoProxy())) //
.build();
}
private static String buildMasterUrl(URL url) {
if (DEFAULT_MASTER_PLACEHOLDER.equalsIgnoreCase(url.getHost())) {
return DEFAULT_MASTER_URL;
}
return (url.getParameter(USE_HTTPS, true) ? "https://" : "http://") + url.getHost() + ":" + url.getPort();
}
private static String decodeBase64(String str) {
return StringUtils.isNotEmpty(str) ? new String(Base64.getDecoder().decode(str)) : null;
}
}

View File

@ -1 +0,0 @@
kubernetes=org.apache.dubbo.registry.kubernetes.KubernetesRegistryFactory

View File

@ -1 +0,0 @@
kubernetes=org.apache.dubbo.registry.kubernetes.KubernetesServiceDiscoveryFactory

View File

@ -1 +0,0 @@
kubernetes=org.apache.dubbo.registry.kubernetes.KubernetesMeshEnvListenerFactory

View File

@ -1,289 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.registry.kubernetes;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.config.ApplicationConfig;
import org.apache.dubbo.registry.client.DefaultServiceInstance;
import org.apache.dubbo.registry.client.ServiceInstance;
import org.apache.dubbo.registry.client.event.ServiceInstancesChangedEvent;
import org.apache.dubbo.registry.client.event.listener.ServiceInstancesChangedListener;
import org.apache.dubbo.registry.kubernetes.util.KubernetesClientConst;
import org.apache.dubbo.rpc.model.ApplicationModel;
import org.apache.dubbo.rpc.model.ScopeModelUtil;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import io.fabric8.kubernetes.api.model.Endpoints;
import io.fabric8.kubernetes.api.model.EndpointsBuilder;
import io.fabric8.kubernetes.api.model.Pod;
import io.fabric8.kubernetes.api.model.PodBuilder;
import io.fabric8.kubernetes.api.model.Service;
import io.fabric8.kubernetes.api.model.ServiceBuilder;
import io.fabric8.kubernetes.client.Config;
import io.fabric8.kubernetes.client.NamespacedKubernetesClient;
import io.fabric8.kubernetes.client.server.mock.KubernetesServer;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import static org.apache.dubbo.registry.kubernetes.util.KubernetesClientConst.NAMESPACE;
import static org.awaitility.Awaitility.await;
@ExtendWith({MockitoExtension.class})
class KubernetesServiceDiscoveryTest {
private static final String SERVICE_NAME = "TestService";
private static final String POD_NAME = "TestServer";
public KubernetesServer mockServer = new KubernetesServer(false, true);
private NamespacedKubernetesClient mockClient;
private ServiceInstancesChangedListener mockListener = Mockito.mock(ServiceInstancesChangedListener.class);
private URL serverUrl;
private Map<String, String> selector;
private KubernetesServiceDiscovery serviceDiscovery;
@BeforeEach
public void setUp() {
mockServer.before();
mockClient = mockServer.getClient().inNamespace("dubbo-demo");
ApplicationModel applicationModel = ApplicationModel.defaultModel();
applicationModel.getApplicationConfigManager().setApplication(new ApplicationConfig());
serverUrl = URL.valueOf(mockClient.getConfiguration().getMasterUrl())
.setProtocol("kubernetes")
.addParameter(NAMESPACE, "dubbo-demo")
.addParameter(KubernetesClientConst.USE_HTTPS, "false")
.addParameter(KubernetesClientConst.HTTP2_DISABLE, "true");
serverUrl.setScopeModel(applicationModel);
this.serviceDiscovery = new KubernetesServiceDiscovery(applicationModel, serverUrl);
System.setProperty(Config.KUBERNETES_AUTH_TRYKUBECONFIG_SYSTEM_PROPERTY, "false");
System.setProperty(Config.KUBERNETES_AUTH_TRYSERVICEACCOUNT_SYSTEM_PROPERTY, "false");
selector = new HashMap<>(4);
selector.put("l", "v");
Pod pod = new PodBuilder()
.withNewMetadata()
.withName(POD_NAME)
.withLabels(selector)
.endMetadata()
.build();
Service service = new ServiceBuilder()
.withNewMetadata()
.withName(SERVICE_NAME)
.endMetadata()
.withNewSpec()
.withSelector(selector)
.endSpec()
.build();
Endpoints endPoints = new EndpointsBuilder()
.withNewMetadata()
.withName(SERVICE_NAME)
.endMetadata()
.addNewSubset()
.addNewAddress()
.withIp("ip1")
.withNewTargetRef()
.withUid("uid1")
.withName(POD_NAME)
.endTargetRef()
.endAddress()
.addNewPort("Test", "Test", 12345, "TCP")
.endSubset()
.build();
mockClient.pods().resource(pod).create();
mockClient.services().resource(service).create();
mockClient.endpoints().resource(endPoints).create();
}
@AfterEach
public void destroy() throws Exception {
serviceDiscovery.destroy();
mockClient.close();
mockServer.after();
}
@Test
void testEndpointsUpdate() {
serviceDiscovery.setCurrentHostname(POD_NAME);
serviceDiscovery.setKubernetesClient(mockClient);
ServiceInstance serviceInstance = new DefaultServiceInstance(
SERVICE_NAME,
"Test",
12345,
ScopeModelUtil.getApplicationModel(serviceDiscovery.getUrl().getScopeModel()));
serviceDiscovery.doRegister(serviceInstance);
HashSet<String> serviceList = new HashSet<>(4);
serviceList.add(SERVICE_NAME);
Mockito.when(mockListener.getServiceNames()).thenReturn(serviceList);
Mockito.doNothing().when(mockListener).onEvent(Mockito.any());
serviceDiscovery.addServiceInstancesChangedListener(mockListener);
mockClient.endpoints().withName(SERVICE_NAME).edit(endpoints -> new EndpointsBuilder(endpoints)
.editFirstSubset()
.addNewAddress()
.withIp("ip2")
.withNewTargetRef()
.withUid("uid2")
.withName(POD_NAME)
.endTargetRef()
.endAddress()
.endSubset()
.build());
await().until(() -> {
ArgumentCaptor<ServiceInstancesChangedEvent> captor =
ArgumentCaptor.forClass(ServiceInstancesChangedEvent.class);
Mockito.verify(mockListener, Mockito.atLeast(0)).onEvent(captor.capture());
return captor.getValue().getServiceInstances().size() == 2;
});
ArgumentCaptor<ServiceInstancesChangedEvent> eventArgumentCaptor =
ArgumentCaptor.forClass(ServiceInstancesChangedEvent.class);
Mockito.verify(mockListener, Mockito.times(2)).onEvent(eventArgumentCaptor.capture());
Assertions.assertEquals(
2, eventArgumentCaptor.getValue().getServiceInstances().size());
serviceDiscovery.doUnregister(serviceInstance);
}
@Test
void testPodsUpdate() throws Exception {
serviceDiscovery.setCurrentHostname(POD_NAME);
serviceDiscovery.setKubernetesClient(mockClient);
ServiceInstance serviceInstance = new DefaultServiceInstance(
SERVICE_NAME,
"Test",
12345,
ScopeModelUtil.getApplicationModel(serviceDiscovery.getUrl().getScopeModel()));
serviceDiscovery.doRegister(serviceInstance);
HashSet<String> serviceList = new HashSet<>(4);
serviceList.add(SERVICE_NAME);
Mockito.when(mockListener.getServiceNames()).thenReturn(serviceList);
Mockito.doNothing().when(mockListener).onEvent(Mockito.any());
serviceDiscovery.addServiceInstancesChangedListener(mockListener);
serviceInstance = new DefaultServiceInstance(
SERVICE_NAME,
"Test12345",
12345,
ScopeModelUtil.getApplicationModel(serviceDiscovery.getUrl().getScopeModel()));
serviceDiscovery.doUpdate(serviceInstance, serviceInstance);
await().until(() -> {
ArgumentCaptor<ServiceInstancesChangedEvent> captor =
ArgumentCaptor.forClass(ServiceInstancesChangedEvent.class);
Mockito.verify(mockListener, Mockito.atLeast(0)).onEvent(captor.capture());
return captor.getValue().getServiceInstances().size() == 1;
});
ArgumentCaptor<ServiceInstancesChangedEvent> eventArgumentCaptor =
ArgumentCaptor.forClass(ServiceInstancesChangedEvent.class);
Mockito.verify(mockListener, Mockito.times(1)).onEvent(eventArgumentCaptor.capture());
Assertions.assertEquals(
1, eventArgumentCaptor.getValue().getServiceInstances().size());
serviceDiscovery.doUnregister(serviceInstance);
}
@Test
void testServiceUpdate() {
serviceDiscovery.setCurrentHostname(POD_NAME);
serviceDiscovery.setKubernetesClient(mockClient);
ServiceInstance serviceInstance = new DefaultServiceInstance(
SERVICE_NAME,
"Test",
12345,
ScopeModelUtil.getApplicationModel(serviceDiscovery.getUrl().getScopeModel()));
serviceDiscovery.doRegister(serviceInstance);
HashSet<String> serviceList = new HashSet<>(4);
serviceList.add(SERVICE_NAME);
Mockito.when(mockListener.getServiceNames()).thenReturn(serviceList);
Mockito.doNothing().when(mockListener).onEvent(Mockito.any());
serviceDiscovery.addServiceInstancesChangedListener(mockListener);
selector.put("app", "test");
mockClient.services().withName(SERVICE_NAME).edit(service -> new ServiceBuilder(service)
.editSpec()
.addToSelector(selector)
.endSpec()
.build());
await().until(() -> {
ArgumentCaptor<ServiceInstancesChangedEvent> captor =
ArgumentCaptor.forClass(ServiceInstancesChangedEvent.class);
Mockito.verify(mockListener, Mockito.atLeast(0)).onEvent(captor.capture());
return captor.getValue().getServiceInstances().size() == 1;
});
ArgumentCaptor<ServiceInstancesChangedEvent> eventArgumentCaptor =
ArgumentCaptor.forClass(ServiceInstancesChangedEvent.class);
Mockito.verify(mockListener, Mockito.times(1)).onEvent(eventArgumentCaptor.capture());
Assertions.assertEquals(
1, eventArgumentCaptor.getValue().getServiceInstances().size());
serviceDiscovery.doUnregister(serviceInstance);
}
@Test
void testGetInstance() {
serviceDiscovery.setCurrentHostname(POD_NAME);
serviceDiscovery.setKubernetesClient(mockClient);
ServiceInstance serviceInstance = new DefaultServiceInstance(
SERVICE_NAME,
"Test",
12345,
ScopeModelUtil.getApplicationModel(serviceDiscovery.getUrl().getScopeModel()));
serviceDiscovery.doRegister(serviceInstance);
serviceDiscovery.doUpdate(serviceInstance, serviceInstance);
Assertions.assertEquals(1, serviceDiscovery.getServices().size());
Assertions.assertEquals(1, serviceDiscovery.getInstances(SERVICE_NAME).size());
serviceDiscovery.doUnregister(serviceInstance);
}
}

View File

@ -120,13 +120,6 @@
<version>${project.version}</version>
</dependency>
<!-- kubernetes -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-kubernetes</artifactId>
<version>${project.version}</version>
</dependency>
<!-- metadata -->
<dependency>
<groupId>org.apache.dubbo</groupId>
@ -471,11 +464,5 @@
<version>${project.version}</version>
</dependency>
<!-- xds -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-xds</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</project>

View File

@ -1,123 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-parent</artifactId>
<version>${revision}</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>dubbo-xds</artifactId>
<name>${project.artifactId}</name>
<description>The xDS Integration</description>
<properties>
<skip_maven_deploy>false</skip_maven_deploy>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-registry-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-common</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty-shaded</artifactId>
</dependency>
<dependency>
<groupId>io.envoyproxy.controlplane</groupId>
<artifactId>api</artifactId>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java-util</artifactId>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-ext-jdk15on</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>${maven_protobuf_plugin_version}</version>
<configuration>
<protocArtifact>com.google.protobuf:protoc:${protobuf-java_version}:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc_version}:exe:${os.detected.classifier}</pluginArtifact>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>${maven_os_plugin_version}</version>
</extension>
</extensions>
</build>
</project>

View File

@ -1,58 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.registry.xds;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.Adaptive;
import org.apache.dubbo.common.extension.SPI;
@SPI
public interface XdsCertificateSigner {
@Adaptive(value = "signer")
CertPair GenerateCert(URL url);
class CertPair {
private final String privateKey;
private final String publicKey;
private final long createTime;
private final long expireTime;
public CertPair(String privateKey, String publicKey, long createTime, long expireTime) {
this.privateKey = privateKey;
this.publicKey = publicKey;
this.createTime = createTime;
this.expireTime = expireTime;
}
public String getPrivateKey() {
return privateKey;
}
public String getPublicKey() {
return publicKey;
}
public long getCreateTime() {
return createTime;
}
public boolean isExpire() {
return System.currentTimeMillis() < expireTime;
}
}
}

View File

@ -1,22 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.registry.xds;
public interface XdsEnv {
String getCluster();
}

View File

@ -1,28 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.registry.xds;
public final class XdsInitializationException extends Exception {
public XdsInitializationException(String message) {
super(message);
}
public XdsInitializationException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@ -1,50 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.registry.xds;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.registry.NotifyListener;
import org.apache.dubbo.registry.support.FailbackRegistry;
/**
* Empty implements for xDS <br/>
* xDS only support `Service Discovery` mode register <br/>
* Used to compat past version like 2.6.x, 2.7.x with interface level register <br/>
* {@link XdsServiceDiscovery} is the real implementation of xDS
*/
public class XdsRegistry extends FailbackRegistry {
public XdsRegistry(URL url) {
super(url);
}
@Override
public boolean isAvailable() {
return true;
}
@Override
public void doRegister(URL url) {}
@Override
public void doUnregister(URL url) {}
@Override
public void doSubscribe(URL url, NotifyListener listener) {}
@Override
public void doUnsubscribe(URL url, NotifyListener listener) {}
}

View File

@ -1,34 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.registry.xds;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.registry.Registry;
import org.apache.dubbo.registry.support.AbstractRegistryFactory;
public class XdsRegistryFactory extends AbstractRegistryFactory {
@Override
protected String createRegistryCacheKey(URL url) {
return url.toFullString();
}
@Override
protected Registry createRegistry(URL url) {
return new XdsRegistry(url);
}
}

View File

@ -1,117 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.registry.xds;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
import org.apache.dubbo.common.logger.LoggerFactory;
import org.apache.dubbo.registry.client.DefaultServiceInstance;
import org.apache.dubbo.registry.client.ReflectionBasedServiceDiscovery;
import org.apache.dubbo.registry.client.ServiceInstance;
import org.apache.dubbo.registry.client.event.listener.ServiceInstancesChangedListener;
import org.apache.dubbo.registry.xds.util.PilotExchanger;
import org.apache.dubbo.registry.xds.util.protocol.message.Endpoint;
import org.apache.dubbo.rpc.model.ApplicationModel;
import org.apache.dubbo.rpc.model.ScopeModelUtil;
import java.util.Collection;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import static org.apache.dubbo.common.constants.LoggerCodeConstants.REGISTRY_ERROR_INITIALIZE_XDS;
import static org.apache.dubbo.common.constants.LoggerCodeConstants.REGISTRY_ERROR_PARSING_XDS;
public class XdsServiceDiscovery extends ReflectionBasedServiceDiscovery {
private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(XdsServiceDiscovery.class);
private PilotExchanger exchanger;
public XdsServiceDiscovery(ApplicationModel applicationModel, URL registryURL) {
super(applicationModel, registryURL);
}
@Override
public void doInitialize(URL registryURL) {
try {
exchanger = PilotExchanger.initialize(registryURL);
} catch (Throwable t) {
logger.error(REGISTRY_ERROR_INITIALIZE_XDS, "", "", t.getMessage(), t);
}
}
@Override
public void doDestroy() {
try {
if (exchanger == null) {
return;
}
exchanger.destroy();
} catch (Throwable t) {
logger.error(REGISTRY_ERROR_INITIALIZE_XDS, "", "", t.getMessage(), t);
}
}
@Override
public Set<String> getServices() {
return exchanger.getServices();
}
@Override
public List<ServiceInstance> getInstances(String serviceName) throws NullPointerException {
Set<Endpoint> endpoints = exchanger.getEndpoints(serviceName);
return changedToInstances(serviceName, endpoints);
}
@Override
public void addServiceInstancesChangedListener(ServiceInstancesChangedListener listener)
throws NullPointerException, IllegalArgumentException {
listener.getServiceNames()
.forEach(serviceName -> exchanger.observeEndpoints(
serviceName,
(endpoints ->
notifyListener(serviceName, listener, changedToInstances(serviceName, endpoints)))));
}
private List<ServiceInstance> changedToInstances(String serviceName, Collection<Endpoint> endpoints) {
List<ServiceInstance> instances = new LinkedList<>();
endpoints.forEach(endpoint -> {
try {
DefaultServiceInstance serviceInstance = new DefaultServiceInstance(
serviceName,
endpoint.getAddress(),
endpoint.getPortValue(),
ScopeModelUtil.getApplicationModel(getUrl().getScopeModel()));
// fill metadata by SelfHostMetaServiceDiscovery, will be fetched by RPC request
serviceInstance.putExtendParam("clusterName", endpoint.getClusterName());
fillServiceInstance(serviceInstance);
instances.add(serviceInstance);
} catch (Throwable t) {
logger.error(
REGISTRY_ERROR_PARSING_XDS,
"",
"",
"Error occurred when parsing endpoints. Endpoints List:" + endpoints,
t);
}
});
instances.sort(Comparator.comparingInt(ServiceInstance::hashCode));
return instances;
}
}

View File

@ -1,48 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.registry.xds;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
import org.apache.dubbo.common.logger.LoggerFactory;
import org.apache.dubbo.registry.client.AbstractServiceDiscoveryFactory;
import org.apache.dubbo.registry.client.ServiceDiscovery;
import org.apache.dubbo.rpc.model.ApplicationModel;
import static org.apache.dubbo.common.constants.LoggerCodeConstants.REGISTRY_ERROR_INITIALIZE_XDS;
public class XdsServiceDiscoveryFactory extends AbstractServiceDiscoveryFactory {
private static final ErrorTypeAwareLogger logger =
LoggerFactory.getErrorTypeAwareLogger(XdsServiceDiscoveryFactory.class);
@Override
protected ServiceDiscovery createDiscovery(URL registryURL) {
XdsServiceDiscovery xdsServiceDiscovery = new XdsServiceDiscovery(ApplicationModel.defaultModel(), registryURL);
try {
xdsServiceDiscovery.doInitialize(registryURL);
} catch (Exception e) {
logger.error(
REGISTRY_ERROR_INITIALIZE_XDS,
"",
"",
"Error occurred when initialize xDS service discovery impl.",
e);
}
return xdsServiceDiscovery;
}
}

View File

@ -1,294 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.registry.xds.istio;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
import org.apache.dubbo.common.logger.LoggerFactory;
import org.apache.dubbo.common.utils.StringUtils;
import org.apache.dubbo.registry.xds.XdsCertificateSigner;
import org.apache.dubbo.rpc.RpcException;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.spec.ECGenParameterSpec;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import io.grpc.ManagedChannel;
import io.grpc.Metadata;
import io.grpc.netty.shaded.io.grpc.netty.GrpcSslContexts;
import io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder;
import io.grpc.netty.shaded.io.netty.handler.ssl.util.InsecureTrustManagerFactory;
import io.grpc.stub.MetadataUtils;
import io.grpc.stub.StreamObserver;
import istio.v1.auth.IstioCertificateRequest;
import istio.v1.auth.IstioCertificateResponse;
import istio.v1.auth.IstioCertificateServiceGrpc;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.ExtensionsGenerator;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.x509.GeneralNames;
import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.pkcs.PKCS10CertificationRequest;
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder;
import org.bouncycastle.util.io.pem.PemObject;
import static org.apache.dubbo.common.constants.LoggerCodeConstants.REGISTRY_FAILED_GENERATE_CERT_ISTIO;
import static org.apache.dubbo.common.constants.LoggerCodeConstants.REGISTRY_FAILED_GENERATE_KEY_ISTIO;
import static org.apache.dubbo.common.constants.LoggerCodeConstants.REGISTRY_RECEIVE_ERROR_MSG_ISTIO;
public class IstioCitadelCertificateSigner implements XdsCertificateSigner {
private static final ErrorTypeAwareLogger logger =
LoggerFactory.getErrorTypeAwareLogger(IstioCitadelCertificateSigner.class);
private final org.apache.dubbo.registry.xds.istio.IstioEnv istioEnv;
private CertPair certPair;
public IstioCitadelCertificateSigner() {
// watch cert, Refresh every 30s
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(1);
scheduledThreadPool.scheduleAtFixedRate(new GenerateCertTask(), 0, 30, TimeUnit.SECONDS);
istioEnv = IstioEnv.getInstance();
}
@Override
public CertPair GenerateCert(URL url) {
if (certPair != null && !certPair.isExpire()) {
return certPair;
}
return doGenerateCert();
}
private class GenerateCertTask implements Runnable {
@Override
public void run() {
doGenerateCert();
}
}
private CertPair doGenerateCert() {
synchronized (this) {
if (certPair == null || certPair.isExpire()) {
try {
certPair = createCert();
} catch (IOException e) {
logger.error(REGISTRY_FAILED_GENERATE_CERT_ISTIO, "", "", "Generate Cert from Istio failed.", e);
throw new RpcException("Generate Cert from Istio failed.", e);
}
}
}
return certPair;
}
public CertPair createCert() throws IOException {
PublicKey publicKey = null;
PrivateKey privateKey = null;
ContentSigner signer = null;
if (istioEnv.isECCFirst()) {
try {
ECGenParameterSpec ecSpec = new ECGenParameterSpec("secp256r1");
KeyPairGenerator g = KeyPairGenerator.getInstance("EC");
g.initialize(ecSpec, new SecureRandom());
KeyPair keypair = g.generateKeyPair();
publicKey = keypair.getPublic();
privateKey = keypair.getPrivate();
signer = new JcaContentSignerBuilder("SHA256withECDSA").build(keypair.getPrivate());
} catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException | OperatorCreationException e) {
logger.error(
REGISTRY_FAILED_GENERATE_KEY_ISTIO,
"",
"",
"Generate Key with secp256r1 algorithm failed. Please check if your system support. "
+ "Will attempt to generate with RSA2048.",
e);
}
}
if (publicKey == null) {
try {
KeyPairGenerator kpGenerator = KeyPairGenerator.getInstance("RSA");
kpGenerator.initialize(istioEnv.getRasKeySize());
KeyPair keypair = kpGenerator.generateKeyPair();
publicKey = keypair.getPublic();
privateKey = keypair.getPrivate();
signer = new JcaContentSignerBuilder("SHA256WithRSA").build(keypair.getPrivate());
} catch (NoSuchAlgorithmException | OperatorCreationException e) {
logger.error(
REGISTRY_FAILED_GENERATE_KEY_ISTIO,
"",
"",
"Generate Key with SHA256WithRSA algorithm failed. Please check if your system support.",
e);
throw new RpcException(e);
}
}
String csr = generateCsr(publicKey, signer);
String caCert = istioEnv.getCaCert();
ManagedChannel channel;
if (StringUtils.isNotEmpty(caCert)) {
channel = NettyChannelBuilder.forTarget(istioEnv.getCaAddr())
.sslContext(GrpcSslContexts.forClient()
.trustManager(new ByteArrayInputStream(caCert.getBytes(StandardCharsets.UTF_8)))
.build())
.build();
} else {
channel = NettyChannelBuilder.forTarget(istioEnv.getCaAddr())
.sslContext(GrpcSslContexts.forClient()
.trustManager(InsecureTrustManagerFactory.INSTANCE)
.build())
.build();
}
Metadata header = new Metadata();
Metadata.Key<String> key = Metadata.Key.of("authorization", Metadata.ASCII_STRING_MARSHALLER);
header.put(key, "Bearer " + istioEnv.getServiceAccount());
key = Metadata.Key.of("ClusterID", Metadata.ASCII_STRING_MARSHALLER);
header.put(key, istioEnv.getIstioMetaClusterId());
IstioCertificateServiceGrpc.IstioCertificateServiceStub stub = IstioCertificateServiceGrpc.newStub(channel);
stub = stub.withInterceptors(MetadataUtils.newAttachHeadersInterceptor(header));
CountDownLatch countDownLatch = new CountDownLatch(1);
StringBuffer publicKeyBuilder = new StringBuffer();
AtomicBoolean failed = new AtomicBoolean(false);
stub.createCertificate(
generateRequest(csr), generateResponseObserver(countDownLatch, publicKeyBuilder, failed));
long expireTime =
System.currentTimeMillis() + (long) (istioEnv.getSecretTTL() * istioEnv.getSecretGracePeriodRatio());
try {
countDownLatch.await();
} catch (InterruptedException e) {
throw new RpcException("Generate Cert Failed. Wait for cert failed.", e);
}
if (failed.get()) {
throw new RpcException("Generate Cert Failed. Send csr request failed. Please check log above.");
}
String privateKeyPem = generatePrivatePemKey(privateKey);
CertPair certPair =
new CertPair(privateKeyPem, publicKeyBuilder.toString(), System.currentTimeMillis(), expireTime);
channel.shutdown();
return certPair;
}
private IstioCertificateRequest generateRequest(String csr) {
return IstioCertificateRequest.newBuilder()
.setCsr(csr)
.setValidityDuration(istioEnv.getSecretTTL())
.build();
}
private StreamObserver<IstioCertificateResponse> generateResponseObserver(
CountDownLatch countDownLatch, StringBuffer publicKeyBuilder, AtomicBoolean failed) {
return new StreamObserver<IstioCertificateResponse>() {
@Override
public void onNext(IstioCertificateResponse istioCertificateResponse) {
for (int i = 0; i < istioCertificateResponse.getCertChainCount(); i++) {
publicKeyBuilder.append(
istioCertificateResponse.getCertChainBytes(i).toStringUtf8());
}
if (logger.isDebugEnabled()) {
logger.debug("Receive Cert chain from Istio Citadel. \n" + publicKeyBuilder);
}
countDownLatch.countDown();
}
@Override
public void onError(Throwable throwable) {
failed.set(true);
logger.error(
REGISTRY_RECEIVE_ERROR_MSG_ISTIO,
"",
"",
"Receive error message from Istio Citadel grpc stub.",
throwable);
countDownLatch.countDown();
}
@Override
public void onCompleted() {
countDownLatch.countDown();
}
};
}
private String generatePrivatePemKey(PrivateKey privateKey) throws IOException {
String key = generatePemKey("RSA PRIVATE KEY", privateKey.getEncoded());
if (logger.isDebugEnabled()) {
logger.debug("Generated Private Key. \n" + key);
}
return key;
}
private String generatePemKey(String type, byte[] content) throws IOException {
PemObject pemObject = new PemObject(type, content);
StringWriter str = new StringWriter();
JcaPEMWriter jcaPEMWriter = new JcaPEMWriter(str);
jcaPEMWriter.writeObject(pemObject);
jcaPEMWriter.close();
str.close();
return str.toString();
}
private String generateCsr(PublicKey publicKey, ContentSigner signer) throws IOException {
GeneralNames subjectAltNames = new GeneralNames(new GeneralName[] {new GeneralName(6, istioEnv.getCsrHost())});
ExtensionsGenerator extGen = new ExtensionsGenerator();
extGen.addExtension(Extension.subjectAlternativeName, true, subjectAltNames);
PKCS10CertificationRequest request = new JcaPKCS10CertificationRequestBuilder(
new X500Name("O=" + istioEnv.getTrustDomain()), publicKey)
.addAttribute(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest, extGen.generate())
.build(signer);
String csr = generatePemKey("CERTIFICATE REQUEST", request.getEncoded());
if (logger.isDebugEnabled()) {
logger.debug("CSR Request to Istio Citadel. \n" + csr);
}
return csr;
}
}

View File

@ -1,109 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.registry.xds.istio;
public class IstioConstant {
/**
* Address of the spiffe certificate provider. Defaults to discoveryAddress
*/
public static final String CA_ADDR_KEY = "CA_ADDR";
/**
* CA and xDS services
*/
public static final String DEFAULT_CA_ADDR = "istiod.istio-system.svc:15012";
/**
* The trust domain for spiffe certificates
*/
public static final String TRUST_DOMAIN_KEY = "TRUST_DOMAIN";
/**
* The trust domain for spiffe certificates default value
*/
public static final String DEFAULT_TRUST_DOMAIN = "cluster.local";
public static final String WORKLOAD_NAMESPACE_KEY = "WORKLOAD_NAMESPACE";
public static final String DEFAULT_WORKLOAD_NAMESPACE = "default";
/**
* k8s jwt token
*/
public static final String KUBERNETES_SA_PATH = "/var/run/secrets/kubernetes.io/serviceaccount/token";
public static final String KUBERNETES_CA_PATH = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt";
public static final String ISTIO_SA_PATH = "/var/run/secrets/tokens/istio-token";
public static final String ISTIO_CA_PATH = "/var/run/secrets/istio/root-cert.pem";
public static final String KUBERNETES_NAMESPACE_PATH = "/var/run/secrets/kubernetes.io/serviceaccount/namespace";
public static final String RSA_KEY_SIZE_KEY = "RSA_KEY_SIZE";
public static final String DEFAULT_RSA_KEY_SIZE = "2048";
/**
* The type of ECC signature algorithm to use when generating private keys
*/
public static final String ECC_SIG_ALG_KEY = "ECC_SIGNATURE_ALGORITHM";
public static final String DEFAULT_ECC_SIG_ALG = "ECDSA";
/**
* The cert lifetime requested by istio agent
*/
public static final String SECRET_TTL_KEY = "SECRET_TTL";
/**
* The cert lifetime default value 24h0m0s
*/
public static final String DEFAULT_SECRET_TTL = "86400"; // 24 * 60 * 60
/**
* The grace period ratio for the cert rotation
*/
public static final String SECRET_GRACE_PERIOD_RATIO_KEY = "SECRET_GRACE_PERIOD_RATIO";
/**
* The grace period ratio for the cert rotation, by default 0.5
*/
public static final String DEFAULT_SECRET_GRACE_PERIOD_RATIO = "0.5";
public static final String ISTIO_META_CLUSTER_ID_KEY = "ISTIO_META_CLUSTER_ID";
public static final String PILOT_CERT_PROVIDER_KEY = "PILOT_CERT_PROVIDER";
public static final String ISTIO_PILOT_CERT_PROVIDER = "istiod";
public static final String DEFAULT_ISTIO_META_CLUSTER_ID = "Kubernetes";
public static final String SPIFFE = "spiffe://";
public static final String NS = "/ns/";
public static final String SA = "/sa/";
public static final String JWT_POLICY = "JWT_POLICY";
public static final String DEFAULT_JWT_POLICY = "first-party-jwt";
public static final String FIRST_PARTY_JWT = "first-party-jwt";
public static final String THIRD_PARTY_JWT = "third-party-jwt";
}

View File

@ -1,195 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.registry.xds.istio;
import org.apache.dubbo.common.constants.LoggerCodeConstants;
import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
import org.apache.dubbo.common.logger.LoggerFactory;
import org.apache.dubbo.registry.xds.XdsEnv;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Optional;
import org.apache.commons.io.FileUtils;
import static org.apache.dubbo.common.constants.LoggerCodeConstants.REGISTRY_ERROR_READ_FILE_ISTIO;
import static org.apache.dubbo.registry.xds.istio.IstioConstant.NS;
import static org.apache.dubbo.registry.xds.istio.IstioConstant.SA;
import static org.apache.dubbo.registry.xds.istio.IstioConstant.SPIFFE;
public class IstioEnv implements XdsEnv {
private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(IstioEnv.class);
private static final IstioEnv INSTANCE = new IstioEnv();
private String podName;
private String caAddr;
private String jwtPolicy;
private String trustDomain;
private String workloadNameSpace;
private int rasKeySize;
private String eccSigAlg;
private int secretTTL;
private float secretGracePeriodRatio;
private String istioMetaClusterId;
private String pilotCertProvider;
private IstioEnv() {
jwtPolicy =
Optional.ofNullable(System.getenv(IstioConstant.JWT_POLICY)).orElse(IstioConstant.DEFAULT_JWT_POLICY);
podName = Optional.ofNullable(System.getenv("POD_NAME")).orElse(System.getenv("HOSTNAME"));
trustDomain = Optional.ofNullable(System.getenv(IstioConstant.TRUST_DOMAIN_KEY))
.orElse(IstioConstant.DEFAULT_TRUST_DOMAIN);
workloadNameSpace = Optional.ofNullable(System.getenv(IstioConstant.WORKLOAD_NAMESPACE_KEY))
.orElseGet(() -> {
File namespaceFile = new File(IstioConstant.KUBERNETES_NAMESPACE_PATH);
if (namespaceFile.canRead()) {
try {
return FileUtils.readFileToString(namespaceFile, StandardCharsets.UTF_8);
} catch (IOException e) {
logger.error(REGISTRY_ERROR_READ_FILE_ISTIO, "", "", "read namespace file error", e);
}
}
return IstioConstant.DEFAULT_WORKLOAD_NAMESPACE;
});
caAddr = Optional.ofNullable(System.getenv(IstioConstant.CA_ADDR_KEY)).orElse(IstioConstant.DEFAULT_CA_ADDR);
rasKeySize = Integer.parseInt(Optional.ofNullable(System.getenv(IstioConstant.RSA_KEY_SIZE_KEY))
.orElse(IstioConstant.DEFAULT_RSA_KEY_SIZE));
eccSigAlg = Optional.ofNullable(System.getenv(IstioConstant.ECC_SIG_ALG_KEY))
.orElse(IstioConstant.DEFAULT_ECC_SIG_ALG);
secretTTL = Integer.parseInt(Optional.ofNullable(System.getenv(IstioConstant.SECRET_TTL_KEY))
.orElse(IstioConstant.DEFAULT_SECRET_TTL));
secretGracePeriodRatio =
Float.parseFloat(Optional.ofNullable(System.getenv(IstioConstant.SECRET_GRACE_PERIOD_RATIO_KEY))
.orElse(IstioConstant.DEFAULT_SECRET_GRACE_PERIOD_RATIO));
istioMetaClusterId = Optional.ofNullable(System.getenv(IstioConstant.ISTIO_META_CLUSTER_ID_KEY))
.orElse(IstioConstant.DEFAULT_ISTIO_META_CLUSTER_ID);
pilotCertProvider = Optional.ofNullable(System.getenv(IstioConstant.PILOT_CERT_PROVIDER_KEY))
.orElse("");
if (getServiceAccount() == null) {
throw new UnsupportedOperationException("Unable to found kubernetes service account token file. "
+ "Please check if work in Kubernetes and mount service account token file correctly.");
}
}
public static IstioEnv getInstance() {
return INSTANCE;
}
public String getPodName() {
return podName;
}
public String getCaAddr() {
return caAddr;
}
public String getServiceAccount() {
File saFile;
switch (jwtPolicy) {
case IstioConstant.FIRST_PARTY_JWT:
saFile = new File(IstioConstant.KUBERNETES_SA_PATH);
break;
case IstioConstant.THIRD_PARTY_JWT:
default:
saFile = new File(IstioConstant.ISTIO_SA_PATH);
}
if (saFile.canRead()) {
try {
return FileUtils.readFileToString(saFile, StandardCharsets.UTF_8);
} catch (IOException e) {
logger.error(
LoggerCodeConstants.REGISTRY_ISTIO_EXCEPTION,
"File Read Failed",
"",
"Unable to read token file.",
e);
}
}
return null;
}
public String getCsrHost() {
// spiffe://<trust_domain>/ns/<namespace>/sa/<service_account>
return SPIFFE + trustDomain + NS + workloadNameSpace + SA + getServiceAccount();
}
public String getTrustDomain() {
return trustDomain;
}
public String getWorkloadNameSpace() {
return workloadNameSpace;
}
@Override
public String getCluster() {
return null;
}
public int getRasKeySize() {
return rasKeySize;
}
public boolean isECCFirst() {
return IstioConstant.DEFAULT_ECC_SIG_ALG.equals(eccSigAlg);
}
public int getSecretTTL() {
return secretTTL;
}
public float getSecretGracePeriodRatio() {
return secretGracePeriodRatio;
}
public String getIstioMetaClusterId() {
return istioMetaClusterId;
}
public String getCaCert() {
File caFile;
if (IstioConstant.ISTIO_PILOT_CERT_PROVIDER.equals(pilotCertProvider)) {
caFile = new File(IstioConstant.ISTIO_CA_PATH);
} else {
return null;
}
if (caFile.canRead()) {
try {
return FileUtils.readFileToString(caFile, StandardCharsets.UTF_8);
} catch (IOException e) {
logger.error(
LoggerCodeConstants.REGISTRY_ISTIO_EXCEPTION, "File Read Failed", "", "read ca file error", e);
}
}
return null;
}
}

View File

@ -1,140 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.registry.xds.util;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
import org.apache.dubbo.common.logger.LoggerFactory;
import org.apache.dubbo.common.threadpool.manager.FrameworkExecutorRepository;
import org.apache.dubbo.registry.xds.util.protocol.AbstractProtocol;
import org.apache.dubbo.registry.xds.util.protocol.DeltaResource;
import org.apache.dubbo.rpc.model.ApplicationModel;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import io.envoyproxy.envoy.config.core.v3.Node;
import io.envoyproxy.envoy.service.discovery.v3.DiscoveryRequest;
import io.envoyproxy.envoy.service.discovery.v3.DiscoveryResponse;
import io.grpc.stub.StreamObserver;
import static org.apache.dubbo.common.constants.LoggerCodeConstants.REGISTRY_ERROR_REQUEST_XDS;
public class AdsObserver {
private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(AdsObserver.class);
private final ApplicationModel applicationModel;
private final URL url;
private final Node node;
private volatile XdsChannel xdsChannel;
private final Map<String, XdsListener> listeners = new ConcurrentHashMap<>();
protected StreamObserver<DiscoveryRequest> requestObserver;
private final Map<String, DiscoveryRequest> observedResources = new ConcurrentHashMap<>();
public AdsObserver(URL url, Node node) {
this.url = url;
this.node = node;
this.xdsChannel = new XdsChannel(url);
this.applicationModel = url.getOrDefaultApplicationModel();
}
public <T, S extends DeltaResource<T>> void addListener(AbstractProtocol<T, S> protocol) {
listeners.put(protocol.getTypeUrl(), protocol);
}
public void request(DiscoveryRequest discoveryRequest) {
if (requestObserver == null) {
requestObserver = xdsChannel.createDeltaDiscoveryRequest(new ResponseObserver(this));
}
requestObserver.onNext(discoveryRequest);
observedResources.put(discoveryRequest.getTypeUrl(), discoveryRequest);
}
private static class ResponseObserver implements StreamObserver<DiscoveryResponse> {
private AdsObserver adsObserver;
public ResponseObserver(AdsObserver adsObserver) {
this.adsObserver = adsObserver;
}
@Override
public void onNext(DiscoveryResponse discoveryResponse) {
XdsListener xdsListener = adsObserver.listeners.get(discoveryResponse.getTypeUrl());
xdsListener.process(discoveryResponse);
adsObserver.requestObserver.onNext(buildAck(discoveryResponse));
}
protected DiscoveryRequest buildAck(DiscoveryResponse response) {
// for ACK
return DiscoveryRequest.newBuilder()
.setNode(adsObserver.node)
.setTypeUrl(response.getTypeUrl())
.setVersionInfo(response.getVersionInfo())
.setResponseNonce(response.getNonce())
.addAllResourceNames(adsObserver
.observedResources
.get(response.getTypeUrl())
.getResourceNamesList())
.build();
}
@Override
public void onError(Throwable throwable) {
logger.error(REGISTRY_ERROR_REQUEST_XDS, "", "", "xDS Client received error message! detail:", throwable);
adsObserver.triggerReConnectTask();
}
@Override
public void onCompleted() {
logger.info("xDS Client completed");
adsObserver.triggerReConnectTask();
}
}
private void triggerReConnectTask() {
ScheduledExecutorService scheduledFuture = applicationModel
.getFrameworkModel()
.getBeanFactory()
.getBean(FrameworkExecutorRepository.class)
.getSharedScheduledExecutor();
scheduledFuture.schedule(this::recover, 3, TimeUnit.SECONDS);
}
private void recover() {
try {
xdsChannel = new XdsChannel(url);
if (xdsChannel.getChannel() != null) {
requestObserver = xdsChannel.createDeltaDiscoveryRequest(new ResponseObserver(this));
observedResources.values().forEach(requestObserver::onNext);
return;
} else {
logger.error(
REGISTRY_ERROR_REQUEST_XDS,
"",
"",
"Recover failed for xDS connection. Will retry. Create channel failed.");
}
} catch (Exception e) {
logger.error(REGISTRY_ERROR_REQUEST_XDS, "", "", "Recover failed for xDS connection. Will retry.", e);
}
triggerReConnectTask();
}
}

View File

@ -1,43 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.registry.xds.util;
import org.apache.dubbo.common.utils.NetUtils;
import org.apache.dubbo.registry.xds.istio.IstioEnv;
import io.envoyproxy.envoy.config.core.v3.Node;
public class NodeBuilder {
private static final String SVC_CLUSTER_LOCAL = ".svc.cluster.local";
public static Node build() {
// String podName = System.getenv("metadata.name");
// String podNamespace = System.getenv("metadata.namespace");
String podName = IstioEnv.getInstance().getPodName();
String podNamespace = IstioEnv.getInstance().getWorkloadNameSpace();
String svcName = IstioEnv.getInstance().getIstioMetaClusterId();
// id -> sidecar~ip~{POD_NAME}~{NAMESPACE_NAME}.svc.cluster.local
// cluster -> {SVC_NAME}
return Node.newBuilder()
.setId("sidecar~" + NetUtils.getLocalHost() + "~" + podName + "~" + podNamespace + SVC_CLUSTER_LOCAL)
.setCluster(svcName)
.build();
}
}

View File

@ -1,250 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.registry.xds.util;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.threadpool.manager.FrameworkExecutorRepository;
import org.apache.dubbo.common.utils.CollectionUtils;
import org.apache.dubbo.common.utils.ConcurrentHashSet;
import org.apache.dubbo.registry.xds.util.protocol.AbstractProtocol;
import org.apache.dubbo.registry.xds.util.protocol.impl.EdsProtocol;
import org.apache.dubbo.registry.xds.util.protocol.impl.LdsProtocol;
import org.apache.dubbo.registry.xds.util.protocol.impl.RdsProtocol;
import org.apache.dubbo.registry.xds.util.protocol.message.Endpoint;
import org.apache.dubbo.registry.xds.util.protocol.message.EndpointResult;
import org.apache.dubbo.registry.xds.util.protocol.message.ListenerResult;
import org.apache.dubbo.registry.xds.util.protocol.message.RouteResult;
import org.apache.dubbo.rpc.cluster.router.xds.RdsVirtualHostListener;
import org.apache.dubbo.rpc.model.ApplicationModel;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
import java.util.stream.Collectors;
public class PilotExchanger {
protected final XdsChannel xdsChannel;
protected final LdsProtocol ldsProtocol;
protected final RdsProtocol rdsProtocol;
protected final EdsProtocol edsProtocol;
protected Map<String, ListenerResult> listenerResult;
protected Map<String, RouteResult> routeResult;
private final AtomicBoolean isRdsObserve = new AtomicBoolean(false);
private final Set<String> domainObserveRequest = new ConcurrentHashSet<String>();
private final Map<String, Set<Consumer<Set<Endpoint>>>> domainObserveConsumer = new ConcurrentHashMap<>();
private final Map<String, Consumer<RdsVirtualHostListener>> rdsObserveConsumer = new ConcurrentHashMap<>();
private static PilotExchanger GLOBAL_PILOT_EXCHANGER = null;
private final ApplicationModel applicationModel;
protected PilotExchanger(URL url) {
xdsChannel = new XdsChannel(url);
int pollingTimeout = url.getParameter("pollingTimeout", 10);
this.applicationModel = url.getOrDefaultApplicationModel();
AdsObserver adsObserver = new AdsObserver(url, NodeBuilder.build());
this.ldsProtocol = new LdsProtocol(adsObserver, NodeBuilder.build(), pollingTimeout);
this.rdsProtocol = new RdsProtocol(adsObserver, NodeBuilder.build(), pollingTimeout);
this.edsProtocol = new EdsProtocol(adsObserver, NodeBuilder.build(), pollingTimeout);
this.listenerResult = ldsProtocol.getListeners();
this.routeResult = rdsProtocol.getResource(
listenerResult.values().iterator().next().getRouteConfigNames());
Set<String> ldsResourcesName = new HashSet<>();
ldsResourcesName.add(AbstractProtocol.emptyResourceName);
// Observer RDS update
if (CollectionUtils.isNotEmpty(listenerResult.values().iterator().next().getRouteConfigNames())) {
createRouteObserve();
isRdsObserve.set(true);
}
// Observe LDS updated
ldsProtocol.observeResource(
ldsResourcesName,
(newListener) -> {
// update local cache
if (!newListener.equals(listenerResult)) {
this.listenerResult = newListener;
// update RDS observation
if (isRdsObserve.get()) {
createRouteObserve();
}
}
},
false);
}
private void createRouteObserve() {
rdsProtocol.observeResource(
listenerResult.values().iterator().next().getRouteConfigNames(),
(newResult) -> {
// check if observed domain update ( will update endpoint observation )
List<String> domainsToUpdate = new LinkedList<>();
domainObserveConsumer.forEach((domain, consumer) -> {
newResult.values().forEach(o -> {
Set<String> newRoute = o.searchDomain(domain);
for (Map.Entry<String, RouteResult> entry : routeResult.entrySet()) {
if (!entry.getValue().searchDomain(domain).equals(newRoute)) {
// routers in observed domain has been updated
// Long domainRequest = domainObserveRequest.get(domain);
// router list is empty when observeEndpoints() called and domainRequest has not
// been created yet
// create new observation
domainsToUpdate.add(domain);
// doObserveEndpoints(domain);
}
}
});
});
routeResult = newResult;
ExecutorService executorService = applicationModel
.getFrameworkModel()
.getBeanFactory()
.getBean(FrameworkExecutorRepository.class)
.getSharedExecutor();
executorService.submit(() -> domainsToUpdate.forEach(this::doObserveEndpoints));
},
false);
}
public static PilotExchanger initialize(URL url) {
synchronized (PilotExchanger.class) {
if (GLOBAL_PILOT_EXCHANGER != null) {
return GLOBAL_PILOT_EXCHANGER;
}
return (GLOBAL_PILOT_EXCHANGER = new PilotExchanger(url));
}
}
public static PilotExchanger getInstance() {
synchronized (PilotExchanger.class) {
return GLOBAL_PILOT_EXCHANGER;
}
}
public static boolean isEnabled() {
return GLOBAL_PILOT_EXCHANGER != null;
}
public void destroy() {
xdsChannel.destroy();
}
public Set<String> getServices() {
Set<String> domains = new HashSet<>();
for (Map.Entry<String, RouteResult> entry : routeResult.entrySet()) {
domains.addAll(entry.getValue().getDomains());
}
return domains;
}
public Set<Endpoint> getEndpoints(String domain) {
Set<Endpoint> endpoints = new HashSet<>();
for (Map.Entry<String, RouteResult> entry : routeResult.entrySet()) {
Set<String> cluster = entry.getValue().searchDomain(domain);
if (CollectionUtils.isNotEmpty(cluster)) {
Map<String, EndpointResult> endpointResultList = edsProtocol.getResource(cluster);
endpointResultList.forEach((k, v) -> endpoints.addAll(v.getEndpoints()));
} else {
return Collections.emptySet();
}
}
return endpoints;
}
public void observeEndpoints(String domain, Consumer<Set<Endpoint>> consumer) {
// store Consumer
domainObserveConsumer.compute(domain, (k, v) -> {
if (v == null) {
v = new ConcurrentHashSet<>();
}
// support multi-consumer
v.add(consumer);
return v;
});
if (!domainObserveRequest.contains(domain)) {
doObserveEndpoints(domain);
}
}
private void doObserveEndpoints(String domain) {
for (Map.Entry<String, RouteResult> entry : routeResult.entrySet()) {
Set<String> router = entry.getValue().searchDomain(domain);
// if router is empty, do nothing
// observation will be created when RDS updates
if (CollectionUtils.isNotEmpty(router)) {
edsProtocol.observeResource(
router,
(endpointResultMap) -> {
Set<Endpoint> endpoints = endpointResultMap.values().stream()
.map(EndpointResult::getEndpoints)
.flatMap(Set::stream)
.collect(Collectors.toSet());
for (Consumer<Set<Endpoint>> consumer : domainObserveConsumer.get(domain)) {
consumer.accept(endpoints);
}
},
false);
domainObserveRequest.add(domain);
}
}
}
public void unObserveEndpoints(String domain, Consumer<Set<Endpoint>> consumer) {
domainObserveConsumer.get(domain).remove(consumer);
domainObserveRequest.remove(domain);
}
public void observeEds(Set<String> clusterNames, Consumer<Map<String, EndpointResult>> consumer) {
edsProtocol.observeResource(clusterNames, consumer, false);
}
public void unObserveEds(Set<String> clusterNames, Consumer<Map<String, EndpointResult>> consumer) {
edsProtocol.unobserveResource(clusterNames, consumer);
}
public void observeRds(Set<String> clusterNames, Consumer<Map<String, RouteResult>> consumer) {
rdsProtocol.observeResource(clusterNames, consumer, false);
}
public void unObserveRds(Set<String> clusterNames, Consumer<Map<String, RouteResult>> consumer) {
rdsProtocol.unobserveResource(clusterNames, consumer);
}
public void observeLds(Consumer<Map<String, ListenerResult>> consumer) {
ldsProtocol.observeResource(Collections.singleton(AbstractProtocol.emptyResourceName), consumer, false);
}
public void unObserveLds(Consumer<Map<String, ListenerResult>> consumer) {
ldsProtocol.unobserveResource(Collections.singleton(AbstractProtocol.emptyResourceName), consumer);
}
}

View File

@ -1,142 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.registry.xds.util;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
import org.apache.dubbo.common.logger.LoggerFactory;
import org.apache.dubbo.common.url.component.URLAddress;
import org.apache.dubbo.registry.xds.XdsCertificateSigner;
import org.apache.dubbo.registry.xds.util.bootstrap.Bootstrapper;
import org.apache.dubbo.registry.xds.util.bootstrap.BootstrapperImpl;
import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import io.envoyproxy.envoy.service.discovery.v3.AggregatedDiscoveryServiceGrpc;
import io.envoyproxy.envoy.service.discovery.v3.DeltaDiscoveryRequest;
import io.envoyproxy.envoy.service.discovery.v3.DeltaDiscoveryResponse;
import io.envoyproxy.envoy.service.discovery.v3.DiscoveryRequest;
import io.envoyproxy.envoy.service.discovery.v3.DiscoveryResponse;
import io.grpc.ManagedChannel;
import io.grpc.netty.shaded.io.grpc.netty.GrpcSslContexts;
import io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder;
import io.grpc.netty.shaded.io.netty.channel.epoll.EpollDomainSocketChannel;
import io.grpc.netty.shaded.io.netty.channel.epoll.EpollEventLoopGroup;
import io.grpc.netty.shaded.io.netty.channel.unix.DomainSocketAddress;
import io.grpc.netty.shaded.io.netty.handler.ssl.SslContext;
import io.grpc.netty.shaded.io.netty.handler.ssl.util.InsecureTrustManagerFactory;
import io.grpc.stub.StreamObserver;
import static org.apache.dubbo.common.constants.LoggerCodeConstants.REGISTRY_ERROR_CREATE_CHANNEL_XDS;
public class XdsChannel {
private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(XdsChannel.class);
private static final String USE_AGENT = "use-agent";
private URL url;
private static final String SECURE = "secure";
private static final String PLAINTEXT = "plaintext";
private final ManagedChannel channel;
public URL getUrl() {
return url;
}
public ManagedChannel getChannel() {
return channel;
}
public XdsChannel(URL url) {
ManagedChannel managedChannel = null;
this.url = url;
try {
if (!url.getParameter(USE_AGENT, false)) {
if (PLAINTEXT.equals(url.getParameter(SECURE))) {
managedChannel = NettyChannelBuilder.forAddress(url.getHost(), url.getPort())
.usePlaintext()
.build();
} else {
XdsCertificateSigner signer = url.getOrDefaultApplicationModel()
.getExtensionLoader(XdsCertificateSigner.class)
.getExtension(url.getParameter("signer", "istio"));
XdsCertificateSigner.CertPair certPair = signer.GenerateCert(url);
SslContext context = GrpcSslContexts.forClient()
.trustManager(InsecureTrustManagerFactory.INSTANCE)
.keyManager(
new ByteArrayInputStream(
certPair.getPublicKey().getBytes(StandardCharsets.UTF_8)),
new ByteArrayInputStream(
certPair.getPrivateKey().getBytes(StandardCharsets.UTF_8)))
.build();
managedChannel = NettyChannelBuilder.forAddress(url.getHost(), url.getPort())
.sslContext(context)
.build();
}
} else {
BootstrapperImpl bootstrapper = new BootstrapperImpl();
Bootstrapper.BootstrapInfo bootstrapInfo = bootstrapper.bootstrap();
URLAddress address =
URLAddress.parse(bootstrapInfo.servers().get(0).target(), null, false);
EpollEventLoopGroup elg = new EpollEventLoopGroup();
managedChannel = NettyChannelBuilder.forAddress(new DomainSocketAddress("/" + address.getPath()))
.eventLoopGroup(elg)
.channelType(EpollDomainSocketChannel.class)
.usePlaintext()
.build();
}
} catch (Exception e) {
logger.error(
REGISTRY_ERROR_CREATE_CHANNEL_XDS,
"",
"",
"Error occurred when creating gRPC channel to control panel.",
e);
}
channel = managedChannel;
}
public StreamObserver<DeltaDiscoveryRequest> observeDeltaDiscoveryRequest(
StreamObserver<DeltaDiscoveryResponse> observer) {
return AggregatedDiscoveryServiceGrpc.newStub(channel).deltaAggregatedResources(observer);
}
public StreamObserver<DiscoveryRequest> createDeltaDiscoveryRequest(StreamObserver<DiscoveryResponse> observer) {
return AggregatedDiscoveryServiceGrpc.newStub(channel).streamAggregatedResources(observer);
}
public StreamObserver<io.envoyproxy.envoy.api.v2.DeltaDiscoveryRequest> observeDeltaDiscoveryRequestV2(
StreamObserver<io.envoyproxy.envoy.api.v2.DeltaDiscoveryResponse> observer) {
return io.envoyproxy.envoy.service.discovery.v2.AggregatedDiscoveryServiceGrpc.newStub(channel)
.deltaAggregatedResources(observer);
}
public StreamObserver<io.envoyproxy.envoy.api.v2.DiscoveryRequest> createDeltaDiscoveryRequestV2(
StreamObserver<io.envoyproxy.envoy.api.v2.DiscoveryResponse> observer) {
return io.envoyproxy.envoy.service.discovery.v2.AggregatedDiscoveryServiceGrpc.newStub(channel)
.streamAggregatedResources(observer);
}
public void destroy() {
channel.shutdown();
}
}

View File

@ -1,23 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.registry.xds.util;
import io.envoyproxy.envoy.service.discovery.v3.DiscoveryResponse;
public interface XdsListener {
void process(DiscoveryResponse discoveryResponse);
}

View File

@ -1,131 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.registry.xds.util.bootstrap;
import javax.annotation.Nullable;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import io.envoyproxy.envoy.config.core.v3.Node;
public final class BootstrapInfoImpl extends Bootstrapper.BootstrapInfo {
private final List<Bootstrapper.ServerInfo> servers;
private final String serverListenerResourceNameTemplate;
private final Map<String, Bootstrapper.CertificateProviderInfo> certProviders;
private final Node node;
BootstrapInfoImpl(
List<Bootstrapper.ServerInfo> servers,
String serverListenerResourceNameTemplate,
Map<String, Bootstrapper.CertificateProviderInfo> certProviders,
Node node) {
this.servers = servers;
this.serverListenerResourceNameTemplate = serverListenerResourceNameTemplate;
this.certProviders = certProviders;
this.node = node;
}
@Override
public List<Bootstrapper.ServerInfo> servers() {
return servers;
}
public Map<String, Bootstrapper.CertificateProviderInfo> certProviders() {
return certProviders;
}
@Override
public Node node() {
return node;
}
@Override
public String serverListenerResourceNameTemplate() {
return serverListenerResourceNameTemplate;
}
@Override
public String toString() {
return "BootstrapInfo{"
+ "servers=" + servers + ", "
+ "serverListenerResourceNameTemplate=" + serverListenerResourceNameTemplate + ", "
+ "node=" + node + ", "
+ "}";
}
public static final class Builder extends Bootstrapper.BootstrapInfo.Builder {
private List<Bootstrapper.ServerInfo> servers;
private Node node;
private Map<String, Bootstrapper.CertificateProviderInfo> certProviders;
private String serverListenerResourceNameTemplate;
Builder() {}
@Override
Bootstrapper.BootstrapInfo.Builder servers(List<Bootstrapper.ServerInfo> servers) {
this.servers = new LinkedList<>(servers);
return this;
}
@Override
Bootstrapper.BootstrapInfo.Builder node(Node node) {
if (node == null) {
throw new NullPointerException("Null node");
}
this.node = node;
return this;
}
@Override
Bootstrapper.BootstrapInfo.Builder certProviders(
@Nullable Map<String, Bootstrapper.CertificateProviderInfo> certProviders) {
this.certProviders = certProviders;
return this;
}
@Override
Bootstrapper.BootstrapInfo.Builder serverListenerResourceNameTemplate(
@Nullable String serverListenerResourceNameTemplate) {
this.serverListenerResourceNameTemplate = serverListenerResourceNameTemplate;
return this;
}
@Override
Bootstrapper.BootstrapInfo build() {
if (this.servers == null || this.node == null) {
StringBuilder missing = new StringBuilder();
if (this.servers == null) {
missing.append(" servers");
}
if (this.node == null) {
missing.append(" node");
}
throw new IllegalStateException("Missing required properties:" + missing);
}
return new BootstrapInfoImpl(
this.servers, this.serverListenerResourceNameTemplate, this.certProviders, this.node);
}
}
}

View File

@ -1,75 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.registry.xds.util.bootstrap;
import org.apache.dubbo.registry.xds.XdsInitializationException;
import javax.annotation.Nullable;
import java.util.List;
import java.util.Map;
import io.envoyproxy.envoy.config.core.v3.Node;
import io.grpc.ChannelCredentials;
public abstract class Bootstrapper {
public abstract BootstrapInfo bootstrap() throws XdsInitializationException;
BootstrapInfo bootstrap(Map<String, ?> rawData) throws XdsInitializationException {
throw new UnsupportedOperationException();
}
public abstract static class ServerInfo {
public abstract String target();
abstract ChannelCredentials channelCredentials();
abstract boolean useProtocolV3();
abstract boolean ignoreResourceDeletion();
}
public abstract static class CertificateProviderInfo {
public abstract String pluginName();
public abstract Map<String, ?> config();
}
public abstract static class BootstrapInfo {
public abstract List<ServerInfo> servers();
public abstract Map<String, CertificateProviderInfo> certProviders();
public abstract Node node();
public abstract String serverListenerResourceNameTemplate();
abstract static class Builder {
abstract Builder servers(List<ServerInfo> servers);
abstract Builder node(Node node);
abstract Builder certProviders(@Nullable Map<String, CertificateProviderInfo> certProviders);
abstract Builder serverListenerResourceNameTemplate(@Nullable String serverListenerResourceNameTemplate);
abstract BootstrapInfo build();
}
}
}

View File

@ -1,179 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.registry.xds.util.bootstrap;
import org.apache.dubbo.common.logger.Logger;
import org.apache.dubbo.common.logger.LoggerFactory;
import org.apache.dubbo.registry.xds.XdsInitializationException;
import javax.annotation.Nullable;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import io.envoyproxy.envoy.config.core.v3.Node;
import io.grpc.ChannelCredentials;
import io.grpc.internal.JsonParser;
import io.grpc.internal.JsonUtil;
public class BootstrapperImpl extends Bootstrapper {
static final String BOOTSTRAP_PATH_SYS_ENV_VAR = "GRPC_XDS_BOOTSTRAP";
static String bootstrapPathFromEnvVar = System.getenv(BOOTSTRAP_PATH_SYS_ENV_VAR);
private static final Logger logger = LoggerFactory.getLogger(BootstrapperImpl.class);
private FileReader reader = LocalFileReader.INSTANCE;
private static final String SERVER_FEATURE_XDS_V3 = "xds_v3";
private static final String SERVER_FEATURE_IGNORE_RESOURCE_DELETION = "ignore_resource_deletion";
public BootstrapInfo bootstrap() throws XdsInitializationException {
String filePath = bootstrapPathFromEnvVar;
String fileContent = null;
if (filePath != null) {
try {
fileContent = reader.readFile(filePath);
} catch (IOException e) {
throw new XdsInitializationException("Fail to read bootstrap file", e);
}
}
if (fileContent == null) throw new XdsInitializationException("Cannot find bootstrap configuration");
Map<String, ?> rawBootstrap;
try {
rawBootstrap = (Map<String, ?>) JsonParser.parse(fileContent);
} catch (IOException e) {
throw new XdsInitializationException("Failed to parse JSON", e);
}
return bootstrap(rawBootstrap);
}
@Override
BootstrapInfo bootstrap(Map<String, ?> rawData) throws XdsInitializationException {
BootstrapInfo.Builder builder = new BootstrapInfoImpl.Builder();
List<?> rawServerConfigs = JsonUtil.getList(rawData, "xds_servers");
if (rawServerConfigs == null) {
throw new XdsInitializationException("Invalid bootstrap: 'xds_servers' does not exist.");
}
List<ServerInfo> servers = parseServerInfos(rawServerConfigs);
builder.servers(servers);
Node.Builder nodeBuilder = Node.newBuilder();
Map<String, ?> rawNode = JsonUtil.getObject(rawData, "node");
if (rawNode != null) {
String id = JsonUtil.getString(rawNode, "id");
if (id != null) {
nodeBuilder.setId(id);
}
String cluster = JsonUtil.getString(rawNode, "cluster");
if (cluster != null) {
nodeBuilder.setCluster(cluster);
}
Map<String, ?> metadata = JsonUtil.getObject(rawNode, "metadata");
Map<String, ?> rawLocality = JsonUtil.getObject(rawNode, "locality");
}
builder.node(nodeBuilder.build());
Map<String, ?> certProvidersBlob = JsonUtil.getObject(rawData, "certificate_providers");
if (certProvidersBlob != null) {
Map<String, CertificateProviderInfo> certProviders = new HashMap<>(certProvidersBlob.size());
for (String name : certProvidersBlob.keySet()) {
Map<String, ?> valueMap = JsonUtil.getObject(certProvidersBlob, name);
String pluginName = checkForNull(JsonUtil.getString(valueMap, "plugin_name"), "plugin_name");
Map<String, ?> config = checkForNull(JsonUtil.getObject(valueMap, "config"), "config");
CertificateProviderInfoImpl certificateProviderInfo =
new CertificateProviderInfoImpl(pluginName, config);
certProviders.put(name, certificateProviderInfo);
}
builder.certProviders(certProviders);
}
return builder.build();
}
private static List<ServerInfo> parseServerInfos(List<?> rawServerConfigs) throws XdsInitializationException {
List<ServerInfo> servers = new LinkedList<>();
List<Map<String, ?>> serverConfigList = JsonUtil.checkObjectList(rawServerConfigs);
for (Map<String, ?> serverConfig : serverConfigList) {
String serverUri = JsonUtil.getString(serverConfig, "server_uri");
if (serverUri == null) {
throw new XdsInitializationException("Invalid bootstrap: missing 'server_uri'");
}
List<?> rawChannelCredsList = JsonUtil.getList(serverConfig, "channel_creds");
if (rawChannelCredsList == null || rawChannelCredsList.isEmpty()) {
throw new XdsInitializationException(
"Invalid bootstrap: server " + serverUri + " 'channel_creds' required");
}
ChannelCredentials channelCredentials =
parseChannelCredentials(JsonUtil.checkObjectList(rawChannelCredsList), serverUri);
// if (channelCredentials == null) {
// throw new XdsInitializationException(
// "Server " + serverUri + ": no supported channel credentials found");
// }
boolean useProtocolV3 = false;
boolean ignoreResourceDeletion = false;
List<String> serverFeatures = JsonUtil.getListOfStrings(serverConfig, "server_features");
if (serverFeatures != null) {
useProtocolV3 = serverFeatures.contains(SERVER_FEATURE_XDS_V3);
ignoreResourceDeletion = serverFeatures.contains(SERVER_FEATURE_IGNORE_RESOURCE_DELETION);
}
servers.add(new ServerInfoImpl(serverUri, channelCredentials, useProtocolV3, ignoreResourceDeletion));
}
return servers;
}
void setFileReader(FileReader reader) {
this.reader = reader;
}
/**
* Reads the content of the file with the given path in the file system.
*/
interface FileReader {
String readFile(String path) throws IOException;
}
private enum LocalFileReader implements FileReader {
INSTANCE;
@Override
public String readFile(String path) throws IOException {
return new String(Files.readAllBytes(Paths.get(path)), StandardCharsets.UTF_8);
}
}
private static <T> T checkForNull(T value, String fieldName) throws XdsInitializationException {
if (value == null) {
throw new XdsInitializationException("Invalid bootstrap: '" + fieldName + "' does not exist.");
}
return value;
}
@Nullable
private static ChannelCredentials parseChannelCredentials(List<Map<String, ?>> jsonList, String serverUri)
throws XdsInitializationException {
return null;
}
}

View File

@ -1,45 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.registry.xds.util.bootstrap;
import java.util.Map;
final class CertificateProviderInfoImpl extends Bootstrapper.CertificateProviderInfo {
private final String pluginName;
private final Map<String, ?> config;
CertificateProviderInfoImpl(String pluginName, Map<String, ?> config) {
this.pluginName = pluginName;
this.config = config;
}
@Override
public String pluginName() {
return pluginName;
}
@Override
public Map<String, ?> config() {
return config;
}
@Override
public String toString() {
return "CertificateProviderInfo{" + "pluginName=" + pluginName + ", " + "config=" + config + "}";
}
}

View File

@ -1,71 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.registry.xds.util.bootstrap;
import io.grpc.ChannelCredentials;
final class ServerInfoImpl extends Bootstrapper.ServerInfo {
private final String target;
private final ChannelCredentials channelCredentials;
private final boolean useProtocolV3;
private final boolean ignoreResourceDeletion;
ServerInfoImpl(
String target,
ChannelCredentials channelCredentials,
boolean useProtocolV3,
boolean ignoreResourceDeletion) {
this.target = target;
this.channelCredentials = channelCredentials;
this.useProtocolV3 = useProtocolV3;
this.ignoreResourceDeletion = ignoreResourceDeletion;
}
@Override
public String target() {
return target;
}
@Override
ChannelCredentials channelCredentials() {
return channelCredentials;
}
@Override
boolean useProtocolV3() {
return useProtocolV3;
}
@Override
boolean ignoreResourceDeletion() {
return ignoreResourceDeletion;
}
@Override
public String toString() {
return "ServerInfo{"
+ "target=" + target + ", "
+ "channelCredentials=" + channelCredentials + ", "
+ "useProtocolV3=" + useProtocolV3 + ", "
+ "ignoreResourceDeletion=" + ignoreResourceDeletion
+ "}";
}
}

View File

@ -1,269 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.registry.xds.util.protocol;
import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
import org.apache.dubbo.common.logger.LoggerFactory;
import org.apache.dubbo.common.utils.ConcurrentHashMapUtils;
import org.apache.dubbo.common.utils.StringUtils;
import org.apache.dubbo.registry.xds.util.AdsObserver;
import org.apache.dubbo.registry.xds.util.XdsListener;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import io.envoyproxy.envoy.config.core.v3.Node;
import io.envoyproxy.envoy.service.discovery.v3.DiscoveryRequest;
import io.envoyproxy.envoy.service.discovery.v3.DiscoveryResponse;
import static org.apache.dubbo.common.constants.LoggerCodeConstants.INTERNAL_INTERRUPTED;
import static org.apache.dubbo.common.constants.LoggerCodeConstants.PROTOCOL_FAILED_REQUEST;
public abstract class AbstractProtocol<T, S extends DeltaResource<T>> implements XdsProtocol<T>, XdsListener {
private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(AbstractProtocol.class);
protected AdsObserver adsObserver;
protected final Node node;
private final int checkInterval;
protected final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
protected final ReentrantReadWriteLock.ReadLock readLock = lock.readLock();
protected final ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();
protected Set<String> observeResourcesName;
public static final String emptyResourceName = "emptyResourcesName";
private final ReentrantLock resourceLock = new ReentrantLock();
protected Map<Set<String>, List<Consumer<Map<String, T>>>> consumerObserveMap = new ConcurrentHashMap<>();
public Map<Set<String>, List<Consumer<Map<String, T>>>> getConsumerObserveMap() {
return consumerObserveMap;
}
protected Map<String, T> resourcesMap = new ConcurrentHashMap<>();
public AbstractProtocol(AdsObserver adsObserver, Node node, int checkInterval) {
this.adsObserver = adsObserver;
this.node = node;
this.checkInterval = checkInterval;
adsObserver.addListener(this);
}
/**
* Abstract method to obtain Type-URL from sub-class
*
* @return Type-URL of xDS
*/
public abstract String getTypeUrl();
public boolean isCacheExistResource(Set<String> resourceNames) {
for (String resourceName : resourceNames) {
if ("".equals(resourceName)) {
continue;
}
if (!resourcesMap.containsKey(resourceName)) {
return false;
}
}
return true;
}
public T getCacheResource(String resourceName) {
if (resourceName == null || resourceName.length() == 0) {
return null;
}
return resourcesMap.get(resourceName);
}
@Override
public Map<String, T> getResource(Set<String> resourceNames) {
resourceNames = resourceNames == null ? Collections.emptySet() : resourceNames;
if (!resourceNames.isEmpty() && isCacheExistResource(resourceNames)) {
return getResourceFromCache(resourceNames);
} else {
return getResourceFromRemote(resourceNames);
}
}
private Map<String, T> getResourceFromCache(Set<String> resourceNames) {
return resourceNames.stream()
.filter(o -> !StringUtils.isEmpty(o))
.collect(Collectors.toMap(k -> k, this::getCacheResource));
}
public Map<String, T> getResourceFromRemote(Set<String> resourceNames) {
try {
resourceLock.lock();
CompletableFuture<Map<String, T>> future = new CompletableFuture<>();
observeResourcesName = resourceNames;
Set<String> consumerObserveResourceNames = new HashSet<>();
if (resourceNames.isEmpty()) {
consumerObserveResourceNames.add(emptyResourceName);
} else {
consumerObserveResourceNames = resourceNames;
}
Consumer<Map<String, T>> futureConsumer = future::complete;
try {
writeLock.lock();
ConcurrentHashMapUtils.computeIfAbsent(
(ConcurrentHashMap<Set<String>, List<Consumer<Map<String, T>>>>) consumerObserveMap,
consumerObserveResourceNames,
key -> new ArrayList<>())
.add(futureConsumer);
} finally {
writeLock.unlock();
}
Set<String> resourceNamesToObserve = new HashSet<>(resourceNames);
resourceNamesToObserve.addAll(resourcesMap.keySet());
adsObserver.request(buildDiscoveryRequest(resourceNamesToObserve));
logger.info("Send xDS Observe request to remote. Resource count: " + resourceNamesToObserve.size()
+ ". Resource Type: " + getTypeUrl());
try {
Map<String, T> result = future.get();
try {
writeLock.lock();
consumerObserveMap.get(consumerObserveResourceNames).removeIf(o -> o.equals(futureConsumer));
} finally {
writeLock.unlock();
}
return result;
} catch (InterruptedException e) {
logger.error(
INTERNAL_INTERRUPTED,
"",
"",
"InterruptedException occur when request control panel. error=",
e);
Thread.currentThread().interrupt();
} catch (Exception e) {
logger.error(PROTOCOL_FAILED_REQUEST, "", "", "Error occur when request control panel. error=", e);
}
} finally {
resourceLock.unlock();
}
return Collections.emptyMap();
}
public void observeResource(Set<String> resourceNames, Consumer<Map<String, T>> consumer, boolean isReConnect) {
// call once for full data
if (!isReConnect) {
consumer.accept(getResource(resourceNames));
try {
writeLock.lock();
consumerObserveMap.compute(resourceNames, (k, v) -> {
if (v == null) {
v = new ArrayList<>();
}
// support multi-consumer
v.add(consumer);
return v;
});
} finally {
writeLock.unlock();
}
}
try {
writeLock.lock();
this.observeResourcesName =
consumerObserveMap.keySet().stream().flatMap(Set::stream).collect(Collectors.toSet());
} finally {
writeLock.unlock();
}
}
public void unobserveResource(Set<String> resourceNames, Consumer<Map<String, T>> consumer) {
// TODO
}
protected DiscoveryRequest buildDiscoveryRequest(Set<String> resourceNames) {
return DiscoveryRequest.newBuilder()
.setNode(node)
.setTypeUrl(getTypeUrl())
.addAllResourceNames(resourceNames)
.build();
}
protected abstract Map<String, T> decodeDiscoveryResponse(DiscoveryResponse response);
@Override
public final void process(DiscoveryResponse discoveryResponse) {
Map<String, T> newResult = decodeDiscoveryResponse(discoveryResponse);
Map<String, T> oldResource = resourcesMap;
discoveryResponseListener(oldResource, newResult);
resourcesMap = newResult;
}
private void discoveryResponseListener(Map<String, T> oldResult, Map<String, T> newResult) {
Set<String> changedResourceNames = new HashSet<>();
oldResult.forEach((key, origin) -> {
if (!Objects.equals(origin, newResult.get(key))) {
changedResourceNames.add(key);
}
});
newResult.forEach((key, origin) -> {
if (!Objects.equals(origin, oldResult.get(key))) {
changedResourceNames.add(key);
}
});
if (changedResourceNames.isEmpty()) {
return;
}
logger.info("Receive resource update notification from xds server. Change resource count: "
+ changedResourceNames.stream() + ". Type: " + getTypeUrl());
// call once for full data
try {
readLock.lock();
for (Map.Entry<Set<String>, List<Consumer<Map<String, T>>>> entry : consumerObserveMap.entrySet()) {
if (entry.getKey().stream().noneMatch(changedResourceNames::contains)) {
// none update
continue;
}
Map<String, T> dsResultMap =
entry.getKey().stream().collect(Collectors.toMap(k -> k, v -> newResult.get(v)));
entry.getValue().forEach(o -> o.accept(dsResultMap));
}
} finally {
readLock.unlock();
}
}
}

View File

@ -1,32 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.registry.xds.util.protocol;
/**
* A interface for resources in xDS, which can be updated by ADS delta stream
* <br/>
* This interface is design to unify the way of fetching data in delta stream
* in {@link org.apache.dubbo.registry.xds.util.PilotExchanger}
*/
public interface DeltaResource<T> {
/**
* Get resource from delta stream
*
* @return the newest resource from stream
*/
T getResource();
}

View File

@ -1,41 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.registry.xds.util.protocol;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
public interface XdsProtocol<T> {
/**
* Gets all {@link T resource} by the specified resource name.
* For LDS, the {@param resourceNames} is ignored
*
* @param resourceNames specified resource name
* @return resources, null if request failed
*/
Map<String, T> getResource(Set<String> resourceNames);
/**
* Add a observer resource with {@link Consumer}
*
* @param resourceNames specified resource name
* @param consumer resource notifier, will be called when resource updated
* @return requestId, used when resourceNames update with {@link XdsProtocol#updateObserve(long, Set)}
*/
void observeResource(Set<String> resourceNames, Consumer<Map<String, T>> consumer, boolean isReConnect);
}

View File

@ -1,48 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.registry.xds.util.protocol.delta;
import org.apache.dubbo.common.utils.CollectionUtils;
import org.apache.dubbo.registry.xds.util.protocol.DeltaResource;
import org.apache.dubbo.registry.xds.util.protocol.message.Endpoint;
import org.apache.dubbo.registry.xds.util.protocol.message.EndpointResult;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
public class DeltaEndpoint implements DeltaResource<EndpointResult> {
private final Map<String, Set<Endpoint>> data = new ConcurrentHashMap<>();
public void addResource(String resourceName, Set<Endpoint> endpoints) {
data.put(resourceName, endpoints);
}
public void removeResource(Collection<String> resourceName) {
if (CollectionUtils.isNotEmpty(resourceName)) {
resourceName.forEach(data::remove);
}
}
@Override
public EndpointResult getResource() {
Set<Endpoint> set = data.values().stream().flatMap(Set::stream).collect(Collectors.toSet());
return new EndpointResult(set);
}
}

View File

@ -1,47 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.registry.xds.util.protocol.delta;
import org.apache.dubbo.common.utils.CollectionUtils;
import org.apache.dubbo.registry.xds.util.protocol.DeltaResource;
import org.apache.dubbo.registry.xds.util.protocol.message.ListenerResult;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
public class DeltaListener implements DeltaResource<ListenerResult> {
private final Map<String, Set<String>> data = new ConcurrentHashMap<>();
public void addResource(String resourceName, Set<String> listeners) {
data.put(resourceName, listeners);
}
public void removeResource(Collection<String> resourceName) {
if (CollectionUtils.isNotEmpty(resourceName)) {
resourceName.forEach(data::remove);
}
}
@Override
public ListenerResult getResource() {
Set<String> set = data.values().stream().flatMap(Set::stream).collect(Collectors.toSet());
return new ListenerResult(set);
}
}

View File

@ -1,47 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.registry.xds.util.protocol.delta;
import org.apache.dubbo.common.utils.CollectionUtils;
import org.apache.dubbo.registry.xds.util.protocol.DeltaResource;
import org.apache.dubbo.registry.xds.util.protocol.message.RouteResult;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
public class DeltaRoute implements DeltaResource<RouteResult> {
private final Map<String, Map<String, Set<String>>> data = new ConcurrentHashMap<>();
public void addResource(String resourceName, Map<String, Set<String>> route) {
data.put(resourceName, route);
}
public void removeResource(Collection<String> resourceName) {
if (CollectionUtils.isNotEmpty(resourceName)) {
resourceName.forEach(data::remove);
}
}
@Override
public RouteResult getResource() {
Map<String, Set<String>> result = new ConcurrentHashMap<>();
data.values().forEach(result::putAll);
return new RouteResult(result);
}
}

View File

@ -1,97 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.registry.xds.util.protocol.impl;
import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
import org.apache.dubbo.common.logger.LoggerFactory;
import org.apache.dubbo.registry.xds.util.AdsObserver;
import org.apache.dubbo.registry.xds.util.protocol.AbstractProtocol;
import org.apache.dubbo.registry.xds.util.protocol.delta.DeltaEndpoint;
import org.apache.dubbo.registry.xds.util.protocol.message.Endpoint;
import org.apache.dubbo.registry.xds.util.protocol.message.EndpointResult;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import com.google.protobuf.Any;
import com.google.protobuf.InvalidProtocolBufferException;
import io.envoyproxy.envoy.config.core.v3.HealthStatus;
import io.envoyproxy.envoy.config.core.v3.Node;
import io.envoyproxy.envoy.config.core.v3.SocketAddress;
import io.envoyproxy.envoy.config.endpoint.v3.ClusterLoadAssignment;
import io.envoyproxy.envoy.config.endpoint.v3.LbEndpoint;
import io.envoyproxy.envoy.service.discovery.v3.DiscoveryResponse;
import static org.apache.dubbo.common.constants.LoggerCodeConstants.REGISTRY_ERROR_RESPONSE_XDS;
public class EdsProtocol extends AbstractProtocol<EndpointResult, DeltaEndpoint> {
private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(EdsProtocol.class);
public EdsProtocol(AdsObserver adsObserver, Node node, int checkInterval) {
super(adsObserver, node, checkInterval);
}
@Override
public String getTypeUrl() {
return "type.googleapis.com/envoy.config.endpoint.v3.ClusterLoadAssignment";
}
@Override
protected Map<String, EndpointResult> decodeDiscoveryResponse(DiscoveryResponse response) {
if (getTypeUrl().equals(response.getTypeUrl())) {
return response.getResourcesList().stream()
.map(EdsProtocol::unpackClusterLoadAssignment)
.filter(Objects::nonNull)
.collect(Collectors.toConcurrentMap(
ClusterLoadAssignment::getClusterName, this::decodeResourceToEndpoint));
}
return new HashMap<>();
}
private EndpointResult decodeResourceToEndpoint(ClusterLoadAssignment resource) {
Set<Endpoint> endpoints = resource.getEndpointsList().stream()
.flatMap(e -> e.getLbEndpointsList().stream())
.map(e -> decodeLbEndpointToEndpoint(resource.getClusterName(), e))
.collect(Collectors.toSet());
return new EndpointResult(endpoints);
}
private static Endpoint decodeLbEndpointToEndpoint(String clusterName, LbEndpoint lbEndpoint) {
Endpoint endpoint = new Endpoint();
SocketAddress address = lbEndpoint.getEndpoint().getAddress().getSocketAddress();
endpoint.setAddress(address.getAddress());
endpoint.setPortValue(address.getPortValue());
boolean healthy = HealthStatus.HEALTHY.equals(lbEndpoint.getHealthStatus())
|| HealthStatus.UNKNOWN.equals(lbEndpoint.getHealthStatus());
endpoint.setHealthy(healthy);
endpoint.setWeight(lbEndpoint.getLoadBalancingWeight().getValue());
return endpoint;
}
private static ClusterLoadAssignment unpackClusterLoadAssignment(Any any) {
try {
return any.unpack(ClusterLoadAssignment.class);
} catch (InvalidProtocolBufferException e) {
logger.error(REGISTRY_ERROR_RESPONSE_XDS, "", "", "Error occur when decode xDS response.", e);
return null;
}
}
}

View File

@ -1,106 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.registry.xds.util.protocol.impl;
import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
import org.apache.dubbo.common.logger.LoggerFactory;
import org.apache.dubbo.registry.xds.util.AdsObserver;
import org.apache.dubbo.registry.xds.util.protocol.AbstractProtocol;
import org.apache.dubbo.registry.xds.util.protocol.delta.DeltaListener;
import org.apache.dubbo.registry.xds.util.protocol.message.ListenerResult;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import com.google.protobuf.Any;
import com.google.protobuf.InvalidProtocolBufferException;
import io.envoyproxy.envoy.config.core.v3.Node;
import io.envoyproxy.envoy.config.listener.v3.Filter;
import io.envoyproxy.envoy.config.listener.v3.Listener;
import io.envoyproxy.envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager;
import io.envoyproxy.envoy.extensions.filters.network.http_connection_manager.v3.Rds;
import io.envoyproxy.envoy.service.discovery.v3.DiscoveryResponse;
import static org.apache.dubbo.common.constants.LoggerCodeConstants.REGISTRY_ERROR_RESPONSE_XDS;
public class LdsProtocol extends AbstractProtocol<ListenerResult, DeltaListener> {
private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(LdsProtocol.class);
public LdsProtocol(AdsObserver adsObserver, Node node, int checkInterval) {
super(adsObserver, node, checkInterval);
}
@Override
public String getTypeUrl() {
return "type.googleapis.com/envoy.config.listener.v3.Listener";
}
public Map<String, ListenerResult> getListeners() {
return getResource(null);
}
@Override
protected Map<String, ListenerResult> decodeDiscoveryResponse(DiscoveryResponse response) {
if (getTypeUrl().equals(response.getTypeUrl())) {
Set<String> set = response.getResourcesList().stream()
.map(LdsProtocol::unpackListener)
.filter(Objects::nonNull)
.flatMap(e -> decodeResourceToListener(e).stream())
.collect(Collectors.toSet());
Map<String, ListenerResult> listenerDecodeResult = new ConcurrentHashMap<>();
listenerDecodeResult.put(emptyResourceName, new ListenerResult(set));
return listenerDecodeResult;
}
return new HashMap<>();
}
private Set<String> decodeResourceToListener(Listener resource) {
return resource.getFilterChainsList().stream()
.flatMap(e -> e.getFiltersList().stream())
.map(Filter::getTypedConfig)
.map(LdsProtocol::unpackHttpConnectionManager)
.filter(Objects::nonNull)
.map(HttpConnectionManager::getRds)
.map(Rds::getRouteConfigName)
.collect(Collectors.toSet());
}
private static Listener unpackListener(Any any) {
try {
return any.unpack(Listener.class);
} catch (InvalidProtocolBufferException e) {
logger.error(REGISTRY_ERROR_RESPONSE_XDS, "", "", "Error occur when decode xDS response.", e);
return null;
}
}
private static HttpConnectionManager unpackHttpConnectionManager(Any any) {
try {
if (!any.is(HttpConnectionManager.class)) {
return null;
}
return any.unpack(HttpConnectionManager.class);
} catch (InvalidProtocolBufferException e) {
logger.error(REGISTRY_ERROR_RESPONSE_XDS, "", "", "Error occur when decode xDS response.", e);
return null;
}
}
}

View File

@ -1,92 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.registry.xds.util.protocol.impl;
import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
import org.apache.dubbo.common.logger.LoggerFactory;
import org.apache.dubbo.registry.xds.util.AdsObserver;
import org.apache.dubbo.registry.xds.util.protocol.AbstractProtocol;
import org.apache.dubbo.registry.xds.util.protocol.delta.DeltaRoute;
import org.apache.dubbo.registry.xds.util.protocol.message.RouteResult;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import com.google.protobuf.Any;
import com.google.protobuf.InvalidProtocolBufferException;
import io.envoyproxy.envoy.config.core.v3.Node;
import io.envoyproxy.envoy.config.route.v3.Route;
import io.envoyproxy.envoy.config.route.v3.RouteAction;
import io.envoyproxy.envoy.config.route.v3.RouteConfiguration;
import io.envoyproxy.envoy.config.route.v3.VirtualHost;
import io.envoyproxy.envoy.service.discovery.v3.DiscoveryResponse;
import static org.apache.dubbo.common.constants.LoggerCodeConstants.REGISTRY_ERROR_RESPONSE_XDS;
public class RdsProtocol extends AbstractProtocol<RouteResult, DeltaRoute> {
private static final ErrorTypeAwareLogger logger = LoggerFactory.getErrorTypeAwareLogger(RdsProtocol.class);
public RdsProtocol(AdsObserver adsObserver, Node node, int checkInterval) {
super(adsObserver, node, checkInterval);
}
@Override
public String getTypeUrl() {
return "type.googleapis.com/envoy.config.route.v3.RouteConfiguration";
}
@Override
protected Map<String, RouteResult> decodeDiscoveryResponse(DiscoveryResponse response) {
if (getTypeUrl().equals(response.getTypeUrl())) {
return response.getResourcesList().stream()
.map(RdsProtocol::unpackRouteConfiguration)
.filter(Objects::nonNull)
.collect(Collectors.toConcurrentMap(RouteConfiguration::getName, this::decodeResourceToListener));
}
return new HashMap<>();
}
private RouteResult decodeResourceToListener(RouteConfiguration resource) {
Map<String, Set<String>> map = new HashMap<>();
Map<String, VirtualHost> rdsVirtualhostMap = new ConcurrentHashMap<>();
resource.getVirtualHostsList().forEach(virtualHost -> {
Set<String> cluster = virtualHost.getRoutesList().stream()
.map(Route::getRoute)
.map(RouteAction::getCluster)
.collect(Collectors.toSet());
for (String domain : virtualHost.getDomainsList()) {
map.put(domain, cluster);
rdsVirtualhostMap.put(domain, virtualHost);
}
});
return new RouteResult(map, rdsVirtualhostMap);
}
private static RouteConfiguration unpackRouteConfiguration(Any any) {
try {
return any.unpack(RouteConfiguration.class);
} catch (InvalidProtocolBufferException e) {
logger.error(REGISTRY_ERROR_RESPONSE_XDS, "", "", "Error occur when decode xDS response.", e);
return null;
}
}
}

View File

@ -1,96 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.registry.xds.util.protocol.message;
import java.util.Objects;
public class Endpoint {
private String clusterName;
private String address;
private int portValue;
private boolean healthy;
private int weight;
public String getClusterName() {
return clusterName;
}
public void setClusterName(String clusterName) {
this.clusterName = clusterName;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public int getPortValue() {
return portValue;
}
public void setPortValue(int portValue) {
this.portValue = portValue;
}
public boolean isHealthy() {
return healthy;
}
public void setHealthy(boolean healthy) {
this.healthy = healthy;
}
public int getWeight() {
return weight;
}
public void setWeight(int weight) {
this.weight = weight;
}
@Override
public String toString() {
return "Endpoint{" + "address='"
+ address + '\'' + ", portValue='"
+ portValue + '\'' + ", healthy="
+ healthy + ", weight="
+ weight + '}';
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Endpoint endpoint = (Endpoint) o;
return healthy == endpoint.healthy
&& weight == endpoint.weight
&& Objects.equals(address, endpoint.address)
&& Objects.equals(portValue, endpoint.portValue);
}
@Override
public int hashCode() {
return Objects.hash(address, portValue, healthy, weight);
}
}

View File

@ -1,60 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.registry.xds.util.protocol.message;
import org.apache.dubbo.common.utils.ConcurrentHashSet;
import java.util.Objects;
import java.util.Set;
public class EndpointResult {
private Set<Endpoint> endpoints;
public EndpointResult() {
this.endpoints = new ConcurrentHashSet<>();
}
public EndpointResult(Set<Endpoint> endpoints) {
this.endpoints = endpoints;
}
public Set<Endpoint> getEndpoints() {
return endpoints;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
EndpointResult that = (EndpointResult) o;
return Objects.equals(endpoints, that.endpoints);
}
@Override
public int hashCode() {
return Objects.hash(endpoints);
}
@Override
public String toString() {
return "EndpointResult{" + "endpoints=" + endpoints + '}';
}
}

View File

@ -1,68 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.registry.xds.util.protocol.message;
import org.apache.dubbo.common.utils.ConcurrentHashSet;
import java.util.Objects;
import java.util.Set;
public class ListenerResult {
private Set<String> routeConfigNames;
public ListenerResult() {
this.routeConfigNames = new ConcurrentHashSet<>();
}
public ListenerResult(Set<String> routeConfigNames) {
this.routeConfigNames = routeConfigNames;
}
public Set<String> getRouteConfigNames() {
return routeConfigNames;
}
public void setRouteConfigNames(Set<String> routeConfigNames) {
this.routeConfigNames = routeConfigNames;
}
public void mergeRouteConfigNames(Set<String> names) {
this.routeConfigNames.addAll(names);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ListenerResult listenerResult = (ListenerResult) o;
return Objects.equals(routeConfigNames, listenerResult.routeConfigNames);
}
@Override
public int hashCode() {
return Objects.hash(routeConfigNames);
}
@Override
public String toString() {
return "ListenerResult{" + "routeConfigNames=" + routeConfigNames + '}';
}
}

View File

@ -1,94 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.registry.xds.util.protocol.message;
import org.apache.dubbo.common.utils.ConcurrentHashSet;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import io.envoyproxy.envoy.config.route.v3.VirtualHost;
public class RouteResult {
private final Map<String, Set<String>> domainMap;
private Map<String, VirtualHost> virtualHostMap;
public RouteResult() {
this.domainMap = new ConcurrentHashMap<>();
this.virtualHostMap = new ConcurrentHashMap<>();
}
public RouteResult(Map<String, Set<String>> domainMap) {
this.domainMap = domainMap;
this.virtualHostMap = new ConcurrentHashMap<>();
}
public RouteResult(Map<String, Set<String>> domainMap, Map<String, VirtualHost> virtualHostMap) {
this.domainMap = domainMap;
this.virtualHostMap = virtualHostMap;
}
public Map<String, Set<String>> getDomainMap() {
return domainMap;
}
public boolean isNotEmpty() {
return !domainMap.isEmpty();
}
public Set<String> searchDomain(String domain) {
return domainMap.getOrDefault(domain, new ConcurrentHashSet<>());
}
public Set<String> getDomains() {
return Collections.unmodifiableSet(domainMap.keySet());
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
RouteResult that = (RouteResult) o;
return Objects.equals(domainMap, that.domainMap) && Objects.equals(virtualHostMap, that.virtualHostMap);
}
@Override
public int hashCode() {
return Objects.hash(domainMap, virtualHostMap);
}
public VirtualHost searchVirtualHost(String domain) {
return virtualHostMap.get(domain);
}
public void removeVirtualHost(String domain) {
virtualHostMap.remove(domain);
}
@Override
public String toString() {
return "RouteResult{" + "domainMap=" + domainMap + ", virtualHostMap=" + virtualHostMap + '}';
}
}

View File

@ -1,26 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.rpc.cluster.router.xds;
import org.apache.dubbo.registry.xds.util.protocol.message.Endpoint;
import java.util.Set;
public interface EdsEndpointListener {
void onEndPointChange(String cluster, Set<Endpoint> endpoints);
}

View File

@ -1,127 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.rpc.cluster.router.xds;
import org.apache.dubbo.common.threadpool.manager.FrameworkExecutorRepository;
import org.apache.dubbo.common.utils.CollectionUtils;
import org.apache.dubbo.common.utils.ConcurrentHashMapUtils;
import org.apache.dubbo.common.utils.ConcurrentHashSet;
import org.apache.dubbo.registry.xds.util.PilotExchanger;
import org.apache.dubbo.registry.xds.util.protocol.message.Endpoint;
import org.apache.dubbo.registry.xds.util.protocol.message.EndpointResult;
import org.apache.dubbo.rpc.model.FrameworkModel;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer;
import java.util.stream.Collectors;
public class EdsEndpointManager {
private static final ConcurrentHashMap<String, Set<EdsEndpointListener>> ENDPOINT_LISTENERS =
new ConcurrentHashMap<>();
private static final ConcurrentHashMap<String, Set<Endpoint>> ENDPOINT_DATA_CACHE = new ConcurrentHashMap<>();
private static final ConcurrentHashMap<String, Consumer<Map<String, EndpointResult>>> EDS_LISTENERS =
new ConcurrentHashMap<>();
public EdsEndpointManager() {}
public synchronized void subscribeEds(String cluster, EdsEndpointListener listener) {
Set<EdsEndpointListener> listeners =
ConcurrentHashMapUtils.computeIfAbsent(ENDPOINT_LISTENERS, cluster, key -> new ConcurrentHashSet<>());
if (CollectionUtils.isEmpty(listeners)) {
doSubscribeEds(cluster);
}
listeners.add(listener);
if (ENDPOINT_DATA_CACHE.containsKey(cluster)) {
listener.onEndPointChange(cluster, ENDPOINT_DATA_CACHE.get(cluster));
}
}
private void doSubscribeEds(String cluster) {
ConcurrentHashMapUtils.computeIfAbsent(EDS_LISTENERS, cluster, key -> endpoints -> {
Set<Endpoint> result = endpoints.values().stream()
.map(EndpointResult::getEndpoints)
.flatMap(Set::stream)
.collect(Collectors.toSet());
notifyEndpointChange(cluster, result);
});
Consumer<Map<String, EndpointResult>> consumer = EDS_LISTENERS.get(cluster);
if (PilotExchanger.isEnabled()) {
FrameworkModel.defaultModel()
.getBeanFactory()
.getBean(FrameworkExecutorRepository.class)
.getSharedExecutor()
.submit(() -> PilotExchanger.getInstance().observeEds(Collections.singleton(cluster), consumer));
}
}
public synchronized void unSubscribeEds(String cluster, EdsEndpointListener listener) {
Set<EdsEndpointListener> listeners = ENDPOINT_LISTENERS.get(cluster);
if (CollectionUtils.isEmpty(listeners)) {
return;
}
listeners.remove(listener);
if (listeners.isEmpty()) {
ENDPOINT_LISTENERS.remove(cluster);
doUnsubscribeEds(cluster);
}
}
private void doUnsubscribeEds(String cluster) {
Consumer<Map<String, EndpointResult>> consumer = EDS_LISTENERS.remove(cluster);
if (consumer != null && PilotExchanger.isEnabled()) {
PilotExchanger.getInstance().unObserveEds(Collections.singleton(cluster), consumer);
}
ENDPOINT_DATA_CACHE.remove(cluster);
}
public void notifyEndpointChange(String cluster, Set<Endpoint> endpoints) {
ENDPOINT_DATA_CACHE.put(cluster, endpoints);
Set<EdsEndpointListener> listeners = ENDPOINT_LISTENERS.get(cluster);
if (CollectionUtils.isEmpty(listeners)) {
return;
}
for (EdsEndpointListener listener : listeners) {
listener.onEndPointChange(cluster, endpoints);
}
}
// for test
static ConcurrentHashMap<String, Set<EdsEndpointListener>> getEndpointListeners() {
return ENDPOINT_LISTENERS;
}
// for test
static ConcurrentHashMap<String, Set<Endpoint>> getEndpointDataCache() {
return ENDPOINT_DATA_CACHE;
}
// for test
static ConcurrentHashMap<String, Consumer<Map<String, EndpointResult>>> getEdsListeners() {
return EDS_LISTENERS;
}
}

View File

@ -1,162 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.rpc.cluster.router.xds;
import org.apache.dubbo.common.utils.CollectionUtils;
import org.apache.dubbo.common.utils.ConcurrentHashMapUtils;
import org.apache.dubbo.common.utils.ConcurrentHashSet;
import org.apache.dubbo.registry.xds.util.PilotExchanger;
import org.apache.dubbo.registry.xds.util.protocol.message.ListenerResult;
import org.apache.dubbo.registry.xds.util.protocol.message.RouteResult;
import org.apache.dubbo.rpc.cluster.router.xds.rule.XdsRouteRule;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Consumer;
import io.envoyproxy.envoy.config.route.v3.VirtualHost;
public class RdsRouteRuleManager {
private static final ConcurrentHashMap<String, Set<XdsRouteRuleListener>> RULE_LISTENERS =
new ConcurrentHashMap<>();
private static final ConcurrentHashMap<String, List<XdsRouteRule>> ROUTE_DATA_CACHE = new ConcurrentHashMap<>();
private static final ConcurrentMap<String, RdsVirtualHostListener> RDS_LISTENERS = new ConcurrentHashMap<>();
private static volatile Consumer<Map<String, ListenerResult>> LDS_LISTENER;
private static volatile Consumer<Map<String, RouteResult>> RDS_LISTENER;
private static Map<String, RouteResult> RDS_RESULT;
public RdsRouteRuleManager() {}
public synchronized void subscribeRds(String domain, XdsRouteRuleListener listener) {
Set<XdsRouteRuleListener> listeners =
ConcurrentHashMapUtils.computeIfAbsent(RULE_LISTENERS, domain, key -> new ConcurrentHashSet<>());
if (CollectionUtils.isEmpty(listeners)) {
doSubscribeRds(domain);
}
listeners.add(listener);
if (ROUTE_DATA_CACHE.containsKey(domain)) {
listener.onRuleChange(domain, ROUTE_DATA_CACHE.get(domain));
}
}
private void doSubscribeRds(String domain) {
synchronized (RdsRouteRuleManager.class) {
if (RDS_LISTENER == null) {
RDS_LISTENER = rds -> {
if (rds == null) {
return;
}
for (RouteResult routeResult : rds.values()) {
for (String domainToNotify : RDS_LISTENERS.keySet()) {
VirtualHost virtualHost = routeResult.searchVirtualHost(domainToNotify);
if (virtualHost != null) {
RDS_LISTENERS.get(domainToNotify).parseVirtualHost(virtualHost);
}
}
}
RDS_RESULT = rds;
};
}
if (LDS_LISTENER == null) {
LDS_LISTENER = new Consumer<Map<String, ListenerResult>>() {
private volatile Set<String> configNames = null;
@Override
public void accept(Map<String, ListenerResult> listenerResults) {
if (listenerResults.size() == 1) {
for (ListenerResult listenerResult : listenerResults.values()) {
Set<String> newConfigNames = listenerResult.getRouteConfigNames();
if (configNames == null) {
PilotExchanger.getInstance().observeRds(newConfigNames, RDS_LISTENER);
} else if (!configNames.equals(newConfigNames)) {
PilotExchanger.getInstance().unObserveRds(configNames, RDS_LISTENER);
PilotExchanger.getInstance().observeRds(newConfigNames, RDS_LISTENER);
}
configNames = newConfigNames;
}
}
}
};
if (PilotExchanger.isEnabled()) {
PilotExchanger.getInstance().observeLds(LDS_LISTENER);
}
}
}
ConcurrentHashMapUtils.computeIfAbsent(RDS_LISTENERS, domain, key -> new RdsVirtualHostListener(domain, this));
RDS_LISTENER.accept(RDS_RESULT);
}
public synchronized void unSubscribeRds(String domain, XdsRouteRuleListener listener) {
Set<XdsRouteRuleListener> listeners = RULE_LISTENERS.get(domain);
if (CollectionUtils.isEmpty(listeners)) {
return;
}
listeners.remove(listener);
if (listeners.isEmpty()) {
RULE_LISTENERS.remove(domain);
doUnsubscribeRds(domain);
}
}
private void doUnsubscribeRds(String domain) {
RDS_LISTENERS.remove(domain);
}
public void notifyRuleChange(String domain, List<XdsRouteRule> xdsRouteRules) {
ROUTE_DATA_CACHE.put(domain, xdsRouteRules);
Set<XdsRouteRuleListener> listeners = RULE_LISTENERS.get(domain);
if (CollectionUtils.isEmpty(listeners)) {
return;
}
boolean empty = CollectionUtils.isEmpty(xdsRouteRules);
for (XdsRouteRuleListener listener : listeners) {
if (empty) {
listener.clearRule(domain);
} else {
listener.onRuleChange(domain, xdsRouteRules);
}
}
}
// for test
static ConcurrentHashMap<String, Set<XdsRouteRuleListener>> getRuleListeners() {
return RULE_LISTENERS;
}
// for test
static ConcurrentHashMap<String, List<XdsRouteRule>> getRouteDataCache() {
return ROUTE_DATA_CACHE;
}
// for test
static Map<String, RdsVirtualHostListener> getRdsListeners() {
return RDS_LISTENERS;
}
}

View File

@ -1,184 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.rpc.cluster.router.xds;
import org.apache.dubbo.common.constants.LoggerCodeConstants;
import org.apache.dubbo.common.logger.ErrorTypeAwareLogger;
import org.apache.dubbo.common.logger.LoggerFactory;
import org.apache.dubbo.common.utils.CollectionUtils;
import org.apache.dubbo.rpc.cluster.router.xds.rule.ClusterWeight;
import org.apache.dubbo.rpc.cluster.router.xds.rule.HTTPRouteDestination;
import org.apache.dubbo.rpc.cluster.router.xds.rule.HeaderMatcher;
import org.apache.dubbo.rpc.cluster.router.xds.rule.HttpRequestMatch;
import org.apache.dubbo.rpc.cluster.router.xds.rule.LongRangeMatch;
import org.apache.dubbo.rpc.cluster.router.xds.rule.PathMatcher;
import org.apache.dubbo.rpc.cluster.router.xds.rule.XdsRouteRule;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import io.envoyproxy.envoy.config.route.v3.Route;
import io.envoyproxy.envoy.config.route.v3.RouteAction;
import io.envoyproxy.envoy.config.route.v3.RouteMatch;
import io.envoyproxy.envoy.config.route.v3.VirtualHost;
public class RdsVirtualHostListener {
private static final ErrorTypeAwareLogger LOGGER =
LoggerFactory.getErrorTypeAwareLogger(RdsVirtualHostListener.class);
private final String domain;
private final RdsRouteRuleManager routeRuleManager;
public RdsVirtualHostListener(String domain, RdsRouteRuleManager routeRuleManager) {
this.domain = domain;
this.routeRuleManager = routeRuleManager;
}
public void parseVirtualHost(VirtualHost virtualHost) {
if (virtualHost == null || CollectionUtils.isEmpty(virtualHost.getRoutesList())) {
// post empty
routeRuleManager.notifyRuleChange(domain, new ArrayList<>());
return;
}
try {
List<XdsRouteRule> xdsRouteRules = virtualHost.getRoutesList().stream()
.map(route -> {
if (route.getMatch().getQueryParametersCount() != 0) {
return null;
}
HttpRequestMatch match = parseMatch(route.getMatch());
HTTPRouteDestination action = parseAction(route);
return new XdsRouteRule(match, action);
})
.filter(Objects::nonNull)
.collect(Collectors.toList());
// post rules
routeRuleManager.notifyRuleChange(domain, xdsRouteRules);
} catch (Exception e) {
LOGGER.error(
LoggerCodeConstants.INTERNAL_ERROR,
"",
"",
"parse domain: " + domain + " xds VirtualHost error",
e);
}
}
private HttpRequestMatch parseMatch(RouteMatch match) {
PathMatcher pathMatcher = parsePathMatch(match);
List<HeaderMatcher> headerMatchers = parseHeadMatch(match);
return new HttpRequestMatch(pathMatcher, headerMatchers);
}
private PathMatcher parsePathMatch(RouteMatch match) {
boolean caseSensitive = match.getCaseSensitive().getValue();
PathMatcher pathMatcher = new PathMatcher();
pathMatcher.setCaseSensitive(caseSensitive);
switch (match.getPathSpecifierCase()) {
case PREFIX:
pathMatcher.setPrefix(match.getPrefix());
return pathMatcher;
case PATH:
pathMatcher.setPath(match.getPath());
return pathMatcher;
case SAFE_REGEX:
String regex = match.getSafeRegex().getRegex();
pathMatcher.setRegex(regex);
return pathMatcher;
case PATHSPECIFIER_NOT_SET:
return null;
default:
throw new IllegalArgumentException("Path specifier is not expect");
}
}
private List<HeaderMatcher> parseHeadMatch(RouteMatch routeMatch) {
List<HeaderMatcher> headerMatchers = new ArrayList<>();
List<io.envoyproxy.envoy.config.route.v3.HeaderMatcher> headersList = routeMatch.getHeadersList();
for (io.envoyproxy.envoy.config.route.v3.HeaderMatcher headerMatcher : headersList) {
HeaderMatcher matcher = new HeaderMatcher();
matcher.setName(headerMatcher.getName());
matcher.setInverted(headerMatcher.getInvertMatch());
switch (headerMatcher.getHeaderMatchSpecifierCase()) {
case EXACT_MATCH:
matcher.setExactValue(headerMatcher.getExactMatch());
headerMatchers.add(matcher);
break;
case SAFE_REGEX_MATCH:
matcher.setRegex(headerMatcher.getSafeRegexMatch().getRegex());
headerMatchers.add(matcher);
break;
case RANGE_MATCH:
LongRangeMatch rang = new LongRangeMatch();
rang.setStart(headerMatcher.getRangeMatch().getStart());
rang.setEnd(headerMatcher.getRangeMatch().getEnd());
matcher.setRange(rang);
headerMatchers.add(matcher);
break;
case PRESENT_MATCH:
matcher.setPresent(headerMatcher.getPresentMatch());
headerMatchers.add(matcher);
break;
case PREFIX_MATCH:
matcher.setPrefix(headerMatcher.getPrefixMatch());
headerMatchers.add(matcher);
break;
case SUFFIX_MATCH:
matcher.setSuffix(headerMatcher.getSuffixMatch());
headerMatchers.add(matcher);
break;
case HEADERMATCHSPECIFIER_NOT_SET:
default:
throw new IllegalArgumentException("Header specifier is not expect");
}
}
return headerMatchers;
}
private HTTPRouteDestination parseAction(Route route) {
switch (route.getActionCase()) {
case ROUTE:
HTTPRouteDestination httpRouteDestination = new HTTPRouteDestination();
// only support cluster and weight cluster
RouteAction routeAction = route.getRoute();
RouteAction.ClusterSpecifierCase clusterSpecifierCase = routeAction.getClusterSpecifierCase();
if (clusterSpecifierCase == RouteAction.ClusterSpecifierCase.CLUSTER) {
httpRouteDestination.setCluster(routeAction.getCluster());
return httpRouteDestination;
} else if (clusterSpecifierCase == RouteAction.ClusterSpecifierCase.WEIGHTED_CLUSTERS) {
List<ClusterWeight> clusterWeights = routeAction.getWeightedClusters().getClustersList().stream()
.map(c ->
new ClusterWeight(c.getName(), c.getWeight().getValue()))
.sorted(Comparator.comparing(ClusterWeight::getWeight))
.collect(Collectors.toList());
httpRouteDestination.setWeightedClusters(clusterWeights);
return httpRouteDestination;
}
case REDIRECT:
case DIRECT_RESPONSE:
case FILTER_ACTION:
case ACTION_NOT_SET:
default:
throw new IllegalArgumentException("Cluster specifier is not expect");
}
}
}

View File

@ -1,28 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.rpc.cluster.router.xds;
import org.apache.dubbo.rpc.cluster.router.xds.rule.XdsRouteRule;
import java.util.List;
public interface XdsRouteRuleListener {
void onRuleChange(String appName, List<XdsRouteRule> xdsRouteRules);
void clearRule(String appName);
}

View File

@ -1,391 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.rpc.cluster.router.xds;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.utils.CollectionUtils;
import org.apache.dubbo.common.utils.ConcurrentHashSet;
import org.apache.dubbo.common.utils.Holder;
import org.apache.dubbo.common.utils.StringUtils;
import org.apache.dubbo.registry.xds.util.PilotExchanger;
import org.apache.dubbo.registry.xds.util.protocol.message.Endpoint;
import org.apache.dubbo.rpc.Invocation;
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.RpcException;
import org.apache.dubbo.rpc.cluster.router.RouterSnapshotNode;
import org.apache.dubbo.rpc.cluster.router.state.AbstractStateRouter;
import org.apache.dubbo.rpc.cluster.router.state.BitList;
import org.apache.dubbo.rpc.cluster.router.xds.rule.ClusterWeight;
import org.apache.dubbo.rpc.cluster.router.xds.rule.DestinationSubset;
import org.apache.dubbo.rpc.cluster.router.xds.rule.HTTPRouteDestination;
import org.apache.dubbo.rpc.cluster.router.xds.rule.HeaderMatcher;
import org.apache.dubbo.rpc.cluster.router.xds.rule.HttpRequestMatch;
import org.apache.dubbo.rpc.cluster.router.xds.rule.PathMatcher;
import org.apache.dubbo.rpc.cluster.router.xds.rule.XdsRouteRule;
import org.apache.dubbo.rpc.support.RpcUtils;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ThreadLocalRandom;
import java.util.stream.Collectors;
public class XdsRouter<T> extends AbstractStateRouter<T> implements XdsRouteRuleListener, EdsEndpointListener {
private Set<String> subscribeApplications;
private final ConcurrentHashMap<String, List<XdsRouteRule>> xdsRouteRuleMap;
private final ConcurrentHashMap<String, DestinationSubset<T>> destinationSubsetMap;
private final RdsRouteRuleManager rdsRouteRuleManager;
private final EdsEndpointManager edsEndpointManager;
private volatile BitList<Invoker<T>> currentInvokeList;
private static final String BINARY_HEADER_SUFFIX = "-bin";
private final boolean isEnable;
public XdsRouter(URL url) {
super(url);
isEnable = PilotExchanger.isEnabled();
rdsRouteRuleManager =
url.getOrDefaultApplicationModel().getBeanFactory().getBean(RdsRouteRuleManager.class);
edsEndpointManager = url.getOrDefaultApplicationModel().getBeanFactory().getBean(EdsEndpointManager.class);
subscribeApplications = new ConcurrentHashSet<>();
destinationSubsetMap = new ConcurrentHashMap<>();
xdsRouteRuleMap = new ConcurrentHashMap<>();
currentInvokeList = new BitList<>(new ArrayList<>());
}
/**
* @deprecated only for uts
*/
protected XdsRouter(
URL url, RdsRouteRuleManager rdsRouteRuleManager, EdsEndpointManager edsEndpointManager, boolean isEnable) {
super(url);
this.isEnable = isEnable;
this.rdsRouteRuleManager = rdsRouteRuleManager;
this.edsEndpointManager = edsEndpointManager;
subscribeApplications = new ConcurrentHashSet<>();
destinationSubsetMap = new ConcurrentHashMap<>();
xdsRouteRuleMap = new ConcurrentHashMap<>();
currentInvokeList = new BitList<>(new ArrayList<>());
}
@Override
protected BitList<Invoker<T>> doRoute(
BitList<Invoker<T>> invokers,
URL url,
Invocation invocation,
boolean needToPrintMessage,
Holder<RouterSnapshotNode<T>> nodeHolder,
Holder<String> messageHolder)
throws RpcException {
if (!isEnable) {
if (needToPrintMessage) {
messageHolder.set(
"Directly Return. Reason: Pilot exchanger has not been initialized, may not in mesh mode.");
}
return invokers;
}
if (CollectionUtils.isEmpty(invokers)) {
if (needToPrintMessage) {
messageHolder.set("Directly Return. Reason: Invokers from previous router is empty.");
}
return invokers;
}
if (CollectionUtils.isEmptyMap(xdsRouteRuleMap)) {
if (needToPrintMessage) {
messageHolder.set("Directly Return. Reason: xds route rule is empty.");
}
return invokers;
}
StringBuilder stringBuilder = needToPrintMessage ? new StringBuilder() : null;
// find match cluster
String matchCluster = null;
Set<String> appNames = subscribeApplications;
for (String subscribeApplication : appNames) {
List<XdsRouteRule> rules = xdsRouteRuleMap.get(subscribeApplication);
if (CollectionUtils.isEmpty(rules)) {
continue;
}
for (XdsRouteRule rule : rules) {
String cluster = computeMatchCluster(invocation, rule);
if (cluster != null) {
matchCluster = cluster;
break;
}
}
if (matchCluster != null) {
if (stringBuilder != null) {
stringBuilder
.append("Match App: ")
.append(subscribeApplication)
.append(" Cluster: ")
.append(matchCluster)
.append(' ');
}
break;
}
}
// not match request just return
if (matchCluster == null) {
if (needToPrintMessage) {
messageHolder.set("Directly Return. Reason: xds rule not match.");
}
return invokers;
}
DestinationSubset<T> destinationSubset = destinationSubsetMap.get(matchCluster);
// cluster no target provider
if (destinationSubset == null) {
if (needToPrintMessage) {
messageHolder.set(stringBuilder.append("no target subset").toString());
}
return BitList.emptyList();
}
if (needToPrintMessage) {
messageHolder.set(stringBuilder.toString());
}
if (destinationSubset.getInvokers() == null) {
return BitList.emptyList();
}
return destinationSubset.getInvokers().and(invokers);
}
private String computeMatchCluster(Invocation invocation, XdsRouteRule rule) {
// compute request match cluster
HttpRequestMatch requestMatch = rule.getMatch();
if (requestMatch.getPathMatcher() == null && CollectionUtils.isEmpty(requestMatch.getHeaderMatcherList())) {
return null;
}
PathMatcher pathMatcher = requestMatch.getPathMatcher();
if (pathMatcher != null) {
String path = "/" + invocation.getInvoker().getUrl().getPath() + "/" + RpcUtils.getMethodName(invocation);
if (!pathMatcher.isMatch(path)) {
return null;
}
}
List<HeaderMatcher> headerMatchers = requestMatch.getHeaderMatcherList();
for (HeaderMatcher headerMatcher : headerMatchers) {
String headerName = headerMatcher.getName();
// not support byte
if (headerName.endsWith(BINARY_HEADER_SUFFIX)) {
return null;
}
String headValue = invocation.getAttachment(headerName);
if (!headerMatcher.match(headValue)) {
return null;
}
}
HTTPRouteDestination route = rule.getRoute();
if (route.getCluster() != null) {
return route.getCluster();
}
return computeWeightCluster(route.getWeightedClusters());
}
private String computeWeightCluster(List<ClusterWeight> weightedClusters) {
int totalWeight = Math.max(
weightedClusters.stream().mapToInt(ClusterWeight::getWeight).sum(), 1);
// target must greater than 0
// if weight is 0, the destination will not receive any traffic.
int target = ThreadLocalRandom.current().nextInt(1, totalWeight + 1);
for (ClusterWeight weightedCluster : weightedClusters) {
int weight = weightedCluster.getWeight();
target -= weight;
if (target <= 0) {
return weightedCluster.getName();
}
}
return null;
}
public void notify(BitList<Invoker<T>> invokers) {
BitList<Invoker<T>> invokerList = invokers == null ? BitList.emptyList() : invokers;
currentInvokeList = invokerList.clone();
// compute need subscribe/unsubscribe rds application
Set<String> currentApplications = new HashSet<>();
for (Invoker<T> invoker : invokerList) {
String applicationName = invoker.getUrl().getRemoteApplication();
if (StringUtils.isNotEmpty(applicationName)) {
currentApplications.add(applicationName);
}
}
if (!subscribeApplications.equals(currentApplications)) {
synchronized (this) {
for (String currentApplication : currentApplications) {
if (!subscribeApplications.contains(currentApplication)) {
rdsRouteRuleManager.subscribeRds(currentApplication, this);
}
}
for (String preApplication : subscribeApplications) {
if (!currentApplications.contains(preApplication)) {
rdsRouteRuleManager.unSubscribeRds(preApplication, this);
}
}
subscribeApplications = currentApplications;
}
}
// update subset
synchronized (this) {
BitList<Invoker<T>> allInvokers = currentInvokeList.clone();
for (DestinationSubset<T> subset : destinationSubsetMap.values()) {
computeSubset(subset, allInvokers);
}
}
}
private void computeSubset(DestinationSubset<T> subset, BitList<Invoker<T>> invokers) {
Set<Endpoint> endpoints = subset.getEndpoints();
List<Invoker<T>> filterInvokers = invokers.stream()
.filter(inv -> {
String host = inv.getUrl().getHost();
int port = inv.getUrl().getPort();
Optional<Endpoint> any = endpoints.stream()
.filter(end -> host.equals(end.getAddress()) && port == end.getPortValue())
.findAny();
return any.isPresent();
})
.collect(Collectors.toList());
subset.setInvokers(new BitList<>(filterInvokers));
}
@Override
public synchronized void onRuleChange(String appName, List<XdsRouteRule> xdsRouteRules) {
if (CollectionUtils.isEmpty(xdsRouteRules)) {
clearRule(appName);
return;
}
Set<String> oldCluster = getAllCluster();
xdsRouteRuleMap.put(appName, xdsRouteRules);
Set<String> newCluster = getAllCluster();
changeClusterSubscribe(oldCluster, newCluster);
}
private Set<String> getAllCluster() {
if (CollectionUtils.isEmptyMap(xdsRouteRuleMap)) {
return new HashSet<>();
}
Set<String> clusters = new HashSet<>();
xdsRouteRuleMap.forEach((appName, rules) -> {
for (XdsRouteRule rule : rules) {
HTTPRouteDestination action = rule.getRoute();
if (action.getCluster() != null) {
clusters.add(action.getCluster());
} else if (CollectionUtils.isNotEmpty(action.getWeightedClusters())) {
for (ClusterWeight weightedCluster : action.getWeightedClusters()) {
clusters.add(weightedCluster.getName());
}
}
}
});
return clusters;
}
private void changeClusterSubscribe(Set<String> oldCluster, Set<String> newCluster) {
Set<String> removeSubscribe = new HashSet<>(oldCluster);
Set<String> addSubscribe = new HashSet<>(newCluster);
removeSubscribe.removeAll(newCluster);
addSubscribe.removeAll(oldCluster);
// remove subscribe cluster
for (String cluster : removeSubscribe) {
edsEndpointManager.unSubscribeEds(cluster, this);
destinationSubsetMap.remove(cluster);
}
// add subscribe cluster
for (String cluster : addSubscribe) {
destinationSubsetMap.put(cluster, new DestinationSubset<>(cluster));
edsEndpointManager.subscribeEds(cluster, this);
}
}
@Override
public synchronized void clearRule(String appName) {
Set<String> oldCluster = getAllCluster();
List<XdsRouteRule> oldRules = xdsRouteRuleMap.remove(appName);
if (CollectionUtils.isEmpty(oldRules)) {
return;
}
Set<String> newCluster = getAllCluster();
changeClusterSubscribe(oldCluster, newCluster);
}
@Override
public synchronized void onEndPointChange(String cluster, Set<Endpoint> endpoints) {
// find and update subset
DestinationSubset<T> subset = destinationSubsetMap.get(cluster);
if (subset == null) {
return;
}
subset.setEndpoints(endpoints);
computeSubset(subset, currentInvokeList.clone());
}
@Override
public void stop() {
for (String app : subscribeApplications) {
rdsRouteRuleManager.unSubscribeRds(app, this);
}
for (String cluster : getAllCluster()) {
edsEndpointManager.unSubscribeEds(cluster, this);
}
}
@Deprecated
Set<String> getSubscribeApplications() {
return subscribeApplications;
}
/**
* for ut only
*/
@Deprecated
BitList<Invoker<T>> getInvokerList() {
return currentInvokeList;
}
/**
* for ut only
*/
@Deprecated
ConcurrentHashMap<String, List<XdsRouteRule>> getXdsRouteRuleMap() {
return xdsRouteRuleMap;
}
/**
* for ut only
*/
@Deprecated
ConcurrentHashMap<String, DestinationSubset<T>> getDestinationSubsetMap() {
return destinationSubsetMap;
}
}

View File

@ -1,31 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.rpc.cluster.router.xds;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.rpc.cluster.router.state.StateRouter;
import org.apache.dubbo.rpc.cluster.router.state.StateRouterFactory;
@Activate(order = 100)
public class XdsRouterFactory implements StateRouterFactory {
@Override
public <T> StateRouter<T> getRouter(Class<T> interfaceClass, URL url) {
return new XdsRouter<>(url);
}
}

View File

@ -1,39 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.rpc.cluster.router.xds;
import org.apache.dubbo.common.beans.factory.ScopeBeanFactory;
import org.apache.dubbo.rpc.model.ApplicationModel;
import org.apache.dubbo.rpc.model.FrameworkModel;
import org.apache.dubbo.rpc.model.ModuleModel;
import org.apache.dubbo.rpc.model.ScopeModelInitializer;
public class XdsScopeModelInitializer implements ScopeModelInitializer {
@Override
public void initializeFrameworkModel(FrameworkModel frameworkModel) {}
@Override
public void initializeApplicationModel(ApplicationModel applicationModel) {
ScopeBeanFactory beanFactory = applicationModel.getBeanFactory();
beanFactory.registerBean(RdsRouteRuleManager.class);
beanFactory.registerBean(EdsEndpointManager.class);
}
@Override
public void initializeModuleModel(ModuleModel moduleModel) {}
}

View File

@ -1,37 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.rpc.cluster.router.xds.rule;
public class ClusterWeight {
private final String name;
private final int weight;
public ClusterWeight(String name, int weight) {
this.name = name;
this.weight = weight;
}
public String getName() {
return name;
}
public int getWeight() {
return weight;
}
}

View File

@ -1,57 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.rpc.cluster.router.xds.rule;
import org.apache.dubbo.registry.xds.util.protocol.message.Endpoint;
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.cluster.router.state.BitList;
import java.util.HashSet;
import java.util.Set;
public class DestinationSubset<T> {
public DestinationSubset(String clusterName) {
this.clusterName = clusterName;
}
private final String clusterName;
private Set<Endpoint> endpoints = new HashSet<>();
private BitList<Invoker<T>> invokers;
public String getClusterName() {
return clusterName;
}
public Set<Endpoint> getEndpoints() {
return endpoints;
}
public void setEndpoints(Set<Endpoint> endpoints) {
this.endpoints = endpoints;
}
public BitList<Invoker<T>> getInvokers() {
return invokers;
}
public void setInvokers(BitList<Invoker<T>> invokers) {
this.invokers = invokers;
}
}

View File

@ -1,42 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.rpc.cluster.router.xds.rule;
import java.util.List;
public class HTTPRouteDestination {
private String cluster;
private List<ClusterWeight> weightedClusters;
public String getCluster() {
return cluster;
}
public void setCluster(String cluster) {
this.cluster = cluster;
}
public List<ClusterWeight> getWeightedClusters() {
return weightedClusters;
}
public void setWeightedClusters(List<ClusterWeight> weightedClusters) {
this.weightedClusters = weightedClusters;
}
}

View File

@ -1,121 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.rpc.cluster.router.xds.rule;
public class HeaderMatcher {
public String name;
public String exactValue;
private String regex;
public LongRangeMatch range;
public Boolean present;
public String prefix;
public String suffix;
public boolean inverted;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getExactValue() {
return exactValue;
}
public void setExactValue(String exactValue) {
this.exactValue = exactValue;
}
public String getRegex() {
return regex;
}
public void setRegex(String regex) {
this.regex = regex;
}
public LongRangeMatch getRange() {
return range;
}
public void setRange(LongRangeMatch range) {
this.range = range;
}
public Boolean getPresent() {
return present;
}
public void setPresent(Boolean present) {
this.present = present;
}
public String getPrefix() {
return prefix;
}
public void setPrefix(String prefix) {
this.prefix = prefix;
}
public String getSuffix() {
return suffix;
}
public void setSuffix(String suffix) {
this.suffix = suffix;
}
public boolean isInverted() {
return inverted;
}
public void setInverted(boolean inverted) {
this.inverted = inverted;
}
public boolean match(String input) {
if (getPresent() != null) {
return (input == null) == getPresent().equals(isInverted());
}
if (input == null) {
return false;
}
if (getExactValue() != null) {
return getExactValue().equals(input) != isInverted();
} else if (getRegex() != null) {
return input.matches(getRegex()) != isInverted();
} else if (getRange() != null) {
return getRange().isMatch(input) != isInverted();
} else if (getPrefix() != null) {
return input.startsWith(getPrefix()) != isInverted();
} else if (getSuffix() != null) {
return input.endsWith(getSuffix()) != isInverted();
}
return false;
}
}

View File

@ -1,39 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.rpc.cluster.router.xds.rule;
import java.util.List;
public class HttpRequestMatch {
private final PathMatcher pathMatcher;
private final List<HeaderMatcher> headerMatcherList;
public HttpRequestMatch(PathMatcher pathMatcher, List<HeaderMatcher> headerMatcherList) {
this.pathMatcher = pathMatcher;
this.headerMatcherList = headerMatcherList;
}
public PathMatcher getPathMatcher() {
return pathMatcher;
}
public List<HeaderMatcher> getHeaderMatcherList() {
return headerMatcherList;
}
}

View File

@ -1,47 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.rpc.cluster.router.xds.rule;
public class LongRangeMatch {
private long start;
private long end;
public long getStart() {
return start;
}
public void setStart(long start) {
this.start = start;
}
public long getEnd() {
return end;
}
public void setEnd(long end) {
this.end = end;
}
public boolean isMatch(String input) {
try {
long num = Long.parseLong(input);
return num >= getStart() && num <= getEnd();
} catch (NumberFormatException ignore) {
return false;
}
}
}

View File

@ -1,71 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.rpc.cluster.router.xds.rule;
public class PathMatcher {
private String path;
private String prefix;
private String regex;
private boolean caseSensitive;
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public String getPrefix() {
return prefix;
}
public void setPrefix(String prefix) {
this.prefix = prefix;
}
public String getRegex() {
return regex;
}
public void setRegex(String regex) {
this.regex = regex;
}
public boolean isCaseSensitive() {
return caseSensitive;
}
public void setCaseSensitive(boolean caseSensitive) {
this.caseSensitive = caseSensitive;
}
public boolean isMatch(String input) {
if (getPath() != null) {
return isCaseSensitive() ? getPath().equals(input) : getPath().equalsIgnoreCase(input);
} else if (getPrefix() != null) {
return isCaseSensitive()
? input.startsWith(getPrefix())
: input.toLowerCase().startsWith(getPrefix());
}
return input.matches(getRegex());
}
}

View File

@ -1,37 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.rpc.cluster.router.xds.rule;
public class XdsRouteRule {
private final HttpRequestMatch match;
private final HTTPRouteDestination route;
public XdsRouteRule(HttpRequestMatch match, HTTPRouteDestination route) {
this.match = match;
this.route = route;
}
public HttpRequestMatch getMatch() {
return match;
}
public HTTPRouteDestination getRoute() {
return route;
}
}

View File

@ -1,62 +0,0 @@
// Copyright Istio Authors
//
// 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.
// The canonical version of this proto can be found at
// https://github.com/istio/api/blob/9abf4c87205f6ad04311fa021ce60803d8b95f78/security/v1alpha1/ca.proto
syntax = "proto3";
import "google/protobuf/struct.proto";
// Keep this package for backward compatibility.
package istio.v1.auth;
option go_package = "istio.io/api/security/v1alpha1";
option java_generic_services = true;
option java_multiple_files = true;
// Certificate request message. The authentication should be based on:
// 1. Bearer tokens carried in the side channel;
// 2. Client-side certificate via Mutual TLS handshake.
// Note: the service implementation is REQUIRED to verify the authenticated caller is authorize to
// all SANs in the CSR. The server side may overwrite any requested certificate field based on its
// policies.
message IstioCertificateRequest {
// PEM-encoded certificate request.
// The public key in the CSR is used to generate the certificate,
// and other fields in the generated certificate may be overwritten by the CA.
string csr = 1;
// Optional: requested certificate validity period, in seconds.
int64 validity_duration = 3;
// $hide_from_docs
// Optional: Opaque metadata provided by the XDS node to Istio.
// Supported metadata: WorkloadName, WorkloadIP, ClusterID
google.protobuf.Struct metadata = 4;
}
// Certificate response message.
message IstioCertificateResponse {
// PEM-encoded certificate chain.
// The leaf cert is the first element, and the root cert is the last element.
repeated string cert_chain = 1;
}
// Service for managing certificates issued by the CA.
service IstioCertificateService {
// Using provided CSR, returns a signed certificate.
rpc CreateCertificate(IstioCertificateRequest)
returns (IstioCertificateResponse) {
}
}

View File

@ -1 +0,0 @@
xds=org.apache.dubbo.registry.xds.XdsRegistryFactory

View File

@ -1 +0,0 @@
xds=org.apache.dubbo.registry.xds.XdsServiceDiscoveryFactory

View File

@ -1 +0,0 @@
istio=org.apache.dubbo.registry.xds.istio.IstioCitadelCertificateSigner

View File

@ -1 +0,0 @@
xds=org.apache.dubbo.rpc.cluster.router.xds.XdsRouterFactory

View File

@ -1 +0,0 @@
xds-route=org.apache.dubbo.rpc.cluster.router.xds.XdsScopeModelInitializer

View File

@ -1,148 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.registry.xds.util.bootstrap;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.url.component.URLAddress;
import org.apache.dubbo.registry.xds.XdsInitializationException;
import java.util.List;
import io.grpc.netty.shaded.io.netty.channel.unix.DomainSocketAddress;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
class BootstrapperTest {
@Test
void testParse() throws XdsInitializationException {
String rawData = "{\n" + " \"xds_servers\": [\n"
+ " {\n"
+ " \"server_uri\": \"unix:///etc/istio/proxy/XDS\",\n"
+ " \"channel_creds\": [\n"
+ " {\n"
+ " \"type\": \"insecure\"\n"
+ " }\n"
+ " ],\n"
+ " \"server_features\": [\n"
+ " \"xds_v3\"\n"
+ " ]\n"
+ " }\n"
+ " ],\n"
+ " \"node\": {\n"
+ " \"id\": \"sidecar~172.17.0.4~dubbo-demo-consumer-deployment-grpc-agent-58585cb9cd-gp79p.dubbo-demo~dubbo-demo.svc.cluster.local\",\n"
+ " \"metadata\": {\n"
+ " \"ANNOTATIONS\": {\n"
+ " \"inject.istio.io/templates\": \"grpc-agent\",\n"
+ " \"kubernetes.io/config.seen\": \"2022-07-19T12:53:29.742565722Z\",\n"
+ " \"kubernetes.io/config.source\": \"api\",\n"
+ " \"prometheus.io/path\": \"/stats/prometheus\",\n"
+ " \"prometheus.io/port\": \"15020\",\n"
+ " \"prometheus.io/scrape\": \"true\",\n"
+ " \"proxy.istio.io/config\": \"{\\\"holdApplicationUntilProxyStarts\\\": true}\",\n"
+ " \"proxy.istio.io/overrides\": \"{\\\"containers\\\":[{\\\"name\\\":\\\"app\\\",\\\"image\\\":\\\"gcr.io/istio-testing/app:latest\\\",\\\"args\\\":[\\\"--metrics=15014\\\",\\\"--port\\\",\\\"18080\\\",\\\"--tcp\\\",\\\"19090\\\",\\\"--xds-grpc-server=17070\\\",\\\"--grpc\\\",\\\"17070\\\",\\\"--grpc\\\",\\\"17171\\\",\\\"--port\\\",\\\"3333\\\",\\\"--port\\\",\\\"8080\\\",\\\"--version\\\",\\\"v1\\\",\\\"--crt=/cert.crt\\\",\\\"--key=/cert.key\\\"],\\\"ports\\\":[{\\\"containerPort\\\":17070,\\\"protocol\\\":\\\"TCP\\\"},{\\\"containerPort\\\":17171,\\\"protocol\\\":\\\"TCP\\\"},{\\\"containerPort\\\":8080,\\\"protocol\\\":\\\"TCP\\\"},{\\\"name\\\":\\\"tcp-health-port\\\",\\\"containerPort\\\":3333,\\\"protocol\\\":\\\"TCP\\\"}],\\\"env\\\":[{\\\"name\\\":\\\"INSTANCE_IP\\\",\\\"valueFrom\\\":{\\\"fieldRef\\\":{\\\"apiVersion\\\":\\\"v1\\\",\\\"fieldPath\\\":\\\"status.podIP\\\"}}}],\\\"resources\\\":{},\\\"volumeMounts\\\":[{\\\"name\\\":\\\"kube-api-access-2tknx\\\",\\\"readOnly\\\":true,\\\"mountPath\\\":\\\"/var/run/secrets/kubernetes.io/serviceaccount\\\"}],\\\"livenessProbe\\\":{\\\"tcpSocket\\\":{\\\"port\\\":\\\"tcp-health-port\\\"},\\\"initialDelaySeconds\\\":10,\\\"timeoutSeconds\\\":1,\\\"periodSeconds\\\":10,\\\"successThreshold\\\":1,\\\"failureThreshold\\\":10},\\\"readinessProbe\\\":{\\\"httpGet\\\":{\\\"path\\\":\\\"/\\\",\\\"port\\\":8080,\\\"scheme\\\":\\\"HTTP\\\"},\\\"initialDelaySeconds\\\":1,\\\"timeoutSeconds\\\":1,\\\"periodSeconds\\\":2,\\\"successThreshold\\\":1,\\\"failureThreshold\\\":10},\\\"startupProbe\\\":{\\\"tcpSocket\\\":{\\\"port\\\":\\\"tcp-health-port\\\"},\\\"timeoutSeconds\\\":1,\\\"periodSeconds\\\":10,\\\"successThreshold\\\":1,\\\"failureThreshold\\\":10},\\\"terminationMessagePath\\\":\\\"/dev/termination-log\\\",\\\"terminationMessagePolicy\\\":\\\"File\\\",\\\"imagePullPolicy\\\":\\\"Always\\\",\\\"securityContext\\\":{\\\"runAsUser\\\":1338,\\\"runAsGroup\\\":1338}},{\\\"name\\\":\\\"dubbo-demo-consumer\\\",\\\"image\\\":\\\"dockeddocking/dubbo:consumer.v1.0\\\",\\\"command\\\":[\\\"sh\\\",\\\"-c\\\",\\\"java $JAVA_OPTS -jar dubbo-demo-consumer.jar \\\"],\\\"resources\\\":{},\\\"volumeMounts\\\":[{\\\"name\\\":\\\"kube-api-access-2tknx\\\",\\\"readOnly\\\":true,\\\"mountPath\\\":\\\"/var/run/secrets/kubernetes.io/serviceaccount\\\"}],\\\"terminationMessagePath\\\":\\\"/dev/termination-log\\\",\\\"terminationMessagePolicy\\\":\\\"File\\\",\\\"imagePullPolicy\\\":\\\"Always\\\"}]}\",\n"
+ " \"sidecar.istio.io/rewriteAppHTTPProbers\": \"false\",\n"
+ " \"sidecar.istio.io/status\": \"{\\\"initContainers\\\":null,\\\"containers\\\":[\\\"app\\\",\\\"dubbo-demo-consumer\\\",\\\"istio-proxy\\\"],\\\"volumes\\\":[\\\"workload-socket\\\",\\\"workload-certs\\\",\\\"istio-xds\\\",\\\"istio-data\\\",\\\"istio-podinfo\\\",\\\"istio-token\\\",\\\"istiod-ca-cert\\\"],\\\"imagePullSecrets\\\":null,\\\"revision\\\":\\\"default\\\"}\"\n"
+ " },\n"
+ " \"APP_CONTAINERS\": \"app,dubbo-demo-consumer\",\n"
+ " \"CLUSTER_ID\": \"Kubernetes\",\n"
+ " \"ENVOY_PROMETHEUS_PORT\": 15090,\n"
+ " \"ENVOY_STATUS_PORT\": 15021,\n"
+ " \"GENERATOR\": \"grpc\",\n"
+ " \"INSTANCE_IPS\": \"172.17.0.4\",\n"
+ " \"INTERCEPTION_MODE\": \"REDIRECT\",\n"
+ " \"ISTIO_PROXY_SHA\": \"2b6009118109b480e1d5abf3188fd7d9c0c0acf0\",\n"
+ " \"ISTIO_VERSION\": \"1.14.1\",\n"
+ " \"LABELS\": {\n"
+ " \"app\": \"dubbo-demo-consumer-dev\",\n"
+ " \"pod-template-hash\": \"58585cb9cd\",\n"
+ " \"service.istio.io/canonical-name\": \"dubbo-demo-consumer-dev\",\n"
+ " \"service.istio.io/canonical-revision\": \"v1\",\n"
+ " \"version\": \"v1\"\n"
+ " },\n"
+ " \"MESH_ID\": \"cluster.local\",\n"
+ " \"NAME\": \"dubbo-demo-consumer-deployment-grpc-agent-58585cb9cd-gp79p\",\n"
+ " \"NAMESPACE\": \"dubbo-demo\",\n"
+ " \"OWNER\": \"kubernetes://apis/apps/v1/namespaces/dubbo-demo/deployments/dubbo-demo-consumer-deployment-grpc-agent\",\n"
+ " \"PILOT_SAN\": [\n"
+ " \"istiod.istio-system.svc\"\n"
+ " ],\n"
+ " \"POD_PORTS\": \"[{\\\"containerPort\\\":17070,\\\"protocol\\\":\\\"TCP\\\"},{\\\"containerPort\\\":17171,\\\"protocol\\\":\\\"TCP\\\"},{\\\"containerPort\\\":8080,\\\"protocol\\\":\\\"TCP\\\"},{\\\"name\\\":\\\"tcp-health-port\\\",\\\"containerPort\\\":3333,\\\"protocol\\\":\\\"TCP\\\"}]\",\n"
+ " \"PROV_CERT\": \"var/run/secrets/istio/root-cert.pem\",\n"
+ " \"PROXY_CONFIG\": {\n"
+ " \"binaryPath\": \"/usr/local/bin/envoy\",\n"
+ " \"concurrency\": 2,\n"
+ " \"configPath\": \"./etc/istio/proxy\",\n"
+ " \"controlPlaneAuthPolicy\": \"MUTUAL_TLS\",\n"
+ " \"discoveryAddress\": \"istiod.istio-system.svc:15012\",\n"
+ " \"drainDuration\": \"45s\",\n"
+ " \"holdApplicationUntilProxyStarts\": true,\n"
+ " \"parentShutdownDuration\": \"60s\",\n"
+ " \"proxyAdminPort\": 15000,\n"
+ " \"serviceCluster\": \"istio-proxy\",\n"
+ " \"statNameLength\": 189,\n"
+ " \"statusPort\": 15020,\n"
+ " \"terminationDrainDuration\": \"5s\",\n"
+ " \"tracing\": {\n"
+ " \"zipkin\": {\n"
+ " \"address\": \"zipkin.istio-system:9411\"\n"
+ " }\n"
+ " }\n"
+ " },\n"
+ " \"SERVICE_ACCOUNT\": \"default\",\n"
+ " \"WORKLOAD_NAME\": \"dubbo-demo-consumer-deployment-grpc-agent\"\n"
+ " },\n"
+ " \"locality\": {},\n"
+ " \"UserAgentVersionType\": null\n"
+ " },\n"
+ " \"certificate_providers\": {\n"
+ " \"default\": {\n"
+ " \"plugin_name\": \"file_watcher\",\n"
+ " \"config\": {\n"
+ " \"certificate_file\": \"/var/lib/istio/data/cert-chain.pem\",\n"
+ " \"private_key_file\": \"/var/lib/istio/data/key.pem\",\n"
+ " \"ca_certificate_file\": \"/var/lib/istio/data/root-cert.pem\",\n"
+ " \"refresh_interval\": \"900s\"\n"
+ " }\n"
+ " }\n"
+ " },\n"
+ " \"server_listener_resource_name_template\": \"xds.istio.io/grpc/lds/inbound/%s\"\n"
+ "}";
BootstrapperImpl.bootstrapPathFromEnvVar = "";
BootstrapperImpl bootstrapper = new BootstrapperImpl();
bootstrapper.setFileReader(createFileReader(rawData));
Bootstrapper.BootstrapInfo info = bootstrapper.bootstrap();
List<Bootstrapper.ServerInfo> serverInfoList = info.servers();
Assertions.assertEquals(serverInfoList.get(0).target(), "unix:///etc/istio/proxy/XDS");
URLAddress address = URLAddress.parse(serverInfoList.get(0).target(), null, false);
Assertions.assertEquals(new DomainSocketAddress(address.getPath()).path(), "etc/istio/proxy/XDS");
}
@Test
void testUrl() {
URL url = URL.valueOf("dubbo://127.0.0.1:23456/TestService?useAgent=true");
Assertions.assertTrue(url.getParameter("useAgent", false));
}
private static BootstrapperImpl.FileReader createFileReader(final String rawData) {
return new BootstrapperImpl.FileReader() {
@Override
public String readFile(String path) {
return rawData;
}
};
}
}

View File

@ -1,51 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.registry.xds.util.protocol.impl;
import org.apache.dubbo.registry.xds.util.AdsObserver;
import org.apache.dubbo.registry.xds.util.protocol.message.EndpointResult;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import io.envoyproxy.envoy.config.core.v3.Node;
public class EdsProtocolMock extends EdsProtocol {
public EdsProtocolMock(AdsObserver adsObserver, Node node, int checkInterval) {
super(adsObserver, node, checkInterval);
}
public Map<String, EndpointResult> getResourcesMap() {
return resourcesMap;
}
public void setResourcesMap(Map<String, EndpointResult> resourcesMap) {
this.resourcesMap = resourcesMap;
}
public void setConsumerObserveMap(
Map<Set<String>, List<Consumer<Map<String, EndpointResult>>>> consumerObserveMap) {
this.consumerObserveMap = consumerObserveMap;
}
public void setObserveResourcesName(Set<String> observeResourcesName) {
this.observeResourcesName = observeResourcesName;
}
}

View File

@ -1,68 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.registry.xds.util.protocol.impl;
import org.apache.dubbo.registry.xds.util.AdsObserver;
import org.apache.dubbo.registry.xds.util.protocol.message.ListenerResult;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import io.envoyproxy.envoy.config.core.v3.Node;
import io.envoyproxy.envoy.service.discovery.v3.DiscoveryRequest;
public class LdsProtocolMock extends LdsProtocol {
public LdsProtocolMock(AdsObserver adsObserver, Node node, int checkInterval) {
super(adsObserver, node, checkInterval);
}
public Map<String, ListenerResult> getResourcesMap() {
return resourcesMap;
}
public void setResourcesMap(Map<String, ListenerResult> resourcesMap) {
this.resourcesMap = resourcesMap;
}
protected DiscoveryRequest buildDiscoveryRequest(Set<String> resourceNames) {
return DiscoveryRequest.newBuilder()
.setNode(node)
.setTypeUrl(getTypeUrl())
.addAllResourceNames(resourceNames)
.build();
}
public Set<String> getObserveResourcesName() {
return observeResourcesName;
}
public void setObserveResourcesName(Set<String> observeResourcesName) {
this.observeResourcesName = observeResourcesName;
}
public Map<Set<String>, List<Consumer<Map<String, ListenerResult>>>> getConsumerObserveMap() {
return consumerObserveMap;
}
public void setConsumerObserveMap(
Map<Set<String>, List<Consumer<Map<String, ListenerResult>>>> consumerObserveMap) {
this.consumerObserveMap = consumerObserveMap;
}
}

View File

@ -1,54 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.registry.xds.util.protocol.impl;
import org.apache.dubbo.registry.xds.util.AdsObserver;
import org.apache.dubbo.registry.xds.util.protocol.message.RouteResult;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import io.envoyproxy.envoy.config.core.v3.Node;
public class RdsProtocolMock extends RdsProtocol {
public RdsProtocolMock(AdsObserver adsObserver, Node node, int checkInterval) {
super(adsObserver, node, checkInterval);
}
public Map<String, RouteResult> getResourcesMap() {
return resourcesMap;
}
public void setResourcesMap(Map<String, RouteResult> resourcesMap) {
this.resourcesMap = resourcesMap;
}
public Set<String> getObserveResourcesName() {
return observeResourcesName;
}
public void setConsumerObserveMap(Map<Set<String>, List<Consumer<Map<String, RouteResult>>>> consumerObserveMap) {
this.consumerObserveMap = consumerObserveMap;
}
public void setObserveResourcesName(Set<String> observeResourcesName) {
this.observeResourcesName = observeResourcesName;
}
}

View File

@ -1,108 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.rpc.cluster.router.xds;
import org.apache.dubbo.registry.xds.util.protocol.message.Endpoint;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
public class EdsEndpointManagerTest {
@BeforeEach
public void before() {
EdsEndpointManager.getEdsListeners().clear();
EdsEndpointManager.getEndpointListeners().clear();
EdsEndpointManager.getEndpointDataCache().clear();
}
@Test
public void subscribeEdsTest() {
EdsEndpointManager manager = new EdsEndpointManager();
String cluster = "testApp";
int subscribeNum = 3;
for (int i = 0; i < subscribeNum; i++) {
manager.subscribeEds(cluster, new EdsEndpointListener() {
@Override
public void onEndPointChange(String cluster, Set<Endpoint> endpoints) {}
});
}
assertNotNull(EdsEndpointManager.getEdsListeners().get(cluster));
assertEquals(EdsEndpointManager.getEndpointListeners().get(cluster).size(), subscribeNum);
}
@Test
public void unsubscribeRdsTest() {
EdsEndpointManager manager = new EdsEndpointManager();
String domain = "testApp";
EdsEndpointListener listener = new EdsEndpointListener() {
@Override
public void onEndPointChange(String cluster, Set<Endpoint> endpoints) {}
};
manager.subscribeEds(domain, listener);
assertNotNull(EdsEndpointManager.getEdsListeners().get(domain));
assertEquals(EdsEndpointManager.getEndpointListeners().get(domain).size(), 1);
manager.unSubscribeEds(domain, listener);
assertNull(EdsEndpointManager.getEdsListeners().get(domain));
assertNull(EdsEndpointManager.getEndpointListeners().get(domain));
}
@Test
public void notifyRuleChangeTest() {
Map<String, Set<Endpoint>> cacheData = new HashMap<>();
String domain = "testApp";
Set<Endpoint> endpoints = new HashSet<>();
Endpoint endpoint = new Endpoint();
endpoints.add(endpoint);
EdsEndpointListener listener = new EdsEndpointListener() {
@Override
public void onEndPointChange(String cluster, Set<Endpoint> endpoints) {
cacheData.put(cluster, endpoints);
}
};
EdsEndpointManager manager = new EdsEndpointManager();
manager.subscribeEds(domain, listener);
manager.notifyEndpointChange(domain, endpoints);
assertEquals(cacheData.get(domain), endpoints);
Map<String, Set<Endpoint>> cacheData2 = new HashMap<>();
EdsEndpointListener listener2 = new EdsEndpointListener() {
@Override
public void onEndPointChange(String cluster, Set<Endpoint> endpoints) {
cacheData2.put(cluster, endpoints);
}
};
manager.subscribeEds(domain, listener2);
assertEquals(cacheData2.get(domain), endpoints);
// clear
manager.notifyEndpointChange(domain, new HashSet<>());
assertEquals(cacheData.get(domain).size(), 0);
}
}

View File

@ -1,126 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.rpc.cluster.router.xds;
import org.apache.dubbo.rpc.cluster.router.xds.rule.HTTPRouteDestination;
import org.apache.dubbo.rpc.cluster.router.xds.rule.HttpRequestMatch;
import org.apache.dubbo.rpc.cluster.router.xds.rule.XdsRouteRule;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
public class RdsRouteRuleManagerTest {
@BeforeEach
public void before() {
RdsRouteRuleManager.getRuleListeners().clear();
RdsRouteRuleManager.getRouteDataCache().clear();
RdsRouteRuleManager.getRdsListeners().clear();
}
@Test
public void subscribeRdsTest() {
RdsRouteRuleManager manager = new RdsRouteRuleManager();
String domain = "testApp";
int subscribeNum = 3;
for (int i = 0; i < subscribeNum; i++) {
manager.subscribeRds(domain, new XdsRouteRuleListener() {
@Override
public void onRuleChange(String appName, List<XdsRouteRule> xdsRouteRules) {}
@Override
public void clearRule(String appName) {}
});
}
assertNotNull(RdsRouteRuleManager.getRdsListeners().get(domain));
assertEquals(RdsRouteRuleManager.getRuleListeners().get(domain).size(), subscribeNum);
}
@Test
public void unsubscribeRdsTest() {
RdsRouteRuleManager manager = new RdsRouteRuleManager();
String domain = "testApp";
XdsRouteRuleListener listener = new XdsRouteRuleListener() {
@Override
public void onRuleChange(String appName, List<XdsRouteRule> xdsRouteRules) {}
@Override
public void clearRule(String appName) {}
};
manager.subscribeRds(domain, listener);
assertNotNull(RdsRouteRuleManager.getRdsListeners().get(domain));
assertEquals(RdsRouteRuleManager.getRuleListeners().get(domain).size(), 1);
manager.unSubscribeRds(domain, listener);
assertNull(RdsRouteRuleManager.getRdsListeners().get(domain));
assertNull(RdsRouteRuleManager.getRuleListeners().get(domain));
}
@Test
public void notifyRuleChangeTest() {
Map<String, List<XdsRouteRule>> cacheData = new HashMap<>();
String domain = "testApp";
List<XdsRouteRule> xdsRouteRules = new ArrayList<>();
XdsRouteRule rule = new XdsRouteRule(new HttpRequestMatch(null, null), new HTTPRouteDestination());
xdsRouteRules.add(rule);
XdsRouteRuleListener listener = new XdsRouteRuleListener() {
@Override
public void onRuleChange(String appName, List<XdsRouteRule> xdsRouteRules) {
cacheData.put(appName, xdsRouteRules);
}
@Override
public void clearRule(String appName) {
cacheData.remove(appName);
}
};
RdsRouteRuleManager manager = new RdsRouteRuleManager();
manager.subscribeRds(domain, listener);
manager.notifyRuleChange(domain, xdsRouteRules);
assertEquals(cacheData.get(domain), xdsRouteRules);
Map<String, List<XdsRouteRule>> cacheData2 = new HashMap<>();
XdsRouteRuleListener listener2 = new XdsRouteRuleListener() {
@Override
public void onRuleChange(String appName, List<XdsRouteRule> xdsRouteRules) {
cacheData2.put(appName, xdsRouteRules);
}
@Override
public void clearRule(String appName) {
cacheData2.remove(appName);
}
};
manager.subscribeRds(domain, listener2);
assertEquals(cacheData2.get(domain), xdsRouteRules);
// clear
manager.notifyRuleChange(domain, new ArrayList<>());
assertNull(cacheData.get(domain));
}
}

View File

@ -1,258 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.rpc.cluster.router.xds;
import org.apache.dubbo.rpc.cluster.router.xds.rule.ClusterWeight;
import org.apache.dubbo.rpc.cluster.router.xds.rule.XdsRouteRule;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.google.protobuf.BoolValue;
import com.google.protobuf.UInt32Value;
import io.envoyproxy.envoy.config.route.v3.HeaderMatcher;
import io.envoyproxy.envoy.config.route.v3.Route;
import io.envoyproxy.envoy.config.route.v3.RouteAction;
import io.envoyproxy.envoy.config.route.v3.RouteMatch;
import io.envoyproxy.envoy.config.route.v3.VirtualHost;
import io.envoyproxy.envoy.config.route.v3.WeightedCluster;
import io.envoyproxy.envoy.type.matcher.v3.RegexMatcher;
import io.envoyproxy.envoy.type.v3.Int64Range;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class RdsVirtualHostListenerTest {
private final String domain = "testApp";
private final Map<String, List<XdsRouteRule>> dataCache = new HashMap<>();
private final XdsRouteRuleListener listener = new XdsRouteRuleListener() {
@Override
public void onRuleChange(String appName, List<XdsRouteRule> xdsRouteRules) {
dataCache.put(appName, xdsRouteRules);
}
@Override
public void clearRule(String appName) {
dataCache.remove(appName);
}
};
private final RdsRouteRuleManager manager = new RdsRouteRuleManager();
private final RdsVirtualHostListener rdsVirtualHostListener = new RdsVirtualHostListener("testApp", manager);
@BeforeEach
public void init() {
dataCache.clear();
manager.subscribeRds(domain, listener);
}
@Test
public void parsePathPathMatcherTest() {
String path = "/test/name";
VirtualHost virtualHost = VirtualHost.newBuilder()
.addDomains(domain)
.addRoutes(Route.newBuilder()
.setName("route-test")
.setMatch(RouteMatch.newBuilder()
.setPath(path)
.setCaseSensitive(
BoolValue.newBuilder().setValue(true).build())
.build())
.setRoute(RouteAction.newBuilder()
.setCluster("cluster-test")
.build())
.build())
.build();
rdsVirtualHostListener.parseVirtualHost(virtualHost);
List<XdsRouteRule> rules = dataCache.get(domain);
assertNotNull(rules);
assertEquals(rules.get(0).getMatch().getPathMatcher().getPath(), path);
assertTrue(rules.get(0).getMatch().getPathMatcher().isCaseSensitive());
}
@Test
public void parsePrefixPathMatcherTest() {
String prefix = "/test";
VirtualHost virtualHost = VirtualHost.newBuilder()
.addDomains(domain)
.addRoutes(Route.newBuilder()
.setName("route-test")
.setMatch(RouteMatch.newBuilder().setPrefix(prefix).build())
.setRoute(RouteAction.newBuilder()
.setCluster("cluster-test")
.build())
.build())
.build();
rdsVirtualHostListener.parseVirtualHost(virtualHost);
List<XdsRouteRule> rules = dataCache.get(domain);
assertNotNull(rules);
assertEquals(rules.get(0).getMatch().getPathMatcher().getPrefix(), prefix);
}
@Test
public void parseRegexPathMatcherTest() {
String regex = "/test/.*";
VirtualHost virtualHost = VirtualHost.newBuilder()
.addDomains(domain)
.addRoutes(Route.newBuilder()
.setName("route-test")
.setMatch(RouteMatch.newBuilder()
.setSafeRegex(RegexMatcher.newBuilder()
.setRegex(regex)
.build())
.build())
.setRoute(RouteAction.newBuilder()
.setCluster("cluster-test")
.build())
.build())
.build();
rdsVirtualHostListener.parseVirtualHost(virtualHost);
List<XdsRouteRule> rules = dataCache.get(domain);
assertNotNull(rules);
assertEquals(rules.get(0).getMatch().getPathMatcher().getRegex(), regex);
}
@Test
public void parseHeadMatcherTest() {
VirtualHost virtualHost = VirtualHost.newBuilder()
.addDomains(domain)
.addRoutes(Route.newBuilder()
.setName("route-test")
.setMatch(RouteMatch.newBuilder()
.addHeaders(HeaderMatcher.newBuilder()
.setName("head-exactValue")
.setExactMatch("exactValue")
.setInvertMatch(true)
.build())
.addHeaders(HeaderMatcher.newBuilder()
.setName("head-regex")
.setSafeRegexMatch(RegexMatcher.newBuilder()
.setRegex("regex")
.build())
.build())
.addHeaders(HeaderMatcher.newBuilder()
.setName("head-range")
.setRangeMatch(Int64Range.newBuilder()
.setStart(1)
.setEnd(100)
.build())
.build())
.addHeaders(HeaderMatcher.newBuilder()
.setName("head-present")
.setPresentMatch(true)
.build())
.addHeaders(HeaderMatcher.newBuilder()
.setName("head-prefix")
.setPrefixMatch("prefix")
.build())
.addHeaders(HeaderMatcher.newBuilder()
.setName("head-suffix")
.setSuffixMatch("suffix")
.build())
.build())
.setRoute(RouteAction.newBuilder()
.setCluster("cluster-test")
.build())
.build())
.build();
rdsVirtualHostListener.parseVirtualHost(virtualHost);
List<XdsRouteRule> rules = dataCache.get(domain);
assertNotNull(rules);
List<org.apache.dubbo.rpc.cluster.router.xds.rule.HeaderMatcher> headerMatcherList =
rules.get(0).getMatch().getHeaderMatcherList();
for (org.apache.dubbo.rpc.cluster.router.xds.rule.HeaderMatcher matcher : headerMatcherList) {
if (matcher.getName().equals("head-exactValue")) {
assertEquals(matcher.getExactValue(), "exactValue");
} else if (matcher.getName().equals("head-regex")) {
assertEquals(matcher.getRegex(), "regex");
} else if (matcher.getName().equals("head-range")) {
assertEquals(matcher.getRange().getStart(), 1);
assertEquals(matcher.getRange().getEnd(), 100);
} else if (matcher.getName().equals("head-present")) {
assertTrue(matcher.getPresent());
} else if (matcher.getName().equals("head-prefix")) {
assertEquals(matcher.getPrefix(), "prefix");
} else if (matcher.getName().equals("head-suffix")) {
assertEquals(matcher.getSuffix(), "suffix");
}
}
}
@Test
public void parseRouteClusterTest() {
String cluster = "cluster-test";
VirtualHost virtualHost = VirtualHost.newBuilder()
.addDomains(domain)
.addRoutes(Route.newBuilder()
.setName("route-test")
.setMatch(RouteMatch.newBuilder().setPrefix("/test").build())
.setRoute(RouteAction.newBuilder().setCluster(cluster).build())
.build())
.build();
rdsVirtualHostListener.parseVirtualHost(virtualHost);
List<XdsRouteRule> rules = dataCache.get(domain);
assertNotNull(rules);
assertEquals(rules.get(0).getRoute().getCluster(), cluster);
}
@Test
public void parseRouteWeightClusterTest() {
VirtualHost virtualHost = VirtualHost.newBuilder()
.addDomains(domain)
.addRoutes(Route.newBuilder()
.setName("route-test")
.setMatch(RouteMatch.newBuilder().setPrefix("/test").build())
.setRoute(RouteAction.newBuilder()
.setWeightedClusters(WeightedCluster.newBuilder()
.addClusters(WeightedCluster.ClusterWeight.newBuilder()
.setName("cluster-test1")
.setWeight(UInt32Value.newBuilder()
.setValue(40)
.build())
.build())
.addClusters(WeightedCluster.ClusterWeight.newBuilder()
.setName("cluster-test2")
.setWeight(UInt32Value.newBuilder()
.setValue(60)
.build())
.build())
.build())
.build())
.build())
.build();
rdsVirtualHostListener.parseVirtualHost(virtualHost);
List<XdsRouteRule> rules = dataCache.get(domain);
assertNotNull(rules);
List<ClusterWeight> weightedClusters = rules.get(0).getRoute().getWeightedClusters();
assertEquals(weightedClusters.size(), 2);
for (ClusterWeight weightedCluster : weightedClusters) {
if (weightedCluster.getName().equals("cluster-test1")) {
assertEquals(weightedCluster.getWeight(), 40);
} else if (weightedCluster.getName().equals("cluster-test2")) {
assertEquals(weightedCluster.getWeight(), 60);
}
}
}
}

View File

@ -1,376 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.rpc.cluster.router.xds;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.utils.Holder;
import org.apache.dubbo.common.utils.StringUtils;
import org.apache.dubbo.registry.xds.util.protocol.message.Endpoint;
import org.apache.dubbo.rpc.Invocation;
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.cluster.router.mesh.util.TracingContextProvider;
import org.apache.dubbo.rpc.cluster.router.state.BitList;
import org.apache.dubbo.rpc.cluster.router.xds.rule.DestinationSubset;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import com.google.protobuf.UInt32Value;
import io.envoyproxy.envoy.config.route.v3.HeaderMatcher;
import io.envoyproxy.envoy.config.route.v3.Route;
import io.envoyproxy.envoy.config.route.v3.RouteAction;
import io.envoyproxy.envoy.config.route.v3.RouteMatch;
import io.envoyproxy.envoy.config.route.v3.VirtualHost;
import io.envoyproxy.envoy.config.route.v3.WeightedCluster;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class XdsRouteTest {
private EdsEndpointManager edsEndpointManager;
private RdsRouteRuleManager rdsRouteRuleManager;
private Set<TracingContextProvider> tracingContextProviders;
private URL url;
@BeforeEach
public void setup() {
edsEndpointManager = Mockito.spy(EdsEndpointManager.class);
rdsRouteRuleManager = Mockito.spy(RdsRouteRuleManager.class);
tracingContextProviders = new HashSet<>();
url = URL.valueOf("test://localhost/DemoInterface");
}
private Invoker<Object> createInvoker(String app) {
URL url = URL.valueOf(
"dubbo://localhost/DemoInterface?" + (StringUtils.isEmpty(app) ? "" : "remote.application=" + app));
Invoker invoker = Mockito.mock(Invoker.class);
when(invoker.getUrl()).thenReturn(url);
return invoker;
}
private Invoker<Object> createInvoker(String app, String address) {
URL url = URL.valueOf("dubbo://" + address + "/DemoInterface?"
+ (StringUtils.isEmpty(app) ? "" : "remote.application=" + app));
Invoker invoker = Mockito.mock(Invoker.class);
when(invoker.getUrl()).thenReturn(url);
return invoker;
}
@Test
public void testNotifyInvoker() {
XdsRouter<Object> xdsRouter = new XdsRouter<>(url, rdsRouteRuleManager, edsEndpointManager, true);
xdsRouter.notify(null);
assertEquals(0, xdsRouter.getSubscribeApplications().size());
BitList<Invoker<Object>> invokers = new BitList<>(Arrays.asList(createInvoker(""), createInvoker("app1")));
xdsRouter.notify(invokers);
assertEquals(1, xdsRouter.getSubscribeApplications().size());
assertTrue(xdsRouter.getSubscribeApplications().contains("app1"));
assertEquals(invokers, xdsRouter.getInvokerList());
verify(rdsRouteRuleManager, times(1)).subscribeRds("app1", xdsRouter);
invokers = new BitList<>(Arrays.asList(createInvoker("app2")));
xdsRouter.notify(invokers);
verify(rdsRouteRuleManager, times(1)).subscribeRds("app2", xdsRouter);
verify(rdsRouteRuleManager, times(1)).unSubscribeRds("app1", xdsRouter);
assertEquals(invokers, xdsRouter.getInvokerList());
xdsRouter.stop();
verify(rdsRouteRuleManager, times(1)).unSubscribeRds("app2", xdsRouter);
}
@Test
public void testRuleChange() {
XdsRouter<Object> xdsRouter = new XdsRouter<>(url, rdsRouteRuleManager, edsEndpointManager, true);
String appName = "app1";
String cluster1 = "cluster-test1";
String cluster2 = "cluster-test2";
BitList<Invoker<Object>> invokers = new BitList<>(Arrays.asList(createInvoker(appName)));
xdsRouter.notify(invokers);
String path = "/DemoInterface/call";
VirtualHost virtualHost = VirtualHost.newBuilder()
.addDomains(appName)
.addRoutes(Route.newBuilder()
.setName("route-test")
.setMatch(RouteMatch.newBuilder().setPath(path).build())
.setRoute(RouteAction.newBuilder().setCluster(cluster1).build())
.build())
.build();
RdsVirtualHostListener hostListener = new RdsVirtualHostListener(appName, rdsRouteRuleManager);
hostListener.parseVirtualHost(virtualHost);
assertEquals(xdsRouter.getXdsRouteRuleMap().get(appName).size(), 1);
verify(edsEndpointManager, times(1)).subscribeEds(cluster1, xdsRouter);
VirtualHost virtualHost2 = VirtualHost.newBuilder()
.addDomains(appName)
.addRoutes(Route.newBuilder()
.setName("route-test")
.setMatch(RouteMatch.newBuilder().setPath(path).build())
.setRoute(RouteAction.newBuilder()
.setCluster("cluster-test2")
.build())
.build())
.build();
hostListener.parseVirtualHost(virtualHost2);
assertEquals(xdsRouter.getXdsRouteRuleMap().get(appName).size(), 1);
verify(edsEndpointManager, times(1)).subscribeEds(cluster2, xdsRouter);
verify(edsEndpointManager, times(1)).unSubscribeEds(cluster1, xdsRouter);
}
@Test
public void testEndpointChange() {
XdsRouter<Object> xdsRouter = new XdsRouter<>(url, rdsRouteRuleManager, edsEndpointManager, true);
String appName = "app1";
String cluster1 = "cluster-test1";
BitList<Invoker<Object>> invokers = new BitList<>(
Arrays.asList(createInvoker(appName, "1.1.1.1:20880"), createInvoker(appName, "2.2.2.2:20880")));
xdsRouter.notify(invokers);
String path = "/DemoInterface/call";
VirtualHost virtualHost = VirtualHost.newBuilder()
.addDomains(appName)
.addRoutes(Route.newBuilder()
.setName("route-test")
.setMatch(RouteMatch.newBuilder().setPath(path).build())
.setRoute(RouteAction.newBuilder().setCluster(cluster1).build())
.build())
.build();
RdsVirtualHostListener hostListener = new RdsVirtualHostListener(appName, rdsRouteRuleManager);
hostListener.parseVirtualHost(virtualHost);
assertEquals(xdsRouter.getXdsRouteRuleMap().get(appName).size(), 1);
verify(edsEndpointManager, times(1)).subscribeEds(cluster1, xdsRouter);
Set<Endpoint> endpoints = new HashSet<>();
Endpoint endpoint1 = new Endpoint();
endpoint1.setAddress("1.1.1.1");
endpoint1.setPortValue(20880);
Endpoint endpoint2 = new Endpoint();
endpoint2.setAddress("2.2.2.2");
endpoint2.setPortValue(20880);
endpoints.add(endpoint1);
endpoints.add(endpoint2);
edsEndpointManager.notifyEndpointChange(cluster1, endpoints);
DestinationSubset<Object> objectDestinationSubset =
xdsRouter.getDestinationSubsetMap().get(cluster1);
assertEquals(invokers, objectDestinationSubset.getInvokers());
}
@Test
public void testRouteNotMatch() {
XdsRouter<Object> xdsRouter = new XdsRouter<>(url, rdsRouteRuleManager, edsEndpointManager, true);
String appName = "app1";
BitList<Invoker<Object>> invokers = new BitList<>(
Arrays.asList(createInvoker(appName, "1.1.1.1:20880"), createInvoker(appName, "2.2.2.2:20880")));
assertEquals(invokers, xdsRouter.route(invokers.clone(), null, null, false, null));
Holder<String> message = new Holder<>();
xdsRouter.doRoute(invokers.clone(), null, null, true, null, message);
assertEquals("Directly Return. Reason: xds route rule is empty.", message.get());
}
@Test
public void testRoutePathMatch() {
XdsRouter<Object> xdsRouter = new XdsRouter<>(url, rdsRouteRuleManager, edsEndpointManager, true);
String appName = "app1";
String cluster1 = "cluster-test1";
Invoker<Object> invoker1 = createInvoker(appName, "1.1.1.1:20880");
BitList<Invoker<Object>> invokers =
new BitList<>(Arrays.asList(invoker1, createInvoker(appName, "2.2.2.2:20880")));
xdsRouter.notify(invokers);
String path = "/DemoInterface/call";
VirtualHost virtualHost = VirtualHost.newBuilder()
.addDomains(appName)
.addRoutes(Route.newBuilder()
.setName("route-test")
.setMatch(RouteMatch.newBuilder().setPath(path).build())
.setRoute(RouteAction.newBuilder().setCluster(cluster1).build())
.build())
.build();
RdsVirtualHostListener hostListener = new RdsVirtualHostListener(appName, rdsRouteRuleManager);
hostListener.parseVirtualHost(virtualHost);
Invocation invocation = Mockito.mock(Invocation.class);
Invoker invoker = Mockito.mock(Invoker.class);
URL url1 = Mockito.mock(URL.class);
when(invoker.getUrl()).thenReturn(url1);
when(url1.getPath()).thenReturn("DemoInterface");
when(invocation.getInvoker()).thenReturn(invoker);
when(invocation.getMethodName()).thenReturn("call");
Set<Endpoint> endpoints = new HashSet<>();
Endpoint endpoint1 = new Endpoint();
endpoint1.setAddress("1.1.1.1");
endpoint1.setPortValue(20880);
endpoints.add(endpoint1);
edsEndpointManager.notifyEndpointChange(cluster1, endpoints);
BitList<Invoker<Object>> routes = xdsRouter.route(invokers.clone(), null, invocation, false, null);
assertEquals(1, routes.size());
assertEquals(invoker1, routes.get(0));
}
@Test
public void testRouteHeadMatch() {
XdsRouter<Object> xdsRouter = new XdsRouter<>(url, rdsRouteRuleManager, edsEndpointManager, true);
String appName = "app1";
String cluster1 = "cluster-test1";
Invoker<Object> invoker1 = createInvoker(appName, "1.1.1.1:20880");
BitList<Invoker<Object>> invokers =
new BitList<>(Arrays.asList(invoker1, createInvoker(appName, "2.2.2.2:20880")));
xdsRouter.notify(invokers);
VirtualHost virtualHost = VirtualHost.newBuilder()
.addDomains(appName)
.addRoutes(Route.newBuilder()
.setName("route-test")
.setMatch(RouteMatch.newBuilder()
.addHeaders(HeaderMatcher.newBuilder()
.setName("userId")
.setExactMatch("123")
.build())
.build())
.setRoute(RouteAction.newBuilder().setCluster(cluster1).build())
.build())
.build();
RdsVirtualHostListener hostListener = new RdsVirtualHostListener(appName, rdsRouteRuleManager);
hostListener.parseVirtualHost(virtualHost);
Invocation invocation = Mockito.mock(Invocation.class);
when(invocation.getAttachment("userId")).thenReturn("123");
Set<Endpoint> endpoints = new HashSet<>();
Endpoint endpoint1 = new Endpoint();
endpoint1.setAddress("1.1.1.1");
endpoint1.setPortValue(20880);
endpoints.add(endpoint1);
edsEndpointManager.notifyEndpointChange(cluster1, endpoints);
BitList<Invoker<Object>> routes = xdsRouter.route(invokers.clone(), null, invocation, false, null);
assertEquals(1, routes.size());
assertEquals(invoker1, routes.get(0));
}
@Test
public void testRouteWeightCluster() {
XdsRouter<Object> xdsRouter = new XdsRouter<>(url, rdsRouteRuleManager, edsEndpointManager, true);
String appName = "app1";
String cluster1 = "cluster-test1";
String cluster2 = "cluster-test2";
Invoker<Object> invoker1 = createInvoker(appName, "1.1.1.1:20880");
BitList<Invoker<Object>> invokers =
new BitList<>(Arrays.asList(invoker1, createInvoker(appName, "2.2.2.2:20880")));
xdsRouter.notify(invokers);
VirtualHost virtualHost = VirtualHost.newBuilder()
.addDomains(appName)
.addRoutes(Route.newBuilder()
.setName("route-test")
.setMatch(RouteMatch.newBuilder()
.addHeaders(HeaderMatcher.newBuilder()
.setName("userId")
.setExactMatch("123")
.build())
.build())
.setRoute(RouteAction.newBuilder()
.setWeightedClusters(WeightedCluster.newBuilder()
.addClusters(WeightedCluster.ClusterWeight.newBuilder()
.setName(cluster1)
.setWeight(UInt32Value.newBuilder()
.setValue(100)
.build())
.build())
.addClusters(WeightedCluster.ClusterWeight.newBuilder()
.setName(cluster2)
.setWeight(UInt32Value.newBuilder()
.setValue(0)
.build())
.build())
.build())
.build())
.build())
.build();
RdsVirtualHostListener hostListener = new RdsVirtualHostListener(appName, rdsRouteRuleManager);
hostListener.parseVirtualHost(virtualHost);
Invocation invocation = Mockito.mock(Invocation.class);
when(invocation.getAttachment("userId")).thenReturn("123");
Set<Endpoint> endpoints = new HashSet<>();
Endpoint endpoint1 = new Endpoint();
endpoint1.setAddress("1.1.1.1");
endpoint1.setPortValue(20880);
endpoints.add(endpoint1);
edsEndpointManager.notifyEndpointChange(cluster1, endpoints);
endpoints = new HashSet<>();
Endpoint endpoint2 = new Endpoint();
endpoint2.setAddress("2.2.2.2");
endpoint2.setPortValue(20880);
endpoints.add(endpoint2);
edsEndpointManager.notifyEndpointChange(cluster2, endpoints);
for (int i = 0; i < 10; i++) {
BitList<Invoker<Object>> routes = xdsRouter.route(invokers.clone(), null, invocation, false, null);
assertEquals(1, routes.size());
assertEquals(invoker1, routes.get(0));
}
}
@Test
public void testRouteMultiApp() {
XdsRouter<Object> xdsRouter = new XdsRouter<>(url, rdsRouteRuleManager, edsEndpointManager, true);
String appName1 = "app1";
String appName2 = "app2";
String cluster1 = "cluster-test1";
Invoker<Object> invoker1 = createInvoker(appName2, "1.1.1.1:20880");
Invoker<Object> invoker2 = createInvoker(appName1, "2.2.2.2:20880");
BitList<Invoker<Object>> invokers = new BitList<>(Arrays.asList(invoker1, invoker2));
xdsRouter.notify(invokers);
assertEquals(xdsRouter.getSubscribeApplications().size(), 2);
String path = "/DemoInterface/call";
VirtualHost virtualHost = VirtualHost.newBuilder()
.addDomains(appName2)
.addRoutes(Route.newBuilder()
.setName("route-test")
.setMatch(RouteMatch.newBuilder().setPath(path).build())
.setRoute(RouteAction.newBuilder().setCluster(cluster1).build())
.build())
.build();
RdsVirtualHostListener hostListener = new RdsVirtualHostListener(appName2, rdsRouteRuleManager);
hostListener.parseVirtualHost(virtualHost);
Invocation invocation = Mockito.mock(Invocation.class);
Invoker invoker = Mockito.mock(Invoker.class);
URL url1 = Mockito.mock(URL.class);
when(invoker.getUrl()).thenReturn(url1);
when(url1.getPath()).thenReturn("DemoInterface");
when(invocation.getInvoker()).thenReturn(invoker);
when(invocation.getMethodName()).thenReturn("call");
Set<Endpoint> endpoints = new HashSet<>();
Endpoint endpoint1 = new Endpoint();
endpoint1.setAddress("1.1.1.1");
endpoint1.setPortValue(20880);
endpoints.add(endpoint1);
edsEndpointManager.notifyEndpointChange(cluster1, endpoints);
BitList<Invoker<Object>> routes = xdsRouter.route(invokers.clone(), null, invocation, false, null);
assertEquals(1, routes.size());
assertEquals(invoker1, routes.get(0));
}
}

View File

@ -1,89 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.rpc.cluster.router.xds.rule;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class HeaderMatcherTest {
@Test
public void exactValueMatcherTest() {
HeaderMatcher headMatcher = new HeaderMatcher();
headMatcher.setName("testHead");
String value = "testValue";
headMatcher.setExactValue(value);
assertTrue(headMatcher.match(value));
}
@Test
public void regexMatcherTest() {
HeaderMatcher headMatcher = new HeaderMatcher();
headMatcher.setRegex("test.*");
String value = "testValue";
headMatcher.setExactValue(value);
assertTrue(headMatcher.match(value));
}
@Test
public void rangMatcherTest() {
HeaderMatcher headMatcher = new HeaderMatcher();
LongRangeMatch range = new LongRangeMatch();
range.setStart(100);
range.setEnd(500);
headMatcher.setRange(range);
assertTrue(headMatcher.match("300"));
}
@Test
public void presentMatcherTest() {
HeaderMatcher headMatcher = new HeaderMatcher();
headMatcher.setName("testHead");
headMatcher.setPresent(true);
assertTrue(headMatcher.match("value"));
headMatcher.setPresent(false);
assertTrue(headMatcher.match(null));
}
@Test
public void prefixMatcherTest() {
HeaderMatcher headMatcher = new HeaderMatcher();
headMatcher.setName("testHead");
headMatcher.setPrefix("test");
assertTrue(headMatcher.match("testValue"));
}
@Test
public void suffixMatcherTest() {
HeaderMatcher headMatcher = new HeaderMatcher();
headMatcher.setName("testHead");
headMatcher.setSuffix("Value");
assertTrue(headMatcher.match("testValue"));
}
@Test
public void invertedMatcherTest() {
HeaderMatcher headMatcher = new HeaderMatcher();
headMatcher.setName("testHead");
String value = "testValue";
headMatcher.setExactValue(value);
headMatcher.setInverted(true);
assertFalse(headMatcher.match("testValue"));
}
}

View File

@ -1,57 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.dubbo.rpc.cluster.router.xds.rule;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class PathMatcherTest {
@Test
public void pathMatcherTest() {
PathMatcher pathMatcher = new PathMatcher();
String path = "/testService/test";
pathMatcher.setPath(path);
assertTrue(pathMatcher.isMatch(path));
assertTrue(pathMatcher.isMatch(path.toUpperCase()));
pathMatcher.setCaseSensitive(true);
assertFalse(pathMatcher.isMatch(path.toUpperCase()));
}
@Test
public void prefixMatcherTest() {
PathMatcher pathMatcher = new PathMatcher();
String prefix = "/test";
String path = "/testService/test";
pathMatcher.setPrefix(prefix);
assertTrue(pathMatcher.isMatch(path));
assertTrue(pathMatcher.isMatch(path.toUpperCase()));
pathMatcher.setCaseSensitive(true);
assertFalse(pathMatcher.isMatch(path.toUpperCase()));
}
@Test
public void regexMatcherTest() {
PathMatcher pathMatcher = new PathMatcher();
String regex = "/testService/.*";
String path = "/testService/test";
pathMatcher.setRegex(regex);
assertTrue(pathMatcher.isMatch(path));
}
}

View File

@ -100,8 +100,6 @@
<module>dubbo-spring-boot</module>
<module>dubbo-native</module>
<module>dubbo-test</module>
<module>dubbo-kubernetes</module>
<module>dubbo-xds</module>
<module>dubbo-native-plugin</module>
<module>dubbo-maven-plugin</module>
</modules>