mirror of https://github.com/grpc/grpc-go.git
878 lines
31 KiB
Go
878 lines
31 KiB
Go
/*
|
|
*
|
|
* Copyright 2021 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 e2e
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"strconv"
|
|
|
|
"github.com/envoyproxy/go-control-plane/pkg/wellknown"
|
|
"google.golang.org/protobuf/proto"
|
|
"google.golang.org/protobuf/types/known/anypb"
|
|
"google.golang.org/protobuf/types/known/wrapperspb"
|
|
|
|
v3clusterpb "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
|
|
v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
|
|
v3endpointpb "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3"
|
|
v3listenerpb "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
|
|
v3routepb "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
|
|
v3aggregateclusterpb "github.com/envoyproxy/go-control-plane/envoy/extensions/clusters/aggregate/v3"
|
|
v3routerpb "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/router/v3"
|
|
v3httppb "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
|
|
v3tlspb "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3"
|
|
v3typepb "github.com/envoyproxy/go-control-plane/envoy/type/v3"
|
|
)
|
|
|
|
const (
|
|
// ServerListenerResourceNameTemplate is the Listener resource name template
|
|
// used on the server side.
|
|
ServerListenerResourceNameTemplate = "grpc/server?xds.resource.listening_address=%s"
|
|
// ClientSideCertProviderInstance is the certificate provider instance name
|
|
// used in the Cluster resource on the client side.
|
|
ClientSideCertProviderInstance = "client-side-certificate-provider-instance"
|
|
// ServerSideCertProviderInstance is the certificate provider instance name
|
|
// used in the Listener resource on the server side.
|
|
ServerSideCertProviderInstance = "server-side-certificate-provider-instance"
|
|
)
|
|
|
|
// SecurityLevel allows the test to control the security level to be used in the
|
|
// resource returned by this package.
|
|
type SecurityLevel int
|
|
|
|
const (
|
|
// SecurityLevelNone is used when no security configuration is required.
|
|
SecurityLevelNone SecurityLevel = iota
|
|
// SecurityLevelTLS is used when security configuration corresponding to TLS
|
|
// is required. Only the server presents an identity certificate in this
|
|
// configuration.
|
|
SecurityLevelTLS
|
|
// SecurityLevelMTLS is used when security configuration corresponding to
|
|
// mTLS is required. Both client and server present identity certificates in
|
|
// this configuration.
|
|
SecurityLevelMTLS
|
|
)
|
|
|
|
// ResourceParams wraps the arguments to be passed to DefaultClientResources.
|
|
type ResourceParams struct {
|
|
// DialTarget is the client's dial target. This is used as the name of the
|
|
// Listener resource.
|
|
DialTarget string
|
|
// NodeID is the id of the xdsClient to which this update is to be pushed.
|
|
NodeID string
|
|
// Host is the host of the default Endpoint resource.
|
|
Host string
|
|
// port is the port of the default Endpoint resource.
|
|
Port uint32
|
|
// SecLevel controls the security configuration in the Cluster resource.
|
|
SecLevel SecurityLevel
|
|
}
|
|
|
|
// DefaultClientResources returns a set of resources (LDS, RDS, CDS, EDS) for a
|
|
// client to generically connect to one server.
|
|
func DefaultClientResources(params ResourceParams) UpdateOptions {
|
|
routeConfigName := "route-" + params.DialTarget
|
|
clusterName := "cluster-" + params.DialTarget
|
|
endpointsName := "endpoints-" + params.DialTarget
|
|
return UpdateOptions{
|
|
NodeID: params.NodeID,
|
|
Listeners: []*v3listenerpb.Listener{DefaultClientListener(params.DialTarget, routeConfigName)},
|
|
Routes: []*v3routepb.RouteConfiguration{DefaultRouteConfig(routeConfigName, params.DialTarget, clusterName)},
|
|
Clusters: []*v3clusterpb.Cluster{DefaultCluster(clusterName, endpointsName, params.SecLevel)},
|
|
Endpoints: []*v3endpointpb.ClusterLoadAssignment{DefaultEndpoint(endpointsName, params.Host, []uint32{params.Port})},
|
|
}
|
|
}
|
|
|
|
// RouterHTTPFilter is the HTTP Filter configuration for the Router filter.
|
|
var RouterHTTPFilter = HTTPFilter("router", &v3routerpb.Router{})
|
|
|
|
// DefaultClientListener returns a basic xds Listener resource to be used on
|
|
// the client side.
|
|
func DefaultClientListener(target, routeName string) *v3listenerpb.Listener {
|
|
hcm := marshalAny(&v3httppb.HttpConnectionManager{
|
|
RouteSpecifier: &v3httppb.HttpConnectionManager_Rds{Rds: &v3httppb.Rds{
|
|
ConfigSource: &v3corepb.ConfigSource{
|
|
ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{Ads: &v3corepb.AggregatedConfigSource{}},
|
|
},
|
|
RouteConfigName: routeName,
|
|
}},
|
|
HttpFilters: []*v3httppb.HttpFilter{HTTPFilter("router", &v3routerpb.Router{})}, // router fields are unused by grpc
|
|
})
|
|
return &v3listenerpb.Listener{
|
|
Name: target,
|
|
ApiListener: &v3listenerpb.ApiListener{ApiListener: hcm},
|
|
FilterChains: []*v3listenerpb.FilterChain{{
|
|
Name: "filter-chain-name",
|
|
Filters: []*v3listenerpb.Filter{{
|
|
Name: wellknown.HTTPConnectionManager,
|
|
ConfigType: &v3listenerpb.Filter_TypedConfig{TypedConfig: hcm},
|
|
}},
|
|
}},
|
|
}
|
|
}
|
|
|
|
func marshalAny(m proto.Message) *anypb.Any {
|
|
a, err := anypb.New(m)
|
|
if err != nil {
|
|
panic(fmt.Sprintf("anypb.New(%+v) failed: %v", m, err))
|
|
}
|
|
return a
|
|
}
|
|
|
|
// filterChainWontMatch returns a filter chain that won't match if running the
|
|
// test locally.
|
|
func filterChainWontMatch(routeName string, addressPrefix string, srcPorts []uint32) *v3listenerpb.FilterChain {
|
|
hcm := &v3httppb.HttpConnectionManager{
|
|
RouteSpecifier: &v3httppb.HttpConnectionManager_Rds{
|
|
Rds: &v3httppb.Rds{
|
|
ConfigSource: &v3corepb.ConfigSource{
|
|
ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{Ads: &v3corepb.AggregatedConfigSource{}},
|
|
},
|
|
RouteConfigName: routeName,
|
|
},
|
|
},
|
|
HttpFilters: []*v3httppb.HttpFilter{RouterHTTPFilter},
|
|
}
|
|
return &v3listenerpb.FilterChain{
|
|
Name: routeName + "-wont-match",
|
|
FilterChainMatch: &v3listenerpb.FilterChainMatch{
|
|
PrefixRanges: []*v3corepb.CidrRange{
|
|
{
|
|
AddressPrefix: addressPrefix,
|
|
PrefixLen: &wrapperspb.UInt32Value{
|
|
Value: uint32(0),
|
|
},
|
|
},
|
|
},
|
|
SourceType: v3listenerpb.FilterChainMatch_SAME_IP_OR_LOOPBACK,
|
|
SourcePorts: srcPorts,
|
|
SourcePrefixRanges: []*v3corepb.CidrRange{
|
|
{
|
|
AddressPrefix: addressPrefix,
|
|
PrefixLen: &wrapperspb.UInt32Value{
|
|
Value: uint32(0),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Filters: []*v3listenerpb.Filter{
|
|
{
|
|
Name: "filter-1",
|
|
ConfigType: &v3listenerpb.Filter_TypedConfig{TypedConfig: marshalAny(hcm)},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
// ListenerResourceThreeRouteResources returns a listener resource that points
|
|
// to three route configurations. Only the filter chain that points to the first
|
|
// route config can be matched to.
|
|
func ListenerResourceThreeRouteResources(host string, port uint32, secLevel SecurityLevel, routeName string) *v3listenerpb.Listener {
|
|
lis := defaultServerListenerCommon(host, port, secLevel, routeName, false)
|
|
lis.FilterChains = append(lis.FilterChains, filterChainWontMatch("routeName2", "1.1.1.1", []uint32{1}))
|
|
lis.FilterChains = append(lis.FilterChains, filterChainWontMatch("routeName3", "2.2.2.2", []uint32{2}))
|
|
return lis
|
|
}
|
|
|
|
// ListenerResourceFallbackToDefault returns a listener resource that contains a
|
|
// filter chain that will never get chosen to process traffic and a default
|
|
// filter chain. The default filter chain points to routeName2.
|
|
func ListenerResourceFallbackToDefault(host string, port uint32, secLevel SecurityLevel) *v3listenerpb.Listener {
|
|
lis := defaultServerListenerCommon(host, port, secLevel, "", false)
|
|
lis.FilterChains = nil
|
|
lis.FilterChains = append(lis.FilterChains, filterChainWontMatch("routeName", "1.1.1.1", []uint32{1}))
|
|
lis.DefaultFilterChain = filterChainWontMatch("routeName2", "2.2.2.2", []uint32{2})
|
|
return lis
|
|
}
|
|
|
|
// DefaultServerListener returns a basic xds Listener resource to be used on the
|
|
// server side. The returned Listener resource contains an inline route
|
|
// configuration with the name of routeName.
|
|
func DefaultServerListener(host string, port uint32, secLevel SecurityLevel, routeName string) *v3listenerpb.Listener {
|
|
return defaultServerListenerCommon(host, port, secLevel, routeName, true)
|
|
}
|
|
|
|
func defaultServerListenerCommon(host string, port uint32, secLevel SecurityLevel, routeName string, inlineRouteConfig bool) *v3listenerpb.Listener {
|
|
var hcm *v3httppb.HttpConnectionManager
|
|
if inlineRouteConfig {
|
|
hcm = &v3httppb.HttpConnectionManager{
|
|
RouteSpecifier: &v3httppb.HttpConnectionManager_RouteConfig{
|
|
RouteConfig: &v3routepb.RouteConfiguration{
|
|
Name: routeName,
|
|
VirtualHosts: []*v3routepb.VirtualHost{{
|
|
// This "*" string matches on any incoming authority. This is to ensure any
|
|
// incoming RPC matches to Route_NonForwardingAction and will proceed as
|
|
// normal.
|
|
Domains: []string{"*"},
|
|
Routes: []*v3routepb.Route{{
|
|
Match: &v3routepb.RouteMatch{
|
|
PathSpecifier: &v3routepb.RouteMatch_Prefix{Prefix: "/"},
|
|
},
|
|
Action: &v3routepb.Route_NonForwardingAction{},
|
|
}}}}},
|
|
},
|
|
HttpFilters: []*v3httppb.HttpFilter{RouterHTTPFilter},
|
|
}
|
|
} else {
|
|
hcm = &v3httppb.HttpConnectionManager{
|
|
RouteSpecifier: &v3httppb.HttpConnectionManager_Rds{
|
|
Rds: &v3httppb.Rds{
|
|
ConfigSource: &v3corepb.ConfigSource{
|
|
ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{Ads: &v3corepb.AggregatedConfigSource{}},
|
|
},
|
|
RouteConfigName: routeName,
|
|
},
|
|
},
|
|
HttpFilters: []*v3httppb.HttpFilter{RouterHTTPFilter},
|
|
}
|
|
}
|
|
|
|
var tlsContext *v3tlspb.DownstreamTlsContext
|
|
switch secLevel {
|
|
case SecurityLevelNone:
|
|
case SecurityLevelTLS:
|
|
tlsContext = &v3tlspb.DownstreamTlsContext{
|
|
CommonTlsContext: &v3tlspb.CommonTlsContext{
|
|
TlsCertificateCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{
|
|
InstanceName: ServerSideCertProviderInstance,
|
|
},
|
|
},
|
|
}
|
|
case SecurityLevelMTLS:
|
|
tlsContext = &v3tlspb.DownstreamTlsContext{
|
|
RequireClientCertificate: &wrapperspb.BoolValue{Value: true},
|
|
CommonTlsContext: &v3tlspb.CommonTlsContext{
|
|
TlsCertificateCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{
|
|
InstanceName: ServerSideCertProviderInstance,
|
|
},
|
|
ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContextCertificateProviderInstance{
|
|
ValidationContextCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{
|
|
InstanceName: ServerSideCertProviderInstance,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
var ts *v3corepb.TransportSocket
|
|
if tlsContext != nil {
|
|
ts = &v3corepb.TransportSocket{
|
|
Name: "envoy.transport_sockets.tls",
|
|
ConfigType: &v3corepb.TransportSocket_TypedConfig{
|
|
TypedConfig: marshalAny(tlsContext),
|
|
},
|
|
}
|
|
}
|
|
return &v3listenerpb.Listener{
|
|
Name: fmt.Sprintf(ServerListenerResourceNameTemplate, net.JoinHostPort(host, strconv.Itoa(int(port)))),
|
|
Address: &v3corepb.Address{
|
|
Address: &v3corepb.Address_SocketAddress{
|
|
SocketAddress: &v3corepb.SocketAddress{
|
|
Address: host,
|
|
PortSpecifier: &v3corepb.SocketAddress_PortValue{
|
|
PortValue: port,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
FilterChains: []*v3listenerpb.FilterChain{
|
|
{
|
|
Name: "v4-wildcard",
|
|
FilterChainMatch: &v3listenerpb.FilterChainMatch{
|
|
PrefixRanges: []*v3corepb.CidrRange{
|
|
{
|
|
AddressPrefix: "0.0.0.0",
|
|
PrefixLen: &wrapperspb.UInt32Value{
|
|
Value: uint32(0),
|
|
},
|
|
},
|
|
},
|
|
SourceType: v3listenerpb.FilterChainMatch_SAME_IP_OR_LOOPBACK,
|
|
SourcePrefixRanges: []*v3corepb.CidrRange{
|
|
{
|
|
AddressPrefix: "0.0.0.0",
|
|
PrefixLen: &wrapperspb.UInt32Value{
|
|
Value: uint32(0),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Filters: []*v3listenerpb.Filter{
|
|
{
|
|
Name: "filter-1",
|
|
ConfigType: &v3listenerpb.Filter_TypedConfig{TypedConfig: marshalAny(hcm)},
|
|
},
|
|
},
|
|
TransportSocket: ts,
|
|
},
|
|
{
|
|
Name: "v6-wildcard",
|
|
FilterChainMatch: &v3listenerpb.FilterChainMatch{
|
|
PrefixRanges: []*v3corepb.CidrRange{
|
|
{
|
|
AddressPrefix: "::",
|
|
PrefixLen: &wrapperspb.UInt32Value{
|
|
Value: uint32(0),
|
|
},
|
|
},
|
|
},
|
|
SourceType: v3listenerpb.FilterChainMatch_SAME_IP_OR_LOOPBACK,
|
|
SourcePrefixRanges: []*v3corepb.CidrRange{
|
|
{
|
|
AddressPrefix: "::",
|
|
PrefixLen: &wrapperspb.UInt32Value{
|
|
Value: uint32(0),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Filters: []*v3listenerpb.Filter{
|
|
{
|
|
Name: "filter-1",
|
|
ConfigType: &v3listenerpb.Filter_TypedConfig{TypedConfig: marshalAny(hcm)},
|
|
},
|
|
},
|
|
TransportSocket: ts,
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
// HTTPFilter constructs an xds HttpFilter with the provided name and config.
|
|
func HTTPFilter(name string, config proto.Message) *v3httppb.HttpFilter {
|
|
return &v3httppb.HttpFilter{
|
|
Name: name,
|
|
ConfigType: &v3httppb.HttpFilter_TypedConfig{
|
|
TypedConfig: marshalAny(config),
|
|
},
|
|
}
|
|
}
|
|
|
|
// DefaultRouteConfig returns a basic xds RouteConfig resource.
|
|
func DefaultRouteConfig(routeName, vhDomain, clusterName string) *v3routepb.RouteConfiguration {
|
|
return &v3routepb.RouteConfiguration{
|
|
Name: routeName,
|
|
VirtualHosts: []*v3routepb.VirtualHost{{
|
|
Domains: []string{vhDomain},
|
|
Routes: []*v3routepb.Route{{
|
|
Match: &v3routepb.RouteMatch{PathSpecifier: &v3routepb.RouteMatch_Prefix{Prefix: "/"}},
|
|
Action: &v3routepb.Route_Route{Route: &v3routepb.RouteAction{
|
|
ClusterSpecifier: &v3routepb.RouteAction_WeightedClusters{WeightedClusters: &v3routepb.WeightedCluster{
|
|
Clusters: []*v3routepb.WeightedCluster_ClusterWeight{
|
|
{
|
|
Name: clusterName,
|
|
Weight: &wrapperspb.UInt32Value{Value: 100},
|
|
},
|
|
},
|
|
}},
|
|
}},
|
|
}},
|
|
}},
|
|
}
|
|
}
|
|
|
|
// RouteConfigClusterSpecifierType determines the cluster specifier type for the
|
|
// route actions configured in the returned RouteConfiguration resource.
|
|
type RouteConfigClusterSpecifierType int
|
|
|
|
const (
|
|
// RouteConfigClusterSpecifierTypeCluster results in the cluster specifier
|
|
// being set to a RouteAction_Cluster.
|
|
RouteConfigClusterSpecifierTypeCluster RouteConfigClusterSpecifierType = iota
|
|
// RouteConfigClusterSpecifierTypeWeightedCluster results in the cluster
|
|
// specifier being set to RouteAction_WeightedClusters.
|
|
RouteConfigClusterSpecifierTypeWeightedCluster
|
|
// RouteConfigClusterSpecifierTypeClusterSpecifierPlugin results in the
|
|
// cluster specifier being set to a RouteAction_ClusterSpecifierPlugin.
|
|
RouteConfigClusterSpecifierTypeClusterSpecifierPlugin
|
|
)
|
|
|
|
// RouteConfigOptions contains options to configure a RouteConfiguration
|
|
// resource.
|
|
type RouteConfigOptions struct {
|
|
// RouteConfigName is the name of the RouteConfiguration resource.
|
|
RouteConfigName string
|
|
// ListenerName is the name of the Listener resource which uses this
|
|
// RouteConfiguration.
|
|
ListenerName string
|
|
// ClusterSpecifierType determines the cluster specifier type.
|
|
ClusterSpecifierType RouteConfigClusterSpecifierType
|
|
// ClusterName is name of the cluster resource used when the cluster
|
|
// specifier type is set to RouteConfigClusterSpecifierTypeCluster.
|
|
//
|
|
// Default value of "A" is used if left unspecified.
|
|
ClusterName string
|
|
// WeightedClusters is a map from cluster name to weights, and is used when
|
|
// the cluster specifier type is set to
|
|
// RouteConfigClusterSpecifierTypeWeightedCluster.
|
|
//
|
|
// Default value of {"A": 75, "B": 25} is used if left unspecified.
|
|
WeightedClusters map[string]int
|
|
// The below two fields specify the name of the cluster specifier plugin and
|
|
// its configuration, and are used when the cluster specifier type is set to
|
|
// RouteConfigClusterSpecifierTypeClusterSpecifierPlugin. Tests are expected
|
|
// to provide valid values for these fields when appropriate.
|
|
ClusterSpecifierPluginName string
|
|
ClusterSpecifierPluginConfig *anypb.Any
|
|
}
|
|
|
|
// RouteConfigResourceWithOptions returns a RouteConfiguration resource
|
|
// configured with the provided options.
|
|
func RouteConfigResourceWithOptions(opts RouteConfigOptions) *v3routepb.RouteConfiguration {
|
|
switch opts.ClusterSpecifierType {
|
|
case RouteConfigClusterSpecifierTypeCluster:
|
|
clusterName := opts.ClusterName
|
|
if clusterName == "" {
|
|
clusterName = "A"
|
|
}
|
|
return &v3routepb.RouteConfiguration{
|
|
Name: opts.RouteConfigName,
|
|
VirtualHosts: []*v3routepb.VirtualHost{{
|
|
Domains: []string{opts.ListenerName},
|
|
Routes: []*v3routepb.Route{{
|
|
Match: &v3routepb.RouteMatch{PathSpecifier: &v3routepb.RouteMatch_Prefix{Prefix: "/"}},
|
|
Action: &v3routepb.Route_Route{Route: &v3routepb.RouteAction{
|
|
ClusterSpecifier: &v3routepb.RouteAction_Cluster{Cluster: clusterName},
|
|
}},
|
|
}},
|
|
}},
|
|
}
|
|
case RouteConfigClusterSpecifierTypeWeightedCluster:
|
|
weightedClusters := opts.WeightedClusters
|
|
if weightedClusters == nil {
|
|
weightedClusters = map[string]int{"A": 75, "B": 25}
|
|
}
|
|
clusters := []*v3routepb.WeightedCluster_ClusterWeight{}
|
|
for name, weight := range weightedClusters {
|
|
clusters = append(clusters, &v3routepb.WeightedCluster_ClusterWeight{
|
|
Name: name,
|
|
Weight: &wrapperspb.UInt32Value{Value: uint32(weight)},
|
|
})
|
|
}
|
|
return &v3routepb.RouteConfiguration{
|
|
Name: opts.RouteConfigName,
|
|
VirtualHosts: []*v3routepb.VirtualHost{{
|
|
Domains: []string{opts.ListenerName},
|
|
Routes: []*v3routepb.Route{{
|
|
Match: &v3routepb.RouteMatch{PathSpecifier: &v3routepb.RouteMatch_Prefix{Prefix: "/"}},
|
|
Action: &v3routepb.Route_Route{Route: &v3routepb.RouteAction{
|
|
ClusterSpecifier: &v3routepb.RouteAction_WeightedClusters{WeightedClusters: &v3routepb.WeightedCluster{Clusters: clusters}},
|
|
}},
|
|
}},
|
|
}},
|
|
}
|
|
case RouteConfigClusterSpecifierTypeClusterSpecifierPlugin:
|
|
return &v3routepb.RouteConfiguration{
|
|
Name: opts.RouteConfigName,
|
|
ClusterSpecifierPlugins: []*v3routepb.ClusterSpecifierPlugin{{
|
|
Extension: &v3corepb.TypedExtensionConfig{
|
|
Name: opts.ClusterSpecifierPluginName,
|
|
TypedConfig: opts.ClusterSpecifierPluginConfig,
|
|
}},
|
|
},
|
|
VirtualHosts: []*v3routepb.VirtualHost{{
|
|
Domains: []string{opts.ListenerName},
|
|
Routes: []*v3routepb.Route{{
|
|
Match: &v3routepb.RouteMatch{PathSpecifier: &v3routepb.RouteMatch_Prefix{Prefix: "/"}},
|
|
Action: &v3routepb.Route_Route{Route: &v3routepb.RouteAction{
|
|
ClusterSpecifier: &v3routepb.RouteAction_ClusterSpecifierPlugin{ClusterSpecifierPlugin: opts.ClusterSpecifierPluginName},
|
|
}},
|
|
}},
|
|
}},
|
|
}
|
|
default:
|
|
panic(fmt.Sprintf("unsupported cluster specifier plugin type: %v", opts.ClusterSpecifierType))
|
|
}
|
|
}
|
|
|
|
// DefaultCluster returns a basic xds Cluster resource.
|
|
func DefaultCluster(clusterName, edsServiceName string, secLevel SecurityLevel) *v3clusterpb.Cluster {
|
|
return ClusterResourceWithOptions(ClusterOptions{
|
|
ClusterName: clusterName,
|
|
ServiceName: edsServiceName,
|
|
Policy: LoadBalancingPolicyRoundRobin,
|
|
SecurityLevel: secLevel,
|
|
})
|
|
}
|
|
|
|
// LoadBalancingPolicy determines the policy used for balancing load across
|
|
// endpoints in the Cluster.
|
|
type LoadBalancingPolicy int
|
|
|
|
const (
|
|
// LoadBalancingPolicyRoundRobin results in the use of the weighted_target
|
|
// LB policy to balance load across localities and endpoints in the cluster.
|
|
LoadBalancingPolicyRoundRobin LoadBalancingPolicy = iota
|
|
// LoadBalancingPolicyRingHash results in the use of the ring_hash LB policy
|
|
// as the leaf policy.
|
|
LoadBalancingPolicyRingHash
|
|
)
|
|
|
|
// ClusterType specifies the type of the Cluster resource.
|
|
type ClusterType int
|
|
|
|
const (
|
|
// ClusterTypeEDS specifies a Cluster that uses EDS to resolve endpoints.
|
|
ClusterTypeEDS ClusterType = iota
|
|
// ClusterTypeLogicalDNS specifies a Cluster that uses DNS to resolve
|
|
// endpoints.
|
|
ClusterTypeLogicalDNS
|
|
// ClusterTypeAggregate specifies a Cluster that is made up of child
|
|
// clusters.
|
|
ClusterTypeAggregate
|
|
)
|
|
|
|
// ClusterOptions contains options to configure a Cluster resource.
|
|
type ClusterOptions struct {
|
|
Type ClusterType
|
|
// ClusterName is the name of the Cluster resource.
|
|
ClusterName string
|
|
// ServiceName is the EDS service name of the Cluster. Applicable only when
|
|
// cluster type is EDS.
|
|
ServiceName string
|
|
// ChildNames is the list of child Cluster names. Applicable only when
|
|
// cluster type is Aggregate.
|
|
ChildNames []string
|
|
// DNSHostName is the dns host name of the Cluster. Applicable only when the
|
|
// cluster type is DNS.
|
|
DNSHostName string
|
|
// DNSPort is the port number of the Cluster. Applicable only when the
|
|
// cluster type is DNS.
|
|
DNSPort uint32
|
|
// Policy is the LB policy to be used.
|
|
Policy LoadBalancingPolicy
|
|
// SecurityLevel determines the security configuration for the Cluster.
|
|
SecurityLevel SecurityLevel
|
|
// EnableLRS adds a load reporting configuration with a config source
|
|
// pointing to self.
|
|
EnableLRS bool
|
|
}
|
|
|
|
// ClusterResourceWithOptions returns an xDS Cluster resource configured with
|
|
// the provided options.
|
|
func ClusterResourceWithOptions(opts ClusterOptions) *v3clusterpb.Cluster {
|
|
var tlsContext *v3tlspb.UpstreamTlsContext
|
|
switch opts.SecurityLevel {
|
|
case SecurityLevelNone:
|
|
case SecurityLevelTLS:
|
|
tlsContext = &v3tlspb.UpstreamTlsContext{
|
|
CommonTlsContext: &v3tlspb.CommonTlsContext{
|
|
ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContextCertificateProviderInstance{
|
|
ValidationContextCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{
|
|
InstanceName: ClientSideCertProviderInstance,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
case SecurityLevelMTLS:
|
|
tlsContext = &v3tlspb.UpstreamTlsContext{
|
|
CommonTlsContext: &v3tlspb.CommonTlsContext{
|
|
ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContextCertificateProviderInstance{
|
|
ValidationContextCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{
|
|
InstanceName: ClientSideCertProviderInstance,
|
|
},
|
|
},
|
|
TlsCertificateCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{
|
|
InstanceName: ClientSideCertProviderInstance,
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
var lbPolicy v3clusterpb.Cluster_LbPolicy
|
|
switch opts.Policy {
|
|
case LoadBalancingPolicyRoundRobin:
|
|
lbPolicy = v3clusterpb.Cluster_ROUND_ROBIN
|
|
case LoadBalancingPolicyRingHash:
|
|
lbPolicy = v3clusterpb.Cluster_RING_HASH
|
|
}
|
|
cluster := &v3clusterpb.Cluster{
|
|
Name: opts.ClusterName,
|
|
LbPolicy: lbPolicy,
|
|
}
|
|
switch opts.Type {
|
|
case ClusterTypeEDS:
|
|
cluster.ClusterDiscoveryType = &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS}
|
|
cluster.EdsClusterConfig = &v3clusterpb.Cluster_EdsClusterConfig{
|
|
EdsConfig: &v3corepb.ConfigSource{
|
|
ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
|
|
Ads: &v3corepb.AggregatedConfigSource{},
|
|
},
|
|
},
|
|
ServiceName: opts.ServiceName,
|
|
}
|
|
case ClusterTypeLogicalDNS:
|
|
cluster.ClusterDiscoveryType = &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_LOGICAL_DNS}
|
|
cluster.LoadAssignment = &v3endpointpb.ClusterLoadAssignment{
|
|
Endpoints: []*v3endpointpb.LocalityLbEndpoints{{
|
|
LbEndpoints: []*v3endpointpb.LbEndpoint{{
|
|
HostIdentifier: &v3endpointpb.LbEndpoint_Endpoint{
|
|
Endpoint: &v3endpointpb.Endpoint{
|
|
Address: &v3corepb.Address{
|
|
Address: &v3corepb.Address_SocketAddress{
|
|
SocketAddress: &v3corepb.SocketAddress{
|
|
Address: opts.DNSHostName,
|
|
PortSpecifier: &v3corepb.SocketAddress_PortValue{
|
|
PortValue: opts.DNSPort,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}},
|
|
}},
|
|
}
|
|
case ClusterTypeAggregate:
|
|
cluster.ClusterDiscoveryType = &v3clusterpb.Cluster_ClusterType{
|
|
ClusterType: &v3clusterpb.Cluster_CustomClusterType{
|
|
Name: "envoy.clusters.aggregate",
|
|
TypedConfig: marshalAny(&v3aggregateclusterpb.ClusterConfig{
|
|
Clusters: opts.ChildNames,
|
|
}),
|
|
},
|
|
}
|
|
}
|
|
if tlsContext != nil {
|
|
cluster.TransportSocket = &v3corepb.TransportSocket{
|
|
Name: "envoy.transport_sockets.tls",
|
|
ConfigType: &v3corepb.TransportSocket_TypedConfig{
|
|
TypedConfig: marshalAny(tlsContext),
|
|
},
|
|
}
|
|
}
|
|
if opts.EnableLRS {
|
|
cluster.LrsServer = &v3corepb.ConfigSource{
|
|
ConfigSourceSpecifier: &v3corepb.ConfigSource_Self{
|
|
Self: &v3corepb.SelfConfigSource{},
|
|
},
|
|
}
|
|
}
|
|
return cluster
|
|
}
|
|
|
|
// LocalityID represents a locality identifier.
|
|
type LocalityID struct {
|
|
Region string
|
|
Zone string
|
|
SubZone string
|
|
}
|
|
|
|
// LocalityOptions contains options to configure a Locality.
|
|
type LocalityOptions struct {
|
|
// Name is the unique locality name.
|
|
Name string
|
|
// Weight is the weight of the locality, used for load balancing.
|
|
Weight uint32
|
|
// Backends is a set of backends belonging to this locality.
|
|
Backends []BackendOptions
|
|
// Priority is the priority of the locality. Defaults to 0.
|
|
Priority uint32
|
|
// Locality is the locality identifier. If not specified, a random
|
|
// identifier is generated.
|
|
Locality LocalityID
|
|
}
|
|
|
|
// BackendOptions contains options to configure individual backends in a
|
|
// locality.
|
|
type BackendOptions struct {
|
|
// Ports on which the backend is accepting connections. All backends
|
|
// are expected to run on localhost, hence host name is not stored here.
|
|
Ports []uint32
|
|
// Health status of the backend. Default is UNKNOWN which is treated the
|
|
// same as HEALTHY.
|
|
HealthStatus v3corepb.HealthStatus
|
|
// Weight sets the backend weight. Defaults to 1.
|
|
Weight uint32
|
|
}
|
|
|
|
// EndpointOptions contains options to configure an Endpoint (or
|
|
// ClusterLoadAssignment) resource.
|
|
type EndpointOptions struct {
|
|
// ClusterName is the name of the Cluster resource (or EDS service name)
|
|
// containing the endpoints specified below.
|
|
ClusterName string
|
|
// Host is the hostname of the endpoints. In our e2e tests, hostname must
|
|
// always be "localhost".
|
|
Host string
|
|
// Localities is a set of localities belonging to this resource.
|
|
Localities []LocalityOptions
|
|
// DropPercents is a map from drop category to a drop percentage. If unset,
|
|
// no drops are configured.
|
|
DropPercents map[string]int
|
|
}
|
|
|
|
// DefaultEndpoint returns a basic xds Endpoint resource.
|
|
func DefaultEndpoint(clusterName string, host string, ports []uint32) *v3endpointpb.ClusterLoadAssignment {
|
|
var bOpts []BackendOptions
|
|
for _, p := range ports {
|
|
bOpts = append(bOpts, BackendOptions{Ports: []uint32{p}, Weight: 1})
|
|
}
|
|
return EndpointResourceWithOptions(EndpointOptions{
|
|
ClusterName: clusterName,
|
|
Host: host,
|
|
Localities: []LocalityOptions{
|
|
{
|
|
Backends: bOpts,
|
|
Weight: 1,
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
// EndpointResourceWithOptions returns an xds Endpoint resource configured with
|
|
// the provided options.
|
|
func EndpointResourceWithOptions(opts EndpointOptions) *v3endpointpb.ClusterLoadAssignment {
|
|
var endpoints []*v3endpointpb.LocalityLbEndpoints
|
|
for i, locality := range opts.Localities {
|
|
var lbEndpoints []*v3endpointpb.LbEndpoint
|
|
for _, b := range locality.Backends {
|
|
// Weight defaults to 1.
|
|
if b.Weight == 0 {
|
|
b.Weight = 1
|
|
}
|
|
additionalAddresses := make([]*v3endpointpb.Endpoint_AdditionalAddress, len(b.Ports)-1)
|
|
for i, p := range b.Ports[1:] {
|
|
additionalAddresses[i] = &v3endpointpb.Endpoint_AdditionalAddress{
|
|
Address: &v3corepb.Address{Address: &v3corepb.Address_SocketAddress{
|
|
SocketAddress: &v3corepb.SocketAddress{
|
|
Protocol: v3corepb.SocketAddress_TCP,
|
|
Address: opts.Host,
|
|
PortSpecifier: &v3corepb.SocketAddress_PortValue{PortValue: p},
|
|
}},
|
|
},
|
|
}
|
|
}
|
|
lbEndpoints = append(lbEndpoints, &v3endpointpb.LbEndpoint{
|
|
HostIdentifier: &v3endpointpb.LbEndpoint_Endpoint{Endpoint: &v3endpointpb.Endpoint{
|
|
Address: &v3corepb.Address{Address: &v3corepb.Address_SocketAddress{
|
|
SocketAddress: &v3corepb.SocketAddress{
|
|
Protocol: v3corepb.SocketAddress_TCP,
|
|
Address: opts.Host,
|
|
PortSpecifier: &v3corepb.SocketAddress_PortValue{PortValue: b.Ports[0]},
|
|
},
|
|
}},
|
|
AdditionalAddresses: additionalAddresses,
|
|
}},
|
|
HealthStatus: b.HealthStatus,
|
|
LoadBalancingWeight: &wrapperspb.UInt32Value{Value: b.Weight},
|
|
})
|
|
}
|
|
|
|
l := locality.Locality
|
|
if l == (LocalityID{}) {
|
|
l = LocalityID{
|
|
Region: fmt.Sprintf("region-%d", i+1),
|
|
Zone: fmt.Sprintf("zone-%d", i+1),
|
|
SubZone: fmt.Sprintf("subzone-%d", i+1),
|
|
}
|
|
}
|
|
endpoints = append(endpoints, &v3endpointpb.LocalityLbEndpoints{
|
|
Locality: &v3corepb.Locality{Region: l.Region, Zone: l.Zone, SubZone: l.SubZone},
|
|
LbEndpoints: lbEndpoints,
|
|
LoadBalancingWeight: &wrapperspb.UInt32Value{Value: locality.Weight},
|
|
Priority: locality.Priority,
|
|
})
|
|
}
|
|
|
|
cla := &v3endpointpb.ClusterLoadAssignment{
|
|
ClusterName: opts.ClusterName,
|
|
Endpoints: endpoints,
|
|
}
|
|
|
|
var drops []*v3endpointpb.ClusterLoadAssignment_Policy_DropOverload
|
|
for category, val := range opts.DropPercents {
|
|
drops = append(drops, &v3endpointpb.ClusterLoadAssignment_Policy_DropOverload{
|
|
Category: category,
|
|
DropPercentage: &v3typepb.FractionalPercent{
|
|
Numerator: uint32(val),
|
|
Denominator: v3typepb.FractionalPercent_HUNDRED,
|
|
},
|
|
})
|
|
}
|
|
if len(drops) != 0 {
|
|
cla.Policy = &v3endpointpb.ClusterLoadAssignment_Policy{
|
|
DropOverloads: drops,
|
|
}
|
|
}
|
|
return cla
|
|
}
|
|
|
|
// DefaultServerListenerWithRouteConfigName returns a basic xds Listener
|
|
// resource to be used on the server side. The returned Listener resource
|
|
// contains a RouteConfiguration resource name that needs to be resolved.
|
|
func DefaultServerListenerWithRouteConfigName(host string, port uint32, secLevel SecurityLevel, routeName string) *v3listenerpb.Listener {
|
|
return defaultServerListenerCommon(host, port, secLevel, routeName, false)
|
|
}
|
|
|
|
// RouteConfigNoRouteMatch returns an xDS RouteConfig resource which a route
|
|
// with no route match. This will be NACKed by the xDS Client.
|
|
func RouteConfigNoRouteMatch(routeName string) *v3routepb.RouteConfiguration {
|
|
return &v3routepb.RouteConfiguration{
|
|
Name: routeName,
|
|
VirtualHosts: []*v3routepb.VirtualHost{{
|
|
// This "*" string matches on any incoming authority. This is to ensure any
|
|
// incoming RPC matches to Route_NonForwardingAction and will proceed as
|
|
// normal.
|
|
Domains: []string{"*"},
|
|
Routes: []*v3routepb.Route{{
|
|
Action: &v3routepb.Route_NonForwardingAction{},
|
|
}}}}}
|
|
}
|
|
|
|
// RouteConfigNonForwardingAction returns an xDS RouteConfig resource which
|
|
// specifies to route to a route specifying non forwarding action. This is
|
|
// intended to be used on the server side for RDS requests, and corresponds to
|
|
// the inline route configuration in DefaultServerListener.
|
|
func RouteConfigNonForwardingAction(routeName string) *v3routepb.RouteConfiguration {
|
|
return &v3routepb.RouteConfiguration{
|
|
Name: routeName,
|
|
VirtualHosts: []*v3routepb.VirtualHost{{
|
|
// This "*" string matches on any incoming authority. This is to ensure any
|
|
// incoming RPC matches to Route_NonForwardingAction and will proceed as
|
|
// normal.
|
|
Domains: []string{"*"},
|
|
Routes: []*v3routepb.Route{{
|
|
Match: &v3routepb.RouteMatch{
|
|
PathSpecifier: &v3routepb.RouteMatch_Prefix{Prefix: "/"},
|
|
},
|
|
Action: &v3routepb.Route_NonForwardingAction{},
|
|
}}}}}
|
|
}
|
|
|
|
// RouteConfigFilterAction returns an xDS RouteConfig resource which specifies
|
|
// to route to a route specifying route filter action. Since this is not type
|
|
// non forwarding action, this should fail requests that match to this server
|
|
// side.
|
|
func RouteConfigFilterAction(routeName string) *v3routepb.RouteConfiguration {
|
|
return &v3routepb.RouteConfiguration{
|
|
Name: routeName,
|
|
VirtualHosts: []*v3routepb.VirtualHost{{
|
|
// This "*" string matches on any incoming authority. This is to
|
|
// ensure any incoming RPC matches to Route_Route and will fail with
|
|
// UNAVAILABLE.
|
|
Domains: []string{"*"},
|
|
Routes: []*v3routepb.Route{{
|
|
Match: &v3routepb.RouteMatch{
|
|
PathSpecifier: &v3routepb.RouteMatch_Prefix{Prefix: "/"},
|
|
},
|
|
Action: &v3routepb.Route_FilterAction{},
|
|
}}}}}
|
|
}
|