From aac826cf0b633bb077ad39831a028ab92ce1b833 Mon Sep 17 00:00:00 2001 From: Arko Dasgupta Date: Fri, 20 Oct 2023 15:14:56 -0700 Subject: [PATCH] Add LoadBalancer IR to HTTPRoute (#2019) * Add LoadBalancer IR to HTTPRoute Relates to https://github.com/envoyproxy/gateway/issues/1105 Signed-off-by: Arko Dasgupta * bump coverage Signed-off-by: Arko Dasgupta --------- Signed-off-by: Arko Dasgupta --- internal/ir/xds.go | 64 +++++++++++ internal/ir/xds_test.go | 45 ++++++++ internal/ir/zz_generated.deepcopy.go | 105 ++++++++++++++++++ internal/xds/translator/accesslog.go | 2 +- internal/xds/translator/authentication.go | 2 +- internal/xds/translator/cluster.go | 29 +++-- internal/xds/translator/cluster_test.go | 8 +- internal/xds/translator/ratelimit.go | 2 +- internal/xds/translator/route.go | 27 ++++- .../testdata/in/xds-ir/load-balancer.yaml | 48 ++++++++ .../out/xds-ir/load-balancer.clusters.yaml | 55 +++++++++ .../out/xds-ir/load-balancer.endpoints.yaml | 44 ++++++++ .../out/xds-ir/load-balancer.listeners.yaml | 33 ++++++ .../out/xds-ir/load-balancer.routes.yaml | 30 +++++ internal/xds/translator/tracing.go | 2 +- internal/xds/translator/translator.go | 16 +-- internal/xds/translator/translator_test.go | 3 + 17 files changed, 494 insertions(+), 21 deletions(-) create mode 100644 internal/xds/translator/testdata/in/xds-ir/load-balancer.yaml create mode 100644 internal/xds/translator/testdata/out/xds-ir/load-balancer.clusters.yaml create mode 100644 internal/xds/translator/testdata/out/xds-ir/load-balancer.endpoints.yaml create mode 100644 internal/xds/translator/testdata/out/xds-ir/load-balancer.listeners.yaml create mode 100644 internal/xds/translator/testdata/out/xds-ir/load-balancer.routes.yaml diff --git a/internal/ir/xds.go b/internal/ir/xds.go index 39c7ecae50b..5cbe448d140 100644 --- a/internal/ir/xds.go +++ b/internal/ir/xds.go @@ -45,6 +45,7 @@ var ( ErrAddHeaderDuplicate = errors.New("header modifier filter attempts to add the same header more than once (case insensitive)") ErrRemoveHeaderDuplicate = errors.New("header modifier filter attempts to remove the same header more than once (case insensitive)") ErrRequestAuthenRequiresJwt = errors.New("jwt field is required when request authentication is set") + ErrLoadBalancerInvalid = errors.New("loadBalancer setting is invalid, only one setting can be set") ) // Xds holds the intermediate representation of a Gateway and is @@ -276,6 +277,8 @@ type HTTPRoute struct { RequestAuthentication *RequestAuthentication `json:"requestAuthentication,omitempty" yaml:"requestAuthentication,omitempty"` // Timeout is the time until which entire response is received from the upstream. Timeout *metav1.Duration `json:"timeout,omitempty" yaml:"timeout,omitempty"` + // load balancer policy to use when routing to the backend endpoints. + LoadBalancer *LoadBalancer `json:"loadBalancer,omitempty" yaml:"loadBalancer,omitempty"` // ExtensionRefs holds unstructured resources that were introduced by an extension and used on the HTTPRoute as extensionRef filters ExtensionRefs []*UnstructuredRef `json:"extensionRefs,omitempty" yaml:"extensionRefs,omitempty"` } @@ -420,6 +423,12 @@ func (h HTTPRoute) Validate() error { } } } + if h.LoadBalancer != nil { + if err := h.LoadBalancer.Validate(); err != nil { + errs = multierror.Append(errs, err) + } + } + return errs } @@ -952,3 +961,58 @@ type TCPKeepalive struct { // Defaults to `75s`. Interval *uint32 `json:"interval,omitempty" yaml:"interval,omitempty"` } + +// LoadBalancer defines the load balancer settings. +// +k8s:deepcopy-gen=true +type LoadBalancer struct { + // RoundRobin load balacning policy + RoundRobin *RoundRobin `json:"roundRobin,omitempty" yaml:"roundRobin,omitempty"` + // LeastRequest load balancer policy + LeastRequest *LeastRequest `json:"leastRequest,omitempty" yaml:"leastRequest,omitempty"` + // Random load balancer policy + Random *Random `json:"random,omitempty" yaml:"random,omitempty"` + // ConsistentHash load balancer policy + ConsistentHash *ConsistentHash `json:"consistentHash,omitempty" yaml:"consistentHash,omitempty"` +} + +// Validate the fields within the LoadBalancer structure +func (l *LoadBalancer) Validate() error { + var errs error + matchCount := 0 + if l.RoundRobin != nil { + matchCount++ + } + if l.LeastRequest != nil { + matchCount++ + } + if l.Random != nil { + matchCount++ + } + if l.ConsistentHash != nil { + matchCount++ + } + if matchCount != 1 { + errs = multierror.Append(errs, ErrLoadBalancerInvalid) + } + + return errs +} + +// RoundRobin load balancer settings +// +k8s:deepcopy-gen=true +type RoundRobin struct{} + +// LeastRequest load balancer settings +// +k8s:deepcopy-gen=true +type LeastRequest struct{} + +// Random load balancer settings +// +k8s:deepcopy-gen=true +type Random struct{} + +// ConsistentHash load balancer settings +// +k8s:deepcopy-gen=true +type ConsistentHash struct { + // Hash based on the Source IP Address + SourceIP *bool `json:"sourceIP,omitempty" yaml:"sourceIP,omitempty"` +} diff --git a/internal/ir/xds_test.go b/internal/ir/xds_test.go index 1e91aaecfa2..5d5344b8e02 100644 --- a/internal/ir/xds_test.go +++ b/internal/ir/xds_test.go @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/require" egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1" + "github.com/envoyproxy/gateway/internal/utils/ptr" ) var ( @@ -1110,6 +1111,50 @@ func TestValidateJwtRequestAuthentication(t *testing.T) { } } +func TestValidateLoadBalancer(t *testing.T) { + tests := []struct { + name string + input LoadBalancer + want error + }{ + { + name: "random", + input: LoadBalancer{ + Random: &Random{}, + }, + want: nil, + }, + { + name: "consistent hash", + input: LoadBalancer{ + ConsistentHash: &ConsistentHash{ + SourceIP: ptr.To(true), + }, + }, + want: nil, + }, + + { + name: "least request and random set", + input: LoadBalancer{ + Random: &Random{}, + LeastRequest: &LeastRequest{}, + }, + want: ErrLoadBalancerInvalid, + }, + } + for i := range tests { + test := tests[i] + t.Run(test.name, func(t *testing.T) { + if test.want == nil { + require.NoError(t, test.input.Validate()) + } else { + require.EqualError(t, test.input.Validate(), test.want.Error()) + } + }) + } +} + func TestPrintable(t *testing.T) { tests := []struct { name string diff --git a/internal/ir/zz_generated.deepcopy.go b/internal/ir/zz_generated.deepcopy.go index 4bea1e327ae..6d8132578b1 100644 --- a/internal/ir/zz_generated.deepcopy.go +++ b/internal/ir/zz_generated.deepcopy.go @@ -77,6 +77,26 @@ func (in *AddHeader) DeepCopy() *AddHeader { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ConsistentHash) DeepCopyInto(out *ConsistentHash) { + *out = *in + if in.SourceIP != nil { + in, out := &in.SourceIP, &out.SourceIP + *out = new(bool) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConsistentHash. +func (in *ConsistentHash) DeepCopy() *ConsistentHash { + if in == nil { + return nil + } + out := new(ConsistentHash) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *DestinationEndpoint) DeepCopyInto(out *DestinationEndpoint) { *out = *in @@ -385,6 +405,11 @@ func (in *HTTPRoute) DeepCopyInto(out *HTTPRoute) { *out = new(v1.Duration) **out = **in } + if in.LoadBalancer != nil { + in, out := &in.LoadBalancer, &out.LoadBalancer + *out = new(LoadBalancer) + (*in).DeepCopyInto(*out) + } if in.ExtensionRefs != nil { in, out := &in.ExtensionRefs, &out.ExtensionRefs *out = make([]*UnstructuredRef, len(*in)) @@ -526,6 +551,21 @@ func (in *JwtRequestAuthentication) DeepCopy() *JwtRequestAuthentication { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LeastRequest) DeepCopyInto(out *LeastRequest) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LeastRequest. +func (in *LeastRequest) DeepCopy() *LeastRequest { + if in == nil { + return nil + } + out := new(LeastRequest) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ListenerPort) DeepCopyInto(out *ListenerPort) { *out = *in @@ -541,6 +581,41 @@ func (in *ListenerPort) DeepCopy() *ListenerPort { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LoadBalancer) DeepCopyInto(out *LoadBalancer) { + *out = *in + if in.RoundRobin != nil { + in, out := &in.RoundRobin, &out.RoundRobin + *out = new(RoundRobin) + **out = **in + } + if in.LeastRequest != nil { + in, out := &in.LeastRequest, &out.LeastRequest + *out = new(LeastRequest) + **out = **in + } + if in.Random != nil { + in, out := &in.Random, &out.Random + *out = new(Random) + **out = **in + } + if in.ConsistentHash != nil { + in, out := &in.ConsistentHash, &out.ConsistentHash + *out = new(ConsistentHash) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LoadBalancer. +func (in *LoadBalancer) DeepCopy() *LoadBalancer { + if in == nil { + return nil + } + out := new(LoadBalancer) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Metrics) DeepCopyInto(out *Metrics) { *out = *in @@ -647,6 +722,21 @@ func (in *ProxyListener) DeepCopy() *ProxyListener { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Random) DeepCopyInto(out *Random) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Random. +func (in *Random) DeepCopy() *Random { + if in == nil { + return nil + } + out := new(Random) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RateLimit) DeepCopyInto(out *RateLimit) { *out = *in @@ -778,6 +868,21 @@ func (in *RequestAuthentication) DeepCopy() *RequestAuthentication { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RoundRobin) DeepCopyInto(out *RoundRobin) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RoundRobin. +func (in *RoundRobin) DeepCopy() *RoundRobin { + if in == nil { + return nil + } + out := new(RoundRobin) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *RouteDestination) DeepCopyInto(out *RouteDestination) { *out = *in diff --git a/internal/xds/translator/accesslog.go b/internal/xds/translator/accesslog.go index 07b9904a4d4..25b627e4b81 100644 --- a/internal/xds/translator/accesslog.go +++ b/internal/xds/translator/accesslog.go @@ -245,7 +245,7 @@ func processClusterForAccessLog(tCtx *types.ResourceVersionTable, al *ir.AccessL Weight: ptr.To(uint32(1)), Endpoints: []*ir.DestinationEndpoint{ir.NewDestEndpoint(otel.Host, otel.Port)}, } - if err := addXdsCluster(tCtx, addXdsClusterArgs{ + if err := addXdsCluster(tCtx, &xdsClusterArgs{ name: clusterName, settings: []*ir.DestinationSetting{ds}, tSocket: nil, diff --git a/internal/xds/translator/authentication.go b/internal/xds/translator/authentication.go index 899b3e2659d..80426d4f320 100644 --- a/internal/xds/translator/authentication.go +++ b/internal/xds/translator/authentication.go @@ -264,7 +264,7 @@ func createJwksClusters(tCtx *types.ResourceVersionTable, routes []*ir.HTTPRoute if err != nil { return err } - if err := addXdsCluster(tCtx, addXdsClusterArgs{ + if err := addXdsCluster(tCtx, &xdsClusterArgs{ name: jwks.name, settings: []*ir.DestinationSetting{ds}, tSocket: tSocket, diff --git a/internal/xds/translator/cluster.go b/internal/xds/translator/cluster.go index 50ddbc3c1eb..0b30c07176c 100644 --- a/internal/xds/translator/cluster.go +++ b/internal/xds/translator/cluster.go @@ -27,11 +27,10 @@ const ( tcpClusterPerConnectionBufferLimitBytes = 32768 ) -func buildXdsCluster(clusterName string, tSocket *corev3.TransportSocket, protocol ProtocolType, endpointType EndpointType) *clusterv3.Cluster { +func buildXdsCluster(args *xdsClusterArgs) *clusterv3.Cluster { cluster := &clusterv3.Cluster{ - Name: clusterName, + Name: args.name, ConnectTimeout: durationpb.New(10 * time.Second), - LbPolicy: clusterv3.Cluster_LEAST_REQUEST, DnsLookupFamily: clusterv3.Cluster_V4_ONLY, CommonLbConfig: &clusterv3.Cluster_CommonLbConfig{ LocalityConfigSpecifier: &clusterv3.Cluster_CommonLbConfig_LocalityWeightedLbConfig_{ @@ -40,14 +39,14 @@ func buildXdsCluster(clusterName string, tSocket *corev3.TransportSocket, protoc PerConnectionBufferLimitBytes: wrapperspb.UInt32(tcpClusterPerConnectionBufferLimitBytes), } - if tSocket != nil { - cluster.TransportSocket = tSocket + if args.tSocket != nil { + cluster.TransportSocket = args.tSocket } - if endpointType == Static { + if args.endpointType == Static { cluster.ClusterDiscoveryType = &clusterv3.Cluster_Type{Type: clusterv3.Cluster_EDS} cluster.EdsClusterConfig = &clusterv3.Cluster_EdsClusterConfig{ - ServiceName: clusterName, + ServiceName: args.name, EdsConfig: &corev3.ConfigSource{ ResourceApiVersion: resource.DefaultAPIVersion, ConfigSourceSpecifier: &corev3.ConfigSource_Ads{ @@ -61,10 +60,24 @@ func buildXdsCluster(clusterName string, tSocket *corev3.TransportSocket, protoc cluster.RespectDnsTtl = true } - if protocol == HTTP2 { + if args.protocol == HTTP2 { cluster.TypedExtensionProtocolOptions = buildTypedExtensionProtocolOptions() } + // Set Load Balancer policy + //nolint:gocritic + if args.loadBalancer == nil { + cluster.LbPolicy = clusterv3.Cluster_LEAST_REQUEST + } else if args.loadBalancer.LeastRequest != nil { + cluster.LbPolicy = clusterv3.Cluster_LEAST_REQUEST + } else if args.loadBalancer.RoundRobin != nil { + cluster.LbPolicy = clusterv3.Cluster_ROUND_ROBIN + } else if args.loadBalancer.Random != nil { + cluster.LbPolicy = clusterv3.Cluster_RANDOM + } else if args.loadBalancer.ConsistentHash != nil { + cluster.LbPolicy = clusterv3.Cluster_MAGLEV + } + return cluster } diff --git a/internal/xds/translator/cluster_test.go b/internal/xds/translator/cluster_test.go index b9013086ca7..00ade71f487 100644 --- a/internal/xds/translator/cluster_test.go +++ b/internal/xds/translator/cluster_test.go @@ -28,7 +28,13 @@ const ( func TestBuildXdsCluster(t *testing.T) { bootstrapXdsCluster := getXdsClusterObjFromBootstrap(t) - dynamicXdsCluster := buildXdsCluster(bootstrapXdsCluster.Name, bootstrapXdsCluster.TransportSocket, HTTP2, DefaultEndpointType) + args := &xdsClusterArgs{ + name: bootstrapXdsCluster.Name, + tSocket: bootstrapXdsCluster.TransportSocket, + protocol: HTTP2, + endpointType: DefaultEndpointType, + } + dynamicXdsCluster := buildXdsCluster(args) require.Equal(t, bootstrapXdsCluster.Name, dynamicXdsCluster.Name) require.Equal(t, bootstrapXdsCluster.ClusterDiscoveryType, dynamicXdsCluster.ClusterDiscoveryType) diff --git a/internal/xds/translator/ratelimit.go b/internal/xds/translator/ratelimit.go index fa4f7f69dd3..36d5c4aa020 100644 --- a/internal/xds/translator/ratelimit.go +++ b/internal/xds/translator/ratelimit.go @@ -440,7 +440,7 @@ func (t *Translator) createRateLimitServiceCluster(tCtx *types.ResourceVersionTa return err } - if err := addXdsCluster(tCtx, addXdsClusterArgs{ + if err := addXdsCluster(tCtx, &xdsClusterArgs{ name: clusterName, settings: []*ir.DestinationSetting{ds}, tSocket: tSocket, diff --git a/internal/xds/translator/route.go b/internal/xds/translator/route.go index 962ef058930..6b273b25027 100644 --- a/internal/xds/translator/route.go +++ b/internal/xds/translator/route.go @@ -66,8 +66,13 @@ func buildXdsRoute(httpRoute *ir.HTTPRoute) *routev3.Route { } } + // Hash Policy + if router.GetRoute() != nil { + router.GetRoute().HashPolicy = buildHashPolicy(httpRoute) + } + // Timeouts - if httpRoute.Timeout != nil { + if router.GetRoute() != nil && httpRoute.Timeout != nil { router.GetRoute().Timeout = durationpb.New(httpRoute.Timeout.Duration) } @@ -330,3 +335,23 @@ func buildXdsAddedHeaders(headersToAdd []ir.AddHeader) []*corev3.HeaderValueOpti return headerValueOptions } + +func buildHashPolicy(httpRoute *ir.HTTPRoute) []*routev3.RouteAction_HashPolicy { + // Return early + if httpRoute == nil || httpRoute.LoadBalancer == nil || httpRoute.LoadBalancer.ConsistentHash == nil { + return nil + } + + if httpRoute.LoadBalancer.ConsistentHash.SourceIP != nil && *httpRoute.LoadBalancer.ConsistentHash.SourceIP { + hashPolicy := &routev3.RouteAction_HashPolicy{ + PolicySpecifier: &routev3.RouteAction_HashPolicy_ConnectionProperties_{ + ConnectionProperties: &routev3.RouteAction_HashPolicy_ConnectionProperties{ + SourceIp: true, + }, + }, + } + return []*routev3.RouteAction_HashPolicy{hashPolicy} + } + + return nil +} diff --git a/internal/xds/translator/testdata/in/xds-ir/load-balancer.yaml b/internal/xds/translator/testdata/in/xds-ir/load-balancer.yaml new file mode 100644 index 00000000000..965ce41425c --- /dev/null +++ b/internal/xds/translator/testdata/in/xds-ir/load-balancer.yaml @@ -0,0 +1,48 @@ +http: +- name: "first-listener" + address: "0.0.0.0" + port: 10080 + hostnames: + - "*" + routes: + - name: "first-route" + hostname: "*" + loadBalancer: + roundRobin: {} + destination: + name: "first-route-dest" + settings: + - endpoints: + - host: "1.2.3.4" + port: 50000 + - name: "second-route" + hostname: "*" + loadBalancer: + random: {} + destination: + name: "second-route-dest" + settings: + - endpoints: + - host: "1.2.3.4" + port: 50000 + - name: "third-route" + hostname: "*" + loadBalancer: + leastRequest: {} + destination: + name: "third-route-dest" + settings: + - endpoints: + - host: "1.2.3.4" + port: 50000 + - name: "fourth-route" + hostname: "*" + loadBalancer: + consistentHash: + sourceIP: true + destination: + name: "fourth-route-dest" + settings: + - endpoints: + - host: "1.2.3.4" + port: 50000 diff --git a/internal/xds/translator/testdata/out/xds-ir/load-balancer.clusters.yaml b/internal/xds/translator/testdata/out/xds-ir/load-balancer.clusters.yaml new file mode 100644 index 00000000000..097e0262991 --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/load-balancer.clusters.yaml @@ -0,0 +1,55 @@ +- commonLbConfig: + localityWeightedLbConfig: {} + connectTimeout: 10s + dnsLookupFamily: V4_ONLY + edsClusterConfig: + edsConfig: + ads: {} + resourceApiVersion: V3 + serviceName: first-route-dest + name: first-route-dest + outlierDetection: {} + perConnectionBufferLimitBytes: 32768 + type: EDS +- commonLbConfig: + localityWeightedLbConfig: {} + connectTimeout: 10s + dnsLookupFamily: V4_ONLY + edsClusterConfig: + edsConfig: + ads: {} + resourceApiVersion: V3 + serviceName: second-route-dest + lbPolicy: RANDOM + name: second-route-dest + outlierDetection: {} + perConnectionBufferLimitBytes: 32768 + type: EDS +- commonLbConfig: + localityWeightedLbConfig: {} + connectTimeout: 10s + dnsLookupFamily: V4_ONLY + edsClusterConfig: + edsConfig: + ads: {} + resourceApiVersion: V3 + serviceName: third-route-dest + lbPolicy: LEAST_REQUEST + name: third-route-dest + outlierDetection: {} + perConnectionBufferLimitBytes: 32768 + type: EDS +- commonLbConfig: + localityWeightedLbConfig: {} + connectTimeout: 10s + dnsLookupFamily: V4_ONLY + edsClusterConfig: + edsConfig: + ads: {} + resourceApiVersion: V3 + serviceName: fourth-route-dest + lbPolicy: MAGLEV + name: fourth-route-dest + outlierDetection: {} + perConnectionBufferLimitBytes: 32768 + type: EDS diff --git a/internal/xds/translator/testdata/out/xds-ir/load-balancer.endpoints.yaml b/internal/xds/translator/testdata/out/xds-ir/load-balancer.endpoints.yaml new file mode 100644 index 00000000000..ee35a1bf520 --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/load-balancer.endpoints.yaml @@ -0,0 +1,44 @@ +- clusterName: first-route-dest + endpoints: + - lbEndpoints: + - endpoint: + address: + socketAddress: + address: 1.2.3.4 + portValue: 50000 + loadBalancingWeight: 1 + loadBalancingWeight: 1 + locality: {} +- clusterName: second-route-dest + endpoints: + - lbEndpoints: + - endpoint: + address: + socketAddress: + address: 1.2.3.4 + portValue: 50000 + loadBalancingWeight: 1 + loadBalancingWeight: 1 + locality: {} +- clusterName: third-route-dest + endpoints: + - lbEndpoints: + - endpoint: + address: + socketAddress: + address: 1.2.3.4 + portValue: 50000 + loadBalancingWeight: 1 + loadBalancingWeight: 1 + locality: {} +- clusterName: fourth-route-dest + endpoints: + - lbEndpoints: + - endpoint: + address: + socketAddress: + address: 1.2.3.4 + portValue: 50000 + loadBalancingWeight: 1 + loadBalancingWeight: 1 + locality: {} diff --git a/internal/xds/translator/testdata/out/xds-ir/load-balancer.listeners.yaml b/internal/xds/translator/testdata/out/xds-ir/load-balancer.listeners.yaml new file mode 100644 index 00000000000..73ee1b42ef6 --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/load-balancer.listeners.yaml @@ -0,0 +1,33 @@ +- address: + socketAddress: + address: 0.0.0.0 + portValue: 10080 + defaultFilterChain: + filters: + - name: envoy.filters.network.http_connection_manager + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + commonHttpProtocolOptions: + headersWithUnderscoresAction: REJECT_REQUEST + http2ProtocolOptions: + initialConnectionWindowSize: 1048576 + initialStreamWindowSize: 65536 + maxConcurrentStreams: 100 + httpFilters: + - name: envoy.filters.http.router + typedConfig: + '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router + mergeSlashes: true + normalizePath: true + pathWithEscapedSlashesAction: UNESCAPE_AND_REDIRECT + rds: + configSource: + ads: {} + resourceApiVersion: V3 + routeConfigName: first-listener + statPrefix: http + upgradeConfigs: + - upgradeType: websocket + useRemoteAddress: true + name: first-listener + perConnectionBufferLimitBytes: 32768 diff --git a/internal/xds/translator/testdata/out/xds-ir/load-balancer.routes.yaml b/internal/xds/translator/testdata/out/xds-ir/load-balancer.routes.yaml new file mode 100644 index 00000000000..214600b6262 --- /dev/null +++ b/internal/xds/translator/testdata/out/xds-ir/load-balancer.routes.yaml @@ -0,0 +1,30 @@ +- ignorePortInHostMatching: true + name: first-listener + virtualHosts: + - domains: + - '*' + name: first-listener/* + routes: + - match: + prefix: / + name: first-route + route: + cluster: first-route-dest + - match: + prefix: / + name: second-route + route: + cluster: second-route-dest + - match: + prefix: / + name: third-route + route: + cluster: third-route-dest + - match: + prefix: / + name: fourth-route + route: + cluster: fourth-route-dest + hashPolicy: + - connectionProperties: + sourceIp: true diff --git a/internal/xds/translator/tracing.go b/internal/xds/translator/tracing.go index a95118c2ffd..53a7c46a885 100644 --- a/internal/xds/translator/tracing.go +++ b/internal/xds/translator/tracing.go @@ -127,7 +127,7 @@ func processClusterForTracing(tCtx *types.ResourceVersionTable, tracing *ir.Trac Weight: ptr.To(uint32(1)), Endpoints: []*ir.DestinationEndpoint{ir.NewDestEndpoint(tracing.Provider.Host, uint32(tracing.Provider.Port))}, } - if err := addXdsCluster(tCtx, addXdsClusterArgs{ + if err := addXdsCluster(tCtx, &xdsClusterArgs{ name: clusterName, settings: []*ir.DestinationSetting{ds}, tSocket: nil, diff --git a/internal/xds/translator/translator.go b/internal/xds/translator/translator.go index 5aa6deeef2a..a72b493e986 100644 --- a/internal/xds/translator/translator.go +++ b/internal/xds/translator/translator.go @@ -211,12 +211,13 @@ func (t *Translator) processHTTPListenerXdsTranslation(tCtx *types.ResourceVersi vHost.Routes = append(vHost.Routes, xdsRoute) if httpRoute.Destination != nil { - if err := addXdsCluster(tCtx, addXdsClusterArgs{ + if err := addXdsCluster(tCtx, &xdsClusterArgs{ name: httpRoute.Destination.Name, settings: httpRoute.Destination.Settings, tSocket: nil, protocol: protocol, endpointType: Static, + loadBalancer: httpRoute.LoadBalancer, }); err != nil && !errors.Is(err, ErrXdsClusterExists) { return err } @@ -224,7 +225,7 @@ func (t *Translator) processHTTPListenerXdsTranslation(tCtx *types.ResourceVersi if httpRoute.Mirrors != nil { for _, mirrorDest := range httpRoute.Mirrors { - if err := addXdsCluster(tCtx, addXdsClusterArgs{ + if err := addXdsCluster(tCtx, &xdsClusterArgs{ name: mirrorDest.Name, settings: mirrorDest.Settings, tSocket: nil, @@ -270,7 +271,7 @@ func (t *Translator) processHTTPListenerXdsTranslation(tCtx *types.ResourceVersi func processTCPListenerXdsTranslation(tCtx *types.ResourceVersionTable, tcpListeners []*ir.TCPListener, accesslog *ir.AccessLog) error { for _, tcpListener := range tcpListeners { // 1:1 between IR TCPListener and xDS Cluster - if err := addXdsCluster(tCtx, addXdsClusterArgs{ + if err := addXdsCluster(tCtx, &xdsClusterArgs{ name: tcpListener.Destination.Name, settings: tcpListener.Destination.Settings, tSocket: nil, @@ -307,7 +308,7 @@ func processTCPListenerXdsTranslation(tCtx *types.ResourceVersionTable, tcpListe func processUDPListenerXdsTranslation(tCtx *types.ResourceVersionTable, udpListeners []*ir.UDPListener, accesslog *ir.AccessLog) error { for _, udpListener := range udpListeners { // 1:1 between IR UDPListener and xDS Cluster - if err := addXdsCluster(tCtx, addXdsClusterArgs{ + if err := addXdsCluster(tCtx, &xdsClusterArgs{ name: udpListener.Destination.Name, settings: udpListener.Destination.Settings, tSocket: nil, @@ -414,13 +415,13 @@ func findXdsEndpoint(tCtx *types.ResourceVersionTable, name string) *endpointv3. return nil } -func addXdsCluster(tCtx *types.ResourceVersionTable, args addXdsClusterArgs) error { +func addXdsCluster(tCtx *types.ResourceVersionTable, args *xdsClusterArgs) error { // Return early if cluster with the same name exists if c := findXdsCluster(tCtx, args.name); c != nil { return ErrXdsClusterExists } - xdsCluster := buildXdsCluster(args.name, args.tSocket, args.protocol, args.endpointType) + xdsCluster := buildXdsCluster(args) xdsEndpoints := buildXdsClusterLoadAssignment(args.name, args.settings) // Use EDS for static endpoints if args.endpointType == Static { @@ -436,12 +437,13 @@ func addXdsCluster(tCtx *types.ResourceVersionTable, args addXdsClusterArgs) err return nil } -type addXdsClusterArgs struct { +type xdsClusterArgs struct { name string settings []*ir.DestinationSetting tSocket *corev3.TransportSocket protocol ProtocolType endpointType EndpointType + loadBalancer *ir.LoadBalancer } type ProtocolType int diff --git a/internal/xds/translator/translator_test.go b/internal/xds/translator/translator_test.go index dacf70abd30..17d774d27b4 100644 --- a/internal/xds/translator/translator_test.go +++ b/internal/xds/translator/translator_test.go @@ -187,6 +187,9 @@ func TestTranslateXds(t *testing.T) { { name: "listener-tcp-keepalive", }, + { + name: "load-balancer", + }, } for _, tc := range testCases {