This is an automated email from the ASF dual-hosted git repository. ronething pushed a commit to branch fix/httproute_backendrefs in repository https://gitbox.apache.org/repos/asf/apisix-ingress-controller.git
commit ab62d5686c93774b73ba02e0359c35d6e6879368 Author: Ashing Zheng <[email protected]> AuthorDate: Tue Sep 2 17:37:27 2025 +0800 fix: handle httproute multi backend refs Signed-off-by: Ashing Zheng <[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 | 4 +- 5 files changed, 76 insertions(+), 15 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 10bcd951..e111dd1e 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 ref start 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 06ae3c72..c9964751 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" @@ -1882,9 +1883,10 @@ spec: beforeEachHTTP() s.DeployNginx(framework.NginxOptions{ Namespace: s.Namespace(), + Replicas: ptr.To(int32(2)), }) }) - It("HTTPRoute Canary", func() { + FIt("HTTPRoute Canary", func() { s.ResourceApplied("HTTPRoute", "httpbin", fmt.Sprintf(sameWeiht, s.Namespace()), 1) time.Sleep(5 * time.Second)
