Skip to content

Commit 4e02ce0

Browse files
committed
e2e test
1 parent 2866936 commit 4e02ce0

File tree

6 files changed

+311
-0
lines changed

6 files changed

+311
-0
lines changed

config/rbac/role.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,13 +92,15 @@ rules:
9292
- gateways/status
9393
- httproutes/status
9494
- referencegrants/status
95+
- tcproutes/status
9596
verbs:
9697
- get
9798
- update
9899
- apiGroups:
99100
- gateway.networking.k8s.io
100101
resources:
101102
- httproutes
103+
- tcproutes
102104
verbs:
103105
- get
104106
- list

examples/httpbin/tcproute.yaml

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# Licensed to the Apache Software Foundation (ASF) under one
2+
# or more contributor license agreements. See the NOTICE file
3+
# distributed with this work for additional information
4+
# regarding copyright ownership. The ASF licenses this file
5+
# to you under the Apache License, Version 2.0 (the
6+
# "License"); you may not use this file except in compliance
7+
# with the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing,
12+
# software distributed under the License is distributed on an
13+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
# KIND, either express or implied. See the License for the
15+
# specific language governing permissions and limitations
16+
# under the License.
17+
18+
apiVersion: gateway.networking.k8s.io/v1
19+
kind: GatewayClass
20+
metadata:
21+
name: apisix
22+
spec:
23+
controllerName: "apisix.apache.org/apisix-ingress-controller"
24+
25+
---
26+
27+
apiVersion: apisix.apache.org/v1alpha1
28+
kind: GatewayProxy
29+
metadata:
30+
name: apisix-proxy-config
31+
spec:
32+
provider:
33+
type: ControlPlane
34+
controlPlane:
35+
endpoints:
36+
- ${ADMIN_ENDPOINT} # https://127.0.0.1:7443
37+
auth:
38+
type: AdminKey
39+
adminKey:
40+
value: "${ADMIN_KEY}"
41+
42+
---
43+
44+
apiVersion: gateway.networking.k8s.io/v1
45+
kind: Gateway
46+
metadata:
47+
name: apisix
48+
spec:
49+
gatewayClassName: apisix
50+
listeners:
51+
- name: foo
52+
protocol: TCP
53+
port: 80
54+
allowedRoutes:
55+
kinds:
56+
- kind: TCPRoute
57+
infrastructure:
58+
parametersRef:
59+
group: apisix.apache.org
60+
kind: GatewayProxy
61+
name: apisix-proxy-config
62+
---
63+
64+
apiVersion: gateway.networking.k8s.io/v1alpha2
65+
kind: TCPRoute
66+
metadata:
67+
name: tcp-app-1
68+
spec:
69+
parentRefs:
70+
- name: apisix
71+
sectionName: foo
72+
rules:
73+
- backendRefs:
74+
- name: httpbin
75+
port: 80

test/e2e/framework/manifests/ingress.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@ rules:
191191
- gateway.networking.k8s.io
192192
resources:
193193
- httproutes
194+
- tcproutes
194195
verbs:
195196
- get
196197
- list
@@ -199,6 +200,7 @@ rules:
199200
- gateway.networking.k8s.io
200201
resources:
201202
- httproutes/status
203+
- tcproutes/status
202204
verbs:
203205
- get
204206
- update

