Skip to content

Commit 4b0b6dc

Browse files
committed
TUN-8728: implement diag/tunnel endpoint
## Summary The new endpoint returns the current information to be used when calling the diagnostic procedure. This also adds: - add indexed connection info and method to extract active connections from connTracker - add edge address to Event struct and conn tracker - remove unnecessary event send - add tunnel configuration handler - adjust cmd and metrics to create diagnostic server Closes TUN-8728
1 parent aab5364 commit 4b0b6dc

File tree

11 files changed

+177
-37
lines changed

11 files changed

+177
-37
lines changed

cmd/cloudflared/tunnel/cmd.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -461,10 +461,11 @@ func StartServer(
461461

462462
go func() {
463463
defer wg.Done()
464-
readinessServer := metrics.NewReadyServer(clientID,
465-
tunnelstate.NewConnTracker(log))
466-
observer.RegisterSink(readinessServer)
467-
diagnosticHandler := diagnostic.NewDiagnosticHandler(log, 0, diagnostic.NewSystemCollectorImpl(buildInfo.CloudflaredVersion))
464+
tracker := tunnelstate.NewConnTracker(log)
465+
observer.RegisterSink(tracker)
466+
467+
readinessServer := metrics.NewReadyServer(clientID, tracker)
468+
diagnosticHandler := diagnostic.NewDiagnosticHandler(log, 0, diagnostic.NewSystemCollectorImpl(buildInfo.CloudflaredVersion), tunnelConfig.NamedTunnel.Credentials.TunnelID, clientID, tracker)
468469
metricsConfig := metrics.Config{
469470
ReadyServer: readinessServer,
470471
DiagnosticHandler: diagnosticHandler,

connection/control.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ func (c *controlStream) ServeControlStream(
102102
c.observer.metrics.regSuccess.WithLabelValues("registerConnection").Inc()
103103

104104
c.observer.logConnected(registrationDetails.UUID, c.connIndex, registrationDetails.Location, c.edgeAddress, c.protocol)
105-
c.observer.sendConnectedEvent(c.connIndex, c.protocol, registrationDetails.Location)
105+
c.observer.sendConnectedEvent(c.connIndex, c.protocol, registrationDetails.Location, c.edgeAddress)
106106
c.connectedFuse.Connected()
107107

108108
// if conn index is 0 and tunnel is not remotely managed, then send local ingress rules configuration

connection/event.go

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
package connection
22

3+
import "net"
4+
35
// Event is something that happened to a connection, e.g. disconnection or registration.
46
type Event struct {
5-
Index uint8
6-
EventType Status
7-
Location string
8-
Protocol Protocol
9-
URL string
7+
Index uint8
8+
EventType Status
9+
Location string
10+
Protocol Protocol
11+
URL string
12+
EdgeAddress net.IP
1013
}
1114

1215
// Status is the status of a connection.

connection/observer.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ func (o *Observer) RegisterSink(sink EventSink) {
4747
}
4848

4949
func (o *Observer) logConnected(connectionID uuid.UUID, connIndex uint8, location string, address net.IP, protocol Protocol) {
50-
o.sendEvent(Event{Index: connIndex, EventType: Connected, Location: location})
5150
o.log.Info().
5251
Int(management.EventTypeKey, int(management.Cloudflared)).
5352
Str(LogFieldConnectionID, connectionID.String()).
@@ -63,8 +62,8 @@ func (o *Observer) sendRegisteringEvent(connIndex uint8) {
6362
o.sendEvent(Event{Index: connIndex, EventType: RegisteringTunnel})
6463
}
6564

66-
func (o *Observer) sendConnectedEvent(connIndex uint8, protocol Protocol, location string) {
67-
o.sendEvent(Event{Index: connIndex, EventType: Connected, Protocol: protocol, Location: location})
65+
func (o *Observer) sendConnectedEvent(connIndex uint8, protocol Protocol, location string, edgeAddress net.IP) {
66+
o.sendEvent(Event{Index: connIndex, EventType: Connected, Protocol: protocol, Location: location, EdgeAddress: edgeAddress})
6867
}
6968

7069
func (o *Observer) SendURL(url string) {

diagnostic/consts.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ package diagnostic
33
import "time"
44

55
const (
6-
defaultCollectorTimeout = time.Second * 10 // This const define the timeout value of a collector operation.
7-
collectorField = "collector" // used for logging purposes
8-
systemCollectorName = "system" // used for logging purposes
6+
defaultCollectorTimeout = time.Second * 10 // This const define the timeout value of a collector operation.
7+
collectorField = "collector" // used for logging purposes
8+
systemCollectorName = "system" // used for logging purposes
9+
tunnelStateCollectorName = "tunnelState" // used for logging purposes
910
)

diagnostic/handlers.go

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,38 +6,49 @@ import (
66
"net/http"
77
"time"
88

9+
"github.com/google/uuid"
910
"github.com/rs/zerolog"
11+
12+
"github.com/cloudflare/cloudflared/tunnelstate"
1013
)
1114

1215
type Handler struct {
1316
log *zerolog.Logger
1417
timeout time.Duration
1518
systemCollector SystemCollector
19+
tunnelID uuid.UUID
20+
connectorID uuid.UUID
21+
tracker *tunnelstate.ConnTracker
1622
}
1723

1824
func NewDiagnosticHandler(
1925
log *zerolog.Logger,
2026
timeout time.Duration,
2127
systemCollector SystemCollector,
28+
tunnelID uuid.UUID,
29+
connectorID uuid.UUID,
30+
tracker *tunnelstate.ConnTracker,
2231
) *Handler {
32+
logger := log.With().Logger()
2333
if timeout == 0 {
2434
timeout = defaultCollectorTimeout
2535
}
2636

2737
return &Handler{
28-
log,
29-
timeout,
30-
systemCollector,
38+
log: &logger,
39+
timeout: timeout,
40+
systemCollector: systemCollector,
41+
tunnelID: tunnelID,
42+
connectorID: connectorID,
43+
tracker: tracker,
3144
}
3245
}
3346

3447
func (handler *Handler) SystemHandler(writer http.ResponseWriter, request *http.Request) {
3548
logger := handler.log.With().Str(collectorField, systemCollectorName).Logger()
3649
logger.Info().Msg("Collection started")
3750

38-
defer func() {
39-
logger.Info().Msg("Collection finished")
40-
}()
51+
defer logger.Info().Msg("Collection finished")
4152

4253
ctx, cancel := context.WithTimeout(request.Context(), handler.timeout)
4354

@@ -73,6 +84,32 @@ func (handler *Handler) SystemHandler(writer http.ResponseWriter, request *http.
7384
}
7485
}
7586

87+
type tunnelStateResponse struct {
88+
TunnelID uuid.UUID `json:"tunnelID,omitempty"`
89+
ConnectorID uuid.UUID `json:"connectorID,omitempty"`
90+
Connections []tunnelstate.IndexedConnectionInfo `json:"connections,omitempty"`
91+
}
92+
93+
func (handler *Handler) TunnelStateHandler(writer http.ResponseWriter, _ *http.Request) {
94+
log := handler.log.With().Str(collectorField, tunnelStateCollectorName).Logger()
95+
log.Info().Msg("Collection started")
96+
97+
defer log.Info().Msg("Collection finished")
98+
99+
body := tunnelStateResponse{
100+
handler.tunnelID,
101+
handler.connectorID,
102+
handler.tracker.GetActiveConnections(),
103+
}
104+
encoder := json.NewEncoder(writer)
105+
106+
err := encoder.Encode(body)
107+
if err != nil {
108+
handler.log.Error().Err(err).Msgf("error occurred whilst serializing information")
109+
writer.WriteHeader(http.StatusInternalServerError)
110+
}
111+
}
112+
76113
func writeResponse(writer http.ResponseWriter, bytes []byte, logger *zerolog.Logger) {
77114
bytesWritten, err := writer.Write(bytes)
78115
if err != nil {

diagnostic/handlers_test.go

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,19 @@ import (
55
"encoding/json"
66
"errors"
77
"io"
8+
"net"
89
"net/http"
910
"net/http/httptest"
1011
"testing"
1112

13+
"github.com/google/uuid"
1214
"github.com/rs/zerolog"
1315
"github.com/stretchr/testify/assert"
1416
"github.com/stretchr/testify/require"
1517

18+
"github.com/cloudflare/cloudflared/connection"
1619
"github.com/cloudflare/cloudflared/diagnostic"
20+
"github.com/cloudflare/cloudflared/tunnelstate"
1721
)
1822

1923
type SystemCollectorMock struct{}
@@ -24,6 +28,23 @@ const (
2428
errorKey = "errkey"
2529
)
2630

31+
func newTrackerFromConns(t *testing.T, connections []tunnelstate.IndexedConnectionInfo) *tunnelstate.ConnTracker {
32+
t.Helper()
33+
34+
log := zerolog.Nop()
35+
tracker := tunnelstate.NewConnTracker(&log)
36+
37+
for _, conn := range connections {
38+
tracker.OnTunnelEvent(connection.Event{
39+
Index: conn.Index,
40+
EventType: connection.Connected,
41+
Protocol: conn.Protocol,
42+
EdgeAddress: conn.EdgeAddress,
43+
})
44+
}
45+
46+
return tracker
47+
}
2748
func setCtxValuesForSystemCollector(
2849
systemInfo *diagnostic.SystemInformation,
2950
rawInfo string,
@@ -83,7 +104,7 @@ func TestSystemHandler(t *testing.T) {
83104
for _, tCase := range tests {
84105
t.Run(tCase.name, func(t *testing.T) {
85106
t.Parallel()
86-
handler := diagnostic.NewDiagnosticHandler(&log, 0, &SystemCollectorMock{})
107+
handler := diagnostic.NewDiagnosticHandler(&log, 0, &SystemCollectorMock{}, uuid.New(), uuid.New(), nil)
87108
recorder := httptest.NewRecorder()
88109
ctx := setCtxValuesForSystemCollector(tCase.systemInfo, tCase.rawInfo, tCase.err)
89110
request, err := http.NewRequestWithContext(ctx, http.MethodGet, "/diag/syste,", nil)
@@ -106,3 +127,58 @@ func TestSystemHandler(t *testing.T) {
106127
})
107128
}
108129
}
130+
131+
func TestTunnelStateHandler(t *testing.T) {
132+
t.Parallel()
133+
134+
log := zerolog.Nop()
135+
tests := []struct {
136+
name string
137+
tunnelID uuid.UUID
138+
clientID uuid.UUID
139+
connections []tunnelstate.IndexedConnectionInfo
140+
}{
141+
{
142+
name: "case1",
143+
tunnelID: uuid.New(),
144+
clientID: uuid.New(),
145+
},
146+
{
147+
name: "case2",
148+
tunnelID: uuid.New(),
149+
clientID: uuid.New(),
150+
connections: []tunnelstate.IndexedConnectionInfo{{
151+
ConnectionInfo: tunnelstate.ConnectionInfo{
152+
IsConnected: true,
153+
Protocol: connection.QUIC,
154+
EdgeAddress: net.IPv4(100, 100, 100, 100),
155+
},
156+
Index: 0,
157+
}},
158+
},
159+
}
160+
161+
for _, tCase := range tests {
162+
t.Run(tCase.name, func(t *testing.T) {
163+
t.Parallel()
164+
tracker := newTrackerFromConns(t, tCase.connections)
165+
handler := diagnostic.NewDiagnosticHandler(&log, 0, nil, tCase.tunnelID, tCase.clientID, tracker)
166+
recorder := httptest.NewRecorder()
167+
handler.TunnelStateHandler(recorder, nil)
168+
decoder := json.NewDecoder(recorder.Body)
169+
170+
var response struct {
171+
TunnelID uuid.UUID `json:"tunnelID,omitempty"`
172+
ConnectorID uuid.UUID `json:"connectorID,omitempty"`
173+
Connections []tunnelstate.IndexedConnectionInfo `json:"connections,omitempty"`
174+
}
175+
176+
err := decoder.Decode(&response)
177+
require.NoError(t, err)
178+
assert.Equal(t, http.StatusOK, recorder.Code)
179+
assert.Equal(t, tCase.tunnelID, response.TunnelID)
180+
assert.Equal(t, tCase.clientID, response.ConnectorID)
181+
assert.Equal(t, tCase.connections, response.Connections)
182+
})
183+
}
184+
}

metrics/metrics.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ func newMetricsHandler(
9494
})
9595
}
9696

97+
router.HandleFunc("/diag/tunnel", config.DiagnosticHandler.TunnelStateHandler)
9798
router.HandleFunc("/diag/system", config.DiagnosticHandler.SystemHandler)
9899

99100
return router

metrics/readiness.go

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77

88
"github.com/google/uuid"
99

10-
conn "github.com/cloudflare/cloudflared/connection"
1110
"github.com/cloudflare/cloudflared/tunnelstate"
1211
)
1312

@@ -28,10 +27,6 @@ func NewReadyServer(
2827
}
2928
}
3029

31-
func (rs *ReadyServer) OnTunnelEvent(c conn.Event) {
32-
rs.tracker.OnTunnelEvent(c)
33-
}
34-
3530
type body struct {
3631
Status int `json:"status"`
3732
ReadyConnections uint `json:"readyConnections"`

metrics/readiness_test.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ func TestReadinessEventHandling(t *testing.T) {
4444
assert.Zero(t, readyConnections)
4545

4646
// one connected => ok
47-
rs.OnTunnelEvent(connection.Event{
47+
tracker.OnTunnelEvent(connection.Event{
4848
Index: 1,
4949
EventType: connection.Connected,
5050
})
@@ -53,7 +53,7 @@ func TestReadinessEventHandling(t *testing.T) {
5353
assert.EqualValues(t, 1, readyConnections)
5454

5555
// another connected => still ok
56-
rs.OnTunnelEvent(connection.Event{
56+
tracker.OnTunnelEvent(connection.Event{
5757
Index: 2,
5858
EventType: connection.Connected,
5959
})
@@ -62,7 +62,7 @@ func TestReadinessEventHandling(t *testing.T) {
6262
assert.EqualValues(t, 2, readyConnections)
6363

6464
// one reconnecting => still ok
65-
rs.OnTunnelEvent(connection.Event{
65+
tracker.OnTunnelEvent(connection.Event{
6666
Index: 2,
6767
EventType: connection.Reconnecting,
6868
})
@@ -71,7 +71,7 @@ func TestReadinessEventHandling(t *testing.T) {
7171
assert.EqualValues(t, 1, readyConnections)
7272

7373
// Regression test for TUN-3777
74-
rs.OnTunnelEvent(connection.Event{
74+
tracker.OnTunnelEvent(connection.Event{
7575
Index: 1,
7676
EventType: connection.RegisteringTunnel,
7777
})
@@ -80,14 +80,14 @@ func TestReadinessEventHandling(t *testing.T) {
8080
assert.Zero(t, readyConnections)
8181

8282
// other connected then unregistered => not ok
83-
rs.OnTunnelEvent(connection.Event{
83+
tracker.OnTunnelEvent(connection.Event{
8484
Index: 1,
8585
EventType: connection.Connected,
8686
})
8787
code, readyConnections = mockRequest(t, rs)
8888
assert.EqualValues(t, http.StatusOK, code)
8989
assert.EqualValues(t, 1, readyConnections)
90-
rs.OnTunnelEvent(connection.Event{
90+
tracker.OnTunnelEvent(connection.Event{
9191
Index: 1,
9292
EventType: connection.Unregistering,
9393
})
@@ -96,7 +96,7 @@ func TestReadinessEventHandling(t *testing.T) {
9696
assert.Zero(t, readyConnections)
9797

9898
// other disconnected => not ok
99-
rs.OnTunnelEvent(connection.Event{
99+
tracker.OnTunnelEvent(connection.Event{
100100
Index: 1,
101101
EventType: connection.Disconnected,
102102
})

0 commit comments

Comments
 (0)