This is an automated email from the ASF dual-hosted git repository.

ronething pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix-ingress-controller.git


The following commit(s) were added to refs/heads/master by this push:
     new ec8624b1 fix: handle httproute multi backend refs (#2540)
ec8624b1 is described below

commit ec8624b11d5c71d6904c6f9b292b260794db3e5f
Author: Ashing Zheng <[email protected]>
AuthorDate: Thu Sep 4 15:20:28 2025 +0800

    fix: handle httproute multi backend refs (#2540)
    
    Signed-off-by: Ashing Zheng <[email protected]>
    Co-authored-by: Copilot <[email protected]>
---
 go.mod                                  |  4 +-
 internal/adc/translator/httproute.go    | 80 ++++++++++++++++++++++++++++-----
 test/e2e/framework/manifests/nginx.yaml |  2 +-
 test/e2e/framework/nginx.go             |  1 +
 test/e2e/gatewayapi/httproute.go        |  2 +
 5 files changed, 75 insertions(+), 14 deletions(-)

diff --git a/go.mod b/go.mod
index 6cd78f73..11483ab9 100644
--- a/go.mod
+++ b/go.mod
@@ -10,7 +10,9 @@ require (
        github.com/gavv/httpexpect/v2 v2.16.0
        github.com/go-logr/logr v1.4.2
        github.com/go-logr/zapr v1.3.0
+       github.com/google/go-cmp v0.6.0
        github.com/google/uuid v1.6.0
+       github.com/gorilla/websocket v1.5.1
        github.com/gruntwork-io/terratest v0.50.0
        github.com/hashicorp/go-memdb v1.3.4
        github.com/incubator4/go-resty-expr v0.1.1
@@ -108,11 +110,9 @@ require (
        github.com/golang/protobuf v1.5.4 // indirect
        github.com/google/cel-go v0.20.1 // indirect
        github.com/google/gnostic-models v0.6.8 // indirect
-       github.com/google/go-cmp v0.6.0 // indirect
        github.com/google/go-querystring v1.1.0 // indirect
        github.com/google/gofuzz v1.2.0 // indirect
        github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 // indirect
-       github.com/gorilla/websocket v1.5.1 // indirect
        github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect
        github.com/gruntwork-io/go-commons v0.8.0 // indirect
        github.com/hashicorp/errwrap v1.1.0 // indirect
diff --git a/internal/adc/translator/httproute.go 
b/internal/adc/translator/httproute.go
index 4c3f6426..90816258 100644
--- a/internal/adc/translator/httproute.go
+++ b/internal/adc/translator/httproute.go
@@ -33,6 +33,7 @@ import (
 
        adctypes "github.com/apache/apisix-ingress-controller/api/adc"
        "github.com/apache/apisix-ingress-controller/api/v1alpha1"
+       apiv2 "github.com/apache/apisix-ingress-controller/api/v2"
        "github.com/apache/apisix-ingress-controller/internal/controller/label"
        "github.com/apache/apisix-ingress-controller/internal/id"
        "github.com/apache/apisix-ingress-controller/internal/provider"
@@ -466,32 +467,89 @@ func (t *Translator) TranslateHTTPRoute(tctx 
*provider.TranslateContext, httpRou
        labels := label.GenLabel(httpRoute)
 
        for ruleIndex, rule := range rules {
-               upstream := adctypes.NewDefaultUpstream()
-               var backendErr error
+               service := adctypes.NewDefaultService()
+               service.Labels = labels
+
+               service.Name = 
adctypes.ComposeServiceNameWithRule(httpRoute.Namespace, httpRoute.Name, 
fmt.Sprintf("%d", ruleIndex))
+               service.ID = id.GenID(service.Name)
+               service.Hosts = hosts
+
+               var (
+                       upstreams         = make([]*adctypes.Upstream, 0)
+                       weightedUpstreams = 
make([]adctypes.TrafficSplitConfigRuleWeightedUpstream, 0)
+                       backendErr        error
+               )
+
                for _, backend := range rule.BackendRefs {
                        if backend.Namespace == nil {
                                namespace := 
gatewayv1.Namespace(httpRoute.Namespace)
                                backend.Namespace = &namespace
                        }
+                       upstream := adctypes.NewDefaultUpstream()
                        upNodes, err := t.translateBackendRef(tctx, 
backend.BackendRef, DefaultEndpointFilter)
                        if err != nil {
                                backendErr = err
                                continue
                        }
+                       if len(upNodes) == 0 {
+                               continue
+                       }
+
                        
t.AttachBackendTrafficPolicyToUpstream(backend.BackendRef, 
tctx.BackendTrafficPolicies, upstream)
-                       upstream.Nodes = append(upstream.Nodes, upNodes...)
+                       upstream.Nodes = upNodes
+                       upstreams = append(upstreams, upstream)
                }
 
-               // todo: support multiple backends
-               service := adctypes.NewDefaultService()
-               service.Labels = labels
+               // Handle multiple backends with traffic-split plugin
+               if len(upstreams) == 0 {
+                       // Create a default upstream if no valid backends
+                       upstream := adctypes.NewDefaultUpstream()
+                       service.Upstream = upstream
+               } else if len(upstreams) == 1 {
+                       // Single backend - use directly as service upstream
+                       service.Upstream = upstreams[0]
+               } else {
+                       // Multiple backends - use traffic-split plugin
+                       service.Upstream = upstreams[0]
+                       upstreams = upstreams[1:]
+
+                       // Set weight in traffic-split for the default upstream
+                       weight := apiv2.DefaultWeight
+                       if rule.BackendRefs[0].Weight != nil {
+                               weight = int(*rule.BackendRefs[0].Weight)
+                       }
+                       weightedUpstreams = append(weightedUpstreams, 
adctypes.TrafficSplitConfigRuleWeightedUpstream{
+                               Weight: weight,
+                       })
 
-               service.Name = 
adctypes.ComposeServiceNameWithRule(httpRoute.Namespace, httpRoute.Name, 
fmt.Sprintf("%d", ruleIndex))
-               service.ID = id.GenID(service.Name)
-               service.Hosts = hosts
-               service.Upstream = upstream
+                       // Set other upstreams in traffic-split
+                       for i, upstream := range upstreams {
+                               weight := apiv2.DefaultWeight
+                               // get weight from the backend refs starting 
from the second backend
+                               if i+1 < len(rule.BackendRefs) && 
rule.BackendRefs[i+1].Weight != nil {
+                                       weight = 
int(*rule.BackendRefs[i+1].Weight)
+                               }
+                               weightedUpstreams = append(weightedUpstreams, 
adctypes.TrafficSplitConfigRuleWeightedUpstream{
+                                       Upstream: upstream,
+                                       Weight:   weight,
+                               })
+                       }
+
+                       if len(weightedUpstreams) > 0 {
+                               if service.Plugins == nil {
+                                       service.Plugins = make(map[string]any)
+                               }
+                               service.Plugins["traffic-split"] = 
&adctypes.TrafficSplitConfig{
+                                       Rules: 
[]adctypes.TrafficSplitConfigRule{
+                                               {
+                                                       WeightedUpstreams: 
weightedUpstreams,
+                                               },
+                                       },
+                               }
+                       }
+               }
 
-               if backendErr != nil && len(upstream.Nodes) == 0 {
+               if backendErr != nil && (service.Upstream == nil || 
len(service.Upstream.Nodes) == 0) {
                        if service.Plugins == nil {
                                service.Plugins = make(map[string]any)
                        }
diff --git a/test/e2e/framework/manifests/nginx.yaml 
b/test/e2e/framework/manifests/nginx.yaml
index c0d97373..7fb93f08 100644
--- a/test/e2e/framework/manifests/nginx.yaml
+++ b/test/e2e/framework/manifests/nginx.yaml
@@ -44,7 +44,7 @@ kind: Deployment
 metadata:
   name: nginx
 spec:
-  replicas: 1
+  replicas: {{ .Replicas | default 1 }}
   selector:
     matchLabels:
       app: nginx
diff --git a/test/e2e/framework/nginx.go b/test/e2e/framework/nginx.go
index ae179597..9612c4e3 100644
--- a/test/e2e/framework/nginx.go
+++ b/test/e2e/framework/nginx.go
@@ -37,6 +37,7 @@ var (
 
 type NginxOptions struct {
        Namespace string
+       Replicas  *int32
 }
 
 func init() {
diff --git a/test/e2e/gatewayapi/httproute.go b/test/e2e/gatewayapi/httproute.go
index 9bdbc7b8..77b50849 100644
--- a/test/e2e/gatewayapi/httproute.go
+++ b/test/e2e/gatewayapi/httproute.go
@@ -30,6 +30,7 @@ import (
        corev1 "k8s.io/api/core/v1"
        metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
        "k8s.io/apimachinery/pkg/types"
+       "k8s.io/utils/ptr"
        "sigs.k8s.io/gateway-api/apis/v1alpha2"
 
        "github.com/apache/apisix-ingress-controller/api/v1alpha1"
@@ -1916,6 +1917,7 @@ spec:
                        beforeEachHTTP()
                        s.DeployNginx(framework.NginxOptions{
                                Namespace: s.Namespace(),
+                               Replicas:  ptr.To(int32(2)),
                        })
                })
                It("HTTPRoute Canary", func() {

Reply via email to