google.golang.org/grpc@v1.72.2/internal/testutils/xds/e2e/clientresources.go (about)

     1  /*
     2   *
     3   * Copyright 2021 gRPC authors.
     4   *
     5   * Licensed under the Apache License, Version 2.0 (the "License");
     6   * you may not use this file except in compliance with the License.
     7   * You may obtain a copy of the License at
     8   *
     9   *     http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the License for the specific language governing permissions and
    15   * limitations under the License.
    16   *
    17   */
    18  
    19  package e2e
    20  
    21  import (
    22  	"fmt"
    23  	"net"
    24  	"strconv"
    25  
    26  	"github.com/envoyproxy/go-control-plane/pkg/wellknown"
    27  	"google.golang.org/protobuf/proto"
    28  	"google.golang.org/protobuf/types/known/anypb"
    29  	"google.golang.org/protobuf/types/known/structpb"
    30  	"google.golang.org/protobuf/types/known/wrapperspb"
    31  
    32  	v3clusterpb "github.com/envoyproxy/go-control-plane/envoy/config/cluster/v3"
    33  	v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
    34  	v3endpointpb "github.com/envoyproxy/go-control-plane/envoy/config/endpoint/v3"
    35  	v3listenerpb "github.com/envoyproxy/go-control-plane/envoy/config/listener/v3"
    36  	v3routepb "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
    37  	v3aggregateclusterpb "github.com/envoyproxy/go-control-plane/envoy/extensions/clusters/aggregate/v3"
    38  	v3routerpb "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/router/v3"
    39  	v3httppb "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/network/http_connection_manager/v3"
    40  	v3tlspb "github.com/envoyproxy/go-control-plane/envoy/extensions/transport_sockets/tls/v3"
    41  	v3typepb "github.com/envoyproxy/go-control-plane/envoy/type/v3"
    42  )
    43  
    44  const (
    45  	// ServerListenerResourceNameTemplate is the Listener resource name template
    46  	// used on the server side.
    47  	ServerListenerResourceNameTemplate = "grpc/server?xds.resource.listening_address=%s"
    48  	// ClientSideCertProviderInstance is the certificate provider instance name
    49  	// used in the Cluster resource on the client side.
    50  	ClientSideCertProviderInstance = "client-side-certificate-provider-instance"
    51  	// ServerSideCertProviderInstance is the certificate provider instance name
    52  	// used in the Listener resource on the server side.
    53  	ServerSideCertProviderInstance = "server-side-certificate-provider-instance"
    54  )
    55  
    56  // SecurityLevel allows the test to control the security level to be used in the
    57  // resource returned by this package.
    58  type SecurityLevel int
    59  
    60  const (
    61  	// SecurityLevelNone is used when no security configuration is required.
    62  	SecurityLevelNone SecurityLevel = iota
    63  	// SecurityLevelTLS is used when security configuration corresponding to TLS
    64  	// is required. Only the server presents an identity certificate in this
    65  	// configuration.
    66  	SecurityLevelTLS
    67  	// SecurityLevelMTLS is used when security configuration corresponding to
    68  	// mTLS is required. Both client and server present identity certificates in
    69  	// this configuration.
    70  	SecurityLevelMTLS
    71  	// SecurityLevelTLSWithSystemRootCerts is used when security configuration
    72  	// corresponding to TLS is required. Only the server presents an identity
    73  	// certificate in this configuration and the client uses system root certs
    74  	// to validate the server certificate.
    75  	SecurityLevelTLSWithSystemRootCerts
    76  )
    77  
    78  // ResourceParams wraps the arguments to be passed to DefaultClientResources.
    79  type ResourceParams struct {
    80  	// DialTarget is the client's dial target. This is used as the name of the
    81  	// Listener resource.
    82  	DialTarget string
    83  	// NodeID is the id of the xdsClient to which this update is to be pushed.
    84  	NodeID string
    85  	// Host is the host of the default Endpoint resource.
    86  	Host string
    87  	// port is the port of the default Endpoint resource.
    88  	Port uint32
    89  	// SecLevel controls the security configuration in the Cluster resource.
    90  	SecLevel SecurityLevel
    91  }
    92  
    93  // DefaultClientResources returns a set of resources (LDS, RDS, CDS, EDS) for a
    94  // client to generically connect to one server.
    95  func DefaultClientResources(params ResourceParams) UpdateOptions {
    96  	routeConfigName := "route-" + params.DialTarget
    97  	clusterName := "cluster-" + params.DialTarget
    98  	endpointsName := "endpoints-" + params.DialTarget
    99  	return UpdateOptions{
   100  		NodeID:    params.NodeID,
   101  		Listeners: []*v3listenerpb.Listener{DefaultClientListener(params.DialTarget, routeConfigName)},
   102  		Routes:    []*v3routepb.RouteConfiguration{DefaultRouteConfig(routeConfigName, params.DialTarget, clusterName)},
   103  		Clusters:  []*v3clusterpb.Cluster{DefaultCluster(clusterName, endpointsName, params.SecLevel)},
   104  		Endpoints: []*v3endpointpb.ClusterLoadAssignment{DefaultEndpoint(endpointsName, params.Host, []uint32{params.Port})},
   105  	}
   106  }
   107  
   108  // RouterHTTPFilter is the HTTP Filter configuration for the Router filter.
   109  var RouterHTTPFilter = HTTPFilter("router", &v3routerpb.Router{})
   110  
   111  // DefaultClientListener returns a basic xds Listener resource to be used on
   112  // the client side.
   113  func DefaultClientListener(target, routeName string) *v3listenerpb.Listener {
   114  	hcm := marshalAny(&v3httppb.HttpConnectionManager{
   115  		RouteSpecifier: &v3httppb.HttpConnectionManager_Rds{Rds: &v3httppb.Rds{
   116  			ConfigSource: &v3corepb.ConfigSource{
   117  				ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{Ads: &v3corepb.AggregatedConfigSource{}},
   118  			},
   119  			RouteConfigName: routeName,
   120  		}},
   121  		HttpFilters: []*v3httppb.HttpFilter{HTTPFilter("router", &v3routerpb.Router{})}, // router fields are unused by grpc
   122  	})
   123  	return &v3listenerpb.Listener{
   124  		Name:        target,
   125  		ApiListener: &v3listenerpb.ApiListener{ApiListener: hcm},
   126  		FilterChains: []*v3listenerpb.FilterChain{{
   127  			Name: "filter-chain-name",
   128  			Filters: []*v3listenerpb.Filter{{
   129  				Name:       wellknown.HTTPConnectionManager,
   130  				ConfigType: &v3listenerpb.Filter_TypedConfig{TypedConfig: hcm},
   131  			}},
   132  		}},
   133  	}
   134  }
   135  
   136  func marshalAny(m proto.Message) *anypb.Any {
   137  	a, err := anypb.New(m)
   138  	if err != nil {
   139  		panic(fmt.Sprintf("anypb.New(%+v) failed: %v", m, err))
   140  	}
   141  	return a
   142  }
   143  
   144  // DefaultServerListener returns a basic xds Listener resource to be used on the
   145  // server side. The returned Listener resource contains an inline route
   146  // configuration with the name of routeName.
   147  func DefaultServerListener(host string, port uint32, secLevel SecurityLevel, routeName string) *v3listenerpb.Listener {
   148  	return defaultServerListenerCommon(host, port, secLevel, routeName, true)
   149  }
   150  
   151  func defaultServerListenerCommon(host string, port uint32, secLevel SecurityLevel, routeName string, inlineRouteConfig bool) *v3listenerpb.Listener {
   152  	var hcm *v3httppb.HttpConnectionManager
   153  	if inlineRouteConfig {
   154  		hcm = &v3httppb.HttpConnectionManager{
   155  			RouteSpecifier: &v3httppb.HttpConnectionManager_RouteConfig{
   156  				RouteConfig: &v3routepb.RouteConfiguration{
   157  					Name: routeName,
   158  					VirtualHosts: []*v3routepb.VirtualHost{{
   159  						// This "*" string matches on any incoming authority. This is to ensure any
   160  						// incoming RPC matches to Route_NonForwardingAction and will proceed as
   161  						// normal.
   162  						Domains: []string{"*"},
   163  						Routes: []*v3routepb.Route{{
   164  							Match: &v3routepb.RouteMatch{
   165  								PathSpecifier: &v3routepb.RouteMatch_Prefix{Prefix: "/"},
   166  							},
   167  							Action: &v3routepb.Route_NonForwardingAction{},
   168  						}}}}},
   169  			},
   170  			HttpFilters: []*v3httppb.HttpFilter{RouterHTTPFilter},
   171  		}
   172  	} else {
   173  		hcm = &v3httppb.HttpConnectionManager{
   174  			RouteSpecifier: &v3httppb.HttpConnectionManager_Rds{
   175  				Rds: &v3httppb.Rds{
   176  					ConfigSource: &v3corepb.ConfigSource{
   177  						ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{Ads: &v3corepb.AggregatedConfigSource{}},
   178  					},
   179  					RouteConfigName: routeName,
   180  				},
   181  			},
   182  			HttpFilters: []*v3httppb.HttpFilter{RouterHTTPFilter},
   183  		}
   184  	}
   185  
   186  	var tlsContext *v3tlspb.DownstreamTlsContext
   187  	switch secLevel {
   188  	case SecurityLevelNone:
   189  	case SecurityLevelTLS:
   190  		tlsContext = &v3tlspb.DownstreamTlsContext{
   191  			CommonTlsContext: &v3tlspb.CommonTlsContext{
   192  				TlsCertificateCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{
   193  					InstanceName: ServerSideCertProviderInstance,
   194  				},
   195  			},
   196  		}
   197  	case SecurityLevelMTLS:
   198  		tlsContext = &v3tlspb.DownstreamTlsContext{
   199  			RequireClientCertificate: &wrapperspb.BoolValue{Value: true},
   200  			CommonTlsContext: &v3tlspb.CommonTlsContext{
   201  				TlsCertificateCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{
   202  					InstanceName: ServerSideCertProviderInstance,
   203  				},
   204  				ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContextCertificateProviderInstance{
   205  					ValidationContextCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{
   206  						InstanceName: ServerSideCertProviderInstance,
   207  					},
   208  				},
   209  			},
   210  		}
   211  	}
   212  
   213  	var ts *v3corepb.TransportSocket
   214  	if tlsContext != nil {
   215  		ts = &v3corepb.TransportSocket{
   216  			Name: "envoy.transport_sockets.tls",
   217  			ConfigType: &v3corepb.TransportSocket_TypedConfig{
   218  				TypedConfig: marshalAny(tlsContext),
   219  			},
   220  		}
   221  	}
   222  	return &v3listenerpb.Listener{
   223  		Name: fmt.Sprintf(ServerListenerResourceNameTemplate, net.JoinHostPort(host, strconv.Itoa(int(port)))),
   224  		Address: &v3corepb.Address{
   225  			Address: &v3corepb.Address_SocketAddress{
   226  				SocketAddress: &v3corepb.SocketAddress{
   227  					Address: host,
   228  					PortSpecifier: &v3corepb.SocketAddress_PortValue{
   229  						PortValue: port,
   230  					},
   231  				},
   232  			},
   233  		},
   234  		FilterChains: []*v3listenerpb.FilterChain{
   235  			{
   236  				Name: "v4-wildcard",
   237  				FilterChainMatch: &v3listenerpb.FilterChainMatch{
   238  					PrefixRanges: []*v3corepb.CidrRange{
   239  						{
   240  							AddressPrefix: "0.0.0.0",
   241  							PrefixLen: &wrapperspb.UInt32Value{
   242  								Value: uint32(0),
   243  							},
   244  						},
   245  					},
   246  					SourceType: v3listenerpb.FilterChainMatch_SAME_IP_OR_LOOPBACK,
   247  					SourcePrefixRanges: []*v3corepb.CidrRange{
   248  						{
   249  							AddressPrefix: "0.0.0.0",
   250  							PrefixLen: &wrapperspb.UInt32Value{
   251  								Value: uint32(0),
   252  							},
   253  						},
   254  					},
   255  				},
   256  				Filters: []*v3listenerpb.Filter{
   257  					{
   258  						Name:       "filter-1",
   259  						ConfigType: &v3listenerpb.Filter_TypedConfig{TypedConfig: marshalAny(hcm)},
   260  					},
   261  				},
   262  				TransportSocket: ts,
   263  			},
   264  			{
   265  				Name: "v6-wildcard",
   266  				FilterChainMatch: &v3listenerpb.FilterChainMatch{
   267  					PrefixRanges: []*v3corepb.CidrRange{
   268  						{
   269  							AddressPrefix: "::",
   270  							PrefixLen: &wrapperspb.UInt32Value{
   271  								Value: uint32(0),
   272  							},
   273  						},
   274  					},
   275  					SourceType: v3listenerpb.FilterChainMatch_SAME_IP_OR_LOOPBACK,
   276  					SourcePrefixRanges: []*v3corepb.CidrRange{
   277  						{
   278  							AddressPrefix: "::",
   279  							PrefixLen: &wrapperspb.UInt32Value{
   280  								Value: uint32(0),
   281  							},
   282  						},
   283  					},
   284  				},
   285  				Filters: []*v3listenerpb.Filter{
   286  					{
   287  						Name:       "filter-1",
   288  						ConfigType: &v3listenerpb.Filter_TypedConfig{TypedConfig: marshalAny(hcm)},
   289  					},
   290  				},
   291  				TransportSocket: ts,
   292  			},
   293  		},
   294  	}
   295  }
   296  
   297  // HTTPFilter constructs an xds HttpFilter with the provided name and config.
   298  func HTTPFilter(name string, config proto.Message) *v3httppb.HttpFilter {
   299  	return &v3httppb.HttpFilter{
   300  		Name: name,
   301  		ConfigType: &v3httppb.HttpFilter_TypedConfig{
   302  			TypedConfig: marshalAny(config),
   303  		},
   304  	}
   305  }
   306  
   307  // DefaultRouteConfig returns a basic xds RouteConfig resource.
   308  func DefaultRouteConfig(routeName, vhDomain, clusterName string) *v3routepb.RouteConfiguration {
   309  	return &v3routepb.RouteConfiguration{
   310  		Name: routeName,
   311  		VirtualHosts: []*v3routepb.VirtualHost{{
   312  			Domains: []string{vhDomain},
   313  			Routes: []*v3routepb.Route{{
   314  				Match: &v3routepb.RouteMatch{PathSpecifier: &v3routepb.RouteMatch_Prefix{Prefix: "/"}},
   315  				Action: &v3routepb.Route_Route{Route: &v3routepb.RouteAction{
   316  					ClusterSpecifier: &v3routepb.RouteAction_WeightedClusters{WeightedClusters: &v3routepb.WeightedCluster{
   317  						Clusters: []*v3routepb.WeightedCluster_ClusterWeight{
   318  							{
   319  								Name:   clusterName,
   320  								Weight: &wrapperspb.UInt32Value{Value: 100},
   321  							},
   322  						},
   323  					}},
   324  				}},
   325  			}},
   326  		}},
   327  	}
   328  }
   329  
   330  // RouteConfigClusterSpecifierType determines the cluster specifier type for the
   331  // route actions configured in the returned RouteConfiguration resource.
   332  type RouteConfigClusterSpecifierType int
   333  
   334  const (
   335  	// RouteConfigClusterSpecifierTypeCluster results in the cluster specifier
   336  	// being set to a RouteAction_Cluster.
   337  	RouteConfigClusterSpecifierTypeCluster RouteConfigClusterSpecifierType = iota
   338  	// RouteConfigClusterSpecifierTypeWeightedCluster results in the cluster
   339  	// specifier being set to RouteAction_WeightedClusters.
   340  	RouteConfigClusterSpecifierTypeWeightedCluster
   341  	// RouteConfigClusterSpecifierTypeClusterSpecifierPlugin results in the
   342  	// cluster specifier being set to a RouteAction_ClusterSpecifierPlugin.
   343  	RouteConfigClusterSpecifierTypeClusterSpecifierPlugin
   344  )
   345  
   346  // RouteConfigOptions contains options to configure a RouteConfiguration
   347  // resource.
   348  type RouteConfigOptions struct {
   349  	// RouteConfigName is the name of the RouteConfiguration resource.
   350  	RouteConfigName string
   351  	// ListenerName is the name of the Listener resource which uses this
   352  	// RouteConfiguration.
   353  	ListenerName string
   354  	// ClusterSpecifierType determines the cluster specifier type.
   355  	ClusterSpecifierType RouteConfigClusterSpecifierType
   356  	// ClusterName is name of the cluster resource used when the cluster
   357  	// specifier type is set to RouteConfigClusterSpecifierTypeCluster.
   358  	//
   359  	// Default value of "A" is used if left unspecified.
   360  	ClusterName string
   361  	// WeightedClusters is a map from cluster name to weights, and is used when
   362  	// the cluster specifier type is set to
   363  	// RouteConfigClusterSpecifierTypeWeightedCluster.
   364  	//
   365  	// Default value of {"A": 75, "B": 25} is used if left unspecified.
   366  	WeightedClusters map[string]int
   367  	// The below two fields specify the name of the cluster specifier plugin and
   368  	// its configuration, and are used when the cluster specifier type is set to
   369  	// RouteConfigClusterSpecifierTypeClusterSpecifierPlugin. Tests are expected
   370  	// to provide valid values for these fields when appropriate.
   371  	ClusterSpecifierPluginName   string
   372  	ClusterSpecifierPluginConfig *anypb.Any
   373  }
   374  
   375  // RouteConfigResourceWithOptions returns a RouteConfiguration resource
   376  // configured with the provided options.
   377  func RouteConfigResourceWithOptions(opts RouteConfigOptions) *v3routepb.RouteConfiguration {
   378  	switch opts.ClusterSpecifierType {
   379  	case RouteConfigClusterSpecifierTypeCluster:
   380  		clusterName := opts.ClusterName
   381  		if clusterName == "" {
   382  			clusterName = "A"
   383  		}
   384  		return &v3routepb.RouteConfiguration{
   385  			Name: opts.RouteConfigName,
   386  			VirtualHosts: []*v3routepb.VirtualHost{{
   387  				Domains: []string{opts.ListenerName},
   388  				Routes: []*v3routepb.Route{{
   389  					Match: &v3routepb.RouteMatch{PathSpecifier: &v3routepb.RouteMatch_Prefix{Prefix: "/"}},
   390  					Action: &v3routepb.Route_Route{Route: &v3routepb.RouteAction{
   391  						ClusterSpecifier: &v3routepb.RouteAction_Cluster{Cluster: clusterName},
   392  					}},
   393  				}},
   394  			}},
   395  		}
   396  	case RouteConfigClusterSpecifierTypeWeightedCluster:
   397  		weightedClusters := opts.WeightedClusters
   398  		if weightedClusters == nil {
   399  			weightedClusters = map[string]int{"A": 75, "B": 25}
   400  		}
   401  		clusters := []*v3routepb.WeightedCluster_ClusterWeight{}
   402  		for name, weight := range weightedClusters {
   403  			clusters = append(clusters, &v3routepb.WeightedCluster_ClusterWeight{
   404  				Name:   name,
   405  				Weight: &wrapperspb.UInt32Value{Value: uint32(weight)},
   406  			})
   407  		}
   408  		return &v3routepb.RouteConfiguration{
   409  			Name: opts.RouteConfigName,
   410  			VirtualHosts: []*v3routepb.VirtualHost{{
   411  				Domains: []string{opts.ListenerName},
   412  				Routes: []*v3routepb.Route{{
   413  					Match: &v3routepb.RouteMatch{PathSpecifier: &v3routepb.RouteMatch_Prefix{Prefix: "/"}},
   414  					Action: &v3routepb.Route_Route{Route: &v3routepb.RouteAction{
   415  						ClusterSpecifier: &v3routepb.RouteAction_WeightedClusters{WeightedClusters: &v3routepb.WeightedCluster{Clusters: clusters}},
   416  					}},
   417  				}},
   418  			}},
   419  		}
   420  	case RouteConfigClusterSpecifierTypeClusterSpecifierPlugin:
   421  		return &v3routepb.RouteConfiguration{
   422  			Name: opts.RouteConfigName,
   423  			ClusterSpecifierPlugins: []*v3routepb.ClusterSpecifierPlugin{{
   424  				Extension: &v3corepb.TypedExtensionConfig{
   425  					Name:        opts.ClusterSpecifierPluginName,
   426  					TypedConfig: opts.ClusterSpecifierPluginConfig,
   427  				}},
   428  			},
   429  			VirtualHosts: []*v3routepb.VirtualHost{{
   430  				Domains: []string{opts.ListenerName},
   431  				Routes: []*v3routepb.Route{{
   432  					Match: &v3routepb.RouteMatch{PathSpecifier: &v3routepb.RouteMatch_Prefix{Prefix: "/"}},
   433  					Action: &v3routepb.Route_Route{Route: &v3routepb.RouteAction{
   434  						ClusterSpecifier: &v3routepb.RouteAction_ClusterSpecifierPlugin{ClusterSpecifierPlugin: opts.ClusterSpecifierPluginName},
   435  					}},
   436  				}},
   437  			}},
   438  		}
   439  	default:
   440  		panic(fmt.Sprintf("unsupported cluster specifier plugin type: %v", opts.ClusterSpecifierType))
   441  	}
   442  }
   443  
   444  // DefaultCluster returns a basic xds Cluster resource.
   445  func DefaultCluster(clusterName, edsServiceName string, secLevel SecurityLevel) *v3clusterpb.Cluster {
   446  	return ClusterResourceWithOptions(ClusterOptions{
   447  		ClusterName:   clusterName,
   448  		ServiceName:   edsServiceName,
   449  		Policy:        LoadBalancingPolicyRoundRobin,
   450  		SecurityLevel: secLevel,
   451  	})
   452  }
   453  
   454  // LoadBalancingPolicy determines the policy used for balancing load across
   455  // endpoints in the Cluster.
   456  type LoadBalancingPolicy int
   457  
   458  const (
   459  	// LoadBalancingPolicyRoundRobin results in the use of the weighted_target
   460  	// LB policy to balance load across localities and endpoints in the cluster.
   461  	LoadBalancingPolicyRoundRobin LoadBalancingPolicy = iota
   462  	// LoadBalancingPolicyRingHash results in the use of the ring_hash LB policy
   463  	// as the leaf policy.
   464  	LoadBalancingPolicyRingHash
   465  )
   466  
   467  // ClusterType specifies the type of the Cluster resource.
   468  type ClusterType int
   469  
   470  const (
   471  	// ClusterTypeEDS specifies a Cluster that uses EDS to resolve endpoints.
   472  	ClusterTypeEDS ClusterType = iota
   473  	// ClusterTypeLogicalDNS specifies a Cluster that uses DNS to resolve
   474  	// endpoints.
   475  	ClusterTypeLogicalDNS
   476  	// ClusterTypeAggregate specifies a Cluster that is made up of child
   477  	// clusters.
   478  	ClusterTypeAggregate
   479  )
   480  
   481  // ClusterOptions contains options to configure a Cluster resource.
   482  type ClusterOptions struct {
   483  	Type ClusterType
   484  	// ClusterName is the name of the Cluster resource.
   485  	ClusterName string
   486  	// ServiceName is the EDS service name of the Cluster. Applicable only when
   487  	// cluster type is EDS.
   488  	ServiceName string
   489  	// ChildNames is the list of child Cluster names. Applicable only when
   490  	// cluster type is Aggregate.
   491  	ChildNames []string
   492  	// DNSHostName is the dns host name of the Cluster. Applicable only when the
   493  	// cluster type is DNS.
   494  	DNSHostName string
   495  	// DNSPort is the port number of the Cluster. Applicable only when the
   496  	// cluster type is DNS.
   497  	DNSPort uint32
   498  	// Policy is the LB policy to be used.
   499  	Policy LoadBalancingPolicy
   500  	// SecurityLevel determines the security configuration for the Cluster.
   501  	SecurityLevel SecurityLevel
   502  	// EnableLRS adds a load reporting configuration with a config source
   503  	// pointing to self.
   504  	EnableLRS bool
   505  }
   506  
   507  // ClusterResourceWithOptions returns an xDS Cluster resource configured with
   508  // the provided options.
   509  func ClusterResourceWithOptions(opts ClusterOptions) *v3clusterpb.Cluster {
   510  	var tlsContext *v3tlspb.UpstreamTlsContext
   511  	switch opts.SecurityLevel {
   512  	case SecurityLevelNone:
   513  	case SecurityLevelTLS:
   514  		tlsContext = &v3tlspb.UpstreamTlsContext{
   515  			CommonTlsContext: &v3tlspb.CommonTlsContext{
   516  				ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContextCertificateProviderInstance{
   517  					ValidationContextCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{
   518  						InstanceName: ClientSideCertProviderInstance,
   519  					},
   520  				},
   521  			},
   522  		}
   523  	case SecurityLevelMTLS:
   524  		tlsContext = &v3tlspb.UpstreamTlsContext{
   525  			CommonTlsContext: &v3tlspb.CommonTlsContext{
   526  				ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContextCertificateProviderInstance{
   527  					ValidationContextCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{
   528  						InstanceName: ClientSideCertProviderInstance,
   529  					},
   530  				},
   531  				TlsCertificateCertificateProviderInstance: &v3tlspb.CommonTlsContext_CertificateProviderInstance{
   532  					InstanceName: ClientSideCertProviderInstance,
   533  				},
   534  			},
   535  		}
   536  	case SecurityLevelTLSWithSystemRootCerts:
   537  		tlsContext = &v3tlspb.UpstreamTlsContext{
   538  			CommonTlsContext: &v3tlspb.CommonTlsContext{
   539  				ValidationContextType: &v3tlspb.CommonTlsContext_ValidationContext{
   540  					ValidationContext: &v3tlspb.CertificateValidationContext{
   541  						SystemRootCerts: &v3tlspb.CertificateValidationContext_SystemRootCerts{},
   542  					},
   543  				},
   544  			},
   545  		}
   546  	}
   547  
   548  	var lbPolicy v3clusterpb.Cluster_LbPolicy
   549  	switch opts.Policy {
   550  	case LoadBalancingPolicyRoundRobin:
   551  		lbPolicy = v3clusterpb.Cluster_ROUND_ROBIN
   552  	case LoadBalancingPolicyRingHash:
   553  		lbPolicy = v3clusterpb.Cluster_RING_HASH
   554  	}
   555  	cluster := &v3clusterpb.Cluster{
   556  		Name:     opts.ClusterName,
   557  		LbPolicy: lbPolicy,
   558  	}
   559  	switch opts.Type {
   560  	case ClusterTypeEDS:
   561  		cluster.ClusterDiscoveryType = &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_EDS}
   562  		cluster.EdsClusterConfig = &v3clusterpb.Cluster_EdsClusterConfig{
   563  			EdsConfig: &v3corepb.ConfigSource{
   564  				ConfigSourceSpecifier: &v3corepb.ConfigSource_Ads{
   565  					Ads: &v3corepb.AggregatedConfigSource{},
   566  				},
   567  			},
   568  			ServiceName: opts.ServiceName,
   569  		}
   570  	case ClusterTypeLogicalDNS:
   571  		cluster.ClusterDiscoveryType = &v3clusterpb.Cluster_Type{Type: v3clusterpb.Cluster_LOGICAL_DNS}
   572  		cluster.LoadAssignment = &v3endpointpb.ClusterLoadAssignment{
   573  			Endpoints: []*v3endpointpb.LocalityLbEndpoints{{
   574  				LbEndpoints: []*v3endpointpb.LbEndpoint{{
   575  					HostIdentifier: &v3endpointpb.LbEndpoint_Endpoint{
   576  						Endpoint: &v3endpointpb.Endpoint{
   577  							Address: &v3corepb.Address{
   578  								Address: &v3corepb.Address_SocketAddress{
   579  									SocketAddress: &v3corepb.SocketAddress{
   580  										Address: opts.DNSHostName,
   581  										PortSpecifier: &v3corepb.SocketAddress_PortValue{
   582  											PortValue: opts.DNSPort,
   583  										},
   584  									},
   585  								},
   586  							},
   587  						},
   588  					},
   589  				}},
   590  			}},
   591  		}
   592  	case ClusterTypeAggregate:
   593  		cluster.ClusterDiscoveryType = &v3clusterpb.Cluster_ClusterType{
   594  			ClusterType: &v3clusterpb.Cluster_CustomClusterType{
   595  				Name: "envoy.clusters.aggregate",
   596  				TypedConfig: marshalAny(&v3aggregateclusterpb.ClusterConfig{
   597  					Clusters: opts.ChildNames,
   598  				}),
   599  			},
   600  		}
   601  	}
   602  	if tlsContext != nil {
   603  		cluster.TransportSocket = &v3corepb.TransportSocket{
   604  			Name: "envoy.transport_sockets.tls",
   605  			ConfigType: &v3corepb.TransportSocket_TypedConfig{
   606  				TypedConfig: marshalAny(tlsContext),
   607  			},
   608  		}
   609  	}
   610  	if opts.EnableLRS {
   611  		cluster.LrsServer = &v3corepb.ConfigSource{
   612  			ConfigSourceSpecifier: &v3corepb.ConfigSource_Self{
   613  				Self: &v3corepb.SelfConfigSource{},
   614  			},
   615  		}
   616  	}
   617  	return cluster
   618  }
   619  
   620  // LocalityID represents a locality identifier.
   621  type LocalityID struct {
   622  	Region  string
   623  	Zone    string
   624  	SubZone string
   625  }
   626  
   627  // LocalityOptions contains options to configure a Locality.
   628  type LocalityOptions struct {
   629  	// Name is the unique locality name.
   630  	Name string
   631  	// Weight is the weight of the locality, used for load balancing.
   632  	Weight uint32
   633  	// Backends is a set of backends belonging to this locality.
   634  	Backends []BackendOptions
   635  	// Priority is the priority of the locality. Defaults to 0.
   636  	Priority uint32
   637  	// Locality is the locality identifier. If not specified, a random
   638  	// identifier is generated.
   639  	Locality LocalityID
   640  }
   641  
   642  // BackendOptions contains options to configure individual backends in a
   643  // locality.
   644  type BackendOptions struct {
   645  	// Ports on which the backend is accepting connections. All backends
   646  	// are expected to run on localhost, hence host name is not stored here.
   647  	Ports []uint32
   648  	// Health status of the backend. Default is UNKNOWN which is treated the
   649  	// same as HEALTHY.
   650  	HealthStatus v3corepb.HealthStatus
   651  	// Weight sets the backend weight. Defaults to 1.
   652  	Weight uint32
   653  	// Metadata sets the LB endpoint metadata (envoy.lb FilterMetadata field).
   654  	// See https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/core/v3/base.proto#envoy-v3-api-msg-config-core-v3-metadata
   655  	Metadata map[string]any
   656  }
   657  
   658  // EndpointOptions contains options to configure an Endpoint (or
   659  // ClusterLoadAssignment) resource.
   660  type EndpointOptions struct {
   661  	// ClusterName is the name of the Cluster resource (or EDS service name)
   662  	// containing the endpoints specified below.
   663  	ClusterName string
   664  	// Host is the hostname of the endpoints. In our e2e tests, hostname must
   665  	// always be "localhost".
   666  	Host string
   667  	// Localities is a set of localities belonging to this resource.
   668  	Localities []LocalityOptions
   669  	// DropPercents is a map from drop category to a drop percentage. If unset,
   670  	// no drops are configured.
   671  	DropPercents map[string]int
   672  }
   673  
   674  // DefaultEndpoint returns a basic xds Endpoint resource.
   675  func DefaultEndpoint(clusterName string, host string, ports []uint32) *v3endpointpb.ClusterLoadAssignment {
   676  	var bOpts []BackendOptions
   677  	for _, p := range ports {
   678  		bOpts = append(bOpts, BackendOptions{Ports: []uint32{p}, Weight: 1})
   679  	}
   680  	return EndpointResourceWithOptions(EndpointOptions{
   681  		ClusterName: clusterName,
   682  		Host:        host,
   683  		Localities: []LocalityOptions{
   684  			{
   685  				Backends: bOpts,
   686  				Weight:   1,
   687  			},
   688  		},
   689  	})
   690  }
   691  
   692  // EndpointResourceWithOptions returns an xds Endpoint resource configured with
   693  // the provided options.
   694  func EndpointResourceWithOptions(opts EndpointOptions) *v3endpointpb.ClusterLoadAssignment {
   695  	var endpoints []*v3endpointpb.LocalityLbEndpoints
   696  	for i, locality := range opts.Localities {
   697  		var lbEndpoints []*v3endpointpb.LbEndpoint
   698  		for _, b := range locality.Backends {
   699  			// Weight defaults to 1.
   700  			if b.Weight == 0 {
   701  				b.Weight = 1
   702  			}
   703  			additionalAddresses := make([]*v3endpointpb.Endpoint_AdditionalAddress, len(b.Ports)-1)
   704  			for i, p := range b.Ports[1:] {
   705  				additionalAddresses[i] = &v3endpointpb.Endpoint_AdditionalAddress{
   706  					Address: &v3corepb.Address{Address: &v3corepb.Address_SocketAddress{
   707  						SocketAddress: &v3corepb.SocketAddress{
   708  							Protocol:      v3corepb.SocketAddress_TCP,
   709  							Address:       opts.Host,
   710  							PortSpecifier: &v3corepb.SocketAddress_PortValue{PortValue: p},
   711  						}},
   712  					},
   713  				}
   714  			}
   715  			metadata, err := structpb.NewStruct(b.Metadata)
   716  			if err != nil {
   717  				panic(err)
   718  			}
   719  			lbEndpoints = append(lbEndpoints, &v3endpointpb.LbEndpoint{
   720  				HostIdentifier: &v3endpointpb.LbEndpoint_Endpoint{Endpoint: &v3endpointpb.Endpoint{
   721  					Address: &v3corepb.Address{Address: &v3corepb.Address_SocketAddress{
   722  						SocketAddress: &v3corepb.SocketAddress{
   723  							Protocol:      v3corepb.SocketAddress_TCP,
   724  							Address:       opts.Host,
   725  							PortSpecifier: &v3corepb.SocketAddress_PortValue{PortValue: b.Ports[0]},
   726  						},
   727  					}},
   728  					AdditionalAddresses: additionalAddresses,
   729  				}},
   730  				HealthStatus:        b.HealthStatus,
   731  				LoadBalancingWeight: &wrapperspb.UInt32Value{Value: b.Weight},
   732  				Metadata: &v3corepb.Metadata{
   733  					FilterMetadata: map[string]*structpb.Struct{
   734  						"envoy.lb": metadata,
   735  					},
   736  				},
   737  			})
   738  		}
   739  
   740  		l := locality.Locality
   741  		if l == (LocalityID{}) {
   742  			l = LocalityID{
   743  				Region:  fmt.Sprintf("region-%d", i+1),
   744  				Zone:    fmt.Sprintf("zone-%d", i+1),
   745  				SubZone: fmt.Sprintf("subzone-%d", i+1),
   746  			}
   747  		}
   748  		endpoints = append(endpoints, &v3endpointpb.LocalityLbEndpoints{
   749  			Locality:            &v3corepb.Locality{Region: l.Region, Zone: l.Zone, SubZone: l.SubZone},
   750  			LbEndpoints:         lbEndpoints,
   751  			LoadBalancingWeight: &wrapperspb.UInt32Value{Value: locality.Weight},
   752  			Priority:            locality.Priority,
   753  		})
   754  	}
   755  
   756  	cla := &v3endpointpb.ClusterLoadAssignment{
   757  		ClusterName: opts.ClusterName,
   758  		Endpoints:   endpoints,
   759  	}
   760  
   761  	var drops []*v3endpointpb.ClusterLoadAssignment_Policy_DropOverload
   762  	for category, val := range opts.DropPercents {
   763  		drops = append(drops, &v3endpointpb.ClusterLoadAssignment_Policy_DropOverload{
   764  			Category: category,
   765  			DropPercentage: &v3typepb.FractionalPercent{
   766  				Numerator:   uint32(val),
   767  				Denominator: v3typepb.FractionalPercent_HUNDRED,
   768  			},
   769  		})
   770  	}
   771  	if len(drops) != 0 {
   772  		cla.Policy = &v3endpointpb.ClusterLoadAssignment_Policy{
   773  			DropOverloads: drops,
   774  		}
   775  	}
   776  	return cla
   777  }
   778  
   779  // DefaultServerListenerWithRouteConfigName returns a basic xds Listener
   780  // resource to be used on the server side. The returned Listener resource
   781  // contains a RouteConfiguration resource name that needs to be resolved.
   782  func DefaultServerListenerWithRouteConfigName(host string, port uint32, secLevel SecurityLevel, routeName string) *v3listenerpb.Listener {
   783  	return defaultServerListenerCommon(host, port, secLevel, routeName, false)
   784  }
   785  
   786  // RouteConfigNoRouteMatch returns an xDS RouteConfig resource which a route
   787  // with no route match. This will be NACKed by the xDS Client.
   788  func RouteConfigNoRouteMatch(routeName string) *v3routepb.RouteConfiguration {
   789  	return &v3routepb.RouteConfiguration{
   790  		Name: routeName,
   791  		VirtualHosts: []*v3routepb.VirtualHost{{
   792  			// This "*" string matches on any incoming authority. This is to ensure any
   793  			// incoming RPC matches to Route_NonForwardingAction and will proceed as
   794  			// normal.
   795  			Domains: []string{"*"},
   796  			Routes: []*v3routepb.Route{{
   797  				Action: &v3routepb.Route_NonForwardingAction{},
   798  			}}}}}
   799  }
   800  
   801  // RouteConfigNonForwardingAction returns an xDS RouteConfig resource which
   802  // specifies to route to a route specifying non forwarding action. This is
   803  // intended to be used on the server side for RDS requests, and corresponds to
   804  // the inline route configuration in DefaultServerListener.
   805  func RouteConfigNonForwardingAction(routeName string) *v3routepb.RouteConfiguration {
   806  	return &v3routepb.RouteConfiguration{
   807  		Name: routeName,
   808  		VirtualHosts: []*v3routepb.VirtualHost{{
   809  			// This "*" string matches on any incoming authority. This is to ensure any
   810  			// incoming RPC matches to Route_NonForwardingAction and will proceed as
   811  			// normal.
   812  			Domains: []string{"*"},
   813  			Routes: []*v3routepb.Route{{
   814  				Match: &v3routepb.RouteMatch{
   815  					PathSpecifier: &v3routepb.RouteMatch_Prefix{Prefix: "/"},
   816  				},
   817  				Action: &v3routepb.Route_NonForwardingAction{},
   818  			}}}}}
   819  }
   820  
   821  // RouteConfigFilterAction returns an xDS RouteConfig resource which specifies
   822  // to route to a route specifying route filter action. Since this is not type
   823  // non forwarding action, this should fail requests that match to this server
   824  // side.
   825  func RouteConfigFilterAction(routeName string) *v3routepb.RouteConfiguration {
   826  	return &v3routepb.RouteConfiguration{
   827  		Name: routeName,
   828  		VirtualHosts: []*v3routepb.VirtualHost{{
   829  			// This "*" string matches on any incoming authority. This is to
   830  			// ensure any incoming RPC matches to Route_Route and will fail with
   831  			// UNAVAILABLE.
   832  			Domains: []string{"*"},
   833  			Routes: []*v3routepb.Route{{
   834  				Match: &v3routepb.RouteMatch{
   835  					PathSpecifier: &v3routepb.RouteMatch_Prefix{Prefix: "/"},
   836  				},
   837  				Action: &v3routepb.Route_FilterAction{},
   838  			}}}}}
   839  }