mirror of
https://github.com/go-gost/gost.git
synced 2026-10-08 03:55:45 +00:00
chore: bump x to v0.15.0 (hopGroup, failCodes fix)
Add e2e regression test for http.failCodes selector bug — FIFO selector + failCodes=429 on first node proves node-429 is excluded after first 429 response and all subsequent requests go to node-good.
This commit is contained in:
@@ -4,7 +4,7 @@ go 1.26.3
|
||||
|
||||
require (
|
||||
github.com/go-gost/core v0.6.0
|
||||
github.com/go-gost/x v0.14.2
|
||||
github.com/go-gost/x v0.15.0
|
||||
github.com/judwhite/go-svc v1.2.1
|
||||
github.com/moby/moby/client v0.4.0
|
||||
github.com/stretchr/testify v1.11.1
|
||||
|
||||
@@ -97,8 +97,8 @@ github.com/go-gost/relay v0.7.0 h1:J8e3Sba6DtBJQotXY5j5EaZNWwtv6Z9CoCFQsnrHNcE=
|
||||
github.com/go-gost/relay v0.7.0/go.mod h1:Dku0f5sfjOClrZFiDmQUrYYJ4uof7rnkCUBfsl0PSAI=
|
||||
github.com/go-gost/tls-dissector v0.3.1 h1:gvOteWog5pjY/HCpc8l+gngmSi8Q6zl5rRrfK8gwRKA=
|
||||
github.com/go-gost/tls-dissector v0.3.1/go.mod h1:vGfog053fIm93iXBtvmVzMQqEJo5YwbbNaPNVDjbiOc=
|
||||
github.com/go-gost/x v0.14.2 h1:CGzZEsP/lzhG6A3u67ulkw5t1oWtDr+RQTm5ZB7R1QY=
|
||||
github.com/go-gost/x v0.14.2/go.mod h1:YQdy2hZ9nZ1w5GDacGKWrC4jIYXzthAKO/Er/K65+c4=
|
||||
github.com/go-gost/x v0.15.0 h1:ugklRGp3fg8lveJ5JOp0wpnF2xxmP3qzb1TyWkEGDGc=
|
||||
github.com/go-gost/x v0.15.0/go.mod h1:YQdy2hZ9nZ1w5GDacGKWrC4jIYXzthAKO/Er/K65+c4=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
package e2e
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/moby/moby/client"
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/testcontainers/testcontainers-go"
|
||||
"github.com/testcontainers/testcontainers-go/wait"
|
||||
)
|
||||
|
||||
type FailCodesSuite struct {
|
||||
suite.Suite
|
||||
ctx context.Context
|
||||
echoC testcontainers.Container
|
||||
echoIP string
|
||||
statusC testcontainers.Container
|
||||
}
|
||||
|
||||
func (s *FailCodesSuite) 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
|
||||
|
||||
statusC, err := RunStatusBackendContainer(s.ctx, SharedNetworkName, 429)
|
||||
s.Require().NoError(err)
|
||||
s.statusC = statusC
|
||||
}
|
||||
|
||||
func (s *FailCodesSuite) TearDownSuite() {
|
||||
if s.echoC != nil {
|
||||
s.echoC.Terminate(s.ctx)
|
||||
}
|
||||
if s.statusC != nil {
|
||||
s.statusC.Terminate(s.ctx)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *FailCodesSuite) sendRawHTTP(gostC testcontainers.Container, host, port string) string {
|
||||
req := fmt.Sprintf(
|
||||
"GET / HTTP/1.1\r\nHost: %s\r\nConnection: close\r\n\r\n",
|
||||
s.echoIP,
|
||||
)
|
||||
encoded := base64.StdEncoding.EncodeToString([]byte(req))
|
||||
cmd := []string{"sh", "-c",
|
||||
fmt.Sprintf("echo %s | base64 -d | nc -w 5 %s %s", encoded, host, port)}
|
||||
_, out, _ := gostC.Exec(s.ctx, cmd)
|
||||
b, _ := io.ReadAll(out)
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// TestFailCodesConvergence: reverse proxy (tcp handler + sniffing) with FIFO.
|
||||
// node-429 is first. After the first 429 response, failCodes marks it (Count=1).
|
||||
// FailFilter (maxFails=1) excludes it. All subsequent requests go to node-good.
|
||||
func (s *FailCodesSuite) TestFailCodesConvergence() {
|
||||
gostC, err := RunGostContainerWithFiles(s.ctx, SharedNetworkName,
|
||||
"testdata/failcodes/failcodes.yaml",
|
||||
nil,
|
||||
"8080/tcp",
|
||||
)
|
||||
s.Require().NoError(err)
|
||||
defer func() {
|
||||
DumpLogs(s.T(), s.ctx, "gost-failcodes", gostC)
|
||||
gostC.Terminate(s.ctx)
|
||||
}()
|
||||
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
|
||||
const totalRequests = 20
|
||||
var successCount, failCount int
|
||||
|
||||
for range totalRequests {
|
||||
body := s.sendRawHTTP(gostC, "127.0.0.1", "8080")
|
||||
|
||||
if strings.Contains(body, "hello-gost") {
|
||||
successCount++
|
||||
s.T().Logf(" ✓ 200 (node-good)")
|
||||
} else if strings.Contains(body, "status-429") {
|
||||
failCount++
|
||||
s.T().Logf(" ✗ 429 (node-429)")
|
||||
} else {
|
||||
s.T().Logf(" ? %s", strings.TrimSpace(body)[:80])
|
||||
}
|
||||
}
|
||||
|
||||
s.T().Logf("Results: %d successes, %d failures out of %d requests",
|
||||
successCount, failCount, totalRequests)
|
||||
|
||||
// After the first 429 marks node-429, FailFilter excludes it for 10s.
|
||||
// At most 1-2 requests may fail before the marker is set.
|
||||
s.Require().LessOrEqual(failCount, 2,
|
||||
"failCodes: node-429 must be excluded after first 429 response. "+
|
||||
"Got %d failures out of %d requests", failCount, totalRequests)
|
||||
}
|
||||
|
||||
func TestFailCodesSuite(t *testing.T) {
|
||||
suite.Run(t, new(FailCodesSuite))
|
||||
}
|
||||
|
||||
func RunStatusBackendContainer(ctx context.Context, networkName string, statusCode int) (testcontainers.Container, error) {
|
||||
req := testcontainers.ContainerRequest{
|
||||
FromDockerfile: testcontainers.FromDockerfile{
|
||||
Context: ".",
|
||||
Dockerfile: "Dockerfile",
|
||||
Repo: "gost-e2e",
|
||||
Tag: "latest",
|
||||
KeepImage: true,
|
||||
BuildOptionsModifier: func(opts *client.ImageBuildOptions) {
|
||||
opts.NetworkMode = "host"
|
||||
},
|
||||
},
|
||||
Networks: []string{networkName},
|
||||
NetworkAliases: map[string][]string{
|
||||
networkName: {"status-backend"},
|
||||
},
|
||||
Files: []testcontainers.ContainerFile{
|
||||
{HostFilePath: "scripts/http_status_backend.py", ContainerFilePath: "/scripts/http_status_backend.py", FileMode: 0644},
|
||||
},
|
||||
ExposedPorts: []string{"5680/tcp"},
|
||||
Cmd: []string{
|
||||
"python3", "/scripts/http_status_backend.py",
|
||||
fmt.Sprintf("%d", statusCode), "5680",
|
||||
},
|
||||
WaitingFor: wait.ForExposedPort(),
|
||||
}
|
||||
|
||||
return testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
|
||||
ContainerRequest: req,
|
||||
Started: true,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
"""HTTP server that always returns a fixed status code."""
|
||||
import sys
|
||||
from http.server import BaseHTTPRequestHandler, HTTPServer
|
||||
|
||||
|
||||
STATUS = int(sys.argv[1]) if len(sys.argv) > 1 else 429
|
||||
PORT = int(sys.argv[2]) if len(sys.argv) > 2 else 5680
|
||||
BODY = f"status-{STATUS}".encode()
|
||||
|
||||
|
||||
class Handler(BaseHTTPRequestHandler):
|
||||
def do_GET(self):
|
||||
self.send_response(STATUS)
|
||||
self.send_header("Content-Length", str(len(BODY)))
|
||||
self.end_headers()
|
||||
self.wfile.write(BODY)
|
||||
|
||||
def do_POST(self):
|
||||
self.send_response(STATUS)
|
||||
self.send_header("Content-Length", str(len(BODY)))
|
||||
self.end_headers()
|
||||
self.wfile.write(BODY)
|
||||
|
||||
# HTTP proxy CONNECT method
|
||||
def do_CONNECT(self):
|
||||
self.send_response(STATUS)
|
||||
self.send_header("Content-Length", str(len(BODY)))
|
||||
self.end_headers()
|
||||
self.wfile.write(BODY)
|
||||
|
||||
def log_message(self, format, *args):
|
||||
return
|
||||
|
||||
|
||||
HTTPServer(("0.0.0.0", PORT), Handler).serve_forever()
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
services:
|
||||
# Reverse proxy with sniffing: raw TCP listener inspects HTTP via the
|
||||
# forwarder's sniffer. The hop has FIFO selector (always picks first node).
|
||||
# node-429 is first in the list with failCodes=429 — it must be marked on
|
||||
# the first 429 response and excluded by FailFilter (maxFails=1).
|
||||
- name: reverse-proxy
|
||||
addr: :8080
|
||||
handler:
|
||||
type: tcp
|
||||
metadata:
|
||||
sniffing: true
|
||||
sniffing.timeout: 2s
|
||||
listener:
|
||||
type: tcp
|
||||
forwarder:
|
||||
selector:
|
||||
strategy: fifo
|
||||
maxFails: 1
|
||||
nodes:
|
||||
- name: node-429
|
||||
addr: status-backend:5680
|
||||
protocol: http
|
||||
http:
|
||||
failCodes: "429"
|
||||
- name: node-good
|
||||
addr: tcp-echo:5678
|
||||
protocol: http
|
||||
Reference in New Issue
Block a user