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() {