diff --git a/api/v1alpha1/loadbalancer_types.go b/api/v1alpha1/loadbalancer_types.go index d4912aebd4a..b29c5bec21e 100644 --- a/api/v1alpha1/loadbalancer_types.go +++ b/api/v1alpha1/loadbalancer_types.go @@ -73,7 +73,6 @@ type ConsistentHash struct { // +kubebuilder:validation:Maximum=5000011 // +kubebuilder:default=65537 // +optional - // +notImplementedHide TableSize *uint64 `json:"tableSize,omitempty"` } diff --git a/internal/gatewayapi/backendtrafficpolicy.go b/internal/gatewayapi/backendtrafficpolicy.go index 1664370d512..0fae4b170c6 100644 --- a/internal/gatewayapi/backendtrafficpolicy.go +++ b/internal/gatewayapi/backendtrafficpolicy.go @@ -9,6 +9,7 @@ import ( "errors" "fmt" "math" + "math/big" "net" "sort" "strings" @@ -28,6 +29,10 @@ import ( "github.com/envoyproxy/gateway/internal/utils/regex" ) +const ( + MaxConsistentHashTableSize = 5000011 // https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/cluster/v3/cluster.proto#config-cluster-v3-cluster-maglevlbconfig +) + func (t *Translator) ProcessBackendTrafficPolicies(backendTrafficPolicies []*egv1a1.BackendTrafficPolicy, gateways []*GatewayContext, routes []RouteContext, @@ -307,7 +312,10 @@ func (t *Translator) translateBackendTrafficPolicyForRoute(policy *egv1a1.Backen } } if policy.Spec.LoadBalancer != nil { - lb = t.buildLoadBalancer(policy) + if lb, err = t.buildLoadBalancer(policy); err != nil { + err = perr.WithMessage(err, "LoadBalancer") + errs = errors.Join(errs, err) + } } if policy.Spec.ProxyProtocol != nil { pp = t.buildProxyProtocol(policy) @@ -431,7 +439,10 @@ func (t *Translator) translateBackendTrafficPolicyForGateway(policy *egv1a1.Back } } if policy.Spec.LoadBalancer != nil { - lb = t.buildLoadBalancer(policy) + if lb, err = t.buildLoadBalancer(policy); err != nil { + err = perr.WithMessage(err, "LoadBalancer") + errs = errors.Join(errs, err) + } } if policy.Spec.ProxyProtocol != nil { pp = t.buildProxyProtocol(policy) @@ -764,12 +775,17 @@ func buildRateLimitRule(rule egv1a1.RateLimitRule) (*ir.RateLimitRule, error) { return irRule, nil } -func (t *Translator) buildLoadBalancer(policy *egv1a1.BackendTrafficPolicy) *ir.LoadBalancer { +func (t *Translator) buildLoadBalancer(policy *egv1a1.BackendTrafficPolicy) (*ir.LoadBalancer, error) { var lb *ir.LoadBalancer switch policy.Spec.LoadBalancer.Type { case egv1a1.ConsistentHashLoadBalancerType: + consistentHash, err := t.buildConsistentHashLoadBalancer(policy) + if err != nil { + return nil, perr.WithMessage(err, "ConsistentHash") + } + lb = &ir.LoadBalancer{ - ConsistentHash: t.buildConsistentHashLoadBalancer(policy), + ConsistentHash: consistentHash, } case egv1a1.LeastRequestLoadBalancerType: lb = &ir.LoadBalancer{} @@ -803,24 +819,32 @@ func (t *Translator) buildLoadBalancer(policy *egv1a1.BackendTrafficPolicy) *ir. } } - return lb + return lb, nil } -func (t *Translator) buildConsistentHashLoadBalancer(policy *egv1a1.BackendTrafficPolicy) *ir.ConsistentHash { +func (t *Translator) buildConsistentHashLoadBalancer(policy *egv1a1.BackendTrafficPolicy) (*ir.ConsistentHash, error) { + consistentHash := &ir.ConsistentHash{} + + if policy.Spec.LoadBalancer.ConsistentHash.TableSize != nil { + tableSize := policy.Spec.LoadBalancer.ConsistentHash.TableSize + + if *tableSize > MaxConsistentHashTableSize || !big.NewInt(int64(*tableSize)).ProbablyPrime(0) { + return nil, fmt.Errorf("invalid TableSize value %d", *tableSize) + } + + consistentHash.TableSize = tableSize + } + switch policy.Spec.LoadBalancer.ConsistentHash.Type { case egv1a1.SourceIPConsistentHashType: - return &ir.ConsistentHash{ - SourceIP: ptr.To(true), - } + consistentHash.SourceIP = ptr.To(true) case egv1a1.HeaderConsistentHashType: - return &ir.ConsistentHash{ - Header: &ir.Header{ - Name: policy.Spec.LoadBalancer.ConsistentHash.Header.Name, - }, + consistentHash.Header = &ir.Header{ + Name: policy.Spec.LoadBalancer.ConsistentHash.Header.Name, } - default: - return &ir.ConsistentHash{} } + + return consistentHash, nil } func (t *Translator) buildProxyProtocol(policy *egv1a1.BackendTrafficPolicy) *ir.ProxyProtocol { diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-with-loadbalancer-invalid-consistent-hash-table-size.in.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-with-loadbalancer-invalid-consistent-hash-table-size.in.yaml new file mode 100644 index 00000000000..0d6bf7ed336 --- /dev/null +++ b/internal/gatewayapi/testdata/backendtrafficpolicy-with-loadbalancer-invalid-consistent-hash-table-size.in.yaml @@ -0,0 +1,83 @@ +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + namespace: envoy-gateway + name: gateway-1 + spec: + gatewayClassName: envoy-gateway-class + listeners: + - name: http + protocol: HTTP + port: 80 + allowedRoutes: + namespaces: + from: All +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-1 + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + sectionName: http + rules: + - matches: + - path: + value: "/" + backendRefs: + - name: service-1 + port: 8080 +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + namespace: default + name: httproute-2 + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - namespace: envoy-gateway + name: gateway-1 + sectionName: http + rules: + - matches: + - path: + value: "/test2" + backendRefs: + - name: service-2 + port: 8080 +backendTrafficPolicies: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: BackendTrafficPolicy + metadata: + namespace: default + name: policy-for-route + spec: + targetRef: + group: gateway.networking.k8s.io + kind: HTTPRoute + name: httproute-1 + loadBalancer: + type: ConsistentHash + consistentHash: + tableSize: 1024 +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: BackendTrafficPolicy + metadata: + namespace: default + name: policy-for-route2 + spec: + targetRef: + group: gateway.networking.k8s.io + kind: HTTPRoute + name: httproute-2 + loadBalancer: + type: ConsistentHash + consistentHash: + tableSize: 5000012 diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-with-loadbalancer-invalid-consistent-hash-table-size.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-with-loadbalancer-invalid-consistent-hash-table-size.out.yaml new file mode 100755 index 00000000000..735381efd8c --- /dev/null +++ b/internal/gatewayapi/testdata/backendtrafficpolicy-with-loadbalancer-invalid-consistent-hash-table-size.out.yaml @@ -0,0 +1,245 @@ +backendTrafficPolicies: +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: BackendTrafficPolicy + metadata: + creationTimestamp: null + name: policy-for-route + namespace: default + spec: + loadBalancer: + consistentHash: + tableSize: 1024 + type: "" + type: ConsistentHash + targetRef: + group: gateway.networking.k8s.io + kind: HTTPRoute + name: httproute-1 + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + sectionName: http + conditions: + - lastTransitionTime: null + message: 'LoadBalancer: ConsistentHash: invalid TableSize value 1024.' + reason: Invalid + status: "False" + type: Accepted + controllerName: gateway.envoyproxy.io/gatewayclass-controller +- apiVersion: gateway.envoyproxy.io/v1alpha1 + kind: BackendTrafficPolicy + metadata: + creationTimestamp: null + name: policy-for-route2 + namespace: default + spec: + loadBalancer: + consistentHash: + tableSize: 5000012 + type: "" + type: ConsistentHash + targetRef: + group: gateway.networking.k8s.io + kind: HTTPRoute + name: httproute-2 + status: + ancestors: + - ancestorRef: + group: gateway.networking.k8s.io + kind: Gateway + name: gateway-1 + namespace: envoy-gateway + sectionName: http + conditions: + - lastTransitionTime: null + message: 'LoadBalancer: ConsistentHash: invalid TableSize value 5000012.' + reason: Invalid + status: "False" + type: Accepted + controllerName: gateway.envoyproxy.io/gatewayclass-controller +gateways: +- apiVersion: gateway.networking.k8s.io/v1 + kind: Gateway + metadata: + creationTimestamp: null + name: gateway-1 + namespace: envoy-gateway + spec: + gatewayClassName: envoy-gateway-class + listeners: + - allowedRoutes: + namespaces: + from: All + name: http + port: 80 + protocol: HTTP + status: + listeners: + - attachedRoutes: 2 + conditions: + - lastTransitionTime: null + message: Sending translated listener configuration to the data plane + reason: Programmed + status: "True" + type: Programmed + - lastTransitionTime: null + message: Listener has been successfully translated + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Listener references have been resolved + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + name: http + supportedKinds: + - group: gateway.networking.k8s.io + kind: HTTPRoute + - group: gateway.networking.k8s.io + kind: GRPCRoute +httpRoutes: +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: httproute-1 + namespace: default + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + sectionName: http + rules: + - backendRefs: + - name: service-1 + port: 8080 + matches: + - path: + value: / + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway + sectionName: http +- apiVersion: gateway.networking.k8s.io/v1 + kind: HTTPRoute + metadata: + creationTimestamp: null + name: httproute-2 + namespace: default + spec: + hostnames: + - gateway.envoyproxy.io + parentRefs: + - name: gateway-1 + namespace: envoy-gateway + sectionName: http + rules: + - backendRefs: + - name: service-2 + port: 8080 + matches: + - path: + value: /test2 + status: + parents: + - conditions: + - lastTransitionTime: null + message: Route is accepted + reason: Accepted + status: "True" + type: Accepted + - lastTransitionTime: null + message: Resolved all the Object references for the Route + reason: ResolvedRefs + status: "True" + type: ResolvedRefs + controllerName: gateway.envoyproxy.io/gatewayclass-controller + parentRef: + name: gateway-1 + namespace: envoy-gateway + sectionName: http +infraIR: + envoy-gateway/gateway-1: + proxy: + listeners: + - address: null + name: envoy-gateway/gateway-1/http + ports: + - containerPort: 10080 + name: http-80 + protocol: HTTP + servicePort: 80 + metadata: + labels: + gateway.envoyproxy.io/owning-gateway-name: gateway-1 + gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway + name: envoy-gateway/gateway-1 +xdsIR: + envoy-gateway/gateway-1: + accessLog: + text: + - path: /dev/stdout + http: + - address: 0.0.0.0 + hostnames: + - '*' + isHTTP2: false + name: envoy-gateway/gateway-1/http + path: + escapedSlashesAction: UnescapeAndRedirect + mergeSlashes: true + port: 10080 + routes: + - destination: + name: httproute/default/httproute-2/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + protocol: HTTP + weight: 1 + hostname: gateway.envoyproxy.io + isHTTP2: false + name: httproute/default/httproute-2/rule/0/match/0/gateway_envoyproxy_io + pathMatch: + distinct: false + name: "" + prefix: /test2 + - destination: + name: httproute/default/httproute-1/rule/0 + settings: + - addressType: IP + endpoints: + - host: 7.7.7.7 + port: 8080 + protocol: HTTP + weight: 1 + hostname: gateway.envoyproxy.io + isHTTP2: false + name: httproute/default/httproute-1/rule/0/match/0/gateway_envoyproxy_io + pathMatch: + distinct: false + name: "" + prefix: / diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-with-loadbalancer.in.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-with-loadbalancer.in.yaml index 8dc0366ed44..f8bb9de05eb 100644 --- a/internal/gatewayapi/testdata/backendtrafficpolicy-with-loadbalancer.in.yaml +++ b/internal/gatewayapi/testdata/backendtrafficpolicy-with-loadbalancer.in.yaml @@ -108,6 +108,7 @@ backendTrafficPolicies: type: ConsistentHash consistentHash: type: SourceIP + tableSize: 524287 - apiVersion: gateway.envoyproxy.io/v1alpha1 kind: BackendTrafficPolicy metadata: diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-with-loadbalancer.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-with-loadbalancer.out.yaml index 36fe54c4648..a8a13a92d74 100644 --- a/internal/gatewayapi/testdata/backendtrafficpolicy-with-loadbalancer.out.yaml +++ b/internal/gatewayapi/testdata/backendtrafficpolicy-with-loadbalancer.out.yaml @@ -8,6 +8,7 @@ backendTrafficPolicies: spec: loadBalancer: consistentHash: + tableSize: 524287 type: SourceIP type: ConsistentHash targetRef: @@ -431,3 +432,4 @@ xdsIR: loadBalancer: consistentHash: sourceIP: true + tableSize: 524287 diff --git a/internal/ir/xds.go b/internal/ir/xds.go index 9afed373cf2..ab8bfd0107b 100644 --- a/internal/ir/xds.go +++ b/internal/ir/xds.go @@ -1651,8 +1651,9 @@ type Random struct{} // +k8s:deepcopy-gen=true type ConsistentHash struct { // Hash based on the Source IP Address - SourceIP *bool `json:"sourceIP,omitempty" yaml:"sourceIP,omitempty"` - Header *Header `json:"header,omitempty" yaml:"header,omitempty"` + SourceIP *bool `json:"sourceIP,omitempty" yaml:"sourceIP,omitempty"` + Header *Header `json:"header,omitempty" yaml:"header,omitempty"` + TableSize *uint64 `json:"tableSize,omitempty" yaml:"tableSize,omitempty"` } // Header consistent hash type settings diff --git a/internal/ir/zz_generated.deepcopy.go b/internal/ir/zz_generated.deepcopy.go index 3d3d8823fdf..303641732de 100644 --- a/internal/ir/zz_generated.deepcopy.go +++ b/internal/ir/zz_generated.deepcopy.go @@ -363,6 +363,11 @@ func (in *ConsistentHash) DeepCopyInto(out *ConsistentHash) { *out = new(Header) **out = **in } + if in.TableSize != nil { + in, out := &in.TableSize, &out.TableSize + *out = new(uint64) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConsistentHash. diff --git a/internal/xds/translator/cluster.go b/internal/xds/translator/cluster.go index a37f2a91170..1b5ed80d612 100644 --- a/internal/xds/translator/cluster.go +++ b/internal/xds/translator/cluster.go @@ -185,6 +185,14 @@ func buildXdsCluster(args *xdsClusterArgs) *clusterv3.Cluster { cluster.LbPolicy = clusterv3.Cluster_RANDOM } else if args.loadBalancer.ConsistentHash != nil { cluster.LbPolicy = clusterv3.Cluster_MAGLEV + + if args.loadBalancer.ConsistentHash.TableSize != nil { + cluster.LbConfig = &clusterv3.Cluster_MaglevLbConfig_{ + MaglevLbConfig: &clusterv3.Cluster_MaglevLbConfig{ + TableSize: &wrapperspb.UInt64Value{Value: *args.loadBalancer.ConsistentHash.TableSize}, + }, + } + } } if args.healthCheck != nil && args.healthCheck.Active != 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 index 2d8ef97efb8..3886f0cfd7d 100644 --- a/internal/xds/translator/testdata/in/xds-ir/load-balancer.yaml +++ b/internal/xds/translator/testdata/in/xds-ir/load-balancer.yaml @@ -92,3 +92,15 @@ http: - endpoints: - host: "1.2.3.4" port: 50000 + - name: "eighth-route" + hostname: "*" + traffic: + loadBalancer: + consistentHash: + tableSize: 524287 + destination: + name: "eighth-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 index d9fedf2c8d3..204667fb0b5 100644 --- a/internal/xds/translator/testdata/out/xds-ir/load-balancer.clusters.yaml +++ b/internal/xds/translator/testdata/out/xds-ir/load-balancer.clusters.yaml @@ -121,3 +121,22 @@ outlierDetection: {} perConnectionBufferLimitBytes: 32768 type: EDS +- circuitBreakers: + thresholds: + - maxRetries: 1024 + commonLbConfig: + localityWeightedLbConfig: {} + connectTimeout: 10s + dnsLookupFamily: V4_ONLY + edsClusterConfig: + edsConfig: + ads: {} + resourceApiVersion: V3 + serviceName: eighth-route-dest + lbPolicy: MAGLEV + maglevLbConfig: + tableSize: "524287" + name: eighth-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 index 0860c1852a5..e262710c4f9 100644 --- a/internal/xds/translator/testdata/out/xds-ir/load-balancer.endpoints.yaml +++ b/internal/xds/translator/testdata/out/xds-ir/load-balancer.endpoints.yaml @@ -82,3 +82,15 @@ loadBalancingWeight: 1 locality: region: seventh-route-dest/backend/0 +- clusterName: eighth-route-dest + endpoints: + - lbEndpoints: + - endpoint: + address: + socketAddress: + address: 1.2.3.4 + portValue: 50000 + loadBalancingWeight: 1 + loadBalancingWeight: 1 + locality: + region: eighth-route-dest/backend/0 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 index 080b9aaff0a..bb34436d3e1 100644 --- a/internal/xds/translator/testdata/out/xds-ir/load-balancer.routes.yaml +++ b/internal/xds/translator/testdata/out/xds-ir/load-balancer.routes.yaml @@ -60,3 +60,10 @@ headerName: name upgradeConfigs: - upgradeType: websocket + - match: + prefix: / + name: eighth-route + route: + cluster: eighth-route-dest + upgradeConfigs: + - upgradeType: websocket diff --git a/site/content/en/latest/api/extension_types.md b/site/content/en/latest/api/extension_types.md index 810eb3b8363..fe199092768 100644 --- a/site/content/en/latest/api/extension_types.md +++ b/site/content/en/latest/api/extension_types.md @@ -700,6 +700,7 @@ _Appears in:_ | --- | --- | --- | --- | | `type` | _[ConsistentHashType](#consistenthashtype)_ | true | ConsistentHashType defines the type of input to hash on. Valid Type values are "SourceIP" or "Header". | | `header` | _[Header](#header)_ | false | Header configures the header hash policy when the consistent hash type is set to Header. | +| `tableSize` | _integer_ | false | The table size for consistent hashing, must be prime number limited to 5000011. | #### ConsistentHashType