test/e2e/gatewayapi/tcproute.go

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
package gatewayapi
19+
20+
import (
21+
"fmt"
22+
"time"
23+
24+
"github.com/apache/apisix-ingress-controller/test/e2e/framework"
25+
"github.com/apache/apisix-ingress-controller/test/e2e/scaffold"
26+
. "github.com/onsi/ginkgo/v2"
27+
. "github.com/onsi/gomega"
28+
)
29+
30+
var _ = Describe("TCPRoute E2E Test", func() {
31+
s := scaffold.NewDefaultScaffold()
32+
33+
var gatewayProxyYaml = `
34+
apiVersion: apisix.apache.org/v1alpha1
35+
kind: GatewayProxy
36+
metadata:
37+
name: %s
38+
spec:
39+
provider:
40+
type: ControlPlane
41+
controlPlane:
42+
service:
43+
name: %s
44+
port: 9180
45+
auth:
46+
type: AdminKey
47+
adminKey:
48+
value: "%s"
49+
`
50+
getGatewayProxySpec := func() string {
51+
return fmt.Sprintf(gatewayProxyYaml, s.Namespace(), framework.ProviderType, s.AdminKey())
52+
}
53+
54+
var gatewayClassYaml = `
55+
apiVersion: gateway.networking.k8s.io/v1
56+
kind: GatewayClass
57+
metadata:
58+
name: %s
59+
spec:
60+
controllerName: %s
61+
`
62+
Context("TCPRoute Base", func() {
63+
var tcpGateway = `
64+
apiVersion: gateway.networking.k8s.io/v1
65+
kind: Gateway
66+
metadata:
67+
name: %s
68+
spec:
69+
gatewayClassName: %s
70+
listeners:
71+
- name: tcp
72+
protocol: TCP
73+
port: 80
74+
allowedRoutes:
75+
kinds:
76+
- kind: TCPRoute
77+
infrastructure:
78+
parametersRef:
79+
group: apisix.apache.org
80+
kind: GatewayProxy
81+
name: %s
82+
`
83+
84+
var tcpRoute = `
85+
apiVersion: gateway.networking.k8s.io/v1alpha2
86+
kind: TCPRoute
87+
metadata:
88+
name: tcp-app-1
89+
spec:
90+
parentRefs:
91+
- name: %s
92+
sectionName: tcp
93+
rules:
94+
- backendRefs:
95+
- name: httpbin-service-e2e-test
96+
port: 80
97+
`
98+
99+
BeforeEach(func() {
100+
// Create GatewayProxy
101+
Expect(s.CreateResourceFromStringWithNamespace(getGatewayProxySpec(), s.Namespace())).
102+
NotTo(HaveOccurred(), "creating GatewayProxy")
103+
104+
// Create GatewayClass
105+
gatewayClassName := s.Namespace()
106+
Expect(s.CreateResourceFromStringWithNamespace(fmt.Sprintf(gatewayClassYaml, gatewayClassName, s.GetControllerName()), "")).
107+
NotTo(HaveOccurred(), "creating GatewayClass")
108+
109+
s.RetryAssertion(func() string {
110+
gcyaml, _ := s.GetResourceYaml("GatewayClass", gatewayClassName)
111+
return gcyaml
112+
}).Should(
113+
And(
114+
ContainSubstring(`status: "True"`),
115+
ContainSubstring("message: the gatewayclass has been accepted by the apisix-ingress-controller"),
116+
),
117+
"check GatewayClass condition",
118+
)
119+
120+
// Create Gateway with TCP listener
121+
gatewayName := s.Namespace()
122+
Expect(s.CreateResourceFromStringWithNamespace(fmt.Sprintf(tcpGateway, gatewayName, gatewayClassName, s.Namespace()), s.Namespace())).
123+
NotTo(HaveOccurred(), "creating Gateway")
124+
125+
s.RetryAssertion(func() string {
126+
gwyaml, _ := s.GetResourceYaml("Gateway", gatewayName)
127+
return gwyaml
128+
}).Should(
129+
And(
130+
ContainSubstring(`status: "True"`),
131+
ContainSubstring("message: the gateway has been accepted by the apisix-ingress-controlle"),
132+
),
133+
"check Gateway condition status",
134+
)
135+
})
136+
137+
FIt("should route TCP traffic to backend service", func() {
138+
gatewayName := s.Namespace()
139+
By("creating TCPRoute")
140+
Expect(s.CreateResourceFromString(fmt.Sprintf(tcpRoute, gatewayName))).
141+
NotTo(HaveOccurred(), "creating TCPRoute")
142+
143+
// Verify TCPRoute status becomes programmed
144+
s.RetryAssertion(func() string {
145+
routeYaml, _ := s.GetResourceYaml("TCPRoute", "tcp-app-1")
146+
return routeYaml
147+
}).Should(
148+
ContainSubstring(`status: "True"`),
149+
"check TCPRoute status",
150+
)
151+
152+
By("verifying TCPRoute is functional")
153+
s.HTTPOverTCPConnectAssert(true, time.Second*10) // should be able to connect
154+
By("sending TCP traffic to verify routing")
155+
s.RequestAssert(&scaffold.RequestAssert{
156+
Client: s.NewAPISIXClientOnTCPPort(),
157+
Method: "GET",
158+
Path: "/get",
159+
Check: scaffold.WithExpectedStatus(200),
160+
Timeout: time.Minute * 30,
161+
Interval: time.Second * 2,
162+
})
163+
164+
By("deleting TCPRoute")
165+
Expect(s.DeleteResource("TCPRoute", "tcp-app-1")).
166+
NotTo(HaveOccurred(), "deleting TCPRoute")
167+
168+
s.HTTPOverTCPConnectAssert(false, time.Second*10)
169+
})
170+
})
171+
})

