TUN-8737: update metrics server port selection

## Summary
Update how metrics server binds to a listener by using a known set of ports whenever the default address is used with the fallback to a random port in case all address are already in use. The default address changes at compile time in order to bind to a different default address when the final deliverable is a docker image.

Refactor ReadyServer tests.

Closes TUN-8737
This commit is contained in:
Luis Neto
2024-11-22 07:23:46 -08:00
parent d779394748
commit e2c2b012f1
8 changed files with 194 additions and 93 deletions

View File

@@ -1,136 +1,106 @@
package metrics
package metrics_test
import (
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"github.com/google/uuid"
"github.com/rs/zerolog"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/cloudflare/cloudflared/connection"
"github.com/cloudflare/cloudflared/metrics"
"github.com/cloudflare/cloudflared/tunnelstate"
)
func TestReadyServer_makeResponse(t *testing.T) {
type fields struct {
isConnected map[uint8]tunnelstate.ConnectionInfo
}
tests := []struct {
name string
fields fields
wantOK bool
wantReadyConnections uint
}{
{
name: "One connection online => HTTP 200",
fields: fields{
isConnected: map[uint8]tunnelstate.ConnectionInfo{
0: {IsConnected: false},
1: {IsConnected: false},
2: {IsConnected: true},
3: {IsConnected: false},
},
},
wantOK: true,
wantReadyConnections: 1,
},
{
name: "No connections online => no HTTP 200",
fields: fields{
isConnected: map[uint8]tunnelstate.ConnectionInfo{
0: {IsConnected: false},
1: {IsConnected: false},
2: {IsConnected: false},
3: {IsConnected: false},
},
},
wantReadyConnections: 0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
rs := &ReadyServer{
tracker: tunnelstate.MockedConnTracker(tt.fields.isConnected),
}
gotStatusCode, gotReadyConnections := rs.makeResponse()
if tt.wantOK && gotStatusCode != http.StatusOK {
t.Errorf("ReadyServer.makeResponse() gotStatusCode = %v, want ok = %v", gotStatusCode, tt.wantOK)
}
if gotReadyConnections != tt.wantReadyConnections {
t.Errorf("ReadyServer.makeResponse() gotReadyConnections = %v, want %v", gotReadyConnections, tt.wantReadyConnections)
}
})
func mockRequest(t *testing.T, readyServer *metrics.ReadyServer) (int, uint) {
t.Helper()
var readyreadyConnections struct {
Status int `json:"status"`
ReadyConnections uint `json:"readyConnections"`
ConnectorID uuid.UUID `json:"connectorId"`
}
rec := httptest.NewRecorder()
readyServer.ServeHTTP(rec, nil)
decoder := json.NewDecoder(rec.Body)
err := decoder.Decode(&readyreadyConnections)
require.NoError(t, err)
return rec.Code, readyreadyConnections.ReadyConnections
}
func TestReadinessEventHandling(t *testing.T) {
nopLogger := zerolog.Nop()
rs := NewReadyServer(&nopLogger, uuid.Nil)
tracker := tunnelstate.NewConnTracker(&nopLogger)
rs := metrics.NewReadyServer(uuid.Nil, tracker)
// start not ok
code, ready := rs.makeResponse()
code, readyConnections := mockRequest(t, rs)
assert.NotEqualValues(t, http.StatusOK, code)
assert.Zero(t, ready)
assert.Zero(t, readyConnections)
// one connected => ok
rs.OnTunnelEvent(connection.Event{
Index: 1,
EventType: connection.Connected,
})
code, ready = rs.makeResponse()
code, readyConnections = mockRequest(t, rs)
assert.EqualValues(t, http.StatusOK, code)
assert.EqualValues(t, 1, ready)
assert.EqualValues(t, 1, readyConnections)
// another connected => still ok
rs.OnTunnelEvent(connection.Event{
Index: 2,
EventType: connection.Connected,
})
code, ready = rs.makeResponse()
code, readyConnections = mockRequest(t, rs)
assert.EqualValues(t, http.StatusOK, code)
assert.EqualValues(t, 2, ready)
assert.EqualValues(t, 2, readyConnections)
// one reconnecting => still ok
rs.OnTunnelEvent(connection.Event{
Index: 2,
EventType: connection.Reconnecting,
})
code, ready = rs.makeResponse()
code, readyConnections = mockRequest(t, rs)
assert.EqualValues(t, http.StatusOK, code)
assert.EqualValues(t, 1, ready)
assert.EqualValues(t, 1, readyConnections)
// Regression test for TUN-3777
rs.OnTunnelEvent(connection.Event{
Index: 1,
EventType: connection.RegisteringTunnel,
})
code, ready = rs.makeResponse()
code, readyConnections = mockRequest(t, rs)
assert.NotEqualValues(t, http.StatusOK, code)
assert.Zero(t, ready)
assert.Zero(t, readyConnections)
// other connected then unregistered => not ok
rs.OnTunnelEvent(connection.Event{
Index: 1,
EventType: connection.Connected,
})
code, ready = rs.makeResponse()
code, readyConnections = mockRequest(t, rs)
assert.EqualValues(t, http.StatusOK, code)
assert.EqualValues(t, 1, ready)
assert.EqualValues(t, 1, readyConnections)
rs.OnTunnelEvent(connection.Event{
Index: 1,
EventType: connection.Unregistering,
})
code, ready = rs.makeResponse()
code, readyConnections = mockRequest(t, rs)
assert.NotEqualValues(t, http.StatusOK, code)
assert.Zero(t, ready)
assert.Zero(t, readyConnections)
// other disconnected => not ok
rs.OnTunnelEvent(connection.Event{
Index: 1,
EventType: connection.Disconnected,
})
code, ready = rs.makeResponse()
code, readyConnections = mockRequest(t, rs)
assert.NotEqualValues(t, http.StatusOK, code)
assert.Zero(t, ready)
assert.Zero(t, readyConnections)
}