Skip to content

Commit b6d92f7

Browse files
committed
Fixes for PR
1 parent 6c10222 commit b6d92f7

File tree

6 files changed

+392
-3
lines changed

6 files changed

+392
-3
lines changed

conf/keepalived.conf.example

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,3 @@ vrrp_script haproxy_portal {
2424

2525
%%vrrp%%
2626

27-
%%lvs%%

conf/systemd/packetfence-pfudpproxy.service

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ After=packetfence-keepalived.service packetfence-base.target packetfence-config.
88
Type=notify
99
WatchdogSec=30s
1010
NotifyAccess=all
11-
Environment=LOG_LEVEL=info
11+
Environment=LOG_LEVEL=INFO
1212
ExecStart=/usr/local/pf/sbin/pfudpproxy
1313
Restart=on-failure
1414
StartLimitBurst=3
Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"net/http"
6+
"net/http/httptest"
7+
"net/url"
8+
"strconv"
9+
"sync/atomic"
10+
"testing"
11+
"time"
12+
)
13+
14+
// parseServerURL extracts the hostname and port from an httptest server URL.
15+
func parseServerURL(t *testing.T, serverURL string) (host string, port int) {
16+
t.Helper()
17+
u, err := url.Parse(serverURL)
18+
if err != nil {
19+
t.Fatalf("failed to parse server URL %q: %v", serverURL, err)
20+
}
21+
host = u.Hostname()
22+
port, err = strconv.Atoi(u.Port())
23+
if err != nil {
24+
t.Fatalf("failed to parse port from server URL %q: %v", serverURL, err)
25+
}
26+
return host, port
27+
}
28+
29+
// setupSingleBackend creates a HealthChecker backed by one backend whose
30+
// ManagementIP and health-check port match the given httptest TLS server.
31+
// The returned HealthChecker uses the server's TLS client so that the
32+
// self-signed certificate is trusted.
33+
func setupSingleBackend(t *testing.T, server *httptest.Server, expectedStatus int) (*HealthChecker, *LoadBalancer) {
34+
t.Helper()
35+
36+
host, port := parseServerURL(t, server.URL)
37+
38+
lb := NewLoadBalancer([]*Backend{
39+
{Host: "test-backend", ManagementIP: host},
40+
})
41+
42+
config := &ProxyConfig{
43+
HealthCheckPort: port,
44+
HealthCheckPath: "/",
45+
HealthCheckInterval: 1 * time.Second,
46+
HealthCheckTimeout: 2 * time.Second,
47+
ExpectedStatusCode: expectedStatus,
48+
}
49+
50+
hc := NewHealthChecker(config, lb)
51+
hc.httpClient = server.Client()
52+
hc.httpClient.Timeout = 2 * time.Second
53+
return hc, lb
54+
}
55+
56+
func TestCheckBackend_HealthyWhenExpectedStatus(t *testing.T) {
57+
server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
58+
w.WriteHeader(http.StatusNotFound)
59+
}))
60+
defer server.Close()
61+
62+
hc, lb := setupSingleBackend(t, server, http.StatusNotFound)
63+
64+
backends := lb.GetAllBackends()
65+
hc.checkBackend(context.Background(), backends[0])
66+
67+
primary := lb.GetPrimary()
68+
if primary == nil {
69+
t.Fatal("expected backend to be healthy, but GetPrimary returned nil")
70+
}
71+
if primary.Host != "test-backend" {
72+
t.Errorf("expected primary host to be test-backend, got %s", primary.Host)
73+
}
74+
}
75+
76+
func TestCheckBackend_UnhealthyWhenUnexpectedStatus(t *testing.T) {
77+
server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
78+
w.WriteHeader(http.StatusOK)
79+
}))
80+
defer server.Close()
81+
82+
// Expected 404 but server returns 200 → unhealthy.
83+
hc, lb := setupSingleBackend(t, server, http.StatusNotFound)
84+
85+
backends := lb.GetAllBackends()
86+
hc.checkBackend(context.Background(), backends[0])
87+
88+
if primary := lb.GetPrimary(); primary != nil {
89+
t.Errorf("expected no healthy backend, but got %s", primary.Host)
90+
}
91+
}
92+
93+
func TestCheckBackend_UnhealthyOnConnectionError(t *testing.T) {
94+
server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
95+
w.WriteHeader(http.StatusNotFound)
96+
}))
97+
98+
// Grab the URL before closing so we can parse host:port.
99+
host, port := parseServerURL(t, server.URL)
100+
server.Close()
101+
102+
lb := NewLoadBalancer([]*Backend{
103+
{Host: "test-backend", ManagementIP: host},
104+
})
105+
config := &ProxyConfig{
106+
HealthCheckPort: port,
107+
HealthCheckPath: "/",
108+
HealthCheckInterval: 1 * time.Second,
109+
HealthCheckTimeout: 2 * time.Second,
110+
ExpectedStatusCode: http.StatusNotFound,
111+
}
112+
hc := NewHealthChecker(config, lb)
113+
114+
backends := lb.GetAllBackends()
115+
hc.checkBackend(context.Background(), backends[0])
116+
117+
if primary := lb.GetPrimary(); primary != nil {
118+
t.Errorf("expected no healthy backend after connection error, but got %s", primary.Host)
119+
}
120+
}
121+
122+
func TestCheckBackend_TransitionHealthyToUnhealthy(t *testing.T) {
123+
var statusCode atomic.Int32
124+
statusCode.Store(http.StatusNotFound)
125+
126+
server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
127+
w.WriteHeader(int(statusCode.Load()))
128+
}))
129+
defer server.Close()
130+
131+
hc, lb := setupSingleBackend(t, server, http.StatusNotFound)
132+
133+
// First check: server returns 404 (expected) → healthy.
134+
backends := lb.GetAllBackends()
135+
hc.checkBackend(context.Background(), backends[0])
136+
137+
if primary := lb.GetPrimary(); primary == nil {
138+
t.Fatal("expected backend to be healthy after first check")
139+
}
140+
141+
// Change server response to 500 → unhealthy.
142+
statusCode.Store(http.StatusInternalServerError)
143+
144+
backends = lb.GetAllBackends()
145+
hc.checkBackend(context.Background(), backends[0])
146+
147+
if primary := lb.GetPrimary(); primary != nil {
148+
t.Errorf("expected backend to be unhealthy after status change, but got %s", primary.Host)
149+
}
150+
}
151+
152+
func TestCheckBackend_TransitionUnhealthyToHealthy(t *testing.T) {
153+
var statusCode atomic.Int32
154+
statusCode.Store(http.StatusInternalServerError)
155+
156+
server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
157+
w.WriteHeader(int(statusCode.Load()))
158+
}))
159+
defer server.Close()
160+
161+
hc, lb := setupSingleBackend(t, server, http.StatusNotFound)
162+
163+
// First check: server returns 500 → unhealthy.
164+
backends := lb.GetAllBackends()
165+
hc.checkBackend(context.Background(), backends[0])
166+
167+
if primary := lb.GetPrimary(); primary != nil {
168+
t.Errorf("expected backend to be unhealthy, but got %s", primary.Host)
169+
}
170+
171+
// Change server response to 404 (expected) → healthy.
172+
statusCode.Store(http.StatusNotFound)
173+
174+
backends = lb.GetAllBackends()
175+
hc.checkBackend(context.Background(), backends[0])
176+
177+
if primary := lb.GetPrimary(); primary == nil {
178+
t.Fatal("expected backend to become healthy after status change")
179+
}
180+
}
181+
182+
func TestCheckAllBackends_MultipleBackends(t *testing.T) {
183+
// All backends share the same HealthCheckPort, so both will hit the
184+
// same httptest server (both ManagementIPs resolve to 127.0.0.1).
185+
// We verify that checkAllBackends processes every backend in
186+
// parallel and applies the health result to each.
187+
188+
var statusCode atomic.Int32
189+
statusCode.Store(http.StatusNotFound)
190+
191+
server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
192+
w.WriteHeader(int(statusCode.Load()))
193+
}))
194+
defer server.Close()
195+
196+
host, port := parseServerURL(t, server.URL)
197+
198+
lb := NewLoadBalancer([]*Backend{
199+
{Host: "backend1", ManagementIP: host},
200+
{Host: "backend2", ManagementIP: host},
201+
})
202+
config := &ProxyConfig{
203+
HealthCheckPort: port,
204+
HealthCheckPath: "/",
205+
HealthCheckInterval: 1 * time.Second,
206+
HealthCheckTimeout: 2 * time.Second,
207+
ExpectedStatusCode: http.StatusNotFound,
208+
}
209+
hc := NewHealthChecker(config, lb)
210+
hc.httpClient = server.Client()
211+
hc.httpClient.Timeout = 2 * time.Second
212+
213+
// Server returns 404 → both backends should become healthy.
214+
hc.checkAllBackends(context.Background())
215+
216+
all := lb.GetAllBackends()
217+
for _, b := range all {
218+
if !b.Healthy {
219+
t.Errorf("expected %s to be healthy after checkAllBackends", b.Host)
220+
}
221+
}
222+
223+
// Change server to return 500 → both should become unhealthy.
224+
statusCode.Store(http.StatusInternalServerError)
225+
226+
hc.checkAllBackends(context.Background())
227+
228+
all = lb.GetAllBackends()
229+
for _, b := range all {
230+
if b.Healthy {
231+
t.Errorf("expected %s to be unhealthy after status change", b.Host)
232+
}
233+
}
234+
}

go/cmd/pfudpproxy/proxy.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ func (p *UDPProxy) Start(ctx context.Context) {
6464
fwd, err := net.ListenUDP("udp", nil)
6565
if err != nil {
6666
log.LoggerWContext(ctx).Error(fmt.Sprintf("Failed to open forwarding socket: %s", err.Error()))
67+
p.mu.Lock()
68+
p.running = false
69+
p.mu.Unlock()
6770
return
6871
}
6972
p.fwdConn = fwd
@@ -86,10 +89,12 @@ func (p *UDPProxy) Stop(ctx context.Context) {
8689
}
8790
p.running = false
8891
close(p.stopChan)
92+
listeners := p.listeners
93+
p.listeners = nil
8994
p.mu.Unlock()
9095

9196
// Close all listeners
92-
for _, listener := range p.listeners {
97+
for _, listener := range listeners {
9398
if listener != nil {
9499
listener.Close()
95100
}

0 commit comments

Comments
 (0)