mirror of https://github.com/grpc/grpc-java.git
xds: Parsing xDS Cluster Metadata (#11741)
This commit is contained in:
parent
4222f77587
commit
1edc4d84d4
|
@ -22,6 +22,7 @@ import com.google.common.primitives.UnsignedLongs;
|
||||||
import com.google.protobuf.Any;
|
import com.google.protobuf.Any;
|
||||||
import com.google.protobuf.InvalidProtocolBufferException;
|
import com.google.protobuf.InvalidProtocolBufferException;
|
||||||
import com.google.protobuf.Message;
|
import com.google.protobuf.Message;
|
||||||
|
import io.envoyproxy.envoy.extensions.filters.http.gcp_authn.v3.Audience;
|
||||||
import io.envoyproxy.envoy.extensions.filters.http.gcp_authn.v3.GcpAuthnFilterConfig;
|
import io.envoyproxy.envoy.extensions.filters.http.gcp_authn.v3.GcpAuthnFilterConfig;
|
||||||
import io.envoyproxy.envoy.extensions.filters.http.gcp_authn.v3.TokenCacheConfig;
|
import io.envoyproxy.envoy.extensions.filters.http.gcp_authn.v3.TokenCacheConfig;
|
||||||
import io.grpc.CallCredentials;
|
import io.grpc.CallCredentials;
|
||||||
|
@ -36,6 +37,7 @@ import io.grpc.MethodDescriptor;
|
||||||
import io.grpc.Status;
|
import io.grpc.Status;
|
||||||
import io.grpc.auth.MoreCallCredentials;
|
import io.grpc.auth.MoreCallCredentials;
|
||||||
import io.grpc.xds.Filter.ClientInterceptorBuilder;
|
import io.grpc.xds.Filter.ClientInterceptorBuilder;
|
||||||
|
import io.grpc.xds.MetadataRegistry.MetadataValueParser;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
@ -219,4 +221,23 @@ final class GcpAuthenticationFilter implements Filter, ClientInterceptorBuilder
|
||||||
return cache.computeIfAbsent(key, create);
|
return cache.computeIfAbsent(key, create);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static class AudienceMetadataParser implements MetadataValueParser {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getTypeUrl() {
|
||||||
|
return "type.googleapis.com/envoy.extensions.filters.http.gcp_authn.v3.Audience";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String parse(Any any) throws InvalidProtocolBufferException {
|
||||||
|
Audience audience = any.unpack(Audience.class);
|
||||||
|
String url = audience.getUrl();
|
||||||
|
if (url.isEmpty()) {
|
||||||
|
throw new InvalidProtocolBufferException(
|
||||||
|
"Audience URL is empty. Metadata value must contain a valid URL.");
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,71 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 The gRPC 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.grpc.xds;
|
||||||
|
|
||||||
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
import com.google.protobuf.Any;
|
||||||
|
import com.google.protobuf.InvalidProtocolBufferException;
|
||||||
|
import io.grpc.xds.GcpAuthenticationFilter.AudienceMetadataParser;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registry for parsing cluster metadata values.
|
||||||
|
*
|
||||||
|
* <p>This class maintains a mapping of type URLs to {@link MetadataValueParser} instances,
|
||||||
|
* allowing for the parsing of different metadata types.
|
||||||
|
*/
|
||||||
|
final class MetadataRegistry {
|
||||||
|
private static final MetadataRegistry INSTANCE = new MetadataRegistry();
|
||||||
|
|
||||||
|
private final Map<String, MetadataValueParser> supportedParsers = new HashMap<>();
|
||||||
|
|
||||||
|
private MetadataRegistry() {
|
||||||
|
registerParser(new AudienceMetadataParser());
|
||||||
|
}
|
||||||
|
|
||||||
|
static MetadataRegistry getInstance() {
|
||||||
|
return INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
MetadataValueParser findParser(String typeUrl) {
|
||||||
|
return supportedParsers.get(typeUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
void registerParser(MetadataValueParser parser) {
|
||||||
|
supportedParsers.put(parser.getTypeUrl(), parser);
|
||||||
|
}
|
||||||
|
|
||||||
|
void removeParser(MetadataValueParser parser) {
|
||||||
|
supportedParsers.remove(parser.getTypeUrl());
|
||||||
|
}
|
||||||
|
|
||||||
|
interface MetadataValueParser {
|
||||||
|
|
||||||
|
String getTypeUrl();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the given {@link Any} object into a specific metadata value.
|
||||||
|
*
|
||||||
|
* @param any the {@link Any} object to parse.
|
||||||
|
* @return the parsed metadata value.
|
||||||
|
* @throws InvalidProtocolBufferException if the parsing fails.
|
||||||
|
*/
|
||||||
|
Object parse(Any any) throws InvalidProtocolBufferException;
|
||||||
|
}
|
||||||
|
}
|
|
@ -25,6 +25,7 @@ import com.google.common.base.MoreObjects;
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import com.google.protobuf.Any;
|
||||||
import com.google.protobuf.Duration;
|
import com.google.protobuf.Duration;
|
||||||
import com.google.protobuf.InvalidProtocolBufferException;
|
import com.google.protobuf.InvalidProtocolBufferException;
|
||||||
import com.google.protobuf.Message;
|
import com.google.protobuf.Message;
|
||||||
|
@ -32,6 +33,7 @@ import com.google.protobuf.Struct;
|
||||||
import com.google.protobuf.util.Durations;
|
import com.google.protobuf.util.Durations;
|
||||||
import io.envoyproxy.envoy.config.cluster.v3.CircuitBreakers.Thresholds;
|
import io.envoyproxy.envoy.config.cluster.v3.CircuitBreakers.Thresholds;
|
||||||
import io.envoyproxy.envoy.config.cluster.v3.Cluster;
|
import io.envoyproxy.envoy.config.cluster.v3.Cluster;
|
||||||
|
import io.envoyproxy.envoy.config.core.v3.Metadata;
|
||||||
import io.envoyproxy.envoy.config.core.v3.RoutingPriority;
|
import io.envoyproxy.envoy.config.core.v3.RoutingPriority;
|
||||||
import io.envoyproxy.envoy.config.core.v3.SocketAddress;
|
import io.envoyproxy.envoy.config.core.v3.SocketAddress;
|
||||||
import io.envoyproxy.envoy.config.endpoint.v3.ClusterLoadAssignment;
|
import io.envoyproxy.envoy.config.endpoint.v3.ClusterLoadAssignment;
|
||||||
|
@ -44,12 +46,15 @@ import io.grpc.internal.ServiceConfigUtil;
|
||||||
import io.grpc.internal.ServiceConfigUtil.LbConfig;
|
import io.grpc.internal.ServiceConfigUtil.LbConfig;
|
||||||
import io.grpc.xds.EnvoyServerProtoData.OutlierDetection;
|
import io.grpc.xds.EnvoyServerProtoData.OutlierDetection;
|
||||||
import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext;
|
import io.grpc.xds.EnvoyServerProtoData.UpstreamTlsContext;
|
||||||
|
import io.grpc.xds.MetadataRegistry.MetadataValueParser;
|
||||||
import io.grpc.xds.XdsClusterResource.CdsUpdate;
|
import io.grpc.xds.XdsClusterResource.CdsUpdate;
|
||||||
import io.grpc.xds.client.XdsClient.ResourceUpdate;
|
import io.grpc.xds.client.XdsClient.ResourceUpdate;
|
||||||
import io.grpc.xds.client.XdsResourceType;
|
import io.grpc.xds.client.XdsResourceType;
|
||||||
|
import io.grpc.xds.internal.ProtobufJsonConverter;
|
||||||
import io.grpc.xds.internal.security.CommonTlsContextUtil;
|
import io.grpc.xds.internal.security.CommonTlsContextUtil;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
|
@ -171,9 +176,62 @@ class XdsClusterResource extends XdsResourceType<CdsUpdate> {
|
||||||
updateBuilder.filterMetadata(
|
updateBuilder.filterMetadata(
|
||||||
ImmutableMap.copyOf(cluster.getMetadata().getFilterMetadataMap()));
|
ImmutableMap.copyOf(cluster.getMetadata().getFilterMetadataMap()));
|
||||||
|
|
||||||
|
try {
|
||||||
|
ImmutableMap<String, Object> parsedFilterMetadata =
|
||||||
|
parseClusterMetadata(cluster.getMetadata());
|
||||||
|
updateBuilder.parsedMetadata(parsedFilterMetadata);
|
||||||
|
} catch (InvalidProtocolBufferException e) {
|
||||||
|
throw new ResourceInvalidException(
|
||||||
|
"Failed to parse xDS filter metadata for cluster '" + cluster.getName() + "': "
|
||||||
|
+ e.getMessage(), e);
|
||||||
|
}
|
||||||
|
|
||||||
return updateBuilder.build();
|
return updateBuilder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses cluster metadata into a structured map.
|
||||||
|
*
|
||||||
|
* <p>Values in {@code typed_filter_metadata} take precedence over
|
||||||
|
* {@code filter_metadata} when keys overlap, following Envoy API behavior. See
|
||||||
|
* <a href="https://github.com/envoyproxy/envoy/blob/main/api/envoy/config/core/v3/base.proto#L217-L259">
|
||||||
|
* Envoy metadata documentation </a> for details.
|
||||||
|
*
|
||||||
|
* @param metadata the {@link Metadata} containing the fields to parse.
|
||||||
|
* @return an immutable map of parsed metadata.
|
||||||
|
* @throws InvalidProtocolBufferException if parsing {@code typed_filter_metadata} fails.
|
||||||
|
*/
|
||||||
|
private static ImmutableMap<String, Object> parseClusterMetadata(Metadata metadata)
|
||||||
|
throws InvalidProtocolBufferException {
|
||||||
|
ImmutableMap.Builder<String, Object> parsedMetadata = ImmutableMap.builder();
|
||||||
|
|
||||||
|
MetadataRegistry registry = MetadataRegistry.getInstance();
|
||||||
|
// Process typed_filter_metadata
|
||||||
|
for (Map.Entry<String, Any> entry : metadata.getTypedFilterMetadataMap().entrySet()) {
|
||||||
|
String key = entry.getKey();
|
||||||
|
Any value = entry.getValue();
|
||||||
|
MetadataValueParser parser = registry.findParser(value.getTypeUrl());
|
||||||
|
if (parser != null) {
|
||||||
|
Object parsedValue = parser.parse(value);
|
||||||
|
parsedMetadata.put(key, parsedValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// building once to reuse in the next loop
|
||||||
|
ImmutableMap<String, Object> intermediateParsedMetadata = parsedMetadata.build();
|
||||||
|
|
||||||
|
// Process filter_metadata for remaining keys
|
||||||
|
for (Map.Entry<String, Struct> entry : metadata.getFilterMetadataMap().entrySet()) {
|
||||||
|
String key = entry.getKey();
|
||||||
|
if (!intermediateParsedMetadata.containsKey(key)) {
|
||||||
|
Struct structValue = entry.getValue();
|
||||||
|
Object jsonValue = ProtobufJsonConverter.convertToJson(structValue);
|
||||||
|
parsedMetadata.put(key, jsonValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return parsedMetadata.build();
|
||||||
|
}
|
||||||
|
|
||||||
private static StructOrError<CdsUpdate.Builder> parseAggregateCluster(Cluster cluster) {
|
private static StructOrError<CdsUpdate.Builder> parseAggregateCluster(Cluster cluster) {
|
||||||
String clusterName = cluster.getName();
|
String clusterName = cluster.getName();
|
||||||
Cluster.CustomClusterType customType = cluster.getClusterType();
|
Cluster.CustomClusterType customType = cluster.getClusterType();
|
||||||
|
@ -573,13 +631,16 @@ class XdsClusterResource extends XdsResourceType<CdsUpdate> {
|
||||||
|
|
||||||
abstract ImmutableMap<String, Struct> filterMetadata();
|
abstract ImmutableMap<String, Struct> filterMetadata();
|
||||||
|
|
||||||
|
abstract ImmutableMap<String, Object> parsedMetadata();
|
||||||
|
|
||||||
private static Builder newBuilder(String clusterName) {
|
private static Builder newBuilder(String clusterName) {
|
||||||
return new AutoValue_XdsClusterResource_CdsUpdate.Builder()
|
return new AutoValue_XdsClusterResource_CdsUpdate.Builder()
|
||||||
.clusterName(clusterName)
|
.clusterName(clusterName)
|
||||||
.minRingSize(0)
|
.minRingSize(0)
|
||||||
.maxRingSize(0)
|
.maxRingSize(0)
|
||||||
.choiceCount(0)
|
.choiceCount(0)
|
||||||
.filterMetadata(ImmutableMap.of());
|
.filterMetadata(ImmutableMap.of())
|
||||||
|
.parsedMetadata(ImmutableMap.of());
|
||||||
}
|
}
|
||||||
|
|
||||||
static Builder forAggregate(String clusterName, List<String> prioritizedClusterNames) {
|
static Builder forAggregate(String clusterName, List<String> prioritizedClusterNames) {
|
||||||
|
@ -698,6 +759,8 @@ class XdsClusterResource extends XdsResourceType<CdsUpdate> {
|
||||||
|
|
||||||
protected abstract Builder filterMetadata(ImmutableMap<String, Struct> filterMetadata);
|
protected abstract Builder filterMetadata(ImmutableMap<String, Struct> filterMetadata);
|
||||||
|
|
||||||
|
protected abstract Builder parsedMetadata(ImmutableMap<String, Object> parsedMetadata);
|
||||||
|
|
||||||
abstract CdsUpdate build();
|
abstract CdsUpdate build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 The gRPC 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.grpc.xds.internal;
|
||||||
|
|
||||||
|
import com.google.protobuf.Struct;
|
||||||
|
import com.google.protobuf.Value;
|
||||||
|
import io.grpc.Internal;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converter for Protobuf {@link Struct} to JSON-like {@link Map}.
|
||||||
|
*/
|
||||||
|
@Internal
|
||||||
|
public final class ProtobufJsonConverter {
|
||||||
|
private ProtobufJsonConverter() {}
|
||||||
|
|
||||||
|
public static Map<String, Object> convertToJson(Struct struct) {
|
||||||
|
Map<String, Object> result = new HashMap<>();
|
||||||
|
for (Map.Entry<String, Value> entry : struct.getFieldsMap().entrySet()) {
|
||||||
|
result.put(entry.getKey(), convertValue(entry.getValue()));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Object convertValue(Value value) {
|
||||||
|
switch (value.getKindCase()) {
|
||||||
|
case STRUCT_VALUE:
|
||||||
|
return convertToJson(value.getStructValue());
|
||||||
|
case LIST_VALUE:
|
||||||
|
return value.getListValue().getValuesList().stream()
|
||||||
|
.map(ProtobufJsonConverter::convertValue)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
case NUMBER_VALUE:
|
||||||
|
return value.getNumberValue();
|
||||||
|
case STRING_VALUE:
|
||||||
|
return value.getStringValue();
|
||||||
|
case BOOL_VALUE:
|
||||||
|
return value.getBoolValue();
|
||||||
|
case NULL_VALUE:
|
||||||
|
return null;
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Unknown Value type: " + value.getKindCase());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -50,6 +50,7 @@ import io.envoyproxy.envoy.config.core.v3.ConfigSource;
|
||||||
import io.envoyproxy.envoy.config.core.v3.DataSource;
|
import io.envoyproxy.envoy.config.core.v3.DataSource;
|
||||||
import io.envoyproxy.envoy.config.core.v3.HttpProtocolOptions;
|
import io.envoyproxy.envoy.config.core.v3.HttpProtocolOptions;
|
||||||
import io.envoyproxy.envoy.config.core.v3.Locality;
|
import io.envoyproxy.envoy.config.core.v3.Locality;
|
||||||
|
import io.envoyproxy.envoy.config.core.v3.Metadata;
|
||||||
import io.envoyproxy.envoy.config.core.v3.PathConfigSource;
|
import io.envoyproxy.envoy.config.core.v3.PathConfigSource;
|
||||||
import io.envoyproxy.envoy.config.core.v3.RuntimeFractionalPercent;
|
import io.envoyproxy.envoy.config.core.v3.RuntimeFractionalPercent;
|
||||||
import io.envoyproxy.envoy.config.core.v3.SelfConfigSource;
|
import io.envoyproxy.envoy.config.core.v3.SelfConfigSource;
|
||||||
|
@ -84,6 +85,7 @@ import io.envoyproxy.envoy.config.route.v3.WeightedCluster;
|
||||||
import io.envoyproxy.envoy.extensions.filters.common.fault.v3.FaultDelay;
|
import io.envoyproxy.envoy.extensions.filters.common.fault.v3.FaultDelay;
|
||||||
import io.envoyproxy.envoy.extensions.filters.http.fault.v3.FaultAbort;
|
import io.envoyproxy.envoy.extensions.filters.http.fault.v3.FaultAbort;
|
||||||
import io.envoyproxy.envoy.extensions.filters.http.fault.v3.HTTPFault;
|
import io.envoyproxy.envoy.extensions.filters.http.fault.v3.HTTPFault;
|
||||||
|
import io.envoyproxy.envoy.extensions.filters.http.gcp_authn.v3.Audience;
|
||||||
import io.envoyproxy.envoy.extensions.filters.http.rbac.v3.RBACPerRoute;
|
import io.envoyproxy.envoy.extensions.filters.http.rbac.v3.RBACPerRoute;
|
||||||
import io.envoyproxy.envoy.extensions.filters.http.router.v3.Router;
|
import io.envoyproxy.envoy.extensions.filters.http.router.v3.Router;
|
||||||
import io.envoyproxy.envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager;
|
import io.envoyproxy.envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager;
|
||||||
|
@ -127,6 +129,7 @@ import io.grpc.xds.ClusterSpecifierPlugin.PluginConfig;
|
||||||
import io.grpc.xds.Endpoints.LbEndpoint;
|
import io.grpc.xds.Endpoints.LbEndpoint;
|
||||||
import io.grpc.xds.Endpoints.LocalityLbEndpoints;
|
import io.grpc.xds.Endpoints.LocalityLbEndpoints;
|
||||||
import io.grpc.xds.Filter.FilterConfig;
|
import io.grpc.xds.Filter.FilterConfig;
|
||||||
|
import io.grpc.xds.MetadataRegistry.MetadataValueParser;
|
||||||
import io.grpc.xds.RouteLookupServiceClusterSpecifierPlugin.RlsPluginConfig;
|
import io.grpc.xds.RouteLookupServiceClusterSpecifierPlugin.RlsPluginConfig;
|
||||||
import io.grpc.xds.VirtualHost.Route;
|
import io.grpc.xds.VirtualHost.Route;
|
||||||
import io.grpc.xds.VirtualHost.Route.RouteAction;
|
import io.grpc.xds.VirtualHost.Route.RouteAction;
|
||||||
|
@ -2341,6 +2344,173 @@ public class GrpcXdsClientImplDataTest {
|
||||||
LoadBalancerRegistry.getDefaultRegistry());
|
LoadBalancerRegistry.getDefaultRegistry());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void processCluster_parsesMetadata()
|
||||||
|
throws ResourceInvalidException, InvalidProtocolBufferException {
|
||||||
|
MetadataRegistry metadataRegistry = MetadataRegistry.getInstance();
|
||||||
|
|
||||||
|
MetadataValueParser testParser =
|
||||||
|
new MetadataValueParser() {
|
||||||
|
@Override
|
||||||
|
public String getTypeUrl() {
|
||||||
|
return "type.googleapis.com/test.Type";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object parse(Any value) {
|
||||||
|
assertThat(value.getValue().toStringUtf8()).isEqualTo("test");
|
||||||
|
return value.getValue().toStringUtf8() + "_processed";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
metadataRegistry.registerParser(testParser);
|
||||||
|
|
||||||
|
Any typedFilterMetadata = Any.newBuilder()
|
||||||
|
.setTypeUrl("type.googleapis.com/test.Type")
|
||||||
|
.setValue(ByteString.copyFromUtf8("test"))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
Struct filterMetadata = Struct.newBuilder()
|
||||||
|
.putFields("key1", Value.newBuilder().setStringValue("value1").build())
|
||||||
|
.putFields("key2", Value.newBuilder().setNumberValue(42).build())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
Metadata metadata = Metadata.newBuilder()
|
||||||
|
.putTypedFilterMetadata("TYPED_FILTER_METADATA", typedFilterMetadata)
|
||||||
|
.putFilterMetadata("FILTER_METADATA", filterMetadata)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
Cluster cluster = Cluster.newBuilder()
|
||||||
|
.setName("cluster-foo.googleapis.com")
|
||||||
|
.setType(DiscoveryType.EDS)
|
||||||
|
.setEdsClusterConfig(
|
||||||
|
EdsClusterConfig.newBuilder()
|
||||||
|
.setEdsConfig(
|
||||||
|
ConfigSource.newBuilder()
|
||||||
|
.setAds(AggregatedConfigSource.getDefaultInstance()))
|
||||||
|
.setServiceName("service-foo.googleapis.com"))
|
||||||
|
.setLbPolicy(LbPolicy.ROUND_ROBIN)
|
||||||
|
.setMetadata(metadata)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
CdsUpdate update = XdsClusterResource.processCluster(
|
||||||
|
cluster, null, LRS_SERVER_INFO,
|
||||||
|
LoadBalancerRegistry.getDefaultRegistry());
|
||||||
|
|
||||||
|
ImmutableMap<String, Object> expectedParsedMetadata = ImmutableMap.of(
|
||||||
|
"TYPED_FILTER_METADATA", "test_processed",
|
||||||
|
"FILTER_METADATA", ImmutableMap.of(
|
||||||
|
"key1", "value1",
|
||||||
|
"key2", 42.0));
|
||||||
|
assertThat(update.parsedMetadata()).isEqualTo(expectedParsedMetadata);
|
||||||
|
metadataRegistry.removeParser(testParser);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void processCluster_parsesAudienceMetadata()
|
||||||
|
throws ResourceInvalidException, InvalidProtocolBufferException {
|
||||||
|
MetadataRegistry.getInstance();
|
||||||
|
|
||||||
|
Audience audience = Audience.newBuilder()
|
||||||
|
.setUrl("https://example.com")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
Any audienceMetadata = Any.newBuilder()
|
||||||
|
.setTypeUrl("type.googleapis.com/envoy.extensions.filters.http.gcp_authn.v3.Audience")
|
||||||
|
.setValue(audience.toByteString())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
Struct filterMetadata = Struct.newBuilder()
|
||||||
|
.putFields("key1", Value.newBuilder().setStringValue("value1").build())
|
||||||
|
.putFields("key2", Value.newBuilder().setNumberValue(42).build())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
Metadata metadata = Metadata.newBuilder()
|
||||||
|
.putTypedFilterMetadata("AUDIENCE_METADATA", audienceMetadata)
|
||||||
|
.putFilterMetadata("FILTER_METADATA", filterMetadata)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
Cluster cluster = Cluster.newBuilder()
|
||||||
|
.setName("cluster-foo.googleapis.com")
|
||||||
|
.setType(DiscoveryType.EDS)
|
||||||
|
.setEdsClusterConfig(
|
||||||
|
EdsClusterConfig.newBuilder()
|
||||||
|
.setEdsConfig(
|
||||||
|
ConfigSource.newBuilder()
|
||||||
|
.setAds(AggregatedConfigSource.getDefaultInstance()))
|
||||||
|
.setServiceName("service-foo.googleapis.com"))
|
||||||
|
.setLbPolicy(LbPolicy.ROUND_ROBIN)
|
||||||
|
.setMetadata(metadata)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
CdsUpdate update = XdsClusterResource.processCluster(
|
||||||
|
cluster, null, LRS_SERVER_INFO,
|
||||||
|
LoadBalancerRegistry.getDefaultRegistry());
|
||||||
|
|
||||||
|
ImmutableMap<String, Object> expectedParsedMetadata = ImmutableMap.of(
|
||||||
|
"AUDIENCE_METADATA", "https://example.com",
|
||||||
|
"FILTER_METADATA", ImmutableMap.of(
|
||||||
|
"key1", "value1",
|
||||||
|
"key2", 42.0));
|
||||||
|
assertThat(update.parsedMetadata()).isEqualTo(expectedParsedMetadata);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void processCluster_metadataKeyCollision_resolvesToTypedMetadata()
|
||||||
|
throws ResourceInvalidException, InvalidProtocolBufferException {
|
||||||
|
MetadataRegistry metadataRegistry = MetadataRegistry.getInstance();
|
||||||
|
|
||||||
|
MetadataValueParser testParser =
|
||||||
|
new MetadataValueParser() {
|
||||||
|
@Override
|
||||||
|
public String getTypeUrl() {
|
||||||
|
return "type.googleapis.com/test.Type";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object parse(Any value) {
|
||||||
|
return "typedMetadataValue";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
metadataRegistry.registerParser(testParser);
|
||||||
|
|
||||||
|
Any typedFilterMetadata = Any.newBuilder()
|
||||||
|
.setTypeUrl("type.googleapis.com/test.Type")
|
||||||
|
.setValue(ByteString.copyFromUtf8("test"))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
Struct filterMetadata = Struct.newBuilder()
|
||||||
|
.putFields("key1", Value.newBuilder().setStringValue("filterMetadataValue").build())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
Metadata metadata = Metadata.newBuilder()
|
||||||
|
.putTypedFilterMetadata("key1", typedFilterMetadata)
|
||||||
|
.putFilterMetadata("key1", filterMetadata)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
Cluster cluster = Cluster.newBuilder()
|
||||||
|
.setName("cluster-foo.googleapis.com")
|
||||||
|
.setType(DiscoveryType.EDS)
|
||||||
|
.setEdsClusterConfig(
|
||||||
|
EdsClusterConfig.newBuilder()
|
||||||
|
.setEdsConfig(
|
||||||
|
ConfigSource.newBuilder()
|
||||||
|
.setAds(AggregatedConfigSource.getDefaultInstance()))
|
||||||
|
.setServiceName("service-foo.googleapis.com"))
|
||||||
|
.setLbPolicy(LbPolicy.ROUND_ROBIN)
|
||||||
|
.setMetadata(metadata)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
CdsUpdate update = XdsClusterResource.processCluster(
|
||||||
|
cluster, null, LRS_SERVER_INFO,
|
||||||
|
LoadBalancerRegistry.getDefaultRegistry());
|
||||||
|
|
||||||
|
ImmutableMap<String, Object> expectedParsedMetadata = ImmutableMap.of(
|
||||||
|
"key1", "typedMetadataValue");
|
||||||
|
assertThat(update.parsedMetadata()).isEqualTo(expectedParsedMetadata);
|
||||||
|
metadataRegistry.removeParser(testParser);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void parseServerSideListener_invalidTrafficDirection() throws ResourceInvalidException {
|
public void parseServerSideListener_invalidTrafficDirection() throws ResourceInvalidException {
|
||||||
Listener listener =
|
Listener listener =
|
||||||
|
|
|
@ -0,0 +1,83 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 The gRPC 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.grpc.xds.internal;
|
||||||
|
|
||||||
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import com.google.protobuf.ListValue;
|
||||||
|
import com.google.protobuf.Struct;
|
||||||
|
import com.google.protobuf.Value;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.junit.runners.JUnit4;
|
||||||
|
|
||||||
|
@RunWith(JUnit4.class)
|
||||||
|
public class ProtobufJsonConverterTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEmptyStruct() {
|
||||||
|
Struct emptyStruct = Struct.newBuilder().build();
|
||||||
|
Map<String, Object> result = ProtobufJsonConverter.convertToJson(emptyStruct);
|
||||||
|
assertThat(result).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testStructWithValues() {
|
||||||
|
Struct struct = Struct.newBuilder()
|
||||||
|
.putFields("stringKey", Value.newBuilder().setStringValue("stringValue").build())
|
||||||
|
.putFields("numberKey", Value.newBuilder().setNumberValue(123.45).build())
|
||||||
|
.putFields("boolKey", Value.newBuilder().setBoolValue(true).build())
|
||||||
|
.putFields("nullKey", Value.newBuilder().setNullValueValue(0).build())
|
||||||
|
.putFields("structKey", Value.newBuilder()
|
||||||
|
.setStructValue(Struct.newBuilder()
|
||||||
|
.putFields("nestedKey", Value.newBuilder().setStringValue("nestedValue").build())
|
||||||
|
.build())
|
||||||
|
.build())
|
||||||
|
.putFields("listKey", Value.newBuilder()
|
||||||
|
.setListValue(ListValue.newBuilder()
|
||||||
|
.addValues(Value.newBuilder().setNumberValue(1).build())
|
||||||
|
.addValues(Value.newBuilder().setStringValue("two").build())
|
||||||
|
.addValues(Value.newBuilder().setBoolValue(false).build())
|
||||||
|
.build())
|
||||||
|
.build())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
Map<String, Object> result = ProtobufJsonConverter.convertToJson(struct);
|
||||||
|
|
||||||
|
Map<String, Object> goldenResult = new HashMap<>();
|
||||||
|
goldenResult.put("stringKey", "stringValue");
|
||||||
|
goldenResult.put("numberKey", 123.45);
|
||||||
|
goldenResult.put("boolKey", true);
|
||||||
|
goldenResult.put("nullKey", null);
|
||||||
|
goldenResult.put("structKey", ImmutableMap.of("nestedKey", "nestedValue"));
|
||||||
|
goldenResult.put("listKey", Arrays.asList(1.0, "two", false));
|
||||||
|
|
||||||
|
assertEquals(goldenResult, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException.class)
|
||||||
|
public void testUnknownValueType() {
|
||||||
|
Value unknownValue = Value.newBuilder().build(); // Default instance with no kind case set.
|
||||||
|
ProtobufJsonConverter.convertToJson(
|
||||||
|
Struct.newBuilder().putFields("unknownKey", unknownValue).build());
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue