Skip to content

Commit 60c6edc

Browse files
committed
wip fault with mock proxy
1 parent f3bab0d commit 60c6edc

17 files changed

+3858
-50
lines changed

Makefile

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,17 @@ docker.start:
1414
docker.stop:
1515
docker compose --profile all down
1616

17+
docker.e2e.start:
18+
@echo "Starting Redis and cae-resp-proxy for E2E tests..."
19+
docker compose --profile e2e up -d --quiet-pull
20+
@echo "Waiting for services to be ready..."
21+
@sleep 3
22+
@echo "Services ready!"
23+
24+
docker.e2e.stop:
25+
@echo "Stopping E2E services..."
26+
docker compose --profile e2e down
27+
1728
test:
1829
$(MAKE) docker.start
1930
@if [ -z "$(REDIS_VERSION)" ]; then \
@@ -66,7 +77,31 @@ bench:
6677
export REDIS_VERSION=$(REDIS_VERSION) && \
6778
go test ./... -test.run=NONE -test.bench=. -test.benchmem -skip Example
6879

69-
.PHONY: all test test.ci test.ci.skip-vectorsets bench fmt
80+
test.e2e:
81+
@echo "Running E2E tests with auto-start proxy..."
82+
$(MAKE) docker.e2e.start
83+
@echo "Running tests..."
84+
@E2E_SCENARIO_TESTS=true go test -v ./maintnotifications/e2e/ -timeout 30m || ($(MAKE) docker.e2e.stop && exit 1)
85+
$(MAKE) docker.e2e.stop
86+
@echo "E2E tests completed!"
87+
88+
test.e2e.docker:
89+
@echo "Running Docker-compatible E2E tests..."
90+
$(MAKE) docker.e2e.start
91+
@echo "Running unified injector tests..."
92+
@E2E_SCENARIO_TESTS=true go test -v -run "TestUnifiedInjector|TestCreateTestFaultInjectorLogic|TestFaultInjectorClientCreation" ./maintnotifications/e2e/ -timeout 10m || ($(MAKE) docker.e2e.stop && exit 1)
93+
$(MAKE) docker.e2e.stop
94+
@echo "Docker E2E tests completed!"
95+
96+
test.e2e.logic:
97+
@echo "Running E2E logic tests (no proxy required)..."
98+
@E2E_SCENARIO_TESTS=true \
99+
REDIS_ENDPOINTS_CONFIG_PATH=/tmp/test_endpoints_verify.json \
100+
FAULT_INJECTION_API_URL=http://localhost:8080 \
101+
go test -v -run "TestCreateTestFaultInjectorLogic|TestFaultInjectorClientCreation" ./maintnotifications/e2e/
102+
@echo "Logic tests completed!"
103+
104+
.PHONY: all test test.ci test.ci.skip-vectorsets bench fmt test.e2e test.e2e.logic docker.e2e.start docker.e2e.stop
70105

71106
build:
72107
export RE_CLUSTER=$(RE_CLUSTER) && \

docker-compose.yml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ services:
2121
- sentinel
2222
- all-stack
2323
- all
24+
- e2e
2425

2526
osscluster:
2627
image: ${CLIENT_LIBS_TEST_IMAGE:-redislabs/client-libs-test:8.4.0}
@@ -39,6 +40,45 @@ services:
3940
- all-stack
4041
- all
4142

