Skip to content

Commit 593a3cc

Browse files
committed
add bats tests
1 parent e95a5e6 commit 593a3cc

File tree

7 files changed

+249
-71
lines changed

7 files changed

+249
-71
lines changed

.github/workflows/bats.yml

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
name: bats
2+
3+
on:
4+
push:
5+
branches:
6+
- 'main'
7+
tags:
8+
- 'v*'
9+
pull_request:
10+
branches: [ main ]
11+
workflow_dispatch:
12+
13+
env:
14+
GO_VERSION: "1.23"
15+
K8S_VERSION: "v1.32.0"
16+
KIND_VERSION: "v0.27.0"
17+
18+
jobs:
19+
bats_tests:
20+
runs-on: ubuntu-22.04
21+
name: Bats e2e tests
22+
steps:
23+
- name: Checkout
24+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
25+
- name: Setup Bats and bats libs
26+
id: setup-bats
27+
uses: bats-core/bats-action@3.0.0
28+
- name: Bats tests
29+
shell: bash
30+
env:
31+
BATS_LIB_PATH: ${{ steps.setup-bats.outputs.lib-path }}
32+
TERM: xterm
33+
run: bats -o _artifacts tests/
34+
35+
- name: Upload logs
36+
if: always()
37+
uses: actions/upload-artifact@v4
38+
with:
39+
name: kind-logs-${{ env.JOB_NAME }}-${{ github.run_id }}
40+
path: ./_artifacts
41+
42+
bats_mac_tests:
43+
runs-on: macos-13
44+
name: Bats e2e tests on Mac
45+
steps:
46+
- name: Checkout
47+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
48+
- name: Set up environment (download dependencies)
49+
run: |
50+
brew install docker colima
51+
brew install kind
52+
brew install golang
53+
brew install bats-core
54+
brew install kubectl
55+
LIMA_SSH_PORT_FORWARDER=true colima start
56+
- name: Bats tests
57+
env:
58+
TERM: linux
59+
run: |
60+
bash --version
61+
bash -c "time bats -o _artifacts --trace --formatter tap tests/"
62+
- name: Debug info
63+
if: always()
64+
run: |
65+
docker ps
66+
67+
- name: Upload logs
68+
if: always()
69+
uses: actions/upload-artifact@v4
70+
with:
71+
name: kind-logs-${{ env.JOB_NAME }}-${{ github.run_id }}
72+
path: ./_artifacts
73+

.github/workflows/k8s.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ jobs:
3232
id: go
3333

3434
- name: Check out code
35-
uses: actions/checkout@v2
35+
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
3636

3737
- name: Enable ipv4 and ipv6 forwarding
3838
run: |

examples/loadbalancer_udp_tcp.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ spec:
66
selector:
77
matchLabels:
88
app: multiprotocol
9-
replicas: 2
9+
replicas: 1
1010
strategy:
1111
rollingUpdate:
1212
maxSurge: 0

pkg/loadbalancer/tunnel.go

Lines changed: 84 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"net"
77
"strings"
88
"sync"
9+
"time"
910

1011
"k8s.io/klog/v2"
1112

