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

tokers 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 9ac8181  feat: tcp route translation (#401)
9ac8181 is described below

commit 9ac8181d4d5006a666d21cbae0590f6a844adfd3
Author: Alex Zhang <[email protected]>
AuthorDate: Fri May 7 10:06:31 2021 +0800

    feat: tcp route translation (#401)
---
 docs/en/latest/concepts/apisix_route.md            |  24 +++++
 docs/en/latest/references/apisix_route_v2alpha1.md |  16 +++-
 pkg/ingress/apisix_route.go                        |  10 +-
 pkg/ingress/manifest.go                            |  67 +++++++++++---
 pkg/ingress/manifest_test.go                       |  45 +++++++++
 pkg/kube/translation/apisix_route.go               |  30 ++++++
 pkg/kube/translation/util.go                       |  39 ++++++++
 pkg/types/apisix/v1/types.go                       |  29 ++++++
 samples/deploy/crd/v1beta1/ApisixRoute.yaml        |  44 +++++++++
 test/e2e/endpoints/endpoints.go                    |  78 +++++++++-------
 test/e2e/features/healthcheck.go                   |  51 ++++++----
 test/e2e/features/retries_timeout.go               |  64 ++++++++-----
 test/e2e/features/scheme.go                        |  31 +++++--
 test/e2e/ingress/namespace.go                      |  49 ++++++----
 test/e2e/ingress/sanity.go                         | 103 +++++++++++++--------
 test/e2e/ingress/tcp.go                            |  72 ++++++++++++++
 test/e2e/scaffold/apisix.go                        |   4 +
 test/e2e/scaffold/k8s.go                           |  42 +++++++++
 test/e2e/scaffold/scaffold.go                      |  21 +++++
 test/e2e/testdata/apisix-gw-config.yaml            |   7 +-
 20 files changed, 663 insertions(+), 163 deletions(-)

diff --git a/docs/en/latest/concepts/apisix_route.md 
b/docs/en/latest/concepts/apisix_route.md
index 8a16659..68799ae 100644
--- a/docs/en/latest/concepts/apisix_route.md
+++ b/docs/en/latest/concepts/apisix_route.md
@@ -240,3 +240,27 @@ spec:
           servicePort: 8080
       websocket: true
 ```
+
+TCP Route
+---------
+
+apisix-ingress-controller supports the port-based tcp route.
+
+```yaml
+apiVersion: apisix.apache.org/v2alpha1
+kind: ApisixRoute
+metadata:
+  name: tcp-route
+spec:
+  tcp:
+    - name: tcp-route-rule1
+      match:
+        ingressPort: 9100
+      backend:
+        serviceName: tcp-server
+        servicePort: 8080
+```
+
+The above yaml configuration guides TCP traffic entered to the Ingress proxy 
server (i.e. [APISIX](https://apisix.apache.org)) port `9100` should be routed 
to the backend service `tcp-server`.
+
+Note since APISIX doesn't support dynamic listening, so here the `9100` port 
should be pre-defined in APISIX 
[configuration](https://github.com/apache/apisix/blob/master/conf/config-default.yaml#L101).
diff --git a/docs/en/latest/references/apisix_route_v2alpha1.md 
b/docs/en/latest/references/apisix_route_v2alpha1.md
index 1f054d1..b22ec79 100644
--- a/docs/en/latest/references/apisix_route_v2alpha1.md
+++ b/docs/en/latest/references/apisix_route_v2alpha1.md
@@ -44,18 +44,26 @@ Meaning of each field in the spec of ApisixRoute are 
followed, the top level fie
 | http[].match.exprs[].set | array | Expected expression result set, only used 
when the operator is `In` or `NotIn`, it's exclusive with 
`http[].match.exprs[].value`.
 | http[].backend | object | The backend service. Deprecated: use 
http[].backends instead.
 | http[].backend.serviceName | string | The backend service name, note the 
service and ApisixRoute should be created in the same namespace. Cross 
namespace referencing is not allowed.
-| http[].backend.servicePort | integer or string | The backend service port, 
can be the port number of the name defined in the service object.
-| http[].backend.resolveGranualrity | string | See [Service Resolve 
Granularity](#service-resolve-granularity) for the details.
+| http[].backend.servicePort | integer or string | The backend service port, 
can be the port number or the name defined in the service object.
+| http[].backend.resolveGranularity | string | See [Service Resolve 
Granularity](#service-resolve-granularity) for the details.
 | http[].backends | object | The backend services. When the number of backends 
more than one, weight based traffic split policy will be applied to shifting 
traffic between these backends.
 | http[].backends[].serviceName | string | The backend service name, note the 
service and ApisixRoute should be created in the same namespace. Cross 
namespace referencing is not allowed.
-| http[].backends[].servicePort | integer or string | The backend service 
port, can be the port number of the name defined in the service object.
-| http[].backends[].resolveGranualrity | string | See [Service Resolve 
Granularity](#service-resolve-granularity) for the details.
+| http[].backends[].servicePort | integer or string | The backend service 
port, can be the port number or the name defined in the service object.
+| http[].backends[].resolveGranularity | string | See [Service Resolve 
Granularity](#service-resolve-granularity) for the details.
 | http[].backends[].weight | int | The backend weight, which is critical when 
shifting traffic between multiple backends, default is `100`. Weight is ignored 
when there is only one backend.
 | http[].plugins | array | A series of APISIX plugins that will be executed 
once this route rule is matched |
 | http[].plugins[].name | string | The plugin name, see 
[docs](http://apisix.apache.org/docs/apisix/getting-started) for learning the 
available plugins.
 | http[].plugins[].enable | boolean | Whether the plugin is in use |
 | http[].plugins[].config | object | The plugin configuration, fields should 
be same as in APISIX. |
 | http[].websocket | boolean | Whether enable websocket proxy. |
+| tcp | array | ApisixRoutes' tcp route rules. |
+| tcp[].name | string (required) | The Route rule name. |
+| tcp[].match | object (required) | The Route match conditions. |
+| tcp[].match.ingressPort | integer (required) | the Ingress proxy server 
listening port, note since APISIX doesn't support dynamic listening, this port 
should be defined in [apisix 
configuration](https://github.com/apache/apisix/blob/master/conf/config-default.yaml#L101).|
+| tcp[].backend | object | The backend service. Deprecated: use 
http[].backends instead.
+| tcp[].backend.serviceName | string | The backend service name, note the 
service and ApisixRoute should be created in the same namespace. Cross 
namespace referencing is not allowed.
+| tcp[].backend.servicePort | integer or string | The backend service port, 
can be the port number or the name defined in the service object.
+| tcp[].backend.resolveGranularity | string | See [Service Resolve 
Granularity](#service-resolve-granularity) for the details.
 
 ## Expression Operators
 
diff --git a/pkg/ingress/apisix_route.go b/pkg/ingress/apisix_route.go
index a1b67a2..f6fe291 100644
--- a/pkg/ingress/apisix_route.go
+++ b/pkg/ingress/apisix_route.go
@@ -155,8 +155,9 @@ func (c *apisixRouteController) sync(ctx context.Context, 
ev *types.Event) error
        )
 
        m := &manifest{
-               routes:    tctx.Routes,
-               upstreams: tctx.Upstreams,
+               routes:       tctx.Routes,
+               upstreams:    tctx.Upstreams,
+               streamRoutes: tctx.StreamRoutes,
        }
 
        var (
@@ -187,8 +188,9 @@ func (c *apisixRouteController) sync(ctx context.Context, 
ev *types.Event) error
                }
 
                om := &manifest{
-                       routes:    oldCtx.Routes,
-                       upstreams: oldCtx.Upstreams,
+                       routes:       oldCtx.Routes,
+                       upstreams:    oldCtx.Upstreams,
+                       streamRoutes: oldCtx.StreamRoutes,
                }
                added, updated, deleted = m.diff(om)
        }
diff --git a/pkg/ingress/manifest.go b/pkg/ingress/manifest.go
index 8e3034c..42cfb61 100644
--- a/pkg/ingress/manifest.go
+++ b/pkg/ingress/manifest.go
@@ -80,30 +80,60 @@ func diffUpstreams(olds, news []*apisixv1.Upstream) (added, 
updated, deleted []*
        return
 }
 
+func diffStreamRoutes(olds, news []*apisixv1.StreamRoute) (added, updated, 
deleted []*apisixv1.StreamRoute) {
+       oldMap := make(map[string]*apisixv1.StreamRoute, len(olds))
+       newMap := make(map[string]*apisixv1.StreamRoute, len(news))
+       for _, sr := range olds {
+               oldMap[sr.ID] = sr
+       }
+       for _, sr := range news {
+               newMap[sr.ID] = sr
+       }
+
+       for _, sr := range news {
+               if ou, ok := oldMap[sr.ID]; !ok {
+                       added = append(added, sr)
+               } else if !reflect.DeepEqual(ou, sr) {
+                       updated = append(updated, sr)
+               }
+       }
+       for _, sr := range olds {
+               if _, ok := newMap[sr.ID]; !ok {
+                       deleted = append(deleted, sr)
+               }
+       }
+       return
+}
+
 type manifest struct {
-       routes    []*apisixv1.Route
-       upstreams []*apisixv1.Upstream
+       routes       []*apisixv1.Route
+       upstreams    []*apisixv1.Upstream
+       streamRoutes []*apisixv1.StreamRoute
 }
 
 func (m *manifest) diff(om *manifest) (added, updated, deleted *manifest) {
        ar, ur, dr := diffRoutes(om.routes, m.routes)
        au, uu, du := diffUpstreams(om.upstreams, m.upstreams)
-       if ar != nil || au != nil {
+       asr, usr, dsr := diffStreamRoutes(om.streamRoutes, m.streamRoutes)
+       if ar != nil || au != nil || asr != nil {
                added = &manifest{
-                       routes:    ar,
-                       upstreams: au,
+                       routes:       ar,
+                       upstreams:    au,
+                       streamRoutes: asr,
                }
        }
-       if ur != nil || uu != nil {
+       if ur != nil || uu != nil || usr != nil {
                updated = &manifest{
-                       routes:    ur,
-                       upstreams: uu,
+                       routes:       ur,
+                       upstreams:    uu,
+                       streamRoutes: usr,
                }
        }
-       if dr != nil || du != nil {
+       if dr != nil || du != nil || dsr != nil {
                deleted = &manifest{
-                       routes:    dr,
-                       upstreams: du,
+                       routes:       dr,
+                       upstreams:    du,
+                       streamRoutes: dsr,
                }
        }
        return
@@ -124,6 +154,11 @@ func (c *Controller) syncManifests(ctx context.Context, 
added, updated, deleted
                                merr = multierror.Append(merr, err)
                        }
                }
+               for _, sr := range deleted.streamRoutes {
+                       if err := 
c.apisix.Cluster(clusterName).StreamRoute().Delete(ctx, sr); err != nil {
+                               merr = multierror.Append(merr, err)
+                       }
+               }
        }
        if added != nil {
                // Should create upstreams firstly due to the dependencies.
@@ -137,6 +172,11 @@ func (c *Controller) syncManifests(ctx context.Context, 
added, updated, deleted
                                merr = multierror.Append(merr, err)
                        }
                }
+               for _, sr := range added.streamRoutes {
+                       if _, err := 
c.apisix.Cluster(clusterName).StreamRoute().Create(ctx, sr); err != nil {
+                               merr = multierror.Append(merr, err)
+                       }
+               }
        }
        if updated != nil {
                for _, r := range updated.upstreams {
@@ -149,6 +189,11 @@ func (c *Controller) syncManifests(ctx context.Context, 
added, updated, deleted
                                merr = multierror.Append(merr, err)
                        }
                }
+               for _, sr := range updated.streamRoutes {
+                       if _, err := 
c.apisix.Cluster(clusterName).StreamRoute().Create(ctx, sr); err != nil {
+                               merr = multierror.Append(merr, err)
+                       }
+               }
        }
        if merr != nil {
                return merr
diff --git a/pkg/ingress/manifest_test.go b/pkg/ingress/manifest_test.go
index 9d568e6..3ad5e3a 100644
--- a/pkg/ingress/manifest_test.go
+++ b/pkg/ingress/manifest_test.go
@@ -75,6 +75,51 @@ func TestDiffRoutes(t *testing.T) {
        assert.Equal(t, deleted[0].ID, "2")
 }
 
+func TestDiffStreamRoutes(t *testing.T) {
+       news := []*apisixv1.StreamRoute{
+               {
+                       ID: "1",
+               },
+               {
+                       ID:         "3",
+                       ServerPort: 8080,
+               },
+       }
+       added, updated, deleted := diffStreamRoutes(nil, news)
+       assert.Nil(t, updated)
+       assert.Nil(t, deleted)
+       assert.Len(t, added, 2)
+       assert.Equal(t, added[0].ID, "1")
+       assert.Equal(t, added[1].ID, "3")
+       assert.Equal(t, added[1].ServerPort, int32(8080))
+
+       olds := []*apisixv1.StreamRoute{
+               {
+                       ID: "2",
+               },
+               {
+                       ID:         "3",
+                       ServerPort: 8081,
+               },
+       }
+       added, updated, deleted = diffStreamRoutes(olds, nil)
+       assert.Nil(t, updated)
+       assert.Nil(t, added)
+       assert.Len(t, deleted, 2)
+       assert.Equal(t, deleted[0].ID, "2")
+       assert.Equal(t, deleted[1].ID, "3")
+       assert.Equal(t, deleted[1].ServerPort, int32(8081))
+
+       added, updated, deleted = diffStreamRoutes(olds, news)
+       assert.Len(t, added, 1)
+       assert.Equal(t, added[0].ID, "1")
+       assert.Len(t, updated, 1)
+       assert.Equal(t, updated[0].ID, "3")
+       assert.Equal(t, updated[0].ServerPort, int32(8080))
+       assert.Len(t, deleted, 1)
+       assert.Equal(t, deleted[0].ID, "2")
+}
+
 func TestDiffUpstreams(t *testing.T) {
        news := []*apisixv1.Upstream{
                {
diff --git a/pkg/kube/translation/apisix_route.go 
b/pkg/kube/translation/apisix_route.go
index 69cbae4..bbf8705 100644
--- a/pkg/kube/translation/apisix_route.go
+++ b/pkg/kube/translation/apisix_route.go
@@ -294,5 +294,35 @@ func (t *translator) translateRouteMatchExprs(nginxVars 
[]configv2alpha1.ApisixR
 }
 
 func (t *translator) translateTCPRoute(ctx *TranslateContext, ar 
*configv2alpha1.ApisixRoute) error {
+       ruleNameMap := make(map[string]struct{})
+       for _, part := range ar.Spec.TCP {
+               if _, ok := ruleNameMap[part.Name]; ok {
+                       return errors.New("duplicated route rule name")
+               }
+               ruleNameMap[part.Name] = struct{}{}
+               backend := &part.Backend
+               svcClusterIP, svcPort, err := 
t.getTCPServiceClusterIPAndPort(backend, ar)
+               if err != nil {
+                       log.Errorw("failed to get service port in backend",
+                               zap.Any("backend", backend),
+                               zap.Any("apisix_route", ar),
+                               zap.Error(err),
+                       )
+                       return err
+               }
+               sr := apisixv1.NewDefaultStreamRoute()
+               name := apisixv1.ComposeStreamRouteName(ar.Namespace, ar.Name, 
part.Name)
+               sr.ID = id.GenID(name)
+               sr.ServerPort = part.Match.IngressPort
+               // TODO use upstream id to refer the upstream object.
+               // Currently, APISIX doesn't use upstream_id field in
+               // APISIX, so we have to embed the entire upstream.
+               ups, err := t.translateUpstream(ar.Namespace, 
backend.ServiceName, backend.ResolveGranularity, svcClusterIP, svcPort)
+               if err != nil {
+                       return err
+               }
+               sr.Upstream = ups
+               ctx.addStreamRoute(sr)
+       }
        return nil
 }
diff --git a/pkg/kube/translation/util.go b/pkg/kube/translation/util.go
index 76af4d3..16ced5a 100644
--- a/pkg/kube/translation/util.go
+++ b/pkg/kube/translation/util.go
@@ -70,6 +70,45 @@ loop:
        return svc.Spec.ClusterIP, svcPort, nil
 }
 
+func (t *translator) getTCPServiceClusterIPAndPort(backend 
*configv2alpha1.ApisixRouteTCPBackend, ar *configv2alpha1.ApisixRoute) (string, 
int32, error) {
+       svc, err := 
t.ServiceLister.Services(ar.Namespace).Get(backend.ServiceName)
+       if err != nil {
+               return "", 0, err
+       }
+       svcPort := int32(-1)
+loop:
+       for _, port := range svc.Spec.Ports {
+               switch backend.ServicePort.Type {
+               case intstr.Int:
+                       if backend.ServicePort.IntVal == port.Port {
+                               svcPort = port.Port
+                               break loop
+                       }
+               case intstr.String:
+                       if backend.ServicePort.StrVal == port.Name {
+                               svcPort = port.Port
+                               break loop
+                       }
+               }
+       }
+       if svcPort == -1 {
+               log.Errorw("ApisixRoute refers to non-existent Service port",
+                       zap.Any("ApisixRoute", ar),
+                       zap.String("port", backend.ServicePort.String()),
+               )
+               return "", 0, err
+       }
+
+       if backend.ResolveGranularity == "service" && svc.Spec.ClusterIP == "" {
+               log.Errorw("ApisixRoute refers to a headless service but want 
to use the service level resolve granularity",
+                       zap.Any("ApisixRoute", ar),
+                       zap.Any("service", svc),
+               )
+               return "", 0, errors.New("conflict headless service and backend 
resolve granularity")
+       }
+       return svc.Spec.ClusterIP, svcPort, nil
+}
+
 func (t *translator) translateUpstream(namespace, svcName, 
svcResolveGranularity, svcClusterIP string, svcPort int32) (*apisixv1.Upstream, 
error) {
        ups, err := t.TranslateUpstream(namespace, svcName, svcPort)
        if err != nil {
diff --git a/pkg/types/apisix/v1/types.go b/pkg/types/apisix/v1/types.go
index 846182b..082beec 100644
--- a/pkg/types/apisix/v1/types.go
+++ b/pkg/types/apisix/v1/types.go
@@ -332,6 +332,7 @@ type StreamRoute struct {
        Labels     map[string]string `json:"labels,omitempty" 
yaml:"labels,omitempty"`
        ServerPort int32             `json:"server_port,omitempty" 
yaml:"server_port,omitempty"`
        UpstreamId string            `json:"upstream_id,omitempty" 
yaml:"upstream_id,omitempty"`
+       Upstream   *Upstream         `json:"upstream,omitempty" 
yaml:"upstream,omitempty"`
 }
 
 // GlobalRule represents the global_rule object in APISIX.
@@ -369,6 +370,16 @@ func NewDefaultRoute() *Route {
        }
 }
 
+// NewDefaultStreamRoute returns an empty StreamRoute with default values.
+func NewDefaultStreamRoute() *StreamRoute {
+       return &StreamRoute{
+               Desc: "Created by apisix-ingress-controller, DO NOT modify it 
manually",
+               Labels: map[string]string{
+                       "managed-by": "apisix-ingress-controller",
+               },
+       }
+}
+
 // ComposeUpstreamName uses namespace, name and port info to compose
 // the upstream name.
 func ComposeUpstreamName(namespace, name string, port int32) string {
@@ -403,3 +414,21 @@ func ComposeRouteName(namespace, name string, rule string) 
string {
 
        return buf.String()
 }
+
+// ComposeStreamRouteName uses namespace, name and rule name to compose
+// the stream_route name.
+func ComposeStreamRouteName(namespace, name string, rule string) string {
+       // FIXME Use sync.Pool to reuse this buffer if the upstream
+       // name composing code path is hot.
+       p := make([]byte, 0, len(namespace)+len(name)+len(rule)+6)
+       buf := bytes.NewBuffer(p)
+
+       buf.WriteString(namespace)
+       buf.WriteByte('_')
+       buf.WriteString(name)
+       buf.WriteByte('_')
+       buf.WriteString(rule)
+       buf.WriteString("_tcp")
+
+       return buf.String()
+}
diff --git a/samples/deploy/crd/v1beta1/ApisixRoute.yaml 
b/samples/deploy/crd/v1beta1/ApisixRoute.yaml
index d335379..89f4e1a 100644
--- a/samples/deploy/crd/v1beta1/ApisixRoute.yaml
+++ b/samples/deploy/crd/v1beta1/ApisixRoute.yaml
@@ -30,6 +30,12 @@ spec:
     - JSONPath: .spec.http[].match.backends[].serviceName
       name: Target Service
       type: string
+    - JSONPath: .spec.tcp[].match.ingressPort
+      name: Ingress Server Port
+      type: integer
+    - JSONPath: .spec.tcp[].match.backend.serviceName
+      name: Target Service
+      type: string
     - JSONPath: .metadata.creationTimestamp
       name: Age
       type: date
@@ -57,6 +63,9 @@ spec:
       properties:
         spec:
           type: object
+          anyOf:
+            - required: ["http"]
+            - required: ["tcp"]
           properties:
             http:
               type: array
@@ -197,3 +206,38 @@ spec:
                     required:
                       - name
                       - enable
+            tcp:
+              type: array
+              minItems: 1
+              items:
+                type: object
+                required: ["name", "match", "backend"]
+                properties:
+                  name:
+                    type: string
+                    minLength: 1
+                  match:
+                    type: object
+                    properties:
+                      ingressPort:
+                        type: integer
+                        minimum: 1
+                        maximum: 65535
+                    required:
+                      - ingressPort
+                  backend:
+                    type: object
+                    properties:
+                      serviceName:
+                        type: string
+                        minLength: 1
+                      servicePort:
+                        type: integer
+                        minimum: 1
+                        maximum: 65535
+                      resolveGranualrity:
+                        type: string
+                        enum: ["endpoint", "service"]
+                    required:
+                      - serviceName
+                      - servicePort
diff --git a/test/e2e/endpoints/endpoints.go b/test/e2e/endpoints/endpoints.go
index 0973a05..9148d02 100644
--- a/test/e2e/endpoints/endpoints.go
+++ b/test/e2e/endpoints/endpoints.go
@@ -26,25 +26,36 @@ import (
 )
 
 var _ = ginkgo.Describe("endpoints", func() {
-       s := scaffold.NewDefaultScaffold()
+       opts := &scaffold.Options{
+               Name:                    "default",
+               Kubeconfig:              scaffold.GetKubeconfig(),
+               APISIXConfigPath:        "testdata/apisix-gw-config.yaml",
+               APISIXDefaultConfigPath: 
"testdata/apisix-gw-config-default.yaml",
+               IngressAPISIXReplicas:   1,
+               HTTPBinServicePort:      80,
+               APISIXRouteVersion:      "apisix.apache.org/v2alpha1",
+       }
+       s := scaffold.NewScaffold(opts)
        ginkgo.It("ignore applied only if there is an ApisixRoute referenced", 
func() {
                time.Sleep(5 * time.Second)
                assert.Nil(ginkgo.GinkgoT(), 
s.EnsureNumApisixUpstreamsCreated(0), "checking number of upstreams")
                backendSvc, backendSvcPort := s.DefaultHTTPBackend()
                ups := fmt.Sprintf(`
-apiVersion: apisix.apache.org/v1
+apiVersion: apisix.apache.org/v2alpha1
 kind: ApisixRoute
 metadata:
   name: httpbin-route
 spec:
-   rules:
-   - host: httpbin.org
-     http:
-       paths:
-       - backend:
-           serviceName: %s
-           servicePort: %d
-         path: /ip
+  http:
+  - name: rule1
+    match:
+      hosts:
+      - httpbin.org
+      paths:
+      - /ip
+    backend:
+      serviceName: %s
+      servicePort: %d
 `, backendSvc, backendSvcPort[0])
                assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(ups))
                assert.Nil(ginkgo.GinkgoT(), 
s.EnsureNumApisixUpstreamsCreated(1), "checking number of upstreams")
@@ -54,19 +65,20 @@ spec:
                ginkgo.Skip("now we don't handle endpoints delete event")
                backendSvc, backendSvcPort := s.DefaultHTTPBackend()
                apisixRoute := fmt.Sprintf(`
-apiVersion: apisix.apache.org/v1
+apiVersion: apisix.apache.org/v2alpha1
 kind: ApisixRoute
 metadata:
  name: httpbin-route
 spec:
- rules:
- - host: httpbin.com
-   http:
-     paths:
-     - backend:
-         serviceName: %s
-         servicePort: %d
-       path: /ip
+  http:
+  - name: rule1
+    match:
+      hosts:
+      - httpbin.com
+      paths: /ip
+    backend:
+      serviceName: %s
+      servicePort: %d
 `, backendSvc, backendSvcPort[0])
                assert.Nil(ginkgo.GinkgoT(), 
s.CreateResourceFromString(apisixRoute))
                assert.Nil(ginkgo.GinkgoT(), 
s.EnsureNumApisixUpstreamsCreated(1), "checking number of upstreams")
@@ -80,30 +92,34 @@ spec:
 })
 
 var _ = ginkgo.Describe("port usage", func() {
-       s := scaffold.NewScaffold(&scaffold.Options{
+       opts := &scaffold.Options{
                Name:                    "endpoints-port",
                Kubeconfig:              scaffold.GetKubeconfig(),
                APISIXConfigPath:        "testdata/apisix-gw-config.yaml",
                APISIXDefaultConfigPath: 
"testdata/apisix-gw-config-default.yaml",
                IngressAPISIXReplicas:   1,
-               HTTPBinServicePort:      8080, // use service port which is 
different from target port (80)
-       })
+               HTTPBinServicePort:      8080,
+               APISIXRouteVersion:      "apisix.apache.org/v2alpha1",
+       }
+       s := scaffold.NewScaffold(opts)
        ginkgo.It("service port != target port", func() {
                backendSvc, backendSvcPort := s.DefaultHTTPBackend()
                apisixRoute := fmt.Sprintf(`
-apiVersion: apisix.apache.org/v1
+apiVersion: apisix.apache.org/v2alpha1
 kind: ApisixRoute
 metadata:
  name: httpbin-route
 spec:
- rules:
- - host: httpbin.com
-   http:
-     paths:
-     - backend:
-         serviceName: %s
-         servicePort: %d
-       path: /ip
+  http:
+  - name: rule1
+    match:
+      hosts:
+      - httpbin.com
+      paths:
+      - /ip
+    backend:
+      serviceName: %s
+      servicePort: %d
 `, backendSvc, backendSvcPort[0])
                assert.Nil(ginkgo.GinkgoT(), 
s.CreateResourceFromString(apisixRoute))
                time.Sleep(5 * time.Second)
diff --git a/test/e2e/features/healthcheck.go b/test/e2e/features/healthcheck.go
index 75c11cd..bf4e7c9 100644
--- a/test/e2e/features/healthcheck.go
+++ b/test/e2e/features/healthcheck.go
@@ -25,7 +25,16 @@ import (
 )
 
 var _ = ginkgo.Describe("health check", func() {
-       s := scaffold.NewDefaultScaffold()
+       opts := &scaffold.Options{
+               Name:                    "default",
+               Kubeconfig:              scaffold.GetKubeconfig(),
+               APISIXConfigPath:        "testdata/apisix-gw-config.yaml",
+               APISIXDefaultConfigPath: 
"testdata/apisix-gw-config-default.yaml",
+               IngressAPISIXReplicas:   1,
+               HTTPBinServicePort:      80,
+               APISIXRouteVersion:      "apisix.apache.org/v2alpha1",
+       }
+       s := scaffold.NewScaffold(opts)
        ginkgo.It("active check", func() {
                backendSvc, backendPorts := s.DefaultHTTPBackend()
 
@@ -50,19 +59,21 @@ spec:
                assert.Nil(ginkgo.GinkgoT(), err, "create ApisixUpstream")
 
                ar := fmt.Sprintf(`
-apiVersion: apisix.apache.org/v1
+apiVersion: apisix.apache.org/v2alpha1
 kind: ApisixRoute
 metadata:
  name: httpbin-route
 spec:
- rules:
- - host: httpbin.org
-   http:
-     paths:
-     - backend:
-         serviceName: %s
-         servicePort: %d
-       path: /*
+  http:
+  - name: rule1
+    match:
+      hosts:
+      - httpbin.org
+      paths:
+      - /*
+    backend:
+      serviceName: %s
+      servicePort: %d
 `, backendSvc, backendPorts[0])
                err = s.CreateResourceFromString(ar)
                assert.Nil(ginkgo.GinkgoT(), err)
@@ -110,19 +121,21 @@ spec:
                assert.Nil(ginkgo.GinkgoT(), err, "create ApisixUpstream")
 
                ar := fmt.Sprintf(`
-apiVersion: apisix.apache.org/v1
+apiVersion: apisix.apache.org/v2alpha1
 kind: ApisixRoute
 metadata:
  name: httpbin-route
 spec:
- rules:
- - host: httpbin.org
-   http:
-     paths:
-     - backend:
-         serviceName: %s
-         servicePort: %d
-       path: /*
+  http:
+  - name: rule1
+    match:
+      hosts:
+      - httpbin.org
+      paths:
+      - /*
+    backend:
+      serviceName: %s
+      servicePort: %d
 `, backendSvc, backendPorts[0])
                err = s.CreateResourceFromString(ar)
                assert.Nil(ginkgo.GinkgoT(), err)
diff --git a/test/e2e/features/retries_timeout.go 
b/test/e2e/features/retries_timeout.go
index 9e3d757..64470ac 100644
--- a/test/e2e/features/retries_timeout.go
+++ b/test/e2e/features/retries_timeout.go
@@ -24,7 +24,16 @@ import (
 )
 
 var _ = ginkgo.Describe("retries", func() {
-       s := scaffold.NewDefaultScaffold()
+       opts := &scaffold.Options{
+               Name:                    "default",
+               Kubeconfig:              scaffold.GetKubeconfig(),
+               APISIXConfigPath:        "testdata/apisix-gw-config.yaml",
+               APISIXDefaultConfigPath: 
"testdata/apisix-gw-config-default.yaml",
+               IngressAPISIXReplicas:   1,
+               HTTPBinServicePort:      80,
+               APISIXRouteVersion:      "apisix.apache.org/v2alpha1",
+       }
+       s := scaffold.NewScaffold(opts)
        ginkgo.It("active check", func() {
                backendSvc, backendPorts := s.DefaultHTTPBackend()
 
@@ -41,19 +50,21 @@ spec:
                time.Sleep(2 * time.Second)
 
                ar := fmt.Sprintf(`
-apiVersion: apisix.apache.org/v1
+apiVersion: apisix.apache.org/v2alpha1
 kind: ApisixRoute
 metadata:
- name: httpbin-route
+  name: httpbin-route
 spec:
- rules:
- - host: httpbin.org
-   http:
-     paths:
-     - backend:
-         serviceName: %s
-         servicePort: %d
-       path: /*
+  http:
+  - name: rule1
+    match:
+      hosts:
+      - httpbin.org
+      paths:
+      - /*
+    backend:
+      serviceName: %s
+      servicePort: %d
 `, backendSvc, backendPorts[0])
                err = s.CreateResourceFromString(ar)
                assert.Nil(ginkgo.GinkgoT(), err)
@@ -67,7 +78,16 @@ spec:
 })
 
 var _ = ginkgo.Describe("timeout", func() {
-       s := scaffold.NewDefaultScaffold()
+       opts := &scaffold.Options{
+               Name:                    "default",
+               Kubeconfig:              scaffold.GetKubeconfig(),
+               APISIXConfigPath:        "testdata/apisix-gw-config.yaml",
+               APISIXDefaultConfigPath: 
"testdata/apisix-gw-config-default.yaml",
+               IngressAPISIXReplicas:   1,
+               HTTPBinServicePort:      80,
+               APISIXRouteVersion:      "apisix.apache.org/v2alpha1",
+       }
+       s := scaffold.NewScaffold(opts)
        ginkgo.It("active check", func() {
                backendSvc, backendPorts := s.DefaultHTTPBackend()
 
@@ -86,19 +106,21 @@ spec:
                time.Sleep(2 * time.Second)
 
                ar := fmt.Sprintf(`
-apiVersion: apisix.apache.org/v1
+apiVersion: apisix.apache.org/v2alpha1
 kind: ApisixRoute
 metadata:
  name: httpbin-route
 spec:
- rules:
- - host: httpbin.org
-   http:
-     paths:
-     - backend:
-         serviceName: %s
-         servicePort: %d
-       path: /*
+  http:
+  - name: rule1
+    match:
+      hosts:
+      - httpbin.org
+      paths:
+      - /*
+    backend:
+      serviceName: %s
+      servicePort: %d
 `, backendSvc, backendPorts[0])
                err = s.CreateResourceFromString(ar)
                assert.Nil(ginkgo.GinkgoT(), err)
diff --git a/test/e2e/features/scheme.go b/test/e2e/features/scheme.go
index bdcd529..8f2334a 100644
--- a/test/e2e/features/scheme.go
+++ b/test/e2e/features/scheme.go
@@ -25,7 +25,16 @@ import (
 )
 
 var _ = ginkgo.Describe("choose scheme", func() {
-       s := scaffold.NewDefaultScaffold()
+       opts := &scaffold.Options{
+               Name:                    "default",
+               Kubeconfig:              scaffold.GetKubeconfig(),
+               APISIXConfigPath:        "testdata/apisix-gw-config.yaml",
+               APISIXDefaultConfigPath: 
"testdata/apisix-gw-config-default.yaml",
+               IngressAPISIXReplicas:   1,
+               HTTPBinServicePort:      80,
+               APISIXRouteVersion:      "apisix.apache.org/v2alpha1",
+       }
+       s := scaffold.NewScaffold(opts)
        ginkgo.It("grpc", func() {
                err := s.CreateResourceFromString(`
 apiVersion: v1
@@ -64,19 +73,21 @@ spec:
       scheme: grpc
 `))
                err = s.CreateResourceFromString(`
-apiVersion: apisix.apache.org/v1
+apiVersion: apisix.apache.org/v2alpha1
 kind: ApisixRoute
 metadata:
  name: grpc-route
 spec:
- rules:
- - host: grpc.local
-   http:
-     paths:
-     - backend:
-         serviceName: grpc-server-service
-         servicePort: 50051
-       path: /helloworld.Greeter/SayHello
+  http:
+  - name: rule1
+    match:
+      hosts:
+      - grpc.local
+      paths:
+      - /helloworld.Greeter/SayHello
+    backend:
+       serviceName: grpc-server-service
+       servicePort: 50051
 `)
                assert.Nil(ginkgo.GinkgoT(), err)
                time.Sleep(2 * time.Second)
diff --git a/test/e2e/ingress/namespace.go b/test/e2e/ingress/namespace.go
index 9f4d1f9..57a211a 100644
--- a/test/e2e/ingress/namespace.go
+++ b/test/e2e/ingress/namespace.go
@@ -28,23 +28,34 @@ import (
 )
 
 var _ = ginkgo.Describe("namespacing filtering", func() {
-       s := scaffold.NewDefaultScaffold()
+       opts := &scaffold.Options{
+               Name:                    "default",
+               Kubeconfig:              scaffold.GetKubeconfig(),
+               APISIXConfigPath:        "testdata/apisix-gw-config.yaml",
+               APISIXDefaultConfigPath: 
"testdata/apisix-gw-config-default.yaml",
+               IngressAPISIXReplicas:   1,
+               HTTPBinServicePort:      80,
+               APISIXRouteVersion:      "apisix.apache.org/v2alpha1",
+       }
+       s := scaffold.NewScaffold(opts)
        ginkgo.It("resources in other namespaces should be ignored", func() {
                backendSvc, backendSvcPort := s.DefaultHTTPBackend()
                route := fmt.Sprintf(`
-apiVersion: apisix.apache.org/v1
+apiVersion: apisix.apache.org/v2alpha1
 kind: ApisixRoute
 metadata:
   name: httpbin-route
 spec:
-  rules:
-  - host: httpbin.com
-    http:
+  http:
+  - name: rule1
+    match:
+      hosts:
+      - httpbin.com
       paths:
-      - backend:
-          serviceName: %s
-          servicePort: %d
-        path: /ip
+      - /ip
+    backend:
+      serviceName: %s
+      servicePort: %d
 `, backendSvc, backendSvcPort[0])
 
                assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(route), 
"creating ApisixRoute")
@@ -62,19 +73,21 @@ spec:
 
                // Now create another ApisixRoute in default namespace.
                route = fmt.Sprintf(`
-apiVersion: apisix.apache.org/v1
+apiVersion: apisix.apache.org/v2alpha1
 kind: ApisixRoute
 metadata:
  name: httpbin-route
 spec:
- rules:
- - host: httpbin.com
-   http:
-     paths:
-     - backend:
-         serviceName: %s
-         servicePort: %d
-       path: /headers
+  http:
+  - name: rule1
+    match:
+      hosts:
+      - httpbin.com
+      paths:
+      - /headers
+    backend:
+      serviceName: %s
+      servicePort: %d
 `, backendSvc, backendSvcPort[0])
 
                assert.Nil(ginkgo.GinkgoT(), 
s.CreateResourceFromStringWithNamespace(route, "default"), "creating 
ApisixRoute")
diff --git a/test/e2e/ingress/sanity.go b/test/e2e/ingress/sanity.go
index 009efb3..7937a55 100644
--- a/test/e2e/ingress/sanity.go
+++ b/test/e2e/ingress/sanity.go
@@ -16,6 +16,7 @@ package ingress
 
 import (
        "encoding/json"
+       "fmt"
        "net/http"
        "time"
 
@@ -30,25 +31,34 @@ type ip struct {
 }
 
 var _ = ginkgo.Describe("single-route", func() {
-       s := scaffold.NewDefaultScaffold()
+       opts := &scaffold.Options{
+               Name:                    "default",
+               Kubeconfig:              scaffold.GetKubeconfig(),
+               APISIXConfigPath:        "testdata/apisix-gw-config.yaml",
+               APISIXDefaultConfigPath: 
"testdata/apisix-gw-config-default.yaml",
+               IngressAPISIXReplicas:   1,
+               HTTPBinServicePort:      80,
+               APISIXRouteVersion:      "apisix.apache.org/v2alpha1",
+       }
+       s := scaffold.NewScaffold(opts)
        ginkgo.It("/ip should return your ip", func() {
                backendSvc, backendSvcPort := s.DefaultHTTPBackend()
-               s.CreateApisixRoute("httpbin-route", []scaffold.ApisixRouteRule{
-                       {
-                               Host: "httpbin.com",
-                               HTTP: scaffold.ApisixRouteRuleHTTP{
-                                       Paths: 
[]scaffold.ApisixRouteRuleHTTPPath{
-                                               {
-                                                       Path: "/ip",
-                                                       Backend: 
scaffold.ApisixRouteRuleHTTPBackend{
-                                                               ServiceName: 
backendSvc,
-                                                               ServicePort: 
backendSvcPort[0],
-                                                       },
-                                               },
-                                       },
-                               },
-                       },
-               })
+               ar := fmt.Sprintf(`
+apiVersion: apisix.apache.org/v2alpha1
+kind: ApisixRoute
+metadata:
+  name: httpbin-route
+spec:
+  http:
+  - name: rule1
+    match:
+      paths:
+      - /ip
+    backend:
+      serviceName: %s
+      servicePort: %d
+`, backendSvc, backendSvcPort[0])
+               assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(ar))
                err := s.EnsureNumApisixRoutesCreated(1)
                assert.Nil(ginkgo.GinkgoT(), err, "checking number of routes")
                err = s.EnsureNumApisixUpstreamsCreated(1)
@@ -68,32 +78,43 @@ var _ = ginkgo.Describe("single-route", func() {
 })
 
 var _ = ginkgo.Describe("double-routes", func() {
-       s := scaffold.NewDefaultScaffold()
+       opts := &scaffold.Options{
+               Name:                    "default",
+               Kubeconfig:              scaffold.GetKubeconfig(),
+               APISIXConfigPath:        "testdata/apisix-gw-config.yaml",
+               APISIXDefaultConfigPath: 
"testdata/apisix-gw-config-default.yaml",
+               IngressAPISIXReplicas:   1,
+               HTTPBinServicePort:      80,
+               APISIXRouteVersion:      "apisix.apache.org/v2alpha1",
+       }
+       s := scaffold.NewScaffold(opts)
        ginkgo.It("double routes work independently", func() {
                backendSvc, backendSvcPort := s.DefaultHTTPBackend()
-               s.CreateApisixRoute("httpbin-route", []scaffold.ApisixRouteRule{
-                       {
-                               Host: "httpbin.com",
-                               HTTP: scaffold.ApisixRouteRuleHTTP{
-                                       Paths: 
[]scaffold.ApisixRouteRuleHTTPPath{
-                                               {
-                                                       Path: "/ip",
-                                                       Backend: 
scaffold.ApisixRouteRuleHTTPBackend{
-                                                               ServiceName: 
backendSvc,
-                                                               ServicePort: 
backendSvcPort[0],
-                                                       },
-                                               },
-                                               {
-                                                       Path: "/json",
-                                                       Backend: 
scaffold.ApisixRouteRuleHTTPBackend{
-                                                               ServiceName: 
backendSvc,
-                                                               ServicePort: 
backendSvcPort[0],
-                                                       },
-                                               },
-                                       },
-                               },
-                       },
-               })
+               ar := `
+apiVersion: apisix.apache.org/v2alpha1
+kind: ApisixRoute
+metadata:
+  name: httpbin-route
+spec:
+  http:
+  - name: rule1
+    match:
+      paths:
+      - /ip
+    backend:
+      serviceName: %s
+      servicePort: %d
+  - name: rule2
+    match:
+      paths:
+      - /json
+    backend:
+      serviceName: %s
+      servicePort: %d
+`
+               ar = fmt.Sprintf(ar, backendSvc, backendSvcPort[0], backendSvc, 
backendSvcPort[0])
+               assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(ar))
+
                err := s.EnsureNumApisixRoutesCreated(2)
                assert.Nil(ginkgo.GinkgoT(), err, "checking number of routes")
                err = s.EnsureNumApisixUpstreamsCreated(1)
diff --git a/test/e2e/ingress/tcp.go b/test/e2e/ingress/tcp.go
new file mode 100644
index 0000000..b745527
--- /dev/null
+++ b/test/e2e/ingress/tcp.go
@@ -0,0 +1,72 @@
+// 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 ingress
+
+import (
+       "fmt"
+       "time"
+
+       "github.com/onsi/ginkgo"
+       "github.com/stretchr/testify/assert"
+
+       "github.com/apache/apisix-ingress-controller/test/e2e/scaffold"
+)
+
+var _ = ginkgo.Describe("ApisixRoute Testing", func() {
+       opts := &scaffold.Options{
+               Name:                    "default",
+               Kubeconfig:              scaffold.GetKubeconfig(),
+               APISIXConfigPath:        "testdata/apisix-gw-config.yaml",
+               APISIXDefaultConfigPath: 
"testdata/apisix-gw-config-default.yaml",
+               IngressAPISIXReplicas:   1,
+               HTTPBinServicePort:      80,
+               APISIXRouteVersion:      "apisix.apache.org/v2alpha1",
+       }
+       s := scaffold.NewScaffold(opts)
+       ginkgo.It("tcp proxy", func() {
+               backendSvc, backendSvcPort := s.DefaultHTTPBackend()
+               apisixRoute := fmt.Sprintf(`
+apiVersion: apisix.apache.org/v2alpha1
+kind: ApisixRoute
+metadata:
+  name: httpbin-tcp-route
+spec:
+  tcp:
+  - name: rule1
+    match:
+      ingressPort: 9100
+    backend:
+      serviceName: %s
+      servicePort: %d
+`, backendSvc, backendSvcPort[0])
+
+               assert.Nil(ginkgo.GinkgoT(), 
s.CreateResourceFromString(apisixRoute))
+               time.Sleep(3 * time.Second)
+
+               err := s.EnsureNumApisixStreamRoutesCreated(1)
+               assert.Nil(ginkgo.GinkgoT(), err, "Checking number of routes")
+
+               sr, err := s.ListApisixStreamRoutes()
+               assert.Nil(ginkgo.GinkgoT(), err)
+               assert.Len(ginkgo.GinkgoT(), sr, 1)
+               assert.Equal(ginkgo.GinkgoT(), sr[0].ServerPort, int32(9100))
+
+               resp := s.NewAPISIXClientWithTCPProxy().GET("/ip").Expect()
+               resp.Body().Contains("origin")
+
+               resp = 
s.NewAPISIXClientWithTCPProxy().GET("/get").WithHeader("x-my-header", 
"x-my-value").Expect()
+               resp.Body().Contains("x-my-value")
+       })
+})
diff --git a/test/e2e/scaffold/apisix.go b/test/e2e/scaffold/apisix.go
index 5719f45..84bed8e 100644
--- a/test/e2e/scaffold/apisix.go
+++ b/test/e2e/scaffold/apisix.go
@@ -120,6 +120,10 @@ spec:
       port: 9443
       protocol: TCP
       targetPort: 9443
+    - name: tcp
+      port: 9100
+      protocol: TCP
+      targetPort: 9100
   type: NodePort
 `
 )
diff --git a/test/e2e/scaffold/k8s.go b/test/e2e/scaffold/k8s.go
index f165d8a..6fcc05b 100644
--- a/test/e2e/scaffold/k8s.go
+++ b/test/e2e/scaffold/k8s.go
@@ -162,6 +162,17 @@ func (s *Scaffold) EnsureNumApisixRoutesCreated(desired 
int) error {
        return ensureNumApisixCRDsCreated(u.String(), desired)
 }
 
+// EnsureNumApisixStreamRoutesCreated waits until desired number of Stream 
Routes are created in
+// APISIX cluster.
+func (s *Scaffold) EnsureNumApisixStreamRoutesCreated(desired int) error {
+       u := url.URL{
+               Scheme: "http",
+               Host:   s.apisixAdminTunnel.Endpoint(),
+               Path:   "/apisix/admin/stream_routes",
+       }
+       return ensureNumApisixCRDsCreated(u.String(), desired)
+}
+
 // EnsureNumApisixUpstreamsCreated waits until desired number of Upstreams are 
created in
 // APISIX cluster.
 func (s *Scaffold) EnsureNumApisixUpstreamsCreated(desired int) error {
@@ -233,6 +244,26 @@ func (s *Scaffold) ListApisixRoutes() ([]*v1.Route, error) 
{
        return cli.Cluster("").Route().List(context.TODO())
 }
 
+// ListApisixStreamRoutes list all stream_routes from APISIX.
+func (s *Scaffold) ListApisixStreamRoutes() ([]*v1.StreamRoute, error) {
+       u := url.URL{
+               Scheme: "http",
+               Host:   s.apisixAdminTunnel.Endpoint(),
+               Path:   "/apisix/admin",
+       }
+       cli, err := apisix.NewClient()
+       if err != nil {
+               return nil, err
+       }
+       err = cli.AddCluster(&apisix.ClusterOptions{
+               BaseURL: u.String(),
+       })
+       if err != nil {
+               return nil, err
+       }
+       return cli.Cluster("").StreamRoute().List(context.TODO())
+}
+
 // ListApisixTls list all ssl from APISIX
 func (s *Scaffold) ListApisixTls() ([]*v1.Ssl, error) {
        u := url.URL{
@@ -261,6 +292,8 @@ func (s *Scaffold) newAPISIXTunnels() error {
                adminPort     int
                httpPort      int
                httpsPort     int
+               tcpPort       int
+               tcpNodePort   int
        )
        for _, port := range s.apisixService.Spec.Ports {
                if port.Name == "http" {
@@ -272,6 +305,9 @@ func (s *Scaffold) newAPISIXTunnels() error {
                } else if port.Name == "http-admin" {
                        adminNodePort = int(port.NodePort)
                        adminPort = int(port.Port)
+               } else if port.Name == "tcp" {
+                       tcpNodePort = int(port.NodePort)
+                       tcpPort = int(port.Port)
                }
        }
 
@@ -281,6 +317,8 @@ func (s *Scaffold) newAPISIXTunnels() error {
                httpNodePort, httpPort)
        s.apisixHttpsTunnel = k8s.NewTunnel(s.kubectlOptions, 
k8s.ResourceTypeService, "apisix-service-e2e-test",
                httpsNodePort, httpsPort)
+       s.apisixTCPTunnel = k8s.NewTunnel(s.kubectlOptions, 
k8s.ResourceTypeService, "apisix-service-e2e-test",
+               tcpNodePort, tcpPort)
 
        if err := s.apisixAdminTunnel.ForwardPortE(s.t); err != nil {
                return err
@@ -294,6 +332,10 @@ func (s *Scaffold) newAPISIXTunnels() error {
                return err
        }
        s.addFinalizers(s.apisixHttpsTunnel.Close)
+       if err := s.apisixTCPTunnel.ForwardPortE(s.t); err != nil {
+               return err
+       }
+       s.addFinalizers(s.apisixTCPTunnel.Close)
        return nil
 }
 
diff --git a/test/e2e/scaffold/scaffold.go b/test/e2e/scaffold/scaffold.go
index 8d4d505..ab63cd2 100644
--- a/test/e2e/scaffold/scaffold.go
+++ b/test/e2e/scaffold/scaffold.go
@@ -65,6 +65,7 @@ type Scaffold struct {
        apisixAdminTunnel *k8s.Tunnel
        apisixHttpTunnel  *k8s.Tunnel
        apisixHttpsTunnel *k8s.Tunnel
+       apisixTCPTunnel   *k8s.Tunnel
 
        // Used for template rendering.
        EtcdServiceFQDN string
@@ -167,6 +168,26 @@ func (s *Scaffold) NewAPISIXClient() *httpexpect.Expect {
        })
 }
 
+// NewAPISIXClientWithTCPProxy creates the HTTP client but with the TCP proxy 
of APISIX.
+func (s *Scaffold) NewAPISIXClientWithTCPProxy() *httpexpect.Expect {
+       u := url.URL{
+               Scheme: "http",
+               Host:   s.apisixTCPTunnel.Endpoint(),
+       }
+       return httpexpect.WithConfig(httpexpect.Config{
+               BaseURL: u.String(),
+               Client: &http.Client{
+                       Transport: &http.Transport{},
+                       CheckRedirect: func(req *http.Request, via 
[]*http.Request) error {
+                               return http.ErrUseLastResponse
+                       },
+               },
+               Reporter: httpexpect.NewAssertReporter(
+                       httpexpect.NewAssertReporter(ginkgo.GinkgoT()),
+               ),
+       })
+}
+
 // NewAPISIXHttpsClient creates the default HTTPs client.
 func (s *Scaffold) NewAPISIXHttpsClient(host string) *httpexpect.Expect {
        u := url.URL{
diff --git a/test/e2e/testdata/apisix-gw-config.yaml 
b/test/e2e/testdata/apisix-gw-config.yaml
index a0ba901..4225a3a 100644
--- a/test/e2e/testdata/apisix-gw-config.yaml
+++ b/test/e2e/testdata/apisix-gw-config.yaml
@@ -53,10 +53,9 @@ apisix:
     http: 'radixtree_uri'         # radixtree_uri: match route by uri(base on 
radixtree)
     # radixtree_host_uri: match route by host + uri(base on radixtree)
     ssl: 'radixtree_sni'          # radixtree_sni: match route by SNI(base on 
radixtree)
-  # stream_proxy:                 # TCP/UDP proxy
-  #   tcp:                        # TCP proxy port list
-  #     - 9100
-  #     - 9101
+  stream_proxy:                 # TCP/UDP proxy
+    tcp:                        # TCP proxy port list
+      - 9100
   #   udp:                        # UDP proxy port list
   #     - 9200
   #     - 9211

Reply via email to