43+
cae-resp-proxy:
44+
image: redislabs/client-resp-proxy:latest
45+
container_name: cae-resp-proxy
46+
environment:
47+
- TARGET_HOST=redis
48+
- TARGET_PORT=6379
49+
- LISTEN_PORT=7000,7001,7002 # Multiple proxy nodes for cluster simulation
50+
- LISTEN_HOST=0.0.0.0
51+
- API_PORT=3000
52+
- DEFAULT_INTERCEPTORS=cluster,hitless
53+
ports:
54+
- "7000:7000" # Proxy node 1 (host:container)
55+
- "7001:7001" # Proxy node 2 (host:container)
56+
- "7002:7002" # Proxy node 3 (host:container)
57+
- "8100:3000" # HTTP API port (host:container)
58+
depends_on:
59+
- redis
60+
profiles:
61+
- e2e
62+
- all
63+
64+
proxy-fault-injector:
65+
image: golang:1.23-alpine
66+
container_name: proxy-fault-injector
67+
working_dir: /app
68+
environment:
69+
- CGO_ENABLED=0
70+
command: >
71+
sh -c "go run ./maintnotifications/e2e/cmd/proxy-fi-server/main.go --listen 0.0.0.0:5000 --proxy-api-url http://cae-resp-proxy:3000"
72+
ports:
73+
- "5000:5000" # Fault injector API port
74+
depends_on:
75+
- cae-resp-proxy
76+
volumes:
77+
- ".:/app"
78+
profiles:
79+
- e2e
80+
- all
81+
4282
sentinel-cluster:
4383
image: ${CLIENT_LIBS_TEST_IMAGE:-redislabs/client-libs-test:8.4.0}
4484
platform: linux/amd64
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Build stage
2+
FROM golang:1.21-alpine AS builder
3+
4+
WORKDIR /build
5+
6+
# Copy go mod files
7+
COPY go.mod go.sum ./
8+
RUN go mod download
9+
10+
# Copy source code
11+
COPY . .
12+
13+
# Build the proxy-fi-server binary
14+
RUN cd maintnotifications/e2e/cmd/proxy-fi-server && \
15+
CGO_ENABLED=0 GOOS=linux go build -o /proxy-fi-server .
16+
17+
# Runtime stage
18+
FROM alpine:latest
19+
20+
RUN apk --no-cache add ca-certificates
21+
22+
WORKDIR /app
23+
24+
# Copy the binary from builder
25+
COPY --from=builder /proxy-fi-server .
26+
27+
# Expose the fault injector API port
28+
EXPOSE 5000
29+
30+
# Run the server
31+
ENTRYPOINT ["/app/proxy-fi-server"]
32+
CMD ["--listen", "0.0.0.0:5000", "--proxy-api-port", "3000"]
33+
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package main
2+
3+
import (
4+
"flag"
5+
"fmt"
6+
"os"
7+
"os/signal"
8+
"syscall"
9+
10+
e2e "github.com/redis/go-redis/v9/maintnotifications/e2e"
11+
)
12+
13+
func main() {
14+
listenAddr := flag.String("listen", "0.0.0.0:5000", "Address to listen on for fault injector API")
15+
proxyAPIURL := flag.String("proxy-api-url", "http://localhost:8100", "URL of the cae-resp-proxy API")
16+
flag.Parse()
17+
18+
fmt.Printf("Starting Proxy Fault Injector Server...\n")
19+
fmt.Printf(" Listen address: %s\n", *listenAddr)
20+
fmt.Printf(" Proxy API URL: %s\n", *proxyAPIURL)
21+
22+
server := e2e.NewProxyFaultInjectorServerWithURL(*listenAddr, *proxyAPIURL)
23+
if err := server.Start(); err != nil {
24+
fmt.Fprintf(os.Stderr, "Failed to start server: %v\n", err)
25+
os.Exit(1)
26+
}
27+
28+
fmt.Printf("Proxy Fault Injector Server started successfully\n")
29+
fmt.Printf("Fault Injector API available at http://%s\n", *listenAddr)
30+
31+
// Wait for interrupt signal
32+
sigChan := make(chan os.Signal, 1)
33+
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
34+
<-sigChan
35+
36+
fmt.Println("\nShutting down...")
37+
if err := server.Stop(); err != nil {
38+
fmt.Fprintf(os.Stderr, "Error during shutdown: %v\n", err)
39+
os.Exit(1)
40+
}
41+
fmt.Println("Server stopped")
42+
}
43+
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
package e2e
2+
3+
import (
4+
"os"
5+
"testing"
6+
)
7+
8+
// TestCreateTestFaultInjectorLogic_WithoutEnv verifies the auto-start logic
9+
// when REDIS_ENDPOINTS_CONFIG_PATH is not set
10+
func TestCreateTestFaultInjectorLogic_WithoutEnv(t *testing.T) {
11+
// Save original environment
12+
origConfigPath := os.Getenv("REDIS_ENDPOINTS_CONFIG_PATH")
13+
origFIURL := os.Getenv("FAULT_INJECTION_API_URL")
14+
15+
// Clear environment to simulate no setup
16+
os.Unsetenv("REDIS_ENDPOINTS_CONFIG_PATH")
17+
os.Unsetenv("FAULT_INJECTION_API_URL")
18+
19+
// Restore environment after test
20+
defer func() {
21+
if origConfigPath != "" {
22+
os.Setenv("REDIS_ENDPOINTS_CONFIG_PATH", origConfigPath)
23+
}
24+
if origFIURL != "" {
25+
os.Setenv("FAULT_INJECTION_API_URL", origFIURL)
26+
}
27+
}()
28+
29+
// Test GetEnvConfig - should fail when REDIS_ENDPOINTS_CONFIG_PATH is not set
30+
envConfig, err := GetEnvConfig()
31+
if err == nil {
32+
t.Fatal("Expected GetEnvConfig() to fail when REDIS_ENDPOINTS_CONFIG_PATH is not set")
33+
}
34+
if envConfig != nil {
35+
t.Fatal("Expected envConfig to be nil when GetEnvConfig() fails")
36+
}
37+
38+
t.Log("✅ GetEnvConfig() correctly fails when REDIS_ENDPOINTS_CONFIG_PATH is not set")
39+
t.Log("✅ This means CreateTestFaultInjectorWithCleanup() will auto-start the proxy")
40+
}
41+
42+
// TestCreateTestFaultInjectorLogic_WithEnv verifies the logic
43+
// when REDIS_ENDPOINTS_CONFIG_PATH is set
44+
func TestCreateTestFaultInjectorLogic_WithEnv(t *testing.T) {
45+
// Create a temporary config file
46+
tmpFile := "/tmp/test_endpoints.json"
47+
content := `{
48+
"standalone": {
49+
"endpoints": ["redis://localhost:6379"]
50+
}
51+
}`
52+
53+
if err := os.WriteFile(tmpFile, []byte(content), 0644); err != nil {
54+
t.Fatalf("Failed to create temp file: %v", err)
55+
}
56+
defer os.Remove(tmpFile)
57+
58+
// Save original environment
59+
origConfigPath := os.Getenv("REDIS_ENDPOINTS_CONFIG_PATH")
60+
origFIURL := os.Getenv("FAULT_INJECTION_API_URL")
61+
62+
// Set environment
63+
os.Setenv("REDIS_ENDPOINTS_CONFIG_PATH", tmpFile)
64+
os.Setenv("FAULT_INJECTION_API_URL", "http://test-fi:9999")
65+
66+
// Restore environment after test
67+
defer func() {
68+
if origConfigPath != "" {
69+
os.Setenv("REDIS_ENDPOINTS_CONFIG_PATH", origConfigPath)
70+
} else {
71+
os.Unsetenv("REDIS_ENDPOINTS_CONFIG_PATH")
72+
}
73+
if origFIURL != "" {
74+
os.Setenv("FAULT_INJECTION_API_URL", origFIURL)
75+
} else {
76+
os.Unsetenv("FAULT_INJECTION_API_URL")
77+
}
78+
}()
79+
80+
// Test GetEnvConfig - should succeed when REDIS_ENDPOINTS_CONFIG_PATH is set
81+
envConfig, err := GetEnvConfig()
82+
if err != nil {
83+
t.Fatalf("Expected GetEnvConfig() to succeed when REDIS_ENDPOINTS_CONFIG_PATH is set, got error: %v", err)
84+
}
85+
if envConfig == nil {
86+
t.Fatal("Expected envConfig to be non-nil when GetEnvConfig() succeeds")
87+
}
88+
89+
// Verify the fault injector URL is correct
90+
if envConfig.FaultInjectorURL != "http://test-fi:9999" {
91+
t.Errorf("Expected FaultInjectorURL to be 'http://test-fi:9999', got '%s'", envConfig.FaultInjectorURL)
92+
}
93+
94+
t.Log("✅ GetEnvConfig() correctly succeeds when REDIS_ENDPOINTS_CONFIG_PATH is set")
95+
t.Log("✅ This means CreateTestFaultInjectorWithCleanup() will use the real fault injector")
96+
t.Logf("✅ Fault injector URL: %s", envConfig.FaultInjectorURL)
97+
}
98+
99+
// TestCreateTestFaultInjectorLogic_DefaultFIURL verifies the default fault injector URL
100+
func TestCreateTestFaultInjectorLogic_DefaultFIURL(t *testing.T) {
101+
// Create a temporary config file
102+
tmpFile := "/tmp/test_endpoints2.json"
103+
content := `{
104+
"standalone": {
105+
"endpoints": ["redis://localhost:6379"]
106+
}
107+
}`
108+
109+
if err := os.WriteFile(tmpFile, []byte(content), 0644); err != nil {
110+
t.Fatalf("Failed to create temp file: %v", err)
111+
}
112+
defer os.Remove(tmpFile)
113+
114+
// Save original environment
115+
origConfigPath := os.Getenv("REDIS_ENDPOINTS_CONFIG_PATH")
116+
origFIURL := os.Getenv("FAULT_INJECTION_API_URL")
117+
118+
// Set only config path, not fault injector URL
119+
os.Setenv("REDIS_ENDPOINTS_CONFIG_PATH", tmpFile)
120+
os.Unsetenv("FAULT_INJECTION_API_URL")
121+
122+
// Restore environment after test
123+
defer func() {
124+
if origConfigPath != "" {
125+
os.Setenv("REDIS_ENDPOINTS_CONFIG_PATH", origConfigPath)
126+
} else {
127+
os.Unsetenv("REDIS_ENDPOINTS_CONFIG_PATH")
128+
}
129+
if origFIURL != "" {
130+
os.Setenv("FAULT_INJECTION_API_URL", origFIURL)
131+
}
132+
}()
133+
134+
// Test GetEnvConfig - should succeed and use default FI URL
135+
envConfig, err := GetEnvConfig()
136+
if err != nil {
137+
t.Fatalf("Expected GetEnvConfig() to succeed, got error: %v", err)
138+
}
139+
140+
// Verify the default fault injector URL
141+
if envConfig.FaultInjectorURL != "http://localhost:8080" {
142+
t.Errorf("Expected default FaultInjectorURL to be 'http://localhost:8080', got '%s'", envConfig.FaultInjectorURL)
143+
}
144+
145+
t.Log("✅ GetEnvConfig() uses default fault injector URL when FAULT_INJECTION_API_URL is not set")
146+
t.Logf("✅ Default fault injector URL: %s", envConfig.FaultInjectorURL)
147+
}
148+
149+
// TestFaultInjectorClientCreation verifies that FaultInjectorClient can be created
150+
func TestFaultInjectorClientCreation(t *testing.T) {
151+
// Test creating client with different URLs
152+
testCases := []struct {
153+
name string
154+
url string
155+
}{
156+
{"localhost", "http://localhost:5000"},
157+
{"with port", "http://test:9999"},
158+
{"with trailing slash", "http://test:9999/"},
159+
}
160+
161+
for _, tc := range testCases {
162+
t.Run(tc.name, func(t *testing.T) {
163+
client := NewFaultInjectorClient(tc.url)
164+
if client == nil {
165+
t.Fatal("Expected non-nil client")
166+
}
167+
168+
// Verify the base URL (should have trailing slash removed)
169+
expectedURL := tc.url
170+
if expectedURL[len(expectedURL)-1] == '/' {
171+
expectedURL = expectedURL[:len(expectedURL)-1]
172+
}
173+
174+
if client.GetBaseURL() != expectedURL {
175+
t.Errorf("Expected base URL '%s', got '%s'", expectedURL, client.GetBaseURL())
176+
}
177+
178+
t.Logf("✅ Client created successfully with URL: %s", client.GetBaseURL())
179+
})
180+
}
181+
}
182+

0 commit comments

Comments
 (0)