@@ -99,6 +100,7 @@ func (t *tunnelManager) removeTunnels(containerName string) error {
99100
// tunnel listens on localIP:localPort and proxies the connection to remoteIP:remotePort
100101
type tunnel struct {
101102
listener net.Listener
103+
udpConn *net.UDPConn
102104
localIP string
103105
localPort string
104106
protocol string
@@ -118,35 +120,57 @@ func NewTunnel(localIP, localPort, protocol, remoteIP, remotePort string) *tunne
118120

119121
func (t *tunnel) Start() error {
120122
klog.Infof("Starting tunnel on %s", net.JoinHostPort(t.localIP, t.localPort))
121-
ln, err := net.Listen(t.protocol, net.JoinHostPort(t.localIP, t.localPort))
122-
if err != nil {
123-
return err
124-
}
123+
if t.protocol == "udp" {
124+
localAddrStr := net.JoinHostPort(t.localIP, t.localPort)
125+
udpAddr, err := net.ResolveUDPAddr("udp", localAddrStr)
126+
if err != nil {
127+
return err
128+
}
129+
conn, err := net.ListenUDP("udp", udpAddr)
130+
t.udpConn = conn
131+
go func() {
132+
for {
133+
err = t.handleUDPConnection(conn)
134+
if err != nil {
135+
klog.Infof("unexpected error on connection: %v", err)
136+
}
137+
}
138+
}()
125139

126-
t.listener = ln
140+
} else {
141+
ln, err := net.Listen(t.protocol, net.JoinHostPort(t.localIP, t.localPort))
142+
if err != nil {
143+
return err
144+
}
127145

128-
go func() {
129-
for {
130-
conn, err := ln.Accept()
131-
if err != nil {
132-
klog.Infof("unexpected error listening: %v", err)
133-
return
134-
} else {
135-
go func() {
136-
err := t.handleConnection(conn)
137-
if err != nil {
138-
klog.Infof("unexpected error on connection: %v", err)
139-
}
140-
}()
146+
t.listener = ln
147+
148+
go func() {
149+
for {
150+
conn, err := ln.Accept()
151+
if err != nil {
152+
klog.Infof("unexpected error listening: %v", err)
153+
return
154+
} else {
155+
go func() {
156+
err := t.handleConnection(conn)
157+
if err != nil {
158+
klog.Infof("unexpected error on connection: %v", err)
159+
}
160+
}()
161+
}
141162
}
142-
}
143-
}()
163+
}()
164+
}
144165
return nil
145166
}
146167

147168
func (t *tunnel) Stop() error {
148169
if t.listener != nil {
149-
return t.listener.Close()
170+
_ = t.listener.Close()
171+
}
172+
if t.udpConn != nil {
173+
_ = t.udpConn.Close()
150174
}
151175
return nil
152176
}
@@ -171,3 +195,42 @@ func (t *tunnel) handleConnection(local net.Conn) error {
171195
wg.Wait()
172196
return nil
173197
}
198+
199+
func (t *tunnel) handleUDPConnection(conn *net.UDPConn) error {
200+
buf := make([]byte, 1500)
201+
nRead, srcAddr, err := conn.ReadFromUDP(buf)
202+
if err != nil {
203+
return err
204+
}
205+
klog.V(4).Infof("Read %d bytes from %s", nRead, srcAddr.String())
206+
207+
klog.V(4).Infof("Connecting to %s", net.JoinHostPort(t.remoteIP, t.remotePort))
208+
remoteConn, err := net.Dial(t.protocol, net.JoinHostPort(t.remoteIP, t.remotePort))
209+
if err != nil {
210+
return fmt.Errorf("can't connect to server %q: %v", net.JoinHostPort(t.remoteIP, t.remotePort), err)
211+
}
212+
defer remoteConn.Close()
213+
214+
nWrite, err := remoteConn.Write(buf)
215+
if err != nil {
216+
return fmt.Errorf("fail to write to remote %s: %s", remoteConn.RemoteAddr(), err)
217+
} else if nWrite < nRead {
218+
klog.V(2).Infof("Buffer underflow %d < %d to remote %s", nWrite, nRead, remoteConn.RemoteAddr())
219+
}
220+
klog.V(4).Infof("Wrote %d bytes to to %s", nWrite, remoteConn.RemoteAddr().String())
221+
222+
buf = make([]byte, 1500)
223+
err = remoteConn.SetReadDeadline(time.Now().Add(5 * time.Second)) // Add deadline to ensure it doesn't block forever
224+
if err != nil {
225+
return fmt.Errorf("can not set read deadline: %v", err)
226+
}
227+
nRead, err = remoteConn.Read(buf)
228+
if err != nil {
229+
return fmt.Errorf("fail to read from remote %s: %s", remoteConn.RemoteAddr(), err)
230+
}
231+
klog.V(4).Infof("Read %d bytes from %s", nRead, remoteConn.RemoteAddr().String())
232+
233+
_, err = conn.WriteToUDP(buf, srcAddr)
234+
235+
return err
236+
}

tests/README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Integration tests
2+
3+
4+
1. Install `bats` https://bats-core.readthedocs.io/en/stable/installation.html
5+
6+
2. Install `kind` https://kind.sigs.k8s.io/
7+
8+
3. Run `bats tests/`

tests/setup_suite.bash

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
#!/bin/bash
2+
3+
set -eu
4+
5+
function setup_suite {
6+
export BATS_TEST_TIMEOUT=120
7+
# Define the name of the kind cluster
8+
export CLUSTER_NAME="ccm-kind"
9+
# Build the cloud-provider-kind
10+
11+
mkdir -p "$BATS_TEST_DIRNAME"/../_artifacts
12+
rm -rf "$BATS_TEST_DIRNAME"/../_artifacts/*
13+
# create cluster
14+
cat <<EOF | kind create cluster \
15+
--name $CLUSTER_NAME \
16+
-v7 --wait 1m --retain --config=-
17+
kind: Cluster
18+
apiVersion: kind.x-k8s.io/v1alpha4
19+
nodes:
20+
- role: control-plane
21+
- role: worker
22+
- role: worker
23+
EOF
24+
25+
cd "$BATS_TEST_DIRNAME"/.. && make
26+
nohup "$BATS_TEST_DIRNAME"/../bin/cloud-provider-kind -v 2 --enable-log-dumping --logs-dir "$BATS_TEST_DIRNAME"/../_artifacts > "$BATS_TEST_DIRNAME"/../_artifacts/ccm-kind.log 2>&1 &
27+
export CCM_PID=$!
28+
29+
# test depend on external connectivity that can be very flaky
30+
sleep 5
31+
}
32+
33+
function teardown_suite {
34+
kill "$CCM_PID"
35+
kind export logs "$BATS_TEST_DIRNAME"/../_artifacts --name "$CLUSTER_NAME"
36+
kind delete cluster --name "$CLUSTER_NAME"
37+
}

0 commit comments

Comments
 (0)