diff --git a/sermant-agentcore/sermant-agentcore-implement/src/main/java/io/sermant/implement/service/xds/utils/CdsProtocolTransformer.java b/sermant-agentcore/sermant-agentcore-implement/src/main/java/io/sermant/implement/service/xds/utils/CdsProtocolTransformer.java new file mode 100644 index 0000000000..0ae4f92a7a --- /dev/null +++ b/sermant-agentcore/sermant-agentcore-implement/src/main/java/io/sermant/implement/service/xds/utils/CdsProtocolTransformer.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2024-2024 Sermant Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.sermant.implement.service.xds.utils; + +import io.envoyproxy.envoy.config.cluster.v3.Cluster; +import io.envoyproxy.envoy.config.cluster.v3.Cluster.LbPolicy; +import io.sermant.core.service.xds.entity.XdsCluster; +import io.sermant.core.service.xds.entity.XdsLbPolicy; +import io.sermant.core.service.xds.entity.XdsServiceCluster; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * Convert cds protocol data to Sermant data model + * + * @author daizhenyu + * @since 2024-05-10 + **/ +public class CdsProtocolTransformer { + private static final int CLUSTER_SUBSET_INDEX = 2; + + private static final String VERTICAL_LINE_SEPARATOR = "\\|"; + + private CdsProtocolTransformer() { + } + + /** + * get the mapping between service name of k8s and cluster of istio + * + * @param clusters clusters + * @return XdsServiceCluster map + */ + public static Map getServiceClusters(List clusters) { + Map> xdsClusters = clusters.stream() + .filter(Objects::nonNull) + .map(cluster -> parseCluster(cluster)) + .filter(xdsCluster -> xdsCluster.getServiceName() != null) + .collect(Collectors.groupingBy( + XdsCluster::getServiceName, + Collectors.toSet() + )); + Map xdsServiceClusterMap = new HashMap<>(); + for (Entry> clusterEntry : xdsClusters.entrySet()) { + XdsServiceCluster serviceCluster = new XdsServiceCluster(); + serviceCluster.setBaseClusterName(getServiceBaseClusterName(clusterEntry.getValue())); + Map clusterMap = clusterEntry.getValue().stream() + .collect(Collectors.toMap( + XdsCluster::getClusterName, + xdsCluster -> xdsCluster + )); + serviceCluster.setClusters(clusterMap); + xdsServiceClusterMap.put(clusterEntry.getKey(), serviceCluster); + } + return xdsServiceClusterMap; + } + + private static XdsCluster parseCluster(Cluster cluster) { + XdsCluster xdsCluster = new XdsCluster(); + Optional serviceNameFromCluster = XdsCommonUtils.getServiceNameFromCluster(cluster.getName()); + if (!serviceNameFromCluster.isPresent()) { + return xdsCluster; + } + xdsCluster.setClusterName(cluster.getName()); + xdsCluster.setServiceName(serviceNameFromCluster.get()); + xdsCluster.setLocalityLb(cluster.getCommonLbConfig().hasLocalityWeightedLbConfig()); + xdsCluster.setLbPolicy(parseClusterLbPolicy(cluster.getLbPolicy())); + return xdsCluster; + } + + private static String getServiceBaseClusterName(Set xdsClusters) { + for (XdsCluster cluster : xdsClusters) { + String clusterName = cluster.getClusterName(); + String[] splitCluster = clusterName.split(VERTICAL_LINE_SEPARATOR); + if (splitCluster[CLUSTER_SUBSET_INDEX].equals("")) { + return clusterName; + } + } + return ""; + } + + private static XdsLbPolicy parseClusterLbPolicy(LbPolicy lbPolicy) { + if (lbPolicy == LbPolicy.RANDOM) { + return XdsLbPolicy.RANDOM; + } + if (lbPolicy == LbPolicy.ROUND_ROBIN) { + return XdsLbPolicy.ROUND_ROBIN; + } + if (lbPolicy == LbPolicy.LEAST_REQUEST) { + return XdsLbPolicy.LEAST_REQUEST; + } + if (lbPolicy == LbPolicy.RING_HASH) { + return XdsLbPolicy.RING_HASH; + } + if (lbPolicy == LbPolicy.MAGLEV) { + return XdsLbPolicy.MAGLEV; + } + return XdsLbPolicy.UNRECOGNIZED; + } +} diff --git a/sermant-agentcore/sermant-agentcore-implement/src/main/java/io/sermant/implement/service/xds/utils/XdsProtocolTransformer.java b/sermant-agentcore/sermant-agentcore-implement/src/main/java/io/sermant/implement/service/xds/utils/EdsProtocolTransformer.java similarity index 52% rename from sermant-agentcore/sermant-agentcore-implement/src/main/java/io/sermant/implement/service/xds/utils/XdsProtocolTransformer.java rename to sermant-agentcore/sermant-agentcore-implement/src/main/java/io/sermant/implement/service/xds/utils/EdsProtocolTransformer.java index 5a9addc5c1..7617d3096c 100644 --- a/sermant-agentcore/sermant-agentcore-implement/src/main/java/io/sermant/implement/service/xds/utils/XdsProtocolTransformer.java +++ b/sermant-agentcore/sermant-agentcore-implement/src/main/java/io/sermant/implement/service/xds/utils/EdsProtocolTransformer.java @@ -16,7 +16,6 @@ package io.sermant.implement.service.xds.utils; -import io.envoyproxy.envoy.config.cluster.v3.Cluster; import io.envoyproxy.envoy.config.core.v3.HealthStatus; import io.envoyproxy.envoy.config.core.v3.Locality; import io.envoyproxy.envoy.config.core.v3.SocketAddress; @@ -24,96 +23,89 @@ import io.envoyproxy.envoy.config.endpoint.v3.LbEndpoint; import io.envoyproxy.envoy.config.endpoint.v3.LocalityLbEndpoints; import io.sermant.core.service.xds.entity.ServiceInstance; +import io.sermant.core.service.xds.entity.XdsClusterLoadAssigment; +import io.sermant.core.service.xds.entity.XdsLocality; +import io.sermant.core.service.xds.entity.XdsServiceClusterLoadAssigment; import io.sermant.core.utils.CollectionUtils; -import io.sermant.core.utils.StringUtils; import io.sermant.implement.service.xds.entity.XdsServiceInstance; import java.util.Collections; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; /** - * Convert xDS protocol data to Sermant data model + * Convert eds protocol data to Sermant data model * * @author daizhenyu * @since 2024-05-10 **/ -public class XdsProtocolTransformer { - private static final int SERVICE_HOST_INDEX = 3; +public class EdsProtocolTransformer { + private static final int CLUSTER_SUBSET_INDEX = 2; - private static final int SERVICE_NAME_INDEX = 0; + private static final String VERTICAL_LINE_SEPARATOR = "\\|"; - private static final int EXPECT_LENGTH = 4; - - private XdsProtocolTransformer() { - } - - /** - * get the mapping between service name of k8s and cluster of istio - * - * @param clusters clusters - * @return mapping - */ - public static Map> getService2ClusterMapping(List clusters) { - Map> nameMapping = new HashMap<>(); - for (Cluster cluster : clusters) { - if (cluster == null) { - continue; - } - Optional serviceNameFromCluster = getServiceNameFromCluster(cluster.getName()); - if (!serviceNameFromCluster.isPresent()) { - continue; - } - String serviceName = serviceNameFromCluster.get(); - nameMapping.computeIfAbsent(serviceName, key -> new HashSet<>()).add(cluster.getName()); - } - return nameMapping; + private EdsProtocolTransformer() { } /** - * get the instance of one service by xds protocol + * get service instances by xds protocol * * @param loadAssignments eds data - * @return instance of service + * @return instances of service */ - public static Set getServiceInstances( + public static XdsServiceClusterLoadAssigment getServiceInstances( List loadAssignments) { - return loadAssignments.stream() + XdsServiceClusterLoadAssigment serviceClusterLoadAssigment = new XdsServiceClusterLoadAssigment(); + + Map clusterLoadAssigmentMap = loadAssignments.stream() .filter(Objects::nonNull) - .flatMap(loadAssignment -> getServiceInstancesFromLoadAssignment(loadAssignment).stream()) - .collect(Collectors.toSet()); + .map(loadAssignment -> parseClusterLoadAssignment(loadAssignment)) + .filter(clusterInstance -> clusterInstance.getClusterName() != null) + .collect(Collectors.toMap(XdsClusterLoadAssigment::getClusterName, + clusterInstance -> clusterInstance)); + serviceClusterLoadAssigment.setClusterLoadAssigments(clusterLoadAssigmentMap); + serviceClusterLoadAssigment.setBaseClusterName(getServiceBaseClusterName(clusterLoadAssigmentMap)); + return serviceClusterLoadAssigment; } - private static Set getServiceInstancesFromLoadAssignment(ClusterLoadAssignment loadAssignment) { + private static XdsClusterLoadAssigment parseClusterLoadAssignment(ClusterLoadAssignment loadAssignment) { String clusterName = loadAssignment.getClusterName(); - Optional serviceNameOptional = getServiceNameFromCluster(clusterName); + Optional serviceNameOptional = XdsCommonUtils.getServiceNameFromCluster(clusterName); + XdsClusterLoadAssigment xdsClusterLoadAssigment = new XdsClusterLoadAssigment(); if (!serviceNameOptional.isPresent()) { - return Collections.EMPTY_SET; + return xdsClusterLoadAssigment; } String serviceName = serviceNameOptional.get(); - return processClusterLoadAssignment(loadAssignment, serviceName, clusterName); + xdsClusterLoadAssigment.setClusterName(clusterName); + xdsClusterLoadAssigment.setServiceName(serviceName); + xdsClusterLoadAssigment + .setLocalityInstances(parseLocalityLbEndpointsList(loadAssignment, serviceName, clusterName)); + return xdsClusterLoadAssigment; } - private static Set processClusterLoadAssignment(ClusterLoadAssignment loadAssignment, + private static Map> parseLocalityLbEndpointsList( + ClusterLoadAssignment loadAssignment, String serviceName, String clusterName) { List localityLbEndpointList = loadAssignment.getEndpointsList(); if (CollectionUtils.isEmpty(localityLbEndpointList)) { - return Collections.EMPTY_SET; + return Collections.EMPTY_MAP; } return localityLbEndpointList.stream() .filter(Objects::nonNull) - .flatMap(localityLbEndpoints -> processLocalityLbEndpoints(localityLbEndpoints, serviceName, - clusterName).stream()) - .collect(Collectors.toSet()); + .collect(Collectors.toMap( + localityLbEndpoints -> + parseLocality(localityLbEndpoints), + localityLbEndpoints -> parseLocalityLbEndpoints(localityLbEndpoints, serviceName, clusterName) + )); } - private static Set processLocalityLbEndpoints(LocalityLbEndpoints localityLbEndpoints, + private static Set parseLocalityLbEndpoints(LocalityLbEndpoints localityLbEndpoints, String serviceName, String clusterName) { List lbEndpointsList = localityLbEndpoints.getLbEndpointsList(); if (CollectionUtils.isEmpty(lbEndpointsList)) { @@ -121,7 +113,7 @@ private static Set processLocalityLbEndpoints(LocalityLbEndpoin } return lbEndpointsList.stream() .filter(Objects::nonNull) - .map(lbEndpoint -> transformEndpoint2Instance(lbEndpoint, serviceName, clusterName, + .map(lbEndpoint -> parseLbEndpoint(lbEndpoint, serviceName, clusterName, getInitializedMetadata(localityLbEndpoints))) .collect(Collectors.toSet()); } @@ -137,7 +129,7 @@ private static Map getInitializedMetadata(LocalityLbEndpoints lo return metadata; } - private static ServiceInstance transformEndpoint2Instance(LbEndpoint endpoint, String serviceName, + private static ServiceInstance parseLbEndpoint(LbEndpoint endpoint, String serviceName, String clusterName, Map metadata) { XdsServiceInstance instance = new XdsServiceInstance(); SocketAddress socketAddress = endpoint.getEndpoint().getAddress().getSocketAddress(); @@ -158,16 +150,24 @@ private static ServiceInstance transformEndpoint2Instance(LbEndpoint endpoint, S return instance; } - private static Optional getServiceNameFromCluster(String clusterName) { - if (StringUtils.isEmpty(clusterName)) { - return Optional.empty(); + private static String getServiceBaseClusterName(Map instanceMap) { + for (Entry instanceEntry : instanceMap.entrySet()) { + String[] splitCluster = instanceEntry.getKey().split(VERTICAL_LINE_SEPARATOR); + if (splitCluster[CLUSTER_SUBSET_INDEX].equals("")) { + return instanceEntry.getKey(); + } } + return ""; + } - // cluster name format: "outbound|8080||xds-service.default.svc.cluster.local", xds-service is service name - String[] clusterSplit = clusterName.split("\\|"); - if (clusterSplit.length != EXPECT_LENGTH) { - return Optional.empty(); - } - return Optional.of(clusterSplit[SERVICE_HOST_INDEX].split("\\.")[SERVICE_NAME_INDEX]); + private static XdsLocality parseLocality(LocalityLbEndpoints localityLbEndpoints) { + XdsLocality xdsLocality = new XdsLocality(); + Locality locality = localityLbEndpoints.getLocality(); + xdsLocality.setRegion(locality.getRegion()); + xdsLocality.setZone(locality.getZone()); + xdsLocality.setSubZone(locality.getSubZone()); + xdsLocality.setLocalityPriority(localityLbEndpoints.getPriority()); + xdsLocality.setLoadBalanceWeight(localityLbEndpoints.getLoadBalancingWeight().getValue()); + return xdsLocality; } } diff --git a/sermant-agentcore/sermant-agentcore-implement/src/main/java/io/sermant/implement/service/xds/utils/LdsProtocolTransformer.java b/sermant-agentcore/sermant-agentcore-implement/src/main/java/io/sermant/implement/service/xds/utils/LdsProtocolTransformer.java new file mode 100644 index 0000000000..6fc21d1fa1 --- /dev/null +++ b/sermant-agentcore/sermant-agentcore-implement/src/main/java/io/sermant/implement/service/xds/utils/LdsProtocolTransformer.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2024-2024 Sermant Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.sermant.implement.service.xds.utils; + +import com.google.protobuf.Any; +import com.google.protobuf.InvalidProtocolBufferException; + +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.sermant.core.common.LoggerFactory; +import io.sermant.core.service.xds.entity.XdsHttpConnectionManager; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; + +/** + * Convert lds protocol data to Sermant data model + * + * @author daizhenyu + * @since 2024-05-10 + **/ +public class LdsProtocolTransformer { + private static final Logger LOGGER = LoggerFactory.getLogger(); + + private LdsProtocolTransformer() { + } + + /** + * get HttpConnectionManager + * + * @param listeners listeners + * @return HttpConnectionManager list + */ + public static List getHttpConnectionManager(List listeners) { + Set rawHcm = listeners.stream() + .filter(Objects::nonNull) + .flatMap(listener -> listener.getFilterChainsList().stream()) + .flatMap(e -> e.getFiltersList().stream()) + .map(Filter::getTypedConfig) + .collect(Collectors.toSet()); + List xdsHcms = new ArrayList<>(); + for (Any any : rawHcm) { + Optional httpConnectionManager = unpackHttpConnectionManager(any); + if (httpConnectionManager.isPresent()) { + XdsHttpConnectionManager xdsHcm = new XdsHttpConnectionManager(); + xdsHcm.setRouteConfigName(httpConnectionManager.get().getRds().getRouteConfigName()); + xdsHcms.add(xdsHcm); + } + } + return xdsHcms; + } + + private static Optional unpackHttpConnectionManager(Any any) { + try { + if (!any.is(HttpConnectionManager.class)) { + return Optional.empty(); + } + return Optional.of(any.unpack(HttpConnectionManager.class)); + } catch (InvalidProtocolBufferException e) { + LOGGER.log(Level.SEVERE, "Decode resource to HttpConnectionManager failed.", e); + return Optional.empty(); + } + } +} diff --git a/sermant-agentcore/sermant-agentcore-implement/src/main/java/io/sermant/implement/service/xds/utils/RdsProtocolTransformer.java b/sermant-agentcore/sermant-agentcore-implement/src/main/java/io/sermant/implement/service/xds/utils/RdsProtocolTransformer.java new file mode 100644 index 0000000000..2f75477053 --- /dev/null +++ b/sermant-agentcore/sermant-agentcore-implement/src/main/java/io/sermant/implement/service/xds/utils/RdsProtocolTransformer.java @@ -0,0 +1,220 @@ +/* + * Copyright (C) 2024-2024 Sermant Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.sermant.implement.service.xds.utils; + +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.RouteConfiguration; +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.config.route.v3.WeightedCluster.ClusterWeight; +import io.envoyproxy.envoy.type.matcher.v3.StringMatcher; +import io.sermant.core.common.LoggerFactory; +import io.sermant.core.service.xds.entity.XdsHeaderMatcher; +import io.sermant.core.service.xds.entity.XdsPathMatcher; +import io.sermant.core.service.xds.entity.XdsRoute; +import io.sermant.core.service.xds.entity.XdsRouteAction; +import io.sermant.core.service.xds.entity.XdsRouteAction.XdsClusterWeight; +import io.sermant.core.service.xds.entity.XdsRouteAction.XdsWeightedClusters; +import io.sermant.core.service.xds.entity.XdsRouteConfiguration; +import io.sermant.core.service.xds.entity.XdsRouteMatch; +import io.sermant.core.service.xds.entity.XdsVirtualHost; +import io.sermant.core.service.xds.entity.match.ExactMatchStrategy; +import io.sermant.core.service.xds.entity.match.PrefixMatchStrategy; +import io.sermant.core.service.xds.entity.match.PresentMatchStrategy; +import io.sermant.core.service.xds.entity.match.RegexMatchStrategy; +import io.sermant.core.service.xds.entity.match.SuffixMatchStrategy; +import io.sermant.core.service.xds.entity.match.UnknownMatchStrategy; + +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; + +/** + * Convert rds protocol data to Sermant data model + * + * @author daizhenyu + * @since 2024-05-10 + **/ +public class RdsProtocolTransformer { + private static final Logger LOGGER = LoggerFactory.getLogger(); + + private static final String POINT_SEPARATOR = "\\."; + + private RdsProtocolTransformer() { + } + + /** + * get XdsRouteConfiguration list + * + * @param routeConfigurations RouteConfiguration list + * @return XdsRouteConfiguration list + */ + public static List getRouteConfigurations(List routeConfigurations) { + return routeConfigurations.stream() + .filter(Objects::nonNull) + .map(routeConfiguration -> parseRouteConfiguration(routeConfiguration)) + .collect(Collectors.toList()); + } + + private static XdsRouteConfiguration parseRouteConfiguration(RouteConfiguration routeConfiguration) { + XdsRouteConfiguration xdsRouteConfiguration = new XdsRouteConfiguration(); + xdsRouteConfiguration.setRouteConfigName(routeConfiguration.getName()); + Map xdsVirtualHostMap = routeConfiguration.getVirtualHostsList().stream() + .map(virtualHost -> parseVirtualHost(virtualHost)) + .collect(Collectors.toMap( + xdsVirtualHost -> xdsVirtualHost.getName().split(POINT_SEPARATOR)[0], + xdsVirtualHost -> xdsVirtualHost + )); + xdsRouteConfiguration.setVirtualHosts(xdsVirtualHostMap); + return xdsRouteConfiguration; + } + + private static XdsVirtualHost parseVirtualHost(VirtualHost virtualHost) { + XdsVirtualHost xdsVirtualHost = new XdsVirtualHost(); + + List domains = virtualHost.getDomainsList(); + + List xdsRoutes = + virtualHost.getRoutesList().stream() + .map(route -> parseRoute(route)) + .collect(Collectors.toList()); + xdsVirtualHost.setName(virtualHost.getName()); + xdsVirtualHost.setRoutes(xdsRoutes); + xdsVirtualHost.setDomains(domains); + return xdsVirtualHost; + } + + private static XdsRoute parseRoute(Route route) { + XdsRoute xdsRoute = new XdsRoute(); + xdsRoute.setName(route.getName()); + xdsRoute.setRouteMatch(parseRouteMatch(route.getMatch())); + xdsRoute.setRouteAction(parseRouteAction(route.getRoute())); + return xdsRoute; + } + + private static XdsRouteMatch parseRouteMatch(RouteMatch routeMatch) { + XdsRouteMatch xdsRouteMatch = new XdsRouteMatch(); + xdsRouteMatch.setPathMatcher(parsePathMatcher(routeMatch)); + xdsRouteMatch.setHeaderMatchers(parseHeaderMatchers(routeMatch.getHeadersList())); + xdsRouteMatch.setCaseSensitive(routeMatch.getCaseSensitive().getValue()); + return xdsRouteMatch; + } + + private static List parseHeaderMatchers(List headerMatchers) { + return headerMatchers.stream() + .filter(Objects::nonNull) + .map(headerMatcher -> parseHeaderMatcher(headerMatcher)) + .collect(Collectors.toList()); + } + + private static XdsHeaderMatcher parseHeaderMatcher(HeaderMatcher headerMatcher) { + if (headerMatcher.getPresentMatch()) { + return new XdsHeaderMatcher(headerMatcher.getName(), + new PresentMatchStrategy()); + } + if (!headerMatcher.hasStringMatch()) { + LOGGER.log(Level.WARNING, + "The xDS route header matching strategy is unknown. Please check the route configuration."); + return new XdsHeaderMatcher(headerMatcher.getName(), + new UnknownMatchStrategy()); + } + StringMatcher stringMatch = headerMatcher.getStringMatch(); + switch (stringMatch.getMatchPatternCase()) { + case EXACT: + return new XdsHeaderMatcher(headerMatcher.getName(), + new ExactMatchStrategy(stringMatch.getExact())); + case PREFIX: + return new XdsHeaderMatcher(headerMatcher.getName(), + new PrefixMatchStrategy(stringMatch.getPrefix())); + case SUFFIX: + return new XdsHeaderMatcher(headerMatcher.getName(), + new SuffixMatchStrategy(stringMatch.getSuffix())); + case SAFE_REGEX: + return new XdsHeaderMatcher(headerMatcher.getName(), + new RegexMatchStrategy(stringMatch.getSafeRegex().getRegex())); + default: + LOGGER.log(Level.WARNING, + "The xDS route header matching strategy is unknown. Please check the route configuration."); + return new XdsHeaderMatcher(headerMatcher.getName(), + new UnknownMatchStrategy()); + } + } + + private static XdsPathMatcher parsePathMatcher(RouteMatch routeMatch) { + boolean caseSensitive = routeMatch.getCaseSensitive().getValue(); + switch (routeMatch.getPathSpecifierCase()) { + case PATH: + String path = routeMatch.getPath(); + return new XdsPathMatcher(new ExactMatchStrategy(caseSensitive ? path : path.toLowerCase(Locale.ROOT)), + caseSensitive); + case PREFIX: + String prefix = routeMatch.getPrefix(); + return new XdsPathMatcher( + new PrefixMatchStrategy(caseSensitive ? prefix : prefix.toLowerCase(Locale.ROOT)), + caseSensitive); + default: + LOGGER.log(Level.WARNING, + "The xDS route path matching strategy is unknown. Please check the route configuration."); + return new XdsPathMatcher(new UnknownMatchStrategy(), caseSensitive); + } + } + + private static XdsRouteAction parseRouteAction(RouteAction routeAction) { + XdsRouteAction xdsRouteAction = new XdsRouteAction(); + switch (routeAction.getClusterSpecifierCase()) { + case CLUSTER: + xdsRouteAction.setCluster(routeAction.getCluster()); + break; + case WEIGHTED_CLUSTERS: + xdsRouteAction.setWeighted(true); + xdsRouteAction.setWeightedClusters(parseWeightedClusters(routeAction.getWeightedClusters())); + break; + default: + LOGGER.log(Level.WARNING, + "The xDS route action strategy is unknown. Please check the route configuration."); + } + return xdsRouteAction; + } + + private static XdsWeightedClusters parseWeightedClusters(WeightedCluster clusters) { + XdsWeightedClusters xdsWeightedClusters = new XdsWeightedClusters(); + xdsWeightedClusters.setTotalWeight(clusters.getClustersList().stream() + .filter(Objects::nonNull) + .mapToInt(clusterWeight -> clusterWeight.getWeight().getValue()) + .sum() + ); + xdsWeightedClusters.setClusters(clusters.getClustersList().stream() + .filter(Objects::nonNull) + .map(clusterWeight -> parseClusterWeight(clusterWeight)) + .collect(Collectors.toList())); + return xdsWeightedClusters; + } + + private static XdsClusterWeight parseClusterWeight(ClusterWeight clusterWeight) { + XdsClusterWeight xdsClusterWeight = new XdsClusterWeight(); + xdsClusterWeight.setWeight(clusterWeight.getWeight().getValue()); + xdsClusterWeight.setClusterName(clusterWeight.getName()); + return xdsClusterWeight; + } +} diff --git a/sermant-agentcore/sermant-agentcore-implement/src/main/java/io/sermant/implement/service/xds/utils/XdsCommonUtils.java b/sermant-agentcore/sermant-agentcore-implement/src/main/java/io/sermant/implement/service/xds/utils/XdsCommonUtils.java new file mode 100644 index 0000000000..cd83e0c521 --- /dev/null +++ b/sermant-agentcore/sermant-agentcore-implement/src/main/java/io/sermant/implement/service/xds/utils/XdsCommonUtils.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2024-2024 Sermant Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.sermant.implement.service.xds.utils; + +import io.sermant.core.utils.StringUtils; + +import java.util.Optional; +import java.util.regex.Pattern; + +/** + * xds common utils + * + * @author daizhenyu + * @since 2024-08-22 + **/ +public class XdsCommonUtils { + private static final int SERVICE_HOST_INDEX = 3; + + private static final int SERVICE_NAME_INDEX = 0; + + private static final String VERTICAL_LINE_SEPARATOR = "\\|"; + + private static final String POINT_SEPARATOR = "\\."; + + private static final Pattern CLUSTER_NAME_FORMAT = Pattern.compile("^\\w+\\|\\w+\\|[^|]*\\|[^|]+$"); + + private XdsCommonUtils() { + } + + /*** + * get service name from cluster, cluster + * + * @param clusterName cluster name + * @return service name + */ + public static Optional getServiceNameFromCluster(String clusterName) { + if (StringUtils.isEmpty(clusterName) || !CLUSTER_NAME_FORMAT.matcher(clusterName).matches()) { + return Optional.empty(); + } + + // cluster name format: "outbound|8080||xds-service.default.svc.cluster.local", xds-service is service name + String[] clusterSplit = clusterName.split(VERTICAL_LINE_SEPARATOR); + return Optional.of(clusterSplit[SERVICE_HOST_INDEX].split(POINT_SEPARATOR)[SERVICE_NAME_INDEX]); + } +} diff --git a/sermant-agentcore/sermant-agentcore-implement/src/test/java/io/sermant/implement/service/xds/utils/CdsProtocolTransformerTest.java b/sermant-agentcore/sermant-agentcore-implement/src/test/java/io/sermant/implement/service/xds/utils/CdsProtocolTransformerTest.java new file mode 100644 index 0000000000..cd12830139 --- /dev/null +++ b/sermant-agentcore/sermant-agentcore-implement/src/test/java/io/sermant/implement/service/xds/utils/CdsProtocolTransformerTest.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2024-2024 Sermant Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.sermant.implement.service.xds.utils; + +import io.envoyproxy.envoy.config.cluster.v3.Cluster; +import io.envoyproxy.envoy.config.cluster.v3.Cluster.CommonLbConfig; +import io.envoyproxy.envoy.config.cluster.v3.Cluster.CommonLbConfig.Builder; +import io.envoyproxy.envoy.config.cluster.v3.Cluster.CommonLbConfig.LocalityWeightedLbConfig; +import io.envoyproxy.envoy.config.cluster.v3.Cluster.LbPolicy; +import io.sermant.core.service.xds.entity.XdsLbPolicy; +import io.sermant.core.service.xds.entity.XdsServiceCluster; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +/** + * CdsProtocolTransformerTest + * + * @author daizhenyu + * @since 2024-08-23 + **/ +public class CdsProtocolTransformerTest { + @Test + public void testGetServiceClusters() { + List clusters = Arrays.asList( + null, + createCluster("outbound|8080||serviceA.default.svc.cluster.local", LbPolicy.RANDOM, true), + createCluster("outbound|8080|subset1|serviceB.default.svc.cluster.local", LbPolicy.RANDOM, false), + createCluster("outbound|8080|subset2|serviceB.default.svc.cluster.local", LbPolicy.CLUSTER_PROVIDED, + false), + createCluster("outbound|8080||serviceB.default.svc.cluster.local", LbPolicy.RANDOM, false), + createCluster("outbound|8080|serviceC.default.svc.cluster.local", LbPolicy.RANDOM, false), + createCluster(null, null, false) + ); + + Map result = CdsProtocolTransformer.getServiceClusters(clusters); + Assert.assertEquals(2, result.size()); + Assert.assertTrue(result.containsKey("serviceA")); + Assert.assertTrue(result.containsKey("serviceB")); + XdsServiceCluster serviceACluster = result.get("serviceA"); + XdsServiceCluster serviceBCluster = result.get("serviceB"); + Assert.assertEquals(1, serviceACluster.getClusterResources().size()); + Assert.assertEquals("outbound|8080||serviceA.default.svc.cluster.local", + serviceACluster.getBaseClusterName()); + Assert.assertEquals(XdsLbPolicy.RANDOM, + serviceACluster.getLbPolicyOfCluster("outbound|8080||serviceA.default.svc.cluster.local")); + Assert.assertEquals(XdsLbPolicy.UNRECOGNIZED, + serviceACluster.getLbPolicyOfCluster("outbound|8080|aaa|serviceA.default.svc.cluster.local")); + Assert.assertEquals(XdsLbPolicy.RANDOM, + serviceACluster.getBaseLbPolicyOfService()); + Assert.assertEquals(true, + serviceACluster.isClusterLocalityLb("outbound|8080||serviceA.default.svc.cluster.local")); + + Assert.assertEquals(3, serviceBCluster.getClusterResources().size()); + Assert.assertEquals("outbound|8080||serviceA.default.svc.cluster.local", + result.get("serviceA").getBaseClusterName()); + Assert.assertEquals(XdsLbPolicy.RANDOM, + serviceBCluster.getLbPolicyOfCluster("outbound|8080||serviceB.default.svc.cluster.local")); + Assert.assertEquals(XdsLbPolicy.UNRECOGNIZED, + serviceBCluster.getLbPolicyOfCluster("outbound|8080|subset2|serviceB.default.svc.cluster.local")); + Assert.assertEquals(XdsLbPolicy.UNRECOGNIZED, + serviceBCluster.getLbPolicyOfCluster("outbound|8080|subset3|serviceB.default.svc.cluster.local")); + Assert.assertEquals(XdsLbPolicy.RANDOM, + serviceBCluster.getBaseLbPolicyOfService()); + Assert.assertEquals(false, + serviceACluster.isClusterLocalityLb("outbound|8080||serviceB.default.svc.cluster.local")); + } + + private Cluster createCluster(String name, LbPolicy lbPolicy, boolean isLocal) { + Cluster.Builder builder = Cluster.newBuilder(); + if (name != null) { + builder.setName(name); + } + if (lbPolicy != null) { + builder.setLbPolicy(lbPolicy); + } + if (isLocal) { + Builder configBuilder = CommonLbConfig.newBuilder(); + configBuilder.setLocalityWeightedLbConfig(LocalityWeightedLbConfig.newBuilder().build()); + builder.setCommonLbConfig(configBuilder.build()); + } + return builder.build(); + } +} \ No newline at end of file diff --git a/sermant-agentcore/sermant-agentcore-implement/src/test/java/io/sermant/implement/service/xds/utils/XdsProtocolTransformerTest.java b/sermant-agentcore/sermant-agentcore-implement/src/test/java/io/sermant/implement/service/xds/utils/EdsProtocolTransformerTest.java similarity index 50% rename from sermant-agentcore/sermant-agentcore-implement/src/test/java/io/sermant/implement/service/xds/utils/XdsProtocolTransformerTest.java rename to sermant-agentcore/sermant-agentcore-implement/src/test/java/io/sermant/implement/service/xds/utils/EdsProtocolTransformerTest.java index b0861fd7c9..5e40435955 100644 --- a/sermant-agentcore/sermant-agentcore-implement/src/test/java/io/sermant/implement/service/xds/utils/XdsProtocolTransformerTest.java +++ b/sermant-agentcore/sermant-agentcore-implement/src/test/java/io/sermant/implement/service/xds/utils/EdsProtocolTransformerTest.java @@ -16,11 +16,13 @@ package io.sermant.implement.service.xds.utils; -import io.envoyproxy.envoy.config.cluster.v3.Cluster; +import io.envoyproxy.envoy.config.core.v3.Locality; import io.envoyproxy.envoy.config.endpoint.v3.ClusterLoadAssignment; import io.envoyproxy.envoy.config.endpoint.v3.LbEndpoint; import io.envoyproxy.envoy.config.endpoint.v3.LocalityLbEndpoints; import io.sermant.core.service.xds.entity.ServiceInstance; +import io.sermant.core.service.xds.entity.XdsLocality; +import io.sermant.core.service.xds.entity.XdsServiceClusterLoadAssigment; import org.junit.Assert; import org.junit.Test; @@ -29,6 +31,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; /** @@ -37,27 +40,7 @@ * @author daizhenyu * @since 2024-05-11 **/ -public class XdsProtocolTransformerTest { - - @Test - public void testGetService2ClusterMapping() { - List clusters = Arrays.asList( - null, - createCluster("outbound|8080||serviceA.default.svc.cluster.local"), - createCluster("outbound|8080|subset1|serviceB.default.svc.cluster.local"), - createCluster("outbound|8080|subset2|serviceB.default.svc.cluster.local"), - createCluster("outbound|8080|serviceC.default.svc.cluster.local"), - createCluster(null) - ); - - Map> result = XdsProtocolTransformer.getService2ClusterMapping(clusters); - Assert.assertEquals(2, result.size()); - Assert.assertTrue(result.containsKey("serviceA")); - Assert.assertTrue(result.containsKey("serviceB")); - Assert.assertEquals(1, result.get("serviceA").size()); - Assert.assertEquals(2, result.get("serviceB").size()); - } - +public class EdsProtocolTransformerTest { @Test public void testGetServiceInstances() { List assignments = Arrays.asList( @@ -67,31 +50,42 @@ public void testGetServiceInstances() { createLoadAssignment("outbound|8080|serviceB.default.svc.cluster.local") ); - Set result = XdsProtocolTransformer.getServiceInstances(assignments); - Assert.assertEquals(2, result.size()); - Iterator iterator = result.iterator(); + XdsServiceClusterLoadAssigment result = EdsProtocolTransformer.getServiceInstances(assignments); + Assert.assertEquals(2, result.getClusterLoadAssigments().size()); + Iterator iterator = result.getServiceInstance().iterator(); while (iterator.hasNext()) { ServiceInstance next = iterator.next(); Assert.assertEquals("serviceB", next.getServiceName()); } - } + Assert.assertEquals(2, result.getClusterLoadAssigments().size()); + Assert.assertEquals("outbound|8080|subset1|serviceB.default.svc.cluster.local", + result.getXdsClusterLoadAssigment("outbound|8080|subset1|serviceB.default.svc.cluster.local") + .getClusterName()); + Assert.assertEquals("serviceB", + result.getXdsClusterLoadAssigment("outbound|8080|subset1|serviceB.default.svc.cluster.local") + .getServiceName()); - private Cluster createCluster(String name) { - Cluster.Builder builder = Cluster.newBuilder(); - if (name != null) { - builder.setName(name); + Map> localityInstances = result + .getXdsClusterLoadAssigment("outbound|8080|subset1|serviceB.default.svc.cluster.local") + .getLocalityInstances(); + Assert.assertEquals(1, + localityInstances.size()); + for (Entry> xdsLocalitySetEntry : localityInstances.entrySet()) { + Assert.assertEquals("test-region", xdsLocalitySetEntry.getKey().getRegion()); + Assert.assertEquals("test-zone", xdsLocalitySetEntry.getKey().getZone()); + Assert.assertEquals("test-subzone", xdsLocalitySetEntry.getKey().getSubZone()); } - return builder.build(); } private ClusterLoadAssignment createLoadAssignment(String clusterName) { ClusterLoadAssignment.Builder assignmentBuilder = ClusterLoadAssignment.newBuilder(); - - LocalityLbEndpoints.Builder localityBuilder = LocalityLbEndpoints.newBuilder(); LbEndpoint.Builder endpointBuilder = LbEndpoint.newBuilder(); - localityBuilder.addLbEndpoints(endpointBuilder.build()); + Locality.Builder localityBuilder = Locality.newBuilder(); + localityBuilder.setRegion("test-region").setZone("test-zone").setSubZone("test-subzone"); + LocalityLbEndpoints.Builder localityEndpointBuilder = LocalityLbEndpoints.newBuilder(); + localityEndpointBuilder.addLbEndpoints(endpointBuilder.build()).setLocality(localityBuilder.build()); assignmentBuilder.setClusterName(clusterName); - assignmentBuilder.addEndpoints(localityBuilder.build()); + assignmentBuilder.addEndpoints(localityEndpointBuilder.build()); return assignmentBuilder.build(); } diff --git a/sermant-agentcore/sermant-agentcore-implement/src/test/java/io/sermant/implement/service/xds/utils/LdsProtocolTransformerTest.java b/sermant-agentcore/sermant-agentcore-implement/src/test/java/io/sermant/implement/service/xds/utils/LdsProtocolTransformerTest.java new file mode 100644 index 0000000000..e50422ee02 --- /dev/null +++ b/sermant-agentcore/sermant-agentcore-implement/src/test/java/io/sermant/implement/service/xds/utils/LdsProtocolTransformerTest.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2024-2024 Sermant Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.sermant.implement.service.xds.utils; + +import com.google.protobuf.Any; + +import io.envoyproxy.envoy.config.core.v3.Address; +import io.envoyproxy.envoy.config.core.v3.SocketAddress; +import io.envoyproxy.envoy.config.listener.v3.Filter; +import io.envoyproxy.envoy.config.listener.v3.FilterChain; +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.sermant.core.service.xds.entity.XdsHttpConnectionManager; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * LdsProtocolTransformerTest + * + * @author daizhenyu + * @since 2024-08-23 + **/ +public class LdsProtocolTransformerTest { + @Test + public void getHttpConnectionManager() { + List listeners = Arrays.asList( + null, + createListener("testA", "test-routeA"), + createListener("testB", "test-routeB"), + createListener("testC", null) + ); + List httpConnectionManagers = LdsProtocolTransformer + .getHttpConnectionManager(listeners); + Assert.assertEquals(2, httpConnectionManagers.size()); + Set actualRouteConfigNames = new HashSet<>(); + actualRouteConfigNames.add(httpConnectionManagers.get(0).getRouteConfigName()); + actualRouteConfigNames.add(httpConnectionManagers.get(1).getRouteConfigName()); + + Set expectRouteConfigNames = new HashSet<>(); + expectRouteConfigNames.add("test-routeA"); + expectRouteConfigNames.add("test-routeB"); + Assert.assertEquals(expectRouteConfigNames, actualRouteConfigNames); + } + + private Listener createListener(String listenerName, String routeName) { + Filter httpFilter; + if (routeName != null) { + HttpConnectionManager httpConnectionManager = createHttpConnectionManager(routeName); + Any httpConnectionManagerAny = Any.pack(httpConnectionManager); + httpFilter = Filter.newBuilder() + .setName("envoy.filters.network.http_connection_manager") + .setTypedConfig(httpConnectionManagerAny) + .build(); + } else { + httpFilter = Filter.newBuilder() + .build(); + } + FilterChain filterChain = FilterChain.newBuilder() + .addFilters(httpFilter) + .build(); + + Address listenerAddress = Address.newBuilder() + .setSocketAddress(SocketAddress.newBuilder() + .setAddress("127.0.0.1") + .setPortValue(8080) + .build()) + .build(); + + return Listener.newBuilder() + .setName(listenerName) + .setAddress(listenerAddress) + .addFilterChains(filterChain) + .build(); + } + + private HttpConnectionManager createHttpConnectionManager(String routeName) { + HttpConnectionManager.Builder managerBuilder = HttpConnectionManager.newBuilder(); + Rds.Builder rdsBuilder = Rds.newBuilder(); + rdsBuilder.setRouteConfigName(routeName); + managerBuilder.setRds(rdsBuilder.build()); + return managerBuilder.build(); + } +} \ No newline at end of file diff --git a/sermant-agentcore/sermant-agentcore-implement/src/test/java/io/sermant/implement/service/xds/utils/RdsProtocolTransformerTest.java b/sermant-agentcore/sermant-agentcore-implement/src/test/java/io/sermant/implement/service/xds/utils/RdsProtocolTransformerTest.java new file mode 100644 index 0000000000..bc67761fc1 --- /dev/null +++ b/sermant-agentcore/sermant-agentcore-implement/src/test/java/io/sermant/implement/service/xds/utils/RdsProtocolTransformerTest.java @@ -0,0 +1,223 @@ +/* + * Copyright (C) 2024-2024 Sermant Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.sermant.implement.service.xds.utils; + +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.Route.Builder; +import io.envoyproxy.envoy.config.route.v3.RouteAction; +import io.envoyproxy.envoy.config.route.v3.RouteConfiguration; +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.config.route.v3.WeightedCluster.ClusterWeight; +import io.envoyproxy.envoy.type.matcher.v3.StringMatcher; +import io.sermant.core.service.xds.entity.XdsRoute; +import io.sermant.core.service.xds.entity.XdsRouteConfiguration; +import io.sermant.core.service.xds.entity.XdsVirtualHost; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import java.util.Arrays; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * @author daizhenyu + * @since 2024-08-24 + **/ +public class RdsProtocolTransformerTest { + private int count = 1; + + @Before + public void setUp() throws Exception { + } + + @After + public void tearDown() throws Exception { + } + + @Test + public void testGetRouteConfigurations() { + List routeConfigurations = Arrays.asList( + null, + createRouteConfiguration("exact", "exact"), + createRouteConfiguration("prefix", "prefix"), + createRouteConfiguration("prefix", "present") + ); + List result = RdsProtocolTransformer + .getRouteConfigurations(routeConfigurations); + result.sort(Comparator.comparing(XdsRouteConfiguration::getRouteConfigName)); + Assert.assertEquals(3, result.size()); + + // pathType:exact headerType:exact + XdsRouteConfiguration routeConfiguration = result.get(0); + Assert.assertEquals("testRoute1", routeConfiguration.getRouteConfigName()); + Map virtualHosts = routeConfiguration.getVirtualHosts(); + Assert.assertEquals(1, virtualHosts.size()); + Assert.assertTrue(virtualHosts.containsKey("serviceA")); + XdsVirtualHost virtualHost = virtualHosts.get("serviceA"); + List routes = virtualHost.getRoutes(); + Assert.assertEquals(2, routes.size()); + Set expectIsWeightedCluster = new HashSet<>(); + expectIsWeightedCluster.add(true); + expectIsWeightedCluster.add(false); + Set actualIsWeightedCluster = new HashSet<>(); + actualIsWeightedCluster.add(routes.get(0).getRouteAction().isWeighted()); + actualIsWeightedCluster.add(routes.get(1).getRouteAction().isWeighted()); + Assert.assertEquals(expectIsWeightedCluster, actualIsWeightedCluster); + Assert.assertTrue(routes.get(0).getRouteMatch().getPathMatcher().isMatch("/test")); + Map headers = new HashMap<>(); + headers.put("testHeader", "value"); + Assert.assertTrue(routes.get(0).getRouteMatch().getHeaderMatchers().get(0).isMatch(headers)); + + // pathType:prefix headerType:prefix + routeConfiguration = result.get(1); + Assert.assertEquals("testRoute2", routeConfiguration.getRouteConfigName()); + virtualHosts = routeConfiguration.getVirtualHosts(); + Assert.assertEquals(1, virtualHosts.size()); + Assert.assertTrue(virtualHosts.containsKey("serviceA")); + virtualHost = virtualHosts.get("serviceA"); + routes = virtualHost.getRoutes(); + Assert.assertEquals(2, routes.size()); + expectIsWeightedCluster = new HashSet<>(); + expectIsWeightedCluster.add(true); + expectIsWeightedCluster.add(false); + actualIsWeightedCluster = new HashSet<>(); + actualIsWeightedCluster.add(routes.get(0).getRouteAction().isWeighted()); + actualIsWeightedCluster.add(routes.get(1).getRouteAction().isWeighted()); + Assert.assertEquals(expectIsWeightedCluster, actualIsWeightedCluster); + Assert.assertTrue(routes.get(0).getRouteMatch().getPathMatcher().isMatch("/test/2")); + headers = new HashMap<>(); + headers.put("testHeader", "value-prefix"); + Assert.assertTrue(routes.get(0).getRouteMatch().getHeaderMatchers().get(0).isMatch(headers)); + + // pathType:prefix headerType:present + routeConfiguration = result.get(2); + Assert.assertEquals("testRoute3", routeConfiguration.getRouteConfigName()); + virtualHosts = routeConfiguration.getVirtualHosts(); + Assert.assertEquals(1, virtualHosts.size()); + Assert.assertTrue(virtualHosts.containsKey("serviceA")); + virtualHost = virtualHosts.get("serviceA"); + routes = virtualHost.getRoutes(); + Assert.assertEquals(2, routes.size()); + expectIsWeightedCluster = new HashSet<>(); + expectIsWeightedCluster.add(true); + expectIsWeightedCluster.add(false); + actualIsWeightedCluster = new HashSet<>(); + actualIsWeightedCluster.add(routes.get(0).getRouteAction().isWeighted()); + actualIsWeightedCluster.add(routes.get(1).getRouteAction().isWeighted()); + Assert.assertEquals(expectIsWeightedCluster, actualIsWeightedCluster); + Assert.assertTrue(routes.get(0).getRouteMatch().getPathMatcher().isMatch("/test/3")); + headers = new HashMap<>(); + headers.put("testHeader", "present"); + Assert.assertTrue(routes.get(0).getRouteMatch().getHeaderMatchers().get(0).isMatch(headers)); + } + + private RouteConfiguration createRouteConfiguration(String pathType, String headerType) { + return RouteConfiguration.newBuilder() + .setName("testRoute" + count++) + .addVirtualHosts(createVirtualHost(pathType, headerType)) + .build(); + } + + private VirtualHost createVirtualHost(String pathType, String headerType) { + return VirtualHost.newBuilder() + .setName("serviceA.example.com") + .addDomains("serviceA.example.com") + .addRoutes(createRoute(true, pathType, headerType)) + .addRoutes(createRoute(false, pathType, headerType)) + .build(); + } + + private Route createRoute(boolean isWeightCluster, String pathType, String headerType) { + Builder routeBuilder = Route.newBuilder(); + if (isWeightCluster) { + routeBuilder.setRoute(createRouteActionWithWeightCluster()); + } else { + routeBuilder.setRoute(createRouteAction()); + } + return routeBuilder.setMatch(createRouteMatch(pathType, headerType)) + .build(); + } + + private RouteMatch createRouteMatch(String pathType, String headerType) { + RouteMatch.Builder matchBuilder = RouteMatch.newBuilder(); + if ("exact".equals(pathType)) { + matchBuilder.setPath("/test"); + } else if ("prefix".equals(pathType)) { + matchBuilder.setPrefix("/test"); + } + return matchBuilder + .setCaseSensitive(BoolValue.of(true)) + .addHeaders(createHeaderMatcher(headerType)) + .build(); + } + + private RouteAction createRouteAction() { + return RouteAction.newBuilder() + .setCluster("testCluster") + .build(); + } + + private RouteAction createRouteActionWithWeightCluster() { + return RouteAction.newBuilder() + .setWeightedClusters(createWeightedCluster()) + .build(); + } + + private HeaderMatcher createHeaderMatcher(String headerType) { + HeaderMatcher.Builder headBuilder = HeaderMatcher.newBuilder().setName("testHeader"); + StringMatcher.Builder stringMatcherBuilder = StringMatcher.newBuilder(); + switch (headerType) { + case "exact": + headBuilder.setStringMatch(stringMatcherBuilder.setExact("value")); + break; + case "prefix": + headBuilder.setStringMatch(stringMatcherBuilder.setPrefix("value")); + break; + case "present": + headBuilder.setPresentMatch(true); + break; + } + return headBuilder.build(); + } + + private WeightedCluster createWeightedCluster() { + return WeightedCluster.newBuilder() + .setTotalWeight(UInt32Value.of(100)) + .addClusters(createClusterWeight()) + .build(); + } + + private ClusterWeight createClusterWeight() { + return ClusterWeight.newBuilder() + .setName("clusterName") + .setWeight(UInt32Value.of(100)) + .build(); + } +} \ No newline at end of file diff --git a/sermant-agentcore/sermant-agentcore-implement/src/test/java/io/sermant/implement/service/xds/utils/XdsCommonUtilsTest.java b/sermant-agentcore/sermant-agentcore-implement/src/test/java/io/sermant/implement/service/xds/utils/XdsCommonUtilsTest.java new file mode 100644 index 0000000000..6c045e9efb --- /dev/null +++ b/sermant-agentcore/sermant-agentcore-implement/src/test/java/io/sermant/implement/service/xds/utils/XdsCommonUtilsTest.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2024-2024 Sermant Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.sermant.implement.service.xds.utils; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.Optional; + +/** + * XdsCommonUtilsTest + * + * @author daizhenyu + * @since 2024-08-23 + **/ +public class XdsCommonUtilsTest { + @Test + public void testGetServiceNameFromCluster() { + String clusterName = "outbound|8080||serviceA.default.svc.cluster.local"; + Optional serviceNameFromCluster = XdsCommonUtils.getServiceNameFromCluster(clusterName); + Assert.assertTrue(serviceNameFromCluster.isPresent()); + Assert.assertEquals("serviceA", serviceNameFromCluster.get()); + + clusterName = "outbound|8080|sub-set|serviceA.default.svc.cluster.local"; + serviceNameFromCluster = XdsCommonUtils.getServiceNameFromCluster(clusterName); + Assert.assertTrue(serviceNameFromCluster.isPresent()); + Assert.assertEquals("serviceA", serviceNameFromCluster.get()); + + clusterName = "outbound|8080|subset|serviceA"; + serviceNameFromCluster = XdsCommonUtils.getServiceNameFromCluster(clusterName); + Assert.assertTrue(serviceNameFromCluster.isPresent()); + Assert.assertEquals("serviceA", serviceNameFromCluster.get()); + + clusterName = "outbound|8080|serviceA.default.svc.cluster.local"; + serviceNameFromCluster = XdsCommonUtils.getServiceNameFromCluster(clusterName); + Assert.assertFalse(serviceNameFromCluster.isPresent()); + } +} \ No newline at end of file