e2e: add node liveness probe tests

TCP probe + FailFilter: dead node pre-marked, all requests succeed.
LowestLatency strategy: two probed nodes, selector picks fastest.
master
ginuerzh 2026-07-16 16:44:48 +08:00
parent 1c6fadcb1e
commit bef00dffef
3 changed files with 183 additions and 0 deletions

View File

@ -0,0 +1,90 @@
package e2e
import (
"context"
"fmt"
"io"
"testing"
"time"
"github.com/stretchr/testify/suite"
"github.com/testcontainers/testcontainers-go"
)
type ProbeSuite struct {
suite.Suite
ctx context.Context
echoC testcontainers.Container
echoIP string
}
func (s *ProbeSuite) SetupSuite() {
s.ctx = context.Background()
echoC, err := RunEchoContainer(s.ctx, SharedNetworkName)
s.Require().NoError(err)
s.echoC = echoC
echoIP, err := echoC.ContainerIP(s.ctx)
s.Require().NoError(err)
s.echoIP = echoIP
}
func (s *ProbeSuite) TearDownSuite() {
if s.echoC != nil {
s.echoC.Terminate(s.ctx)
}
}
func (s *ProbeSuite) proxyRequest(gostC testcontainers.Container, port string) (int, string) {
cmd := []string{
"curl", "-s",
"-x", fmt.Sprintf("http://127.0.0.1:%s", port),
fmt.Sprintf("http://%s:5678", s.echoIP),
}
code, out, err := gostC.Exec(s.ctx, cmd)
s.Require().NoError(err)
body, err := io.ReadAll(out)
s.Require().NoError(err)
return code, string(body)
}
// TestTCPProbeFailover verifies that the TCP probe detects a dead node and
// marks it before any real traffic, so the FailFilter excludes it. With the
// dead node pre-marked, every request succeeds immediately.
func (s *ProbeSuite) TestTCPProbeFailover() {
gostC, err := RunGostContainerWithPorts(s.ctx, SharedNetworkName, "testdata/probe/tcp.yaml", "8080/tcp")
s.Require().NoError(err)
defer gostC.Terminate(s.ctx)
// The probe fires immediately at startup, so the dead node is already
// marked by the time we send requests.
for range 10 {
code, body := s.proxyRequest(gostC, "8080")
s.Require().Equal(0, code, "every request must succeed; dead node pre-marked by probe")
s.Require().Contains(body, "hello-gost")
}
}
// TestLowestLatencyProbe verifies that the lowestlatency strategy works with
// probed nodes. Both nodes are live; the strategy selects the one with the
// lowest measured latency.
func (s *ProbeSuite) TestLowestLatencyProbe() {
gostC, err := RunGostContainerWithPorts(s.ctx, SharedNetworkName, "testdata/probe/lowestlatency.yaml", "8080/tcp")
s.Require().NoError(err)
defer gostC.Terminate(s.ctx)
// Give the initial probe a moment to fire so both nodes show healthy.
time.Sleep(200 * time.Millisecond)
for range 10 {
code, body := s.proxyRequest(gostC, "8080")
s.Require().Equal(0, code, "all requests must succeed with lowestlatency strategy")
s.Require().Contains(body, "hello-gost")
}
}
func TestProbeSuite(t *testing.T) {
suite.Run(t, new(ProbeSuite))
}

View File

@ -0,0 +1,49 @@
services:
- name: proxy
addr: :8080
handler:
type: http
chain: my-chain
listener:
type: tcp
# Two live relays — both forward to the echo server.
- name: relay-a
addr: 127.0.0.1:18081
handler:
type: http
listener:
type: tcp
- name: relay-b
addr: 127.0.0.1:18082
handler:
type: http
listener:
type: tcp
chains:
- name: my-chain
hops:
- name: hop-1
selector:
strategy: lowestlatency
maxFails: 1
failTimeout: 10s
nodes:
- name: node-a
addr: 127.0.0.1:18081
connector:
type: http
probe:
type: tcp
addr: 127.0.0.1:18081
interval: 5s
- name: node-b
addr: 127.0.0.1:18082
connector:
type: http
probe:
type: tcp
addr: 127.0.0.1:18082
interval: 5s

View File

@ -0,0 +1,44 @@
services:
- name: proxy
addr: :8080
handler:
type: http
chain: my-chain
listener:
type: tcp
# Live relay: forwards to the echo server.
- name: relay
addr: 127.0.0.1:18081
handler:
type: http
listener:
type: tcp
chains:
- name: my-chain
hops:
- name: hop-1
selector:
strategy: round
maxFails: 1
failTimeout: 10s
nodes:
- name: node-live
addr: 127.0.0.1:18081
connector:
type: http
probe:
type: tcp
addr: 127.0.0.1:18081
interval: 5s
# dead node: probed, marked as failed by the probe goroutine,
# then excluded by FailFilter before any real traffic tries it.
- name: node-dead
addr: 127.0.0.1:18082
connector:
type: http
probe:
type: tcp
addr: 127.0.0.1:18082
interval: 5s