You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@apisix.apache.org by kv...@apache.org on 2021/04/25 10:44:37 UTC

[apisix-ingress-controller] branch master updated: chore: TranslateContext (#400)

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

kvn 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 9855521  chore: TranslateContext (#400)
9855521 is described below

commit 98555210326c02db4c2c4ca8e2a8e0a27b17c92b
Author: Alex Zhang <zc...@gmail.com>
AuthorDate: Sun Apr 25 18:44:27 2021 +0800

    chore: TranslateContext (#400)
    
    * chore: TranslateContext
    
    * test: add unit test case
---
 pkg/ingress/apisix_route.go               |  32 +++--
 pkg/ingress/ingress.go                    |  22 ++--
 pkg/kube/translation/apisix_route.go      |  96 ++++++--------
 pkg/kube/translation/apisix_route_test.go |  22 +---
 pkg/kube/translation/context.go           |  47 +++++++
 pkg/kube/translation/context_test.go      |  76 ++++++++++++
 pkg/kube/translation/ingress.go           |  51 ++++----
 pkg/kube/translation/ingress_test.go      | 200 +++++++++++++++---------------
 pkg/kube/translation/plugin.go            |  15 ++-
 pkg/kube/translation/plugin_test.go       |  66 +++++-----
 pkg/kube/translation/translator.go        |  12 +-
 11 files changed, 349 insertions(+), 290 deletions(-)

diff --git a/pkg/ingress/apisix_route.go b/pkg/ingress/apisix_route.go
index b91c8b7..293ec6a 100644
--- a/pkg/ingress/apisix_route.go
+++ b/pkg/ingress/apisix_route.go
@@ -18,6 +18,7 @@ import (
 	"context"
 	"time"
 
+	"github.com/apache/apisix-ingress-controller/pkg/kube/translation"
 	"go.uber.org/zap"
 	v1 "k8s.io/api/core/v1"
 	k8serrors "k8s.io/apimachinery/pkg/api/errors"
@@ -28,7 +29,6 @@ import (
 	"github.com/apache/apisix-ingress-controller/pkg/kube"
 	"github.com/apache/apisix-ingress-controller/pkg/log"
 	"github.com/apache/apisix-ingress-controller/pkg/types"
-	apisixv1 "github.com/apache/apisix-ingress-controller/pkg/types/apisix/v1"
 )
 
 type apisixRouteController struct {
@@ -89,9 +89,8 @@ func (c *apisixRouteController) sync(ctx context.Context, ev *types.Event) error
 		return err
 	}
 	var (
-		ar        kube.ApisixRoute
-		routes    []*apisixv1.Route
-		upstreams []*apisixv1.Upstream
+		ar   kube.ApisixRoute
+		tctx *translation.TranslateContext
 	)
 	if obj.GroupVersion == kube.ApisixRouteV1 {
 		ar, err = c.controller.apisixRouteLister.V1(namespace, name)
@@ -130,7 +129,7 @@ func (c *apisixRouteController) sync(ctx context.Context, ev *types.Event) error
 		ar = ev.Tombstone.(kube.ApisixRoute)
 	}
 	if obj.GroupVersion == kube.ApisixRouteV1 {
-		routes, upstreams, err = c.controller.translator.TranslateRouteV1(ar.V1())
+		tctx, err = c.controller.translator.TranslateRouteV1(ar.V1())
 		if err != nil {
 			log.Errorw("failed to translate ApisixRoute v1",
 				zap.Error(err),
@@ -139,7 +138,7 @@ func (c *apisixRouteController) sync(ctx context.Context, ev *types.Event) error
 			return err
 		}
 	} else {
-		routes, upstreams, err = c.controller.translator.TranslateRouteV2alpha1(ar.V2alpha1())
+		tctx, err = c.controller.translator.TranslateRouteV2alpha1(ar.V2alpha1())
 		if err != nil {
 			log.Errorw("failed to translate ApisixRoute v2alpha1",
 				zap.Error(err),
@@ -150,14 +149,14 @@ func (c *apisixRouteController) sync(ctx context.Context, ev *types.Event) error
 	}
 
 	log.Debugw("translated ApisixRoute",
-		zap.Any("routes", routes),
-		zap.Any("upstreams", upstreams),
+		zap.Any("routes", tctx.Routes),
+		zap.Any("upstreams", tctx.Upstreams),
 		zap.Any("apisix_route", ar),
 	)
 
 	m := &manifest{
-		routes:    routes,
-		upstreams: upstreams,
+		routes:    tctx.Routes,
+		upstreams: tctx.Upstreams,
 	}
 
 	var (
@@ -171,14 +170,11 @@ func (c *apisixRouteController) sync(ctx context.Context, ev *types.Event) error
 	} else if ev.Type == types.EventAdd {
 		added = m
 	} else {
-		var (
-			oldRoutes    []*apisixv1.Route
-			oldUpstreams []*apisixv1.Upstream
-		)
+		var oldCtx *translation.TranslateContext
 		if obj.GroupVersion == kube.ApisixRouteV1 {
-			oldRoutes, oldUpstreams, err = c.controller.translator.TranslateRouteV1(obj.OldObject.V1())
+			oldCtx, err = c.controller.translator.TranslateRouteV1(obj.OldObject.V1())
 		} else {
-			oldRoutes, oldUpstreams, err = c.controller.translator.TranslateRouteV2alpha1(obj.OldObject.V2alpha1())
+			oldCtx, err = c.controller.translator.TranslateRouteV2alpha1(obj.OldObject.V2alpha1())
 		}
 		if err != nil {
 			log.Errorw("failed to translate old ApisixRoute v2alpha1",
@@ -191,8 +187,8 @@ func (c *apisixRouteController) sync(ctx context.Context, ev *types.Event) error
 		}
 
 		om := &manifest{
-			routes:    oldRoutes,
-			upstreams: oldUpstreams,
+			routes:    oldCtx.Routes,
+			upstreams: oldCtx.Upstreams,
 		}
 		added, updated, deleted = m.diff(om)
 	}
diff --git a/pkg/ingress/ingress.go b/pkg/ingress/ingress.go
index fb3281e..9e2e7a1 100644
--- a/pkg/ingress/ingress.go
+++ b/pkg/ingress/ingress.go
@@ -28,7 +28,6 @@ import (
 	"github.com/apache/apisix-ingress-controller/pkg/kube"
 	"github.com/apache/apisix-ingress-controller/pkg/log"
 	"github.com/apache/apisix-ingress-controller/pkg/types"
-	apisixv1 "github.com/apache/apisix-ingress-controller/pkg/types/apisix/v1"
 )
 
 const (
@@ -127,8 +126,7 @@ func (c *ingressController) sync(ctx context.Context, ev *types.Event) error {
 		ing = ev.Tombstone.(kube.Ingress)
 	}
 
-	// Translator should generate ID, fullname and name.
-	routes, upstreams, err := c.controller.translator.TranslateIngress(ing)
+	tctx, err := c.controller.translator.TranslateIngress(ing)
 	if err != nil {
 		log.Errorw("failed to translate ingress",
 			zap.Error(err),
@@ -139,13 +137,13 @@ func (c *ingressController) sync(ctx context.Context, ev *types.Event) error {
 
 	log.Debugw("translated ingress resource to a couple of routes and upstreams",
 		zap.Any("ingress", ing),
-		zap.Any("routes", routes),
-		zap.Any("upstreams", upstreams),
+		zap.Any("routes", tctx.Routes),
+		zap.Any("upstreams", tctx.Upstreams),
 	)
 
 	m := &manifest{
-		routes:    routes,
-		upstreams: upstreams,
+		routes:    tctx.Routes,
+		upstreams: tctx.Upstreams,
 	}
 
 	var (
@@ -159,11 +157,7 @@ func (c *ingressController) sync(ctx context.Context, ev *types.Event) error {
 	} else if ev.Type == types.EventAdd {
 		added = m
 	} else {
-		var (
-			oldRoutes    []*apisixv1.Route
-			oldUpstreams []*apisixv1.Upstream
-		)
-		oldRoutes, oldUpstreams, err := c.controller.translator.TranslateIngress(ingEv.OldObject)
+		oldCtx, err := c.controller.translator.TranslateIngress(ingEv.OldObject)
 		if err != nil {
 			log.Errorw("failed to translate ingress",
 				zap.String("event", "update"),
@@ -173,8 +167,8 @@ func (c *ingressController) sync(ctx context.Context, ev *types.Event) error {
 			return err
 		}
 		om := &manifest{
-			routes:    oldRoutes,
-			upstreams: oldUpstreams,
+			routes:    oldCtx.Routes,
+			upstreams: oldCtx.Upstreams,
 		}
 		added, updated, deleted = m.diff(om)
 	}
diff --git a/pkg/kube/translation/apisix_route.go b/pkg/kube/translation/apisix_route.go
index bcd4b99..69cbae4 100644
--- a/pkg/kube/translation/apisix_route.go
+++ b/pkg/kube/translation/apisix_route.go
@@ -27,14 +27,11 @@ import (
 	apisixv1 "github.com/apache/apisix-ingress-controller/pkg/types/apisix/v1"
 )
 
-func (t *translator) TranslateRouteV1(ar *configv1.ApisixRoute) ([]*apisixv1.Route, []*apisixv1.Upstream, error) {
-	var (
-		routes    []*apisixv1.Route
-		upstreams []*apisixv1.Upstream
-	)
-
+func (t *translator) TranslateRouteV1(ar *configv1.ApisixRoute) (*TranslateContext, error) {
+	ctx := &TranslateContext{
+		upstreamMap: make(map[string]struct{}),
+	}
 	plugins := t.TranslateAnnotations(ar.Annotations)
-	upstreamMap := make(map[string]*apisixv1.Upstream)
 
 	for _, r := range ar.Spec.Rules {
 		for _, p := range r.Http.Paths {
@@ -66,53 +63,42 @@ func (t *translator) TranslateRouteV1(ar *configv1.ApisixRoute) ([]*apisixv1.Rou
 			route.Plugins = pluginMap
 			route.UpstreamId = id.GenID(upstreamName)
 
-			if _, ok := upstreamMap[upstreamName]; !ok {
+			if !ctx.checkUpstreamExist(upstreamName) {
 				ups, err := t.TranslateUpstream(ar.Namespace, p.Backend.ServiceName, int32(p.Backend.ServicePort))
 				if err != nil {
-					return nil, nil, err
+					return nil, err
 				}
 				ups.ID = route.UpstreamId
 				ups.Name = upstreamName
-				upstreamMap[ups.Name] = ups
+				ctx.addUpstream(ups)
 			}
-			routes = append(routes, route)
+			ctx.addRoute(route)
 		}
 	}
-	for _, ups := range upstreamMap {
-		upstreams = append(upstreams, ups)
-	}
-	return routes, upstreams, nil
+	return ctx, nil
 }
 
-func (t *translator) TranslateRouteV2alpha1(ar *configv2alpha1.ApisixRoute) ([]*apisixv1.Route, []*apisixv1.Upstream, error) {
-	var (
-		routes    []*apisixv1.Route
-		upstreams []*apisixv1.Upstream
-	)
+func (t *translator) TranslateRouteV2alpha1(ar *configv2alpha1.ApisixRoute) (*TranslateContext, error) {
+	ctx := &TranslateContext{
+		upstreamMap: make(map[string]struct{}),
+	}
 
-	ruleNameMap := make(map[string]struct{})
-	upstreamMap := make(map[string]*apisixv1.Upstream)
+	if err := t.translateHTTPRoute(ctx, ar); err != nil {
+		return nil, err
+	}
+	if err := t.translateTCPRoute(ctx, ar); err != nil {
+		return nil, err
+	}
+	return ctx, nil
+}
 
+func (t *translator) translateHTTPRoute(ctx *TranslateContext, ar *configv2alpha1.ApisixRoute) error {
+	ruleNameMap := make(map[string]struct{})
 	for _, part := range ar.Spec.HTTP {
-		if part.Name == "" {
-			return nil, nil, errors.New("empty route rule name")
-		}
 		if _, ok := ruleNameMap[part.Name]; ok {
-			return nil, nil, errors.New("duplicated route rule name")
+			return errors.New("duplicated route rule name")
 		}
 		ruleNameMap[part.Name] = struct{}{}
-		if part.Match == nil {
-			return nil, nil, errors.New("empty route match section")
-		}
-		if len(part.Match.Paths) < 1 {
-			return nil, nil, errors.New("empty route paths match")
-		}
-		if part.Backend != nil && len(part.Backends) > 1 {
-			return nil, nil, errors.New("backend and backends are exclusive")
-		}
-		if part.Backend == nil && len(part.Backends) == 0 {
-			return nil, nil, errors.New("no specified backend")
-		}
 		backends := part.Backends
 		backend := part.Backend
 		if len(backends) > 0 {
@@ -129,7 +115,7 @@ func (t *translator) TranslateRouteV2alpha1(ar *configv2alpha1.ApisixRoute) ([]*
 				zap.Any("apisix_route", ar),
 				zap.Error(err),
 			)
-			return nil, nil, err
+			return err
 		}
 
 		pluginMap := make(apisixv1.Plugins)
@@ -152,7 +138,7 @@ func (t *translator) TranslateRouteV2alpha1(ar *configv2alpha1.ApisixRoute) ([]*
 					zap.Error(err),
 					zap.Any("ApisixRoute", ar),
 				)
-				return nil, nil, err
+				return err
 			}
 		}
 		if err := validateRemoteAddrs(part.Match.RemoteAddrs); err != nil {
@@ -161,7 +147,7 @@ func (t *translator) TranslateRouteV2alpha1(ar *configv2alpha1.ApisixRoute) ([]*
 				zap.Strings("remote_addrs", part.Match.RemoteAddrs),
 				zap.Any("ApisixRoute", ar),
 			)
-			return nil, nil, err
+			return err
 		}
 
 		upstreamName := apisixv1.ComposeUpstreamName(ar.Namespace, backend.ServiceName, svcPort)
@@ -183,36 +169,26 @@ func (t *translator) TranslateRouteV2alpha1(ar *configv2alpha1.ApisixRoute) ([]*
 			if backend.Weight != nil {
 				weight = *backend.Weight
 			}
-			ups, plugin, err := t.translateTrafficSplitPlugin(ar, weight, backends)
+			plugin, err := t.translateTrafficSplitPlugin(ctx, ar, weight, backends)
 			if err != nil {
 				log.Errorw("failed to translate traffic-split plugin",
 					zap.Error(err),
 					zap.Any("ApisixRoute", ar),
 				)
-				return nil, nil, err
-			}
-			for _, u := range ups {
-				if _, ok := upstreamMap[u.Name]; !ok {
-					upstreamMap[u.Name] = u
-				}
+				return err
 			}
 			route.Plugins["traffic-split"] = plugin
 		}
-
-		routes = append(routes, route)
-		if _, ok := upstreamMap[upstreamName]; !ok {
+		ctx.addRoute(route)
+		if !ctx.checkUpstreamExist(upstreamName) {
 			ups, err := t.translateUpstream(ar.Namespace, backend.ServiceName, backend.ResolveGranularity, svcClusterIP, svcPort)
 			if err != nil {
-				return nil, nil, err
+				return err
 			}
-			upstreamMap[ups.Name] = ups
+			ctx.addUpstream(ups)
 		}
 	}
-
-	for _, ups := range upstreamMap {
-		upstreams = append(upstreams, ups)
-	}
-	return routes, upstreams, nil
+	return nil
 }
 
 func (t *translator) translateRouteMatchExprs(nginxVars []configv2alpha1.ApisixRouteHTTPMatchExpr) ([][]apisixv1.StringOrSlice, error) {
@@ -316,3 +292,7 @@ func (t *translator) translateRouteMatchExprs(nginxVars []configv2alpha1.ApisixR
 
 	return vars, nil
 }
+
+func (t *translator) translateTCPRoute(ctx *TranslateContext, ar *configv2alpha1.ApisixRoute) error {
+	return nil
+}
diff --git a/pkg/kube/translation/apisix_route_test.go b/pkg/kube/translation/apisix_route_test.go
index e0a6c59..60baca1 100644
--- a/pkg/kube/translation/apisix_route_test.go
+++ b/pkg/kube/translation/apisix_route_test.go
@@ -170,26 +170,6 @@ func TestRouteMatchExpr(t *testing.T) {
 	assert.Equal(t, results[8][2].SliceVal, []string{"a.com", "b.com"})
 }
 
-func TestTranslateApisixRouteV2alpha1WithEmptyName(t *testing.T) {
-	ar := &configv2alpha1.ApisixRoute{
-		ObjectMeta: metav1.ObjectMeta{
-			Name:      "ar",
-			Namespace: "test",
-		},
-		Spec: &configv2alpha1.ApisixRouteSpec{
-			HTTP: []*configv2alpha1.ApisixRouteHTTP{
-				{
-					Name:     "",
-					Priority: 0,
-				},
-			},
-		},
-	}
-	tr := &translator{}
-	_, _, err := tr.TranslateRouteV2alpha1(ar)
-	assert.Equal(t, err.Error(), "empty route rule name")
-}
-
 func TestTranslateApisixRouteV2alpha1WithDuplicatedName(t *testing.T) {
 	svc := &corev1.Service{
 		TypeMeta: metav1.TypeMeta{},
@@ -326,6 +306,6 @@ func TestTranslateApisixRouteV2alpha1WithDuplicatedName(t *testing.T) {
 		},
 	}
 
-	_, _, err = tr.TranslateRouteV2alpha1(ar)
+	_, err = tr.TranslateRouteV2alpha1(ar)
 	assert.Equal(t, err.Error(), "duplicated route rule name")
 }
diff --git a/pkg/kube/translation/context.go b/pkg/kube/translation/context.go
new file mode 100644
index 0000000..aa6478c
--- /dev/null
+++ b/pkg/kube/translation/context.go
@@ -0,0 +1,47 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package translation
+
+import apisix "github.com/apache/apisix-ingress-controller/pkg/types/apisix/v1"
+
+// TranslateContext contains APISIX resources generated by the translator.
+type TranslateContext struct {
+	Routes       []*apisix.Route
+	StreamRoutes []*apisix.StreamRoute
+	Upstreams    []*apisix.Upstream
+
+	upstreamMap map[string]struct{}
+}
+
+func (tc *TranslateContext) addRoute(r *apisix.Route) {
+	tc.Routes = append(tc.Routes, r)
+}
+
+func (tc *TranslateContext) addStreamRoute(sr *apisix.StreamRoute) {
+	tc.StreamRoutes = append(tc.StreamRoutes, sr)
+}
+
+func (tc *TranslateContext) addUpstream(u *apisix.Upstream) {
+	if _, ok := tc.upstreamMap[u.Name]; ok {
+		return
+	}
+	tc.upstreamMap[u.Name] = struct{}{}
+	tc.Upstreams = append(tc.Upstreams, u)
+}
+
+func (tc *TranslateContext) checkUpstreamExist(name string) (ok bool) {
+	_, ok = tc.upstreamMap[name]
+	return
+}
diff --git a/pkg/kube/translation/context_test.go b/pkg/kube/translation/context_test.go
new file mode 100644
index 0000000..b1097a6
--- /dev/null
+++ b/pkg/kube/translation/context_test.go
@@ -0,0 +1,76 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package translation
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+
+	apisix "github.com/apache/apisix-ingress-controller/pkg/types/apisix/v1"
+)
+
+func TestTranslateContext(t *testing.T) {
+	ctx := &TranslateContext{
+		upstreamMap: make(map[string]struct{}),
+	}
+	r1 := &apisix.Route{
+		Metadata: apisix.Metadata{
+			ID: "1",
+		},
+	}
+	r2 := &apisix.Route{
+		Metadata: apisix.Metadata{
+			ID: "2",
+		},
+	}
+	sr1 := &apisix.StreamRoute{
+		ID: "1",
+	}
+	sr2 := &apisix.StreamRoute{
+		ID: "2",
+	}
+	u1 := &apisix.Upstream{
+		Metadata: apisix.Metadata{
+			ID:   "1",
+			Name: "aaa",
+		},
+	}
+	u2 := &apisix.Upstream{
+		Metadata: apisix.Metadata{
+			ID:   "1",
+			Name: "aaa",
+		},
+	}
+	ctx.addRoute(r1)
+	ctx.addRoute(r2)
+	ctx.addStreamRoute(sr1)
+	ctx.addStreamRoute(sr2)
+	ctx.addUpstream(u1)
+	ctx.addUpstream(u2)
+
+	assert.Len(t, ctx.Routes, 2)
+	assert.Len(t, ctx.StreamRoutes, 2)
+	assert.Len(t, ctx.Upstreams, 1)
+
+	assert.Equal(t, ctx.Routes[0], r1)
+	assert.Equal(t, ctx.Routes[1], r2)
+	assert.Equal(t, ctx.StreamRoutes[0], sr1)
+	assert.Equal(t, ctx.StreamRoutes[1], sr2)
+	assert.Equal(t, ctx.Upstreams[0], u1)
+
+	assert.Equal(t, ctx.checkUpstreamExist("aaa"), true)
+	assert.Equal(t, ctx.checkUpstreamExist("bbb"), false)
+}
diff --git a/pkg/kube/translation/ingress.go b/pkg/kube/translation/ingress.go
index 0ea9903..0590592 100644
--- a/pkg/kube/translation/ingress.go
+++ b/pkg/kube/translation/ingress.go
@@ -29,11 +29,10 @@ import (
 	apisixv1 "github.com/apache/apisix-ingress-controller/pkg/types/apisix/v1"
 )
 
-func (t *translator) translateIngressV1(ing *networkingv1.Ingress) ([]*apisixv1.Route, []*apisixv1.Upstream, error) {
-	var (
-		routes    []*apisixv1.Route
-		upstreams []*apisixv1.Upstream
-	)
+func (t *translator) translateIngressV1(ing *networkingv1.Ingress) (*TranslateContext, error) {
+	ctx := &TranslateContext{
+		upstreamMap: make(map[string]struct{}),
+	}
 
 	for _, rule := range ing.Spec.Rules {
 		for _, pathRule := range rule.HTTP.Paths {
@@ -48,9 +47,9 @@ func (t *translator) translateIngressV1(ing *networkingv1.Ingress) ([]*apisixv1.
 						zap.Error(err),
 						zap.Any("ingress", ing),
 					)
-					return nil, nil, err
+					return nil, err
 				}
-				upstreams = append(upstreams, ups)
+				ctx.addUpstream(ups)
 			}
 			uris := []string{pathRule.Path}
 			if pathRule.PathType != nil && *pathRule.PathType == networkingv1.PathTypePrefix {
@@ -79,17 +78,16 @@ func (t *translator) translateIngressV1(ing *networkingv1.Ingress) ([]*apisixv1.
 			if ups != nil {
 				route.UpstreamId = ups.ID
 			}
-			routes = append(routes, route)
+			ctx.addRoute(route)
 		}
 	}
-	return routes, upstreams, nil
+	return ctx, nil
 }
 
-func (t *translator) translateIngressV1beta1(ing *networkingv1beta1.Ingress) ([]*apisixv1.Route, []*apisixv1.Upstream, error) {
-	var (
-		routes    []*apisixv1.Route
-		upstreams []*apisixv1.Upstream
-	)
+func (t *translator) translateIngressV1beta1(ing *networkingv1beta1.Ingress) (*TranslateContext, error) {
+	ctx := &TranslateContext{
+		upstreamMap: make(map[string]struct{}),
+	}
 
 	for _, rule := range ing.Spec.Rules {
 		for _, pathRule := range rule.HTTP.Paths {
@@ -104,9 +102,9 @@ func (t *translator) translateIngressV1beta1(ing *networkingv1beta1.Ingress) ([]
 						zap.Error(err),
 						zap.Any("ingress", ing),
 					)
-					return nil, nil, err
+					return nil, err
 				}
-				upstreams = append(upstreams, ups)
+				ctx.addUpstream(ups)
 			}
 			uris := []string{pathRule.Path}
 			if pathRule.PathType != nil && *pathRule.PathType == networkingv1beta1.PathTypePrefix {
@@ -135,10 +133,10 @@ func (t *translator) translateIngressV1beta1(ing *networkingv1beta1.Ingress) ([]
 			if ups != nil {
 				route.UpstreamId = ups.ID
 			}
-			routes = append(routes, route)
+			ctx.addRoute(route)
 		}
 	}
-	return routes, upstreams, nil
+	return ctx, nil
 }
 
 func (t *translator) translateUpstreamFromIngressV1(namespace string, backend *networkingv1.IngressServiceBackend) (*apisixv1.Upstream, error) {
@@ -172,11 +170,10 @@ func (t *translator) translateUpstreamFromIngressV1(namespace string, backend *n
 	return ups, nil
 }
 
-func (t *translator) translateIngressExtensionsV1beta1(ing *extensionsv1beta1.Ingress) ([]*apisixv1.Route, []*apisixv1.Upstream, error) {
-	var (
-		routes    []*apisixv1.Route
-		upstreams []*apisixv1.Upstream
-	)
+func (t *translator) translateIngressExtensionsV1beta1(ing *extensionsv1beta1.Ingress) (*TranslateContext, error) {
+	ctx := &TranslateContext{
+		upstreamMap: make(map[string]struct{}),
+	}
 
 	for _, rule := range ing.Spec.Rules {
 		for _, pathRule := range rule.HTTP.Paths {
@@ -192,9 +189,9 @@ func (t *translator) translateIngressExtensionsV1beta1(ing *extensionsv1beta1.In
 						zap.Error(err),
 						zap.Any("ingress", ing),
 					)
-					return nil, nil, err
+					return nil, err
 				}
-				upstreams = append(upstreams, ups)
+				ctx.addUpstream(ups)
 			}
 			uris := []string{pathRule.Path}
 			if pathRule.PathType != nil && *pathRule.PathType == extensionsv1beta1.PathTypePrefix {
@@ -226,10 +223,10 @@ func (t *translator) translateIngressExtensionsV1beta1(ing *extensionsv1beta1.In
 			if ups != nil {
 				route.UpstreamId = ups.ID
 			}
-			routes = append(routes, route)
+			ctx.addRoute(route)
 		}
 	}
-	return routes, upstreams, nil
+	return ctx, nil
 }
 
 func (t *translator) translateUpstreamFromIngressV1beta1(namespace string, svcName string, svcPort intstr.IntOrString) (*apisixv1.Upstream, error) {
diff --git a/pkg/kube/translation/ingress_test.go b/pkg/kube/translation/ingress_test.go
index b6d44c5..9afbd97 100644
--- a/pkg/kube/translation/ingress_test.go
+++ b/pkg/kube/translation/ingress_test.go
@@ -116,12 +116,12 @@ func TestTranslateIngressV1NoBackend(t *testing.T) {
 		},
 	}
 	tr := &translator{}
-	routes, upstreams, err := tr.translateIngressV1(ing)
-	assert.Len(t, routes, 1)
-	assert.Len(t, upstreams, 0)
+	ctx, err := tr.translateIngressV1(ing)
+	assert.Len(t, ctx.Routes, 1)
+	assert.Len(t, ctx.Upstreams, 0)
 	assert.Nil(t, err)
-	assert.Equal(t, routes[0].UpstreamId, "")
-	assert.Equal(t, routes[0].Uris, []string{"/foo", "/foo/*"})
+	assert.Equal(t, ctx.Routes[0].UpstreamId, "")
+	assert.Equal(t, ctx.Routes[0].Uris, []string{"/foo", "/foo/*"})
 }
 
 func TestTranslateIngressV1BackendWithInvalidService(t *testing.T) {
@@ -167,10 +167,9 @@ func TestTranslateIngressV1BackendWithInvalidService(t *testing.T) {
 			ServiceLister: svcLister,
 		},
 	}
-	routes, upstreams, err := tr.translateIngressV1(ing)
-	assert.Len(t, routes, 0)
-	assert.Len(t, upstreams, 0)
+	ctx, err := tr.translateIngressV1(ing)
 	assert.NotNil(t, err)
+	assert.Nil(t, ctx)
 	assert.Equal(t, err.Error(), "service \"test-service\" not found")
 
 	processCh := make(chan struct{})
@@ -191,9 +190,8 @@ func TestTranslateIngressV1BackendWithInvalidService(t *testing.T) {
 	assert.Nil(t, err)
 
 	<-processCh
-	routes, upstreams, err = tr.translateIngressV1(ing)
-	assert.Len(t, routes, 0)
-	assert.Len(t, upstreams, 0)
+	ctx, err = tr.translateIngressV1(ing)
+	assert.Nil(t, ctx, nil)
 	assert.Equal(t, err, &translateError{
 		field:  "service",
 		reason: "port not found",
@@ -286,33 +284,33 @@ func TestTranslateIngressV1(t *testing.T) {
 
 	<-processCh
 	<-processCh
-	routes, upstreams, err := tr.translateIngressV1(ing)
-	assert.Len(t, routes, 2)
-	assert.Len(t, upstreams, 2)
+	ctx, err := tr.translateIngressV1(ing)
+	assert.Len(t, ctx.Routes, 2)
+	assert.Len(t, ctx.Upstreams, 2)
 	assert.Nil(t, err)
 
-	assert.Equal(t, routes[0].Uris, []string{"/foo", "/foo/*"})
-	assert.Equal(t, routes[0].UpstreamId, upstreams[0].ID)
-	assert.Equal(t, routes[0].Host, "apisix.apache.org")
-	assert.Equal(t, routes[1].Uris, []string{"/bar"})
-	assert.Equal(t, routes[1].UpstreamId, upstreams[1].ID)
-	assert.Equal(t, routes[1].Host, "apisix.apache.org")
-
-	assert.Equal(t, upstreams[0].Type, "roundrobin")
-	assert.Equal(t, upstreams[0].Scheme, "http")
-	assert.Len(t, upstreams[0].Nodes, 2)
-	assert.Equal(t, upstreams[0].Nodes[0].Port, 9080)
-	assert.Equal(t, upstreams[0].Nodes[0].Host, "192.168.1.1")
-	assert.Equal(t, upstreams[0].Nodes[1].Port, 9080)
-	assert.Equal(t, upstreams[0].Nodes[1].Host, "192.168.1.2")
-
-	assert.Equal(t, upstreams[1].Type, "roundrobin")
-	assert.Equal(t, upstreams[1].Scheme, "http")
-	assert.Len(t, upstreams[1].Nodes, 2)
-	assert.Equal(t, upstreams[1].Nodes[0].Port, 9443)
-	assert.Equal(t, upstreams[1].Nodes[0].Host, "192.168.1.1")
-	assert.Equal(t, upstreams[1].Nodes[1].Port, 9443)
-	assert.Equal(t, upstreams[1].Nodes[1].Host, "192.168.1.2")
+	assert.Equal(t, ctx.Routes[0].Uris, []string{"/foo", "/foo/*"})
+	assert.Equal(t, ctx.Routes[0].UpstreamId, ctx.Upstreams[0].ID)
+	assert.Equal(t, ctx.Routes[0].Host, "apisix.apache.org")
+	assert.Equal(t, ctx.Routes[1].Uris, []string{"/bar"})
+	assert.Equal(t, ctx.Routes[1].UpstreamId, ctx.Upstreams[1].ID)
+	assert.Equal(t, ctx.Routes[1].Host, "apisix.apache.org")
+
+	assert.Equal(t, ctx.Upstreams[0].Type, "roundrobin")
+	assert.Equal(t, ctx.Upstreams[0].Scheme, "http")
+	assert.Len(t, ctx.Upstreams[0].Nodes, 2)
+	assert.Equal(t, ctx.Upstreams[0].Nodes[0].Port, 9080)
+	assert.Equal(t, ctx.Upstreams[0].Nodes[0].Host, "192.168.1.1")
+	assert.Equal(t, ctx.Upstreams[0].Nodes[1].Port, 9080)
+	assert.Equal(t, ctx.Upstreams[0].Nodes[1].Host, "192.168.1.2")
+
+	assert.Equal(t, ctx.Upstreams[1].Type, "roundrobin")
+	assert.Equal(t, ctx.Upstreams[1].Scheme, "http")
+	assert.Len(t, ctx.Upstreams[1].Nodes, 2)
+	assert.Equal(t, ctx.Upstreams[1].Nodes[0].Port, 9443)
+	assert.Equal(t, ctx.Upstreams[1].Nodes[0].Host, "192.168.1.1")
+	assert.Equal(t, ctx.Upstreams[1].Nodes[1].Port, 9443)
+	assert.Equal(t, ctx.Upstreams[1].Nodes[1].Host, "192.168.1.2")
 }
 
 func TestTranslateIngressV1beta1NoBackend(t *testing.T) {
@@ -342,12 +340,12 @@ func TestTranslateIngressV1beta1NoBackend(t *testing.T) {
 		},
 	}
 	tr := &translator{}
-	routes, upstreams, err := tr.translateIngressV1beta1(ing)
-	assert.Len(t, routes, 1)
-	assert.Len(t, upstreams, 0)
+	ctx, err := tr.translateIngressV1beta1(ing)
+	assert.Len(t, ctx.Routes, 1)
+	assert.Len(t, ctx.Upstreams, 0)
 	assert.Nil(t, err)
-	assert.Equal(t, routes[0].UpstreamId, "")
-	assert.Equal(t, routes[0].Uris, []string{"/foo", "/foo/*"})
+	assert.Equal(t, ctx.Routes[0].UpstreamId, "")
+	assert.Equal(t, ctx.Routes[0].Uris, []string{"/foo", "/foo/*"})
 }
 
 func TestTranslateIngressV1beta1BackendWithInvalidService(t *testing.T) {
@@ -392,10 +390,9 @@ func TestTranslateIngressV1beta1BackendWithInvalidService(t *testing.T) {
 			ServiceLister: svcLister,
 		},
 	}
-	routes, upstreams, err := tr.translateIngressV1beta1(ing)
-	assert.Len(t, routes, 0)
-	assert.Len(t, upstreams, 0)
+	ctx, err := tr.translateIngressV1beta1(ing)
 	assert.NotNil(t, err)
+	assert.Nil(t, ctx)
 	assert.Equal(t, err.Error(), "service \"test-service\" not found")
 
 	processCh := make(chan struct{})
@@ -416,9 +413,8 @@ func TestTranslateIngressV1beta1BackendWithInvalidService(t *testing.T) {
 	assert.Nil(t, err)
 
 	<-processCh
-	routes, upstreams, err = tr.translateIngressV1beta1(ing)
-	assert.Len(t, routes, 0)
-	assert.Len(t, upstreams, 0)
+	ctx, err = tr.translateIngressV1beta1(ing)
+	assert.Nil(t, ctx)
 	assert.Equal(t, err, &translateError{
 		field:  "service",
 		reason: "port not found",
@@ -509,33 +505,33 @@ func TestTranslateIngressV1beta1(t *testing.T) {
 
 	<-processCh
 	<-processCh
-	routes, upstreams, err := tr.translateIngressV1beta1(ing)
-	assert.Len(t, routes, 2)
-	assert.Len(t, upstreams, 2)
+	ctx, err := tr.translateIngressV1beta1(ing)
+	assert.Len(t, ctx.Routes, 2)
+	assert.Len(t, ctx.Upstreams, 2)
 	assert.Nil(t, err)
 
-	assert.Equal(t, routes[0].Uris, []string{"/foo", "/foo/*"})
-	assert.Equal(t, routes[0].UpstreamId, upstreams[0].ID)
-	assert.Equal(t, routes[0].Host, "apisix.apache.org")
-	assert.Equal(t, routes[1].Uris, []string{"/bar"})
-	assert.Equal(t, routes[1].UpstreamId, upstreams[1].ID)
-	assert.Equal(t, routes[1].Host, "apisix.apache.org")
-
-	assert.Equal(t, upstreams[0].Type, "roundrobin")
-	assert.Equal(t, upstreams[0].Scheme, "http")
-	assert.Len(t, upstreams[0].Nodes, 2)
-	assert.Equal(t, upstreams[0].Nodes[0].Port, 9080)
-	assert.Equal(t, upstreams[0].Nodes[0].Host, "192.168.1.1")
-	assert.Equal(t, upstreams[0].Nodes[1].Port, 9080)
-	assert.Equal(t, upstreams[0].Nodes[1].Host, "192.168.1.2")
-
-	assert.Equal(t, upstreams[1].Type, "roundrobin")
-	assert.Equal(t, upstreams[1].Scheme, "http")
-	assert.Len(t, upstreams[1].Nodes, 2)
-	assert.Equal(t, upstreams[1].Nodes[0].Port, 9443)
-	assert.Equal(t, upstreams[1].Nodes[0].Host, "192.168.1.1")
-	assert.Equal(t, upstreams[1].Nodes[1].Port, 9443)
-	assert.Equal(t, upstreams[1].Nodes[1].Host, "192.168.1.2")
+	assert.Equal(t, ctx.Routes[0].Uris, []string{"/foo", "/foo/*"})
+	assert.Equal(t, ctx.Routes[0].UpstreamId, ctx.Upstreams[0].ID)
+	assert.Equal(t, ctx.Routes[0].Host, "apisix.apache.org")
+	assert.Equal(t, ctx.Routes[1].Uris, []string{"/bar"})
+	assert.Equal(t, ctx.Routes[1].UpstreamId, ctx.Upstreams[1].ID)
+	assert.Equal(t, ctx.Routes[1].Host, "apisix.apache.org")
+
+	assert.Equal(t, ctx.Upstreams[0].Type, "roundrobin")
+	assert.Equal(t, ctx.Upstreams[0].Scheme, "http")
+	assert.Len(t, ctx.Upstreams[0].Nodes, 2)
+	assert.Equal(t, ctx.Upstreams[0].Nodes[0].Port, 9080)
+	assert.Equal(t, ctx.Upstreams[0].Nodes[0].Host, "192.168.1.1")
+	assert.Equal(t, ctx.Upstreams[0].Nodes[1].Port, 9080)
+	assert.Equal(t, ctx.Upstreams[0].Nodes[1].Host, "192.168.1.2")
+
+	assert.Equal(t, ctx.Upstreams[1].Type, "roundrobin")
+	assert.Equal(t, ctx.Upstreams[1].Scheme, "http")
+	assert.Len(t, ctx.Upstreams[1].Nodes, 2)
+	assert.Equal(t, ctx.Upstreams[1].Nodes[0].Port, 9443)
+	assert.Equal(t, ctx.Upstreams[1].Nodes[0].Host, "192.168.1.1")
+	assert.Equal(t, ctx.Upstreams[1].Nodes[1].Port, 9443)
+	assert.Equal(t, ctx.Upstreams[1].Nodes[1].Host, "192.168.1.2")
 }
 
 func TestTranslateIngressExtensionsV1beta1(t *testing.T) {
@@ -622,33 +618,33 @@ func TestTranslateIngressExtensionsV1beta1(t *testing.T) {
 
 	<-processCh
 	<-processCh
-	routes, upstreams, err := tr.translateIngressExtensionsV1beta1(ing)
-	assert.Len(t, routes, 2)
-	assert.Len(t, upstreams, 2)
+	ctx, err := tr.translateIngressExtensionsV1beta1(ing)
+	assert.Len(t, ctx.Routes, 2)
+	assert.Len(t, ctx.Upstreams, 2)
 	assert.Nil(t, err)
 
-	assert.Equal(t, routes[0].Uris, []string{"/foo", "/foo/*"})
-	assert.Equal(t, routes[0].UpstreamId, upstreams[0].ID)
-	assert.Equal(t, routes[0].Host, "apisix.apache.org")
-	assert.Equal(t, routes[1].Uris, []string{"/bar"})
-	assert.Equal(t, routes[1].UpstreamId, upstreams[1].ID)
-	assert.Equal(t, routes[1].Host, "apisix.apache.org")
-
-	assert.Equal(t, upstreams[0].Type, "roundrobin")
-	assert.Equal(t, upstreams[0].Scheme, "http")
-	assert.Len(t, upstreams[0].Nodes, 2)
-	assert.Equal(t, upstreams[0].Nodes[0].Port, 9080)
-	assert.Equal(t, upstreams[0].Nodes[0].Host, "192.168.1.1")
-	assert.Equal(t, upstreams[0].Nodes[1].Port, 9080)
-	assert.Equal(t, upstreams[0].Nodes[1].Host, "192.168.1.2")
-
-	assert.Equal(t, upstreams[1].Type, "roundrobin")
-	assert.Equal(t, upstreams[1].Scheme, "http")
-	assert.Len(t, upstreams[1].Nodes, 2)
-	assert.Equal(t, upstreams[1].Nodes[0].Port, 9443)
-	assert.Equal(t, upstreams[1].Nodes[0].Host, "192.168.1.1")
-	assert.Equal(t, upstreams[1].Nodes[1].Port, 9443)
-	assert.Equal(t, upstreams[1].Nodes[1].Host, "192.168.1.2")
+	assert.Equal(t, ctx.Routes[0].Uris, []string{"/foo", "/foo/*"})
+	assert.Equal(t, ctx.Routes[0].UpstreamId, ctx.Upstreams[0].ID)
+	assert.Equal(t, ctx.Routes[0].Host, "apisix.apache.org")
+	assert.Equal(t, ctx.Routes[1].Uris, []string{"/bar"})
+	assert.Equal(t, ctx.Routes[1].UpstreamId, ctx.Upstreams[1].ID)
+	assert.Equal(t, ctx.Routes[1].Host, "apisix.apache.org")
+
+	assert.Equal(t, ctx.Upstreams[0].Type, "roundrobin")
+	assert.Equal(t, ctx.Upstreams[0].Scheme, "http")
+	assert.Len(t, ctx.Upstreams[0].Nodes, 2)
+	assert.Equal(t, ctx.Upstreams[0].Nodes[0].Port, 9080)
+	assert.Equal(t, ctx.Upstreams[0].Nodes[0].Host, "192.168.1.1")
+	assert.Equal(t, ctx.Upstreams[0].Nodes[1].Port, 9080)
+	assert.Equal(t, ctx.Upstreams[0].Nodes[1].Host, "192.168.1.2")
+
+	assert.Equal(t, ctx.Upstreams[1].Type, "roundrobin")
+	assert.Equal(t, ctx.Upstreams[1].Scheme, "http")
+	assert.Len(t, ctx.Upstreams[1].Nodes, 2)
+	assert.Equal(t, ctx.Upstreams[1].Nodes[0].Port, 9443)
+	assert.Equal(t, ctx.Upstreams[1].Nodes[0].Host, "192.168.1.1")
+	assert.Equal(t, ctx.Upstreams[1].Nodes[1].Port, 9443)
+	assert.Equal(t, ctx.Upstreams[1].Nodes[1].Host, "192.168.1.2")
 }
 
 func TestTranslateIngressExtensionsV1beta1BackendWithInvalidService(t *testing.T) {
@@ -693,9 +689,8 @@ func TestTranslateIngressExtensionsV1beta1BackendWithInvalidService(t *testing.T
 			ServiceLister: svcLister,
 		},
 	}
-	routes, upstreams, err := tr.translateIngressExtensionsV1beta1(ing)
-	assert.Len(t, routes, 0)
-	assert.Len(t, upstreams, 0)
+	ctx, err := tr.translateIngressExtensionsV1beta1(ing)
+	assert.Nil(t, ctx)
 	assert.NotNil(t, err)
 	assert.Equal(t, err.Error(), "service \"test-service\" not found")
 
@@ -717,9 +712,8 @@ func TestTranslateIngressExtensionsV1beta1BackendWithInvalidService(t *testing.T
 	assert.Nil(t, err)
 
 	<-processCh
-	routes, upstreams, err = tr.translateIngressExtensionsV1beta1(ing)
-	assert.Len(t, routes, 0)
-	assert.Len(t, upstreams, 0)
+	ctx, err = tr.translateIngressExtensionsV1beta1(ing)
+	assert.Nil(t, ctx)
 	assert.Equal(t, err, &translateError{
 		field:  "service",
 		reason: "port not found",
diff --git a/pkg/kube/translation/plugin.go b/pkg/kube/translation/plugin.go
index c76f600..9e14356 100644
--- a/pkg/kube/translation/plugin.go
+++ b/pkg/kube/translation/plugin.go
@@ -19,23 +19,22 @@ import (
 	apisixv1 "github.com/apache/apisix-ingress-controller/pkg/types/apisix/v1"
 )
 
-func (t *translator) translateTrafficSplitPlugin(ar *configv2alpha1.ApisixRoute, defaultBackendWeight int,
-	backends []*configv2alpha1.ApisixRouteHTTPBackend) ([]*apisixv1.Upstream, *apisixv1.TrafficSplitConfig, error) {
+func (t *translator) translateTrafficSplitPlugin(ctx *TranslateContext, ar *configv2alpha1.ApisixRoute, defaultBackendWeight int,
+	backends []*configv2alpha1.ApisixRouteHTTPBackend) (*apisixv1.TrafficSplitConfig, error) {
 	var (
-		upstreams []*apisixv1.Upstream
-		wups      []apisixv1.TrafficSplitConfigRuleWeightedUpstream
+		wups []apisixv1.TrafficSplitConfigRuleWeightedUpstream
 	)
 
 	for _, backend := range backends {
 		svcClusterIP, svcPort, err := t.getServiceClusterIPAndPort(backend, ar)
 		if err != nil {
-			return nil, nil, err
+			return nil, err
 		}
 		ups, err := t.translateUpstream(ar.Namespace, backend.ServiceName, backend.ResolveGranularity, svcClusterIP, svcPort)
 		if err != nil {
-			return nil, nil, err
+			return nil, err
 		}
-		upstreams = append(upstreams, ups)
+		ctx.addUpstream(ups)
 
 		weight := _defaultWeight
 		if backend.Weight != nil {
@@ -59,5 +58,5 @@ func (t *translator) translateTrafficSplitPlugin(ar *configv2alpha1.ApisixRoute,
 			},
 		},
 	}
-	return upstreams, tsCfg, nil
+	return tsCfg, nil
 }
diff --git a/pkg/kube/translation/plugin_test.go b/pkg/kube/translation/plugin_test.go
index e8ab921..172429a 100644
--- a/pkg/kube/translation/plugin_test.go
+++ b/pkg/kube/translation/plugin_test.go
@@ -176,21 +176,24 @@ func TestTranslateTrafficSplitPlugin(t *testing.T) {
 		EndpointsLister:      epLister,
 		ApisixUpstreamLister: auLister,
 	}}
-	ups, cfg, err := tr.translateTrafficSplitPlugin(ar1, 30, backends)
+	ctx := &TranslateContext{
+		upstreamMap: make(map[string]struct{}),
+	}
+	cfg, err := tr.translateTrafficSplitPlugin(ctx, ar1, 30, backends)
 	assert.Nil(t, err)
 
-	assert.Len(t, ups, 2)
-	assert.Equal(t, ups[0].Name, "test_svc-1_80")
-	assert.Len(t, ups[0].Nodes, 2)
-	assert.Equal(t, ups[0].Nodes[0].Host, "192.168.1.1")
-	assert.Equal(t, ups[0].Nodes[0].Port, 9080)
-	assert.Equal(t, ups[0].Nodes[1].Host, "192.168.1.2")
-	assert.Equal(t, ups[0].Nodes[1].Port, 9080)
+	assert.Len(t, ctx.Upstreams, 2)
+	assert.Equal(t, ctx.Upstreams[0].Name, "test_svc-1_80")
+	assert.Len(t, ctx.Upstreams[0].Nodes, 2)
+	assert.Equal(t, ctx.Upstreams[0].Nodes[0].Host, "192.168.1.1")
+	assert.Equal(t, ctx.Upstreams[0].Nodes[0].Port, 9080)
+	assert.Equal(t, ctx.Upstreams[0].Nodes[1].Host, "192.168.1.2")
+	assert.Equal(t, ctx.Upstreams[0].Nodes[1].Port, 9080)
 
-	assert.Equal(t, ups[1].Name, "test_svc-1_443")
-	assert.Len(t, ups[1].Nodes, 1)
-	assert.Equal(t, ups[1].Nodes[0].Host, "10.0.5.3")
-	assert.Equal(t, ups[1].Nodes[0].Port, 443)
+	assert.Equal(t, ctx.Upstreams[1].Name, "test_svc-1_443")
+	assert.Len(t, ctx.Upstreams[1].Nodes, 1)
+	assert.Equal(t, ctx.Upstreams[1].Nodes[0].Host, "10.0.5.3")
+	assert.Equal(t, ctx.Upstreams[1].Nodes[0].Port, 443)
 
 	assert.Len(t, cfg.Rules, 1)
 	assert.Len(t, cfg.Rules[0].WeightedUpstreams, 3)
@@ -345,25 +348,17 @@ func TestTranslateTrafficSplitPluginWithSameUpstreams(t *testing.T) {
 		EndpointsLister:      epLister,
 		ApisixUpstreamLister: auLister,
 	}}
-	ups, cfg, err := tr.translateTrafficSplitPlugin(ar1, 30, backends)
+	ctx := &TranslateContext{upstreamMap: make(map[string]struct{})}
+	cfg, err := tr.translateTrafficSplitPlugin(ctx, ar1, 30, backends)
 	assert.Nil(t, err)
 
-	// Here ups has two elements, but the duplicated one will be
-	// removed in TranslateApisixRouteV2alpha1.
-	assert.Len(t, ups, 2)
-	assert.Equal(t, ups[0].Name, "test_svc-1_80")
-	assert.Len(t, ups[0].Nodes, 2)
-	assert.Equal(t, ups[0].Nodes[0].Host, "192.168.1.1")
-	assert.Equal(t, ups[0].Nodes[0].Port, 9080)
-	assert.Equal(t, ups[0].Nodes[1].Host, "192.168.1.2")
-	assert.Equal(t, ups[0].Nodes[1].Port, 9080)
-
-	assert.Equal(t, ups[1].Name, "test_svc-1_80")
-	assert.Len(t, ups[1].Nodes, 2)
-	assert.Equal(t, ups[1].Nodes[0].Host, "192.168.1.1")
-	assert.Equal(t, ups[1].Nodes[0].Port, 9080)
-	assert.Equal(t, ups[1].Nodes[1].Host, "192.168.1.2")
-	assert.Equal(t, ups[1].Nodes[1].Port, 9080)
+	assert.Len(t, ctx.Upstreams, 1)
+	assert.Equal(t, ctx.Upstreams[0].Name, "test_svc-1_80")
+	assert.Len(t, ctx.Upstreams[0].Nodes, 2)
+	assert.Equal(t, ctx.Upstreams[0].Nodes[0].Host, "192.168.1.1")
+	assert.Equal(t, ctx.Upstreams[0].Nodes[0].Port, 9080)
+	assert.Equal(t, ctx.Upstreams[0].Nodes[1].Host, "192.168.1.2")
+	assert.Equal(t, ctx.Upstreams[0].Nodes[1].Port, 9080)
 
 	assert.Len(t, cfg.Rules, 1)
 	assert.Len(t, cfg.Rules[0].WeightedUpstreams, 3)
@@ -518,22 +513,23 @@ func TestTranslateTrafficSplitPluginBadCases(t *testing.T) {
 		EndpointsLister:      epLister,
 		ApisixUpstreamLister: auLister,
 	}}
-	ups, cfg, err := tr.translateTrafficSplitPlugin(ar1, 30, backends)
-	assert.Nil(t, ups)
+	ctx := &TranslateContext{upstreamMap: make(map[string]struct{})}
+	cfg, err := tr.translateTrafficSplitPlugin(ctx, ar1, 30, backends)
 	assert.Nil(t, cfg)
+	assert.Len(t, ctx.Upstreams, 0)
 	assert.Equal(t, err.Error(), "service \"svc-2\" not found")
 
 	backends[0].ServiceName = "svc-1"
 	backends[1].ServicePort.StrVal = "port-not-found"
-	ups, cfg, err = tr.translateTrafficSplitPlugin(ar1, 30, backends)
-	assert.Nil(t, ups)
+	ctx = &TranslateContext{upstreamMap: make(map[string]struct{})}
+	cfg, err = tr.translateTrafficSplitPlugin(ctx, ar1, 30, backends)
 	assert.Nil(t, cfg)
 	assert.Equal(t, err.Error(), "service.spec.ports: port not defined")
 
 	backends[1].ServicePort.StrVal = "port2"
 	backends[1].ResolveGranularity = "service"
-	ups, cfg, err = tr.translateTrafficSplitPlugin(ar1, 30, backends)
-	assert.Nil(t, ups)
+	ctx = &TranslateContext{upstreamMap: make(map[string]struct{})}
+	cfg, err = tr.translateTrafficSplitPlugin(ctx, ar1, 30, backends)
 	assert.Nil(t, cfg)
 	assert.Equal(t, err.Error(), "conflict headless service and backend resolve granularity")
 }
diff --git a/pkg/kube/translation/translator.go b/pkg/kube/translation/translator.go
index f134050..701d7dc 100644
--- a/pkg/kube/translation/translator.go
+++ b/pkg/kube/translation/translator.go
@@ -57,13 +57,13 @@ type Translator interface {
 	TranslateUpstream(string, string, int32) (*apisixv1.Upstream, error)
 	// TranslateIngress composes a couple of APISIX Routes and upstreams according
 	// to the given Ingress resource.
-	TranslateIngress(kube.Ingress) ([]*apisixv1.Route, []*apisixv1.Upstream, error)
+	TranslateIngress(kube.Ingress) (*TranslateContext, error)
 	// TranslateRouteV1 translates the configv1.ApisixRoute object into several Route
 	// and Upstream resources.
-	TranslateRouteV1(*configv1.ApisixRoute) ([]*apisixv1.Route, []*apisixv1.Upstream, error)
-	// TranslateRouteV2alpha1 translates the configv2alph1.ApisixRoute object into several Route
+	TranslateRouteV1(*configv1.ApisixRoute) (*TranslateContext, error)
+	// TranslateRouteV2alpha1 translates the configv2alpha1.ApisixRoute object into several Route
 	// and Upstream resources.
-	TranslateRouteV2alpha1(*configv2alpha1.ApisixRoute) ([]*apisixv1.Route, []*apisixv1.Upstream, error)
+	TranslateRouteV2alpha1(*configv2alpha1.ApisixRoute) (*TranslateContext, error)
 	// TranslateSSL translates the configv2alpha1.ApisixTls object into the APISIX SSL resource.
 	TranslateSSL(*configv1.ApisixTls) (*apisixv1.Ssl, error)
 }
@@ -191,7 +191,7 @@ func (t *translator) TranslateUpstreamNodes(endpoints *corev1.Endpoints, port in
 	return nodes, nil
 }
 
-func (t *translator) TranslateIngress(ing kube.Ingress) ([]*apisixv1.Route, []*apisixv1.Upstream, error) {
+func (t *translator) TranslateIngress(ing kube.Ingress) (*TranslateContext, error) {
 	switch ing.GroupVersion() {
 	case kube.IngressV1:
 		return t.translateIngressV1(ing.V1())
@@ -200,6 +200,6 @@ func (t *translator) TranslateIngress(ing kube.Ingress) ([]*apisixv1.Route, []*a
 	case kube.IngressExtensionsV1beta1:
 		return t.translateIngressExtensionsV1beta1(ing.ExtensionsV1beta1())
 	default:
-		return nil, nil, fmt.Errorf("translator: source group version not supported: %s", ing.GroupVersion())
+		return nil, fmt.Errorf("translator: source group version not supported: %s", ing.GroupVersion())
 	}
 }