test/e2e/scaffold/assertion.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ package scaffold
1919

2020
import (
2121
"fmt"
22+
"io"
23+
"net"
2224
"net/http"
2325
"strings"
2426
"time"
@@ -189,6 +191,44 @@ func WithExpectedNotHeaders(unexpectedHeaders []string) ResponseCheckFunc {
189191
}
190192
}
191193

194+
func (s *Scaffold) HTTPOverTCPConnectAssert(shouldRespond bool, timeout time.Duration) {
195+
EventuallyWithOffset(1, func() error {
196+
conn, err := net.DialTimeout("tcp", s.GetAPISIXTCPEndpoint(), 3*time.Second)
197+
if err != nil {
198+
return fmt.Errorf("failed to connect: %v", err)
199+
}
200+
defer conn.Close()
201+
fmt.Fprintf(conn, "GET /get HTTP/1.1\r\nHost: localhost\r\n\r\n")
202+
203+
// Read response
204+
conn.SetReadDeadline(time.Now().Add(2 * time.Second))
205+
buf := make([]byte, 1024)
206+
n, err := conn.Read(buf)
207+
208+
if shouldRespond {
209+
// Should get a response (HTTP 200 from httpbin)
210+
if err != nil || n == 0 {
211+
return fmt.Errorf("expected response but got error: %v or empty response", err)
212+
}
213+
// Check if we got a valid HTTP response
214+
response := string(buf[:n])
215+
if !strings.Contains(response, "HTTP/1.1") {
216+
return fmt.Errorf("expected HTTP response but got: %s", response)
217+
}
218+
} else {
219+
// Should get no response or connection reset
220+
if err == nil && n > 0 {
221+
return fmt.Errorf("expected no response but got: %s", string(buf[:n]))
222+
}
223+
// EOF or timeout is expected when no route is configured
224+
if err != io.EOF && !strings.Contains(err.Error(), "timeout") {
225+
return fmt.Errorf("expected EOF or timeout but got: %v", err)
226+
}
227+
}
228+
return nil
229+
}).WithTimeout(timeout).WithPolling(2 * time.Second).Should(Succeed())
230+
}
231+
192232
func (s *Scaffold) RequestAssert(r *RequestAssert) bool {
193233
if r.Client == nil {
194234
r.Client = s.NewAPISIXClient()

test/e2e/scaffold/scaffold.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,27 @@ func (s *Scaffold) NewAPISIXClient() *httpexpect.Expect {
192192
})
193193
}
194194

195+
func (s *Scaffold) NewAPISIXClientOnTCPPort() *httpexpect.Expect {
196+
u := url.URL{
197+
Scheme: "http",
198+
Host: s.apisixTunnels.TCP.Endpoint(),
199+
}
200+
fmt.Println("tcp endpoint:", u.String())
201+
fmt.Println("http endpoint", s.apisixTunnels.HTTP.Endpoint())
202+
return httpexpect.WithConfig(httpexpect.Config{
203+
BaseURL: u.String(),
204+
Client: &http.Client{
205+
Transport: &http.Transport{},
206+
CheckRedirect: func(req *http.Request, via []*http.Request) error {
207+
return http.ErrUseLastResponse
208+
},
209+
},
210+
Reporter: httpexpect.NewAssertReporter(
211+
httpexpect.NewAssertReporter(GinkgoT()),
212+
),
213+
})
214+
}
215+
195216
func (s *Scaffold) ApisixHTTPEndpoint() string {
196217
return s.apisixTunnels.HTTP.Endpoint()
197218
}

0 commit comments

Comments
 (0)