detached 1955dc9

fix(traffic): use boundary mocks and context logging

commit
1955dc9c4e2fcb19a7e69c9fb590c66a5b441ccd
author
brice · · 2026-06-30 02:22 UTC
parent
e891c6e
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
14 files · +219 -71
M .mockery.yaml +1 -0
@@ -45,6 +45,7 @@ packages:
4545 LogService:
4646 VolumeService:
4747 PublicTLSService:
48+ TrafficStatusService:
4849 # Exception: pushImageOps is a CLI-local interface, not a boundary port.
4950 # Mocked here because it abstracts Docker SDK calls that require a running
5051 # daemon, making unit/integration tests impractical without a test double.
@@ -45,6 +45,7 @@ packages:
45 LogService:45 LogService:
46 VolumeService:46 VolumeService:
47 PublicTLSService:47 PublicTLSService:
48+ TrafficStatusService:
48 # Exception: pushImageOps is a CLI-local interface, not a boundary port.49 # Exception: pushImageOps is a CLI-local interface, not a boundary port.
49 # Mocked here because it abstracts Docker SDK calls that require a running50 # Mocked here because it abstracts Docker SDK calls that require a running
50 # daemon, making unit/integration tests impractical without a test double.51 # daemon, making unit/integration tests impractical without a test double.
M internal/adapters/in/http/admin/handler.go +2 -6
@@ -38,10 +38,6 @@ type reloadTrigger interface {
3838 Trigger(ctx context.Context) error
3939 }
4040
41-type trafficStatusService interface {
42- Status() domain.TrafficStatus
43-}
44-
4541 // Handler implements the HTTP handler for the admin API.
4642 type Handler struct {
4743 configSvc in.ConfigService
@@ -58,7 +54,7 @@ type Handler struct {
5854 previewSvc previewService
5955 reloadTrigger reloadTrigger
6056 publicTLSSvc in.PublicTLSService
61- trafficSvc trafficStatusService
57+ trafficSvc in.TrafficStatusService
6258 log zerowrap.Logger
6359 }
6460
@@ -207,7 +203,7 @@ type HandlerDeps struct {
207203 VolumeSvc in.VolumeService
208204 ReloadTrigger reloadTrigger
209205 PublicTLSSvc in.PublicTLSService
210- TrafficSvc trafficStatusService
206+ TrafficSvc in.TrafficStatusService
211207 }
212208
213209 // NewHandler creates a new admin HTTP handler.
@@ -38,10 +38,6 @@ type reloadTrigger interface {
38 Trigger(ctx context.Context) error38 Trigger(ctx context.Context) error
39 }39 }
40 40
41-type trafficStatusService interface {
42- Status() domain.TrafficStatus
43-}
44-
45 // Handler implements the HTTP handler for the admin API.41 // Handler implements the HTTP handler for the admin API.
46 type Handler struct {42 type Handler struct {
47 configSvc in.ConfigService43 configSvc in.ConfigService
@@ -58,7 +54,7 @@ type Handler struct {
58 previewSvc previewService54 previewSvc previewService
59 reloadTrigger reloadTrigger55 reloadTrigger reloadTrigger
60 publicTLSSvc in.PublicTLSService56 publicTLSSvc in.PublicTLSService
61- trafficSvc trafficStatusService57+ trafficSvc in.TrafficStatusService
62 log zerowrap.Logger58 log zerowrap.Logger
63 }59 }
64 60
@@ -207,7 +203,7 @@ type HandlerDeps struct {
207 VolumeSvc in.VolumeService203 VolumeSvc in.VolumeService
208 ReloadTrigger reloadTrigger204 ReloadTrigger reloadTrigger
209 PublicTLSSvc in.PublicTLSService205 PublicTLSSvc in.PublicTLSService
210- TrafficSvc trafficStatusService206+ TrafficSvc in.TrafficStatusService
211 }207 }
212 208
213 // NewHandler creates a new admin HTTP handler.209 // NewHandler creates a new admin HTTP handler.
M internal/adapters/in/http/admin/handler_traffic_test.go +13 -13
@@ -9,23 +9,22 @@ import (
99 "github.com/stretchr/testify/require"
1010
1111 "github.com/bnema/gordon/internal/adapters/dto"
12+ inmocks "github.com/bnema/gordon/internal/boundaries/in/mocks"
1213 "github.com/bnema/gordon/internal/domain"
1314 )
1415
15-type fakeTrafficStatusService struct{ status domain.TrafficStatus }
16-
17-func (f fakeTrafficStatusService) Status() domain.TrafficStatus { return f.status }
18-
1916 func TestHandler_TrafficStatus(t *testing.T) {
17+ trafficSvc := inmocks.NewMockTrafficStatusService(t)
18+ trafficSvc.EXPECT().Status().Return(domain.TrafficStatus{
19+ LastReloadStatus: "ok",
20+ EntryPoints: []domain.EntryPointStatus{{
21+ Name: "postgres", Address: "127.0.0.1:5432", Protocol: domain.EntryPointProtocolTCP,
22+ Active: true, ActiveTCPConnections: 2, TotalAccepted: 3, BytesIn: 11, BytesOut: 22,
23+ }},
24+ Counters: domain.TrafficCounters{ActiveTCPConnections: 2, TotalAccepted: 3, BytesIn: 11, BytesOut: 22},
25+ })
2026 handler := newTestHandler(t, func(d *HandlerDeps) {
21- d.TrafficSvc = fakeTrafficStatusService{status: domain.TrafficStatus{
22- LastReloadStatus: "ok",
23- EntryPoints: []domain.EntryPointStatus{{
24- Name: "postgres", Address: "127.0.0.1:5432", Protocol: domain.EntryPointProtocolTCP,
25- Active: true, ActiveTCPConnections: 2, TotalAccepted: 3, BytesIn: 11, BytesOut: 22,
26- }},
27- Counters: domain.TrafficCounters{ActiveTCPConnections: 2, TotalAccepted: 3, BytesIn: 11, BytesOut: 22},
28- }}
27+ d.TrafficSvc = trafficSvc
2928 })
3029 server := newScopedTestServer(t, handler, "admin:status:read")
3130
@@ -44,7 +43,8 @@ func TestHandler_TrafficStatus(t *testing.T) {
4443 }
4544
4645 func TestHandler_TrafficStatusRequiresStatusRead(t *testing.T) {
47- handler := newTestHandler(t, func(d *HandlerDeps) { d.TrafficSvc = fakeTrafficStatusService{} })
46+ trafficSvc := inmocks.NewMockTrafficStatusService(t)
47+ handler := newTestHandler(t, func(d *HandlerDeps) { d.TrafficSvc = trafficSvc })
4848 server := newScopedTestServer(t, handler, "admin:config:read")
4949
5050 resp, err := http.Get(server.URL + "/admin/traffic/status")
@@ -9,23 +9,22 @@ import (
9 "github.com/stretchr/testify/require"9 "github.com/stretchr/testify/require"
10 10
11 "github.com/bnema/gordon/internal/adapters/dto"11 "github.com/bnema/gordon/internal/adapters/dto"
12+ inmocks "github.com/bnema/gordon/internal/boundaries/in/mocks"
12 "github.com/bnema/gordon/internal/domain"13 "github.com/bnema/gordon/internal/domain"
13 )14 )
14 15
15-type fakeTrafficStatusService struct{ status domain.TrafficStatus }
16-
17-func (f fakeTrafficStatusService) Status() domain.TrafficStatus { return f.status }
18-
19 func TestHandler_TrafficStatus(t *testing.T) {16 func TestHandler_TrafficStatus(t *testing.T) {
17+ trafficSvc := inmocks.NewMockTrafficStatusService(t)
18+ trafficSvc.EXPECT().Status().Return(domain.TrafficStatus{
19+ LastReloadStatus: "ok",
20+ EntryPoints: []domain.EntryPointStatus{{
21+ Name: "postgres", Address: "127.0.0.1:5432", Protocol: domain.EntryPointProtocolTCP,
22+ Active: true, ActiveTCPConnections: 2, TotalAccepted: 3, BytesIn: 11, BytesOut: 22,
23+ }},
24+ Counters: domain.TrafficCounters{ActiveTCPConnections: 2, TotalAccepted: 3, BytesIn: 11, BytesOut: 22},
25+ })
20 handler := newTestHandler(t, func(d *HandlerDeps) {26 handler := newTestHandler(t, func(d *HandlerDeps) {
21- d.TrafficSvc = fakeTrafficStatusService{status: domain.TrafficStatus{27+ d.TrafficSvc = trafficSvc
22- LastReloadStatus: "ok",
23- EntryPoints: []domain.EntryPointStatus{{
24- Name: "postgres", Address: "127.0.0.1:5432", Protocol: domain.EntryPointProtocolTCP,
25- Active: true, ActiveTCPConnections: 2, TotalAccepted: 3, BytesIn: 11, BytesOut: 22,
26- }},
27- Counters: domain.TrafficCounters{ActiveTCPConnections: 2, TotalAccepted: 3, BytesIn: 11, BytesOut: 22},
28- }}
29 })28 })
30 server := newScopedTestServer(t, handler, "admin:status:read")29 server := newScopedTestServer(t, handler, "admin:status:read")
31 30
@@ -44,7 +43,8 @@ func TestHandler_TrafficStatus(t *testing.T) {
44 }43 }
45 44
46 func TestHandler_TrafficStatusRequiresStatusRead(t *testing.T) {45 func TestHandler_TrafficStatusRequiresStatusRead(t *testing.T) {
47- handler := newTestHandler(t, func(d *HandlerDeps) { d.TrafficSvc = fakeTrafficStatusService{} })46+ trafficSvc := inmocks.NewMockTrafficStatusService(t)
47+ handler := newTestHandler(t, func(d *HandlerDeps) { d.TrafficSvc = trafficSvc })
48 server := newScopedTestServer(t, handler, "admin:config:read")48 server := newScopedTestServer(t, handler, "admin:config:read")
49 49
50 resp, err := http.Get(server.URL + "/admin/traffic/status")50 resp, err := http.Get(server.URL + "/admin/traffic/status")
M internal/adapters/in/traffic/manager.go +65 -9
@@ -12,6 +12,7 @@ import (
1212 "time"
1313
1414 "github.com/bnema/zerowrap"
15+ "github.com/rs/zerolog"
1516
1617 "github.com/bnema/gordon/internal/domain"
1718 )
@@ -21,10 +22,30 @@ const (
2122 reloadStatusError = "error"
2223 )
2324
25+func trafficLog(ctx context.Context) zerowrap.Logger {
26+ return zerowrap.FromCtxWithFields(ctx, map[string]any{
27+ zerowrap.FieldLayer: "adapter",
28+ zerowrap.FieldAdapter: "traffic",
29+ })
30+}
31+
32+func trafficInfo(ctx context.Context) *zerolog.Event {
33+ log := trafficLog(ctx)
34+ return log.Info()
35+}
36+
37+func trafficDebug(ctx context.Context) *zerolog.Event {
38+ log := trafficLog(ctx)
39+ return log.Debug()
40+}
41+
42+func trafficWarn(ctx context.Context) *zerolog.Event {
43+ log := trafficLog(ctx)
44+ return log.Warn()
45+}
46+
2447 // Manager owns runtime entrypoints for the traffic plane.
2548 type Manager struct {
26- log zerowrap.Logger
27-
2849 mu sync.Mutex
2950 snapshot atomic.Pointer[domain.TrafficGraph]
3051 listeners map[string]*entryPointRuntime
@@ -36,9 +57,8 @@ type Manager struct {
3657 }
3758
3859 // NewManager creates a traffic manager.
39-func NewManager(log zerowrap.Logger) *Manager {
60+func NewManager() *Manager {
4061 manager := &Manager{
41- log: log,
4262 listeners: map[string]*entryPointRuntime{},
4363 udpListeners: map[string]*udpEntryPointRuntime{},
4464 lastReloadStatus: reloadStatusOK,
@@ -50,11 +70,12 @@ func NewManager(log zerowrap.Logger) *Manager {
5070 // Apply validates and applies a new traffic graph snapshot.
5171 func (m *Manager) Apply(ctx context.Context, graph *domain.TrafficGraph) error {
5272 if graph == nil {
53- return m.recordReloadError(errors.New("traffic graph is required"))
73+ return m.recordReloadError(ctx, errors.New("traffic graph is required"))
5474 }
5575 if err := graph.Validate(); err != nil {
56- return m.recordReloadError(err)
76+ return m.recordReloadError(ctx, err)
5777 }
78+ trafficDebug(ctx).Int("entrypoints", len(graph.EntryPoints)).Int("routers", len(graph.Routers)).Int("services", len(graph.Services)).Msg("applying traffic graph")
5879
5980 nextGraph := cloneTrafficGraph(*graph)
6081 m.mu.Lock()
@@ -64,6 +85,7 @@ func (m *Manager) Apply(ctx context.Context, graph *domain.TrafficGraph) error {
6485 if err != nil {
6586 m.lastReloadStatus = reloadStatusError
6687 m.lastReloadError = err.Error()
88+ trafficWarn(ctx).Err(err).Msg("failed to prepare tcp traffic listeners")
6789 return err
6890 }
6991 newUDPListeners, createdUDPListeners, err := m.prepareUDPListeners(ctx, &nextGraph)
@@ -73,6 +95,7 @@ func (m *Manager) Apply(ctx context.Context, graph *domain.TrafficGraph) error {
7395 }
7496 m.lastReloadStatus = reloadStatusError
7597 m.lastReloadError = err.Error()
98+ trafficWarn(ctx).Err(err).Msg("failed to prepare udp traffic listeners")
7699 return err
77100 }
78101
@@ -91,15 +114,18 @@ func (m *Manager) Apply(ctx context.Context, graph *domain.TrafficGraph) error {
91114 m.lastReloadError = ""
92115
93116 tcpDrainTimeout := effectiveTCPOptions(snapshotTCPOptions(&nextGraph)).DrainTimeout
117+ stoppedTCP := 0
94118 for name, runtime := range oldListeners {
95119 if newListeners[name] == runtime {
96120 continue
97121 }
98122 if runtime.shouldStopWith(newListeners[name]) {
123+ stoppedTCP++
99124 runtime.stop(ctx, tcpDrainTimeout)
100125 }
101126 }
102127 udpDrainTimeout := effectiveUDPOptions(snapshotUDPOptions(&nextGraph)).DrainTimeout
128+ stoppedUDP := 0
103129 for name, runtime := range oldUDPListeners {
104130 if newUDPListeners[name] == runtime {
105131 if _, ok := runtime.resolveUDPBackend(); !ok {
@@ -107,13 +133,39 @@ func (m *Manager) Apply(ctx context.Context, graph *domain.TrafficGraph) error {
107133 }
108134 continue
109135 }
136+ stoppedUDP++
110137 runtime.stop(ctx, udpDrainTimeout)
111138 }
139+ logAppliedTrafficGraph(ctx, newListeners, newUDPListeners, createdListeners, createdUDPListeners, stoppedTCP, stoppedUDP)
112140 return nil
113141 }
114142
115143 // Shutdown stops all listeners and active TCP streams.
144+func logAppliedTrafficGraph(
145+ ctx context.Context,
146+ newListeners map[string]*entryPointRuntime,
147+ newUDPListeners map[string]*udpEntryPointRuntime,
148+ createdListeners []*entryPointRuntime,
149+ createdUDPListeners []*udpEntryPointRuntime,
150+ stoppedTCP int,
151+ stoppedUDP int,
152+) {
153+ logEvent := trafficInfo(ctx)
154+ if len(newListeners) == 0 && len(newUDPListeners) == 0 && stoppedTCP == 0 && stoppedUDP == 0 {
155+ logEvent = trafficDebug(ctx)
156+ }
157+ logEvent.
158+ Int("tcp_listeners", len(newListeners)).
159+ Int("udp_listeners", len(newUDPListeners)).
160+ Int("created_tcp_listeners", len(createdListeners)).
161+ Int("created_udp_listeners", len(createdUDPListeners)).
162+ Int("stopped_tcp_listeners", stoppedTCP).
163+ Int("stopped_udp_listeners", stoppedUDP).
164+ Msg("traffic graph applied")
165+}
166+
116167 func (m *Manager) Shutdown(ctx context.Context) error {
168+ trafficInfo(ctx).Msg("shutting down traffic manager")
117169 m.mu.Lock()
118170 defer m.mu.Unlock()
119171
@@ -136,6 +188,7 @@ func (m *Manager) Shutdown(ctx context.Context) error {
136188 for _, runtime := range udpListeners {
137189 runtime.stop(ctx, udpDrainTimeout)
138190 }
191+ trafficInfo(ctx).Int("tcp_listeners", len(listeners)).Int("udp_listeners", len(udpListeners)).Msg("traffic manager shut down")
139192 return nil
140193 }
141194
@@ -165,11 +218,12 @@ func (m *Manager) Status() domain.TrafficStatus {
165218 return status
166219 }
167220
168-func (m *Manager) recordReloadError(err error) error {
221+func (m *Manager) recordReloadError(ctx context.Context, err error) error {
169222 m.mu.Lock()
170223 m.lastReloadStatus = reloadStatusError
171224 m.lastReloadError = err.Error()
172225 m.mu.Unlock()
226+ trafficWarn(ctx).Err(err).Msg("traffic graph rejected")
173227 return err
174228 }
175229
@@ -211,7 +265,8 @@ func (m *Manager) bindTCPEntryPoint(ctx context.Context, entryPoint domain.Entry
211265 if err != nil {
212266 return nil, fmt.Errorf("bind tcp entrypoint %q on %s: %w", entryPoint.Name, entryPoint.Address, err)
213267 }
214- runtime := newEntryPointRuntime(m, entryPoint, listener)
268+ trafficInfo(ctx).Str("entrypoint", entryPoint.Name).Str("address", entryPoint.Address).Str("protocol", string(entryPoint.Protocol)).Msg("bound tcp traffic entrypoint")
269+ runtime := newEntryPointRuntime(ctx, m, entryPoint, listener)
215270 return runtime, nil
216271 }
217272
@@ -249,7 +304,8 @@ func (m *Manager) bindUDPEntryPoint(ctx context.Context, entryPoint domain.Entry
249304 if err != nil {
250305 return nil, fmt.Errorf("bind udp entrypoint %q on %s: %w", entryPoint.Name, entryPoint.Address, err)
251306 }
252- return newUDPEntryPointRuntime(m, entryPoint, packetConn), nil
307+ trafficInfo(ctx).Str("entrypoint", entryPoint.Name).Str("address", entryPoint.Address).Str("protocol", string(entryPoint.Protocol)).Msg("bound udp traffic entrypoint")
308+ return newUDPEntryPointRuntime(ctx, m, entryPoint, packetConn), nil
253309 }
254310
255311 func entryPointStatuses(entries []domain.EntryPoint, listeners map[string]*entryPointRuntime, udpListeners map[string]*udpEntryPointRuntime) []domain.EntryPointStatus {
@@ -12,6 +12,7 @@ import (
12 "time"12 "time"
13 13
14 "github.com/bnema/zerowrap"14 "github.com/bnema/zerowrap"
15+ "github.com/rs/zerolog"
15 16
16 "github.com/bnema/gordon/internal/domain"17 "github.com/bnema/gordon/internal/domain"
17 )18 )
@@ -21,10 +22,30 @@ const (
21 reloadStatusError = "error"22 reloadStatusError = "error"
22 )23 )
23 24
25+func trafficLog(ctx context.Context) zerowrap.Logger {
26+ return zerowrap.FromCtxWithFields(ctx, map[string]any{
27+ zerowrap.FieldLayer: "adapter",
28+ zerowrap.FieldAdapter: "traffic",
29+ })
30+}
31+
32+func trafficInfo(ctx context.Context) *zerolog.Event {
33+ log := trafficLog(ctx)
34+ return log.Info()
35+}
36+
37+func trafficDebug(ctx context.Context) *zerolog.Event {
38+ log := trafficLog(ctx)
39+ return log.Debug()
40+}
41+
42+func trafficWarn(ctx context.Context) *zerolog.Event {
43+ log := trafficLog(ctx)
44+ return log.Warn()
45+}
46+
24 // Manager owns runtime entrypoints for the traffic plane.47 // Manager owns runtime entrypoints for the traffic plane.
25 type Manager struct {48 type Manager struct {
26- log zerowrap.Logger
27-
28 mu sync.Mutex49 mu sync.Mutex
29 snapshot atomic.Pointer[domain.TrafficGraph]50 snapshot atomic.Pointer[domain.TrafficGraph]
30 listeners map[string]*entryPointRuntime51 listeners map[string]*entryPointRuntime
@@ -36,9 +57,8 @@ type Manager struct {
36 }57 }
37 58
38 // NewManager creates a traffic manager.59 // NewManager creates a traffic manager.
39-func NewManager(log zerowrap.Logger) *Manager {60+func NewManager() *Manager {
40 manager := &Manager{61 manager := &Manager{
41- log: log,
42 listeners: map[string]*entryPointRuntime{},62 listeners: map[string]*entryPointRuntime{},
43 udpListeners: map[string]*udpEntryPointRuntime{},63 udpListeners: map[string]*udpEntryPointRuntime{},
44 lastReloadStatus: reloadStatusOK,64 lastReloadStatus: reloadStatusOK,
@@ -50,11 +70,12 @@ func NewManager(log zerowrap.Logger) *Manager {
50 // Apply validates and applies a new traffic graph snapshot.70 // Apply validates and applies a new traffic graph snapshot.
51 func (m *Manager) Apply(ctx context.Context, graph *domain.TrafficGraph) error {71 func (m *Manager) Apply(ctx context.Context, graph *domain.TrafficGraph) error {
52 if graph == nil {72 if graph == nil {
53- return m.recordReloadError(errors.New("traffic graph is required"))73+ return m.recordReloadError(ctx, errors.New("traffic graph is required"))
54 }74 }
55 if err := graph.Validate(); err != nil {75 if err := graph.Validate(); err != nil {
56- return m.recordReloadError(err)76+ return m.recordReloadError(ctx, err)
57 }77 }
78+ trafficDebug(ctx).Int("entrypoints", len(graph.EntryPoints)).Int("routers", len(graph.Routers)).Int("services", len(graph.Services)).Msg("applying traffic graph")
58 79
59 nextGraph := cloneTrafficGraph(*graph)80 nextGraph := cloneTrafficGraph(*graph)
60 m.mu.Lock()81 m.mu.Lock()
@@ -64,6 +85,7 @@ func (m *Manager) Apply(ctx context.Context, graph *domain.TrafficGraph) error {
64 if err != nil {85 if err != nil {
65 m.lastReloadStatus = reloadStatusError86 m.lastReloadStatus = reloadStatusError
66 m.lastReloadError = err.Error()87 m.lastReloadError = err.Error()
88+ trafficWarn(ctx).Err(err).Msg("failed to prepare tcp traffic listeners")
67 return err89 return err
68 }90 }
69 newUDPListeners, createdUDPListeners, err := m.prepareUDPListeners(ctx, &nextGraph)91 newUDPListeners, createdUDPListeners, err := m.prepareUDPListeners(ctx, &nextGraph)
@@ -73,6 +95,7 @@ func (m *Manager) Apply(ctx context.Context, graph *domain.TrafficGraph) error {
73 }95 }
74 m.lastReloadStatus = reloadStatusError96 m.lastReloadStatus = reloadStatusError
75 m.lastReloadError = err.Error()97 m.lastReloadError = err.Error()
98+ trafficWarn(ctx).Err(err).Msg("failed to prepare udp traffic listeners")
76 return err99 return err
77 }100 }
78 101
@@ -91,15 +114,18 @@ func (m *Manager) Apply(ctx context.Context, graph *domain.TrafficGraph) error {
91 m.lastReloadError = ""114 m.lastReloadError = ""
92 115
93 tcpDrainTimeout := effectiveTCPOptions(snapshotTCPOptions(&nextGraph)).DrainTimeout116 tcpDrainTimeout := effectiveTCPOptions(snapshotTCPOptions(&nextGraph)).DrainTimeout
117+ stoppedTCP := 0
94 for name, runtime := range oldListeners {118 for name, runtime := range oldListeners {
95 if newListeners[name] == runtime {119 if newListeners[name] == runtime {
96 continue120 continue
97 }121 }
98 if runtime.shouldStopWith(newListeners[name]) {122 if runtime.shouldStopWith(newListeners[name]) {
123+ stoppedTCP++
99 runtime.stop(ctx, tcpDrainTimeout)124 runtime.stop(ctx, tcpDrainTimeout)
100 }125 }
101 }126 }
102 udpDrainTimeout := effectiveUDPOptions(snapshotUDPOptions(&nextGraph)).DrainTimeout127 udpDrainTimeout := effectiveUDPOptions(snapshotUDPOptions(&nextGraph)).DrainTimeout
128+ stoppedUDP := 0
103 for name, runtime := range oldUDPListeners {129 for name, runtime := range oldUDPListeners {
104 if newUDPListeners[name] == runtime {130 if newUDPListeners[name] == runtime {
105 if _, ok := runtime.resolveUDPBackend(); !ok {131 if _, ok := runtime.resolveUDPBackend(); !ok {
@@ -107,13 +133,39 @@ func (m *Manager) Apply(ctx context.Context, graph *domain.TrafficGraph) error {
107 }133 }
108 continue134 continue
109 }135 }
136+ stoppedUDP++
110 runtime.stop(ctx, udpDrainTimeout)137 runtime.stop(ctx, udpDrainTimeout)
111 }138 }
139+ logAppliedTrafficGraph(ctx, newListeners, newUDPListeners, createdListeners, createdUDPListeners, stoppedTCP, stoppedUDP)
112 return nil140 return nil
113 }141 }
114 142
115 // Shutdown stops all listeners and active TCP streams.143 // Shutdown stops all listeners and active TCP streams.
144+func logAppliedTrafficGraph(
145+ ctx context.Context,
146+ newListeners map[string]*entryPointRuntime,
147+ newUDPListeners map[string]*udpEntryPointRuntime,
148+ createdListeners []*entryPointRuntime,
149+ createdUDPListeners []*udpEntryPointRuntime,
150+ stoppedTCP int,
151+ stoppedUDP int,
152+) {
153+ logEvent := trafficInfo(ctx)
154+ if len(newListeners) == 0 && len(newUDPListeners) == 0 && stoppedTCP == 0 && stoppedUDP == 0 {
155+ logEvent = trafficDebug(ctx)
156+ }
157+ logEvent.
158+ Int("tcp_listeners", len(newListeners)).
159+ Int("udp_listeners", len(newUDPListeners)).
160+ Int("created_tcp_listeners", len(createdListeners)).
161+ Int("created_udp_listeners", len(createdUDPListeners)).
162+ Int("stopped_tcp_listeners", stoppedTCP).
163+ Int("stopped_udp_listeners", stoppedUDP).
164+ Msg("traffic graph applied")
165+}
166+
116 func (m *Manager) Shutdown(ctx context.Context) error {167 func (m *Manager) Shutdown(ctx context.Context) error {
168+ trafficInfo(ctx).Msg("shutting down traffic manager")
117 m.mu.Lock()169 m.mu.Lock()
118 defer m.mu.Unlock()170 defer m.mu.Unlock()
119 171
@@ -136,6 +188,7 @@ func (m *Manager) Shutdown(ctx context.Context) error {
136 for _, runtime := range udpListeners {188 for _, runtime := range udpListeners {
137 runtime.stop(ctx, udpDrainTimeout)189 runtime.stop(ctx, udpDrainTimeout)
138 }190 }
191+ trafficInfo(ctx).Int("tcp_listeners", len(listeners)).Int("udp_listeners", len(udpListeners)).Msg("traffic manager shut down")
139 return nil192 return nil
140 }193 }
141 194
@@ -165,11 +218,12 @@ func (m *Manager) Status() domain.TrafficStatus {
165 return status218 return status
166 }219 }
167 220
168-func (m *Manager) recordReloadError(err error) error {221+func (m *Manager) recordReloadError(ctx context.Context, err error) error {
169 m.mu.Lock()222 m.mu.Lock()
170 m.lastReloadStatus = reloadStatusError223 m.lastReloadStatus = reloadStatusError
171 m.lastReloadError = err.Error()224 m.lastReloadError = err.Error()
172 m.mu.Unlock()225 m.mu.Unlock()
226+ trafficWarn(ctx).Err(err).Msg("traffic graph rejected")
173 return err227 return err
174 }228 }
175 229
@@ -211,7 +265,8 @@ func (m *Manager) bindTCPEntryPoint(ctx context.Context, entryPoint domain.Entry
211 if err != nil {265 if err != nil {
212 return nil, fmt.Errorf("bind tcp entrypoint %q on %s: %w", entryPoint.Name, entryPoint.Address, err)266 return nil, fmt.Errorf("bind tcp entrypoint %q on %s: %w", entryPoint.Name, entryPoint.Address, err)
213 }267 }
214- runtime := newEntryPointRuntime(m, entryPoint, listener)268+ trafficInfo(ctx).Str("entrypoint", entryPoint.Name).Str("address", entryPoint.Address).Str("protocol", string(entryPoint.Protocol)).Msg("bound tcp traffic entrypoint")
269+ runtime := newEntryPointRuntime(ctx, m, entryPoint, listener)
215 return runtime, nil270 return runtime, nil
216 }271 }
217 272
@@ -249,7 +304,8 @@ func (m *Manager) bindUDPEntryPoint(ctx context.Context, entryPoint domain.Entry
249 if err != nil {304 if err != nil {
250 return nil, fmt.Errorf("bind udp entrypoint %q on %s: %w", entryPoint.Name, entryPoint.Address, err)305 return nil, fmt.Errorf("bind udp entrypoint %q on %s: %w", entryPoint.Name, entryPoint.Address, err)
251 }306 }
252- return newUDPEntryPointRuntime(m, entryPoint, packetConn), nil307+ trafficInfo(ctx).Str("entrypoint", entryPoint.Name).Str("address", entryPoint.Address).Str("protocol", string(entryPoint.Protocol)).Msg("bound udp traffic entrypoint")
308+ return newUDPEntryPointRuntime(ctx, m, entryPoint, packetConn), nil
253 }309 }
254 310
255 func entryPointStatuses(entries []domain.EntryPoint, listeners map[string]*entryPointRuntime, udpListeners map[string]*udpEntryPointRuntime) []domain.EntryPointStatus {311 func entryPointStatuses(entries []domain.EntryPoint, listeners map[string]*entryPointRuntime, udpListeners map[string]*udpEntryPointRuntime) []domain.EntryPointStatus {
M internal/adapters/in/traffic/tcp.go +8 -2
@@ -39,8 +39,8 @@ type trackedTCPConn struct {
3939 backend net.Conn
4040 }
4141
42-func newEntryPointRuntime(manager *Manager, entryPoint domain.EntryPoint, listener net.Listener) *entryPointRuntime {
43- ctx, cancel := context.WithCancel(context.Background())
42+func newEntryPointRuntime(parentCtx context.Context, manager *Manager, entryPoint domain.EntryPoint, listener net.Listener) *entryPointRuntime {
43+ ctx, cancel := context.WithCancel(context.WithoutCancel(parentCtx))
4444 return &entryPointRuntime{
4545 manager: manager,
4646 entryPoint: entryPoint,
@@ -56,6 +56,7 @@ func (r *entryPointRuntime) start() {
5656 if !r.started.CompareAndSwap(false, true) {
5757 return
5858 }
59+ trafficInfo(r.ctx).Str("entrypoint", r.entryPoint.Name).Str("address", r.entryPoint.Address).Str("protocol", string(r.entryPoint.Protocol)).Msg("started tcp traffic entrypoint")
5960 go r.acceptLoop()
6061 }
6162
@@ -71,6 +72,7 @@ func (r *entryPointRuntime) acceptLoop() {
7172 if temporary, ok := err.(interface{ Temporary() bool }); ok && temporary.Temporary() {
7273 continue
7374 }
75+ trafficWarn(r.ctx).Err(err).Str("entrypoint", r.entryPoint.Name).Msg("tcp traffic accept loop stopped")
7476 return
7577 }
7678 r.activeWG.Add(1)
@@ -346,6 +348,7 @@ func (r *entryPointRuntime) untrack(conn *trackedTCPConn) {
346348
347349 func (r *entryPointRuntime) stop(ctx context.Context, drainTimeout time.Duration) {
348350 if r.closed.CompareAndSwap(false, true) {
351+ trafficInfo(ctx).Str("entrypoint", r.entryPoint.Name).Str("address", r.entryPoint.Address).Msg("stopping tcp traffic entrypoint")
349352 r.cancel()
350353 _ = r.listener.Close()
351354 if !r.started.Load() {
@@ -358,11 +361,14 @@ func (r *entryPointRuntime) stop(ctx context.Context, drainTimeout time.Duration
358361 return
359362 }
360363 if r.waitActive(ctx, drainTimeout) {
364+ trafficInfo(ctx).Str("entrypoint", r.entryPoint.Name).Msg("stopped tcp traffic entrypoint")
361365 return
362366 }
367+ trafficDebug(ctx).Str("entrypoint", r.entryPoint.Name).Dur("drain_timeout", drainTimeout).Msg("forcing tcp traffic entrypoint drain")
363368 r.closeActiveConns()
364369 select {
365370 case <-r.activeDone():
371+ trafficInfo(ctx).Str("entrypoint", r.entryPoint.Name).Msg("stopped tcp traffic entrypoint")
366372 case <-ctx.Done():
367373 }
368374 }
@@ -39,8 +39,8 @@ type trackedTCPConn struct {
39 backend net.Conn39 backend net.Conn
40 }40 }
41 41
42-func newEntryPointRuntime(manager *Manager, entryPoint domain.EntryPoint, listener net.Listener) *entryPointRuntime {42+func newEntryPointRuntime(parentCtx context.Context, manager *Manager, entryPoint domain.EntryPoint, listener net.Listener) *entryPointRuntime {
43- ctx, cancel := context.WithCancel(context.Background())43+ ctx, cancel := context.WithCancel(context.WithoutCancel(parentCtx))
44 return &entryPointRuntime{44 return &entryPointRuntime{
45 manager: manager,45 manager: manager,
46 entryPoint: entryPoint,46 entryPoint: entryPoint,
@@ -56,6 +56,7 @@ func (r *entryPointRuntime) start() {
56 if !r.started.CompareAndSwap(false, true) {56 if !r.started.CompareAndSwap(false, true) {
57 return57 return
58 }58 }
59+ trafficInfo(r.ctx).Str("entrypoint", r.entryPoint.Name).Str("address", r.entryPoint.Address).Str("protocol", string(r.entryPoint.Protocol)).Msg("started tcp traffic entrypoint")
59 go r.acceptLoop()60 go r.acceptLoop()
60 }61 }
61 62
@@ -71,6 +72,7 @@ func (r *entryPointRuntime) acceptLoop() {
71 if temporary, ok := err.(interface{ Temporary() bool }); ok && temporary.Temporary() {72 if temporary, ok := err.(interface{ Temporary() bool }); ok && temporary.Temporary() {
72 continue73 continue
73 }74 }
75+ trafficWarn(r.ctx).Err(err).Str("entrypoint", r.entryPoint.Name).Msg("tcp traffic accept loop stopped")
74 return76 return
75 }77 }
76 r.activeWG.Add(1)78 r.activeWG.Add(1)
@@ -346,6 +348,7 @@ func (r *entryPointRuntime) untrack(conn *trackedTCPConn) {
346 348
347 func (r *entryPointRuntime) stop(ctx context.Context, drainTimeout time.Duration) {349 func (r *entryPointRuntime) stop(ctx context.Context, drainTimeout time.Duration) {
348 if r.closed.CompareAndSwap(false, true) {350 if r.closed.CompareAndSwap(false, true) {
351+ trafficInfo(ctx).Str("entrypoint", r.entryPoint.Name).Str("address", r.entryPoint.Address).Msg("stopping tcp traffic entrypoint")
349 r.cancel()352 r.cancel()
350 _ = r.listener.Close()353 _ = r.listener.Close()
351 if !r.started.Load() {354 if !r.started.Load() {
@@ -358,11 +361,14 @@ func (r *entryPointRuntime) stop(ctx context.Context, drainTimeout time.Duration
358 return361 return
359 }362 }
360 if r.waitActive(ctx, drainTimeout) {363 if r.waitActive(ctx, drainTimeout) {
364+ trafficInfo(ctx).Str("entrypoint", r.entryPoint.Name).Msg("stopped tcp traffic entrypoint")
361 return365 return
362 }366 }
367+ trafficDebug(ctx).Str("entrypoint", r.entryPoint.Name).Dur("drain_timeout", drainTimeout).Msg("forcing tcp traffic entrypoint drain")
363 r.closeActiveConns()368 r.closeActiveConns()
364 select {369 select {
365 case <-r.activeDone():370 case <-r.activeDone():
371+ trafficInfo(ctx).Str("entrypoint", r.entryPoint.Name).Msg("stopped tcp traffic entrypoint")
366 case <-ctx.Done():372 case <-ctx.Done():
367 }373 }
368 }374 }
M internal/adapters/in/traffic/tcp_test.go +10 -11
@@ -8,7 +8,6 @@ import (
88 "testing"
99 "time"
1010
11- "github.com/bnema/zerowrap"
1211 "github.com/stretchr/testify/assert"
1312 "github.com/stretchr/testify/require"
1413
@@ -18,7 +17,7 @@ import (
1817 func TestTCPManagerLifecycle(t *testing.T) {
1918 backend := startTCPEchoServer(t, 0)
2019 graph := tcpGraph(t, freeTCPAddress(t), backend.address)
21- manager := NewManager(zerowrap.Default())
20+ manager := NewManager()
2221
2322 require.NoError(t, manager.Apply(context.Background(), &graph))
2423 defer shutdownManager(t, manager)
@@ -39,7 +38,7 @@ func TestTCPManagerLifecycle(t *testing.T) {
3938 func TestTCPManagerRejectsInvalidGraphWithoutReplacingSnapshot(t *testing.T) {
4039 backend := startTCPEchoServer(t, 0)
4140 graph := tcpGraph(t, freeTCPAddress(t), backend.address)
42- manager := NewManager(zerowrap.Default())
41+ manager := NewManager()
4342 require.NoError(t, manager.Apply(context.Background(), &graph))
4443 defer shutdownManager(t, manager)
4544
@@ -60,7 +59,7 @@ func TestTCPManagerRejectsInvalidGraphWithoutReplacingSnapshot(t *testing.T) {
6059 func TestTCPPassthroughEcho(t *testing.T) {
6160 backend := startTCPEchoServer(t, 0)
6261 graph := tcpGraph(t, freeTCPAddress(t), backend.address)
63- manager := NewManager(zerowrap.Default())
62+ manager := NewManager()
6463 require.NoError(t, manager.Apply(context.Background(), &graph))
6564 defer shutdownManager(t, manager)
6665
@@ -73,7 +72,7 @@ func TestTCPPassthroughUnknownRouterCloses(t *testing.T) {
7372 graph := tcpGraph(t, freeTCPAddress(t), freeTCPAddress(t))
7473 graph.Routers = nil
7574 graph.Services = nil
76- manager := NewManager(zerowrap.Default())
75+ manager := NewManager()
7776 require.NoError(t, manager.Apply(context.Background(), &graph))
7877 defer shutdownManager(t, manager)
7978
@@ -89,7 +88,7 @@ func TestTCPPassthroughUnknownRouterCloses(t *testing.T) {
8988 func TestTCPPassthroughBackendDialFailure(t *testing.T) {
9089 graph := tcpGraph(t, freeTCPAddress(t), freeTCPAddress(t))
9190 graph.Options.TCP.DialTimeout = 50 * time.Millisecond
92- manager := NewManager(zerowrap.Default())
91+ manager := NewManager()
9392 require.NoError(t, manager.Apply(context.Background(), &graph))
9493 defer shutdownManager(t, manager)
9594
@@ -105,7 +104,7 @@ func TestTCPPassthroughBackendDialFailure(t *testing.T) {
105104 func TestTCPPassthroughShutdownStopsAccepting(t *testing.T) {
106105 backend := startTCPEchoServer(t, 0)
107106 graph := tcpGraph(t, freeTCPAddress(t), backend.address)
108- manager := NewManager(zerowrap.Default())
107+ manager := NewManager()
109108 require.NoError(t, manager.Apply(context.Background(), &graph))
110109
111110 require.NoError(t, manager.Shutdown(context.Background()))
@@ -117,7 +116,7 @@ func TestTCPPassthroughIdleTimeoutClosesConnection(t *testing.T) {
117116 backend := startTCPEchoServer(t, 0)
118117 graph := tcpGraph(t, freeTCPAddress(t), backend.address)
119118 graph.Options.TCP.IdleTimeout = 50 * time.Millisecond
120- manager := NewManager(zerowrap.Default())
119+ manager := NewManager()
121120 require.NoError(t, manager.Apply(context.Background(), &graph))
122121 defer shutdownManager(t, manager)
123122
@@ -132,7 +131,7 @@ func TestTCPPassthroughMaxConnectionsRejectsOverflow(t *testing.T) {
132131 backend := startTCPEchoServer(t, 0)
133132 graph := tcpGraph(t, freeTCPAddress(t), backend.address)
134133 graph.Options.TCP.MaxConnections = 1
135- manager := NewManager(zerowrap.Default())
134+ manager := NewManager()
136135 require.NoError(t, manager.Apply(context.Background(), &graph))
137136 defer shutdownManager(t, manager)
138137
@@ -154,7 +153,7 @@ func TestTCPPassthroughDrainWaitsForActiveConnectionThenTimesOut(t *testing.T) {
154153 backend := startTCPEchoServer(t, 0)
155154 graph := tcpGraph(t, freeTCPAddress(t), backend.address)
156155 graph.Options.TCP.DrainTimeout = 50 * time.Millisecond
157- manager := NewManager(zerowrap.Default())
156+ manager := NewManager()
158157 require.NoError(t, manager.Apply(context.Background(), &graph))
159158 defer shutdownManager(t, manager)
160159
@@ -179,7 +178,7 @@ func TestTCPPassthroughStatusCountersTrackAcceptedRefusedErrorsAndBytes(t *testi
179178 backend := startTCPEchoServer(t, 0)
180179 graph := tcpGraph(t, freeTCPAddress(t), backend.address)
181180 graph.Options.TCP.MaxConnections = 1
182- manager := NewManager(zerowrap.Default())
181+ manager := NewManager()
183182 require.NoError(t, manager.Apply(context.Background(), &graph))
184183 defer shutdownManager(t, manager)
185184
@@ -8,7 +8,6 @@ import (
8 "testing"8 "testing"
9 "time"9 "time"
10 10
11- "github.com/bnema/zerowrap"
12 "github.com/stretchr/testify/assert"11 "github.com/stretchr/testify/assert"
13 "github.com/stretchr/testify/require"12 "github.com/stretchr/testify/require"
14 13
@@ -18,7 +17,7 @@ import (
18 func TestTCPManagerLifecycle(t *testing.T) {17 func TestTCPManagerLifecycle(t *testing.T) {
19 backend := startTCPEchoServer(t, 0)18 backend := startTCPEchoServer(t, 0)
20 graph := tcpGraph(t, freeTCPAddress(t), backend.address)19 graph := tcpGraph(t, freeTCPAddress(t), backend.address)
21- manager := NewManager(zerowrap.Default())20+ manager := NewManager()
22 21
23 require.NoError(t, manager.Apply(context.Background(), &graph))22 require.NoError(t, manager.Apply(context.Background(), &graph))
24 defer shutdownManager(t, manager)23 defer shutdownManager(t, manager)
@@ -39,7 +38,7 @@ func TestTCPManagerLifecycle(t *testing.T) {
39 func TestTCPManagerRejectsInvalidGraphWithoutReplacingSnapshot(t *testing.T) {38 func TestTCPManagerRejectsInvalidGraphWithoutReplacingSnapshot(t *testing.T) {
40 backend := startTCPEchoServer(t, 0)39 backend := startTCPEchoServer(t, 0)
41 graph := tcpGraph(t, freeTCPAddress(t), backend.address)40 graph := tcpGraph(t, freeTCPAddress(t), backend.address)
42- manager := NewManager(zerowrap.Default())41+ manager := NewManager()
43 require.NoError(t, manager.Apply(context.Background(), &graph))42 require.NoError(t, manager.Apply(context.Background(), &graph))
44 defer shutdownManager(t, manager)43 defer shutdownManager(t, manager)
45 44
@@ -60,7 +59,7 @@ func TestTCPManagerRejectsInvalidGraphWithoutReplacingSnapshot(t *testing.T) {
60 func TestTCPPassthroughEcho(t *testing.T) {59 func TestTCPPassthroughEcho(t *testing.T) {
61 backend := startTCPEchoServer(t, 0)60 backend := startTCPEchoServer(t, 0)
62 graph := tcpGraph(t, freeTCPAddress(t), backend.address)61 graph := tcpGraph(t, freeTCPAddress(t), backend.address)
63- manager := NewManager(zerowrap.Default())62+ manager := NewManager()
64 require.NoError(t, manager.Apply(context.Background(), &graph))63 require.NoError(t, manager.Apply(context.Background(), &graph))
65 defer shutdownManager(t, manager)64 defer shutdownManager(t, manager)
66 65
@@ -73,7 +72,7 @@ func TestTCPPassthroughUnknownRouterCloses(t *testing.T) {
73 graph := tcpGraph(t, freeTCPAddress(t), freeTCPAddress(t))72 graph := tcpGraph(t, freeTCPAddress(t), freeTCPAddress(t))
74 graph.Routers = nil73 graph.Routers = nil
75 graph.Services = nil74 graph.Services = nil
76- manager := NewManager(zerowrap.Default())75+ manager := NewManager()
77 require.NoError(t, manager.Apply(context.Background(), &graph))76 require.NoError(t, manager.Apply(context.Background(), &graph))
78 defer shutdownManager(t, manager)77 defer shutdownManager(t, manager)
79 78
@@ -89,7 +88,7 @@ func TestTCPPassthroughUnknownRouterCloses(t *testing.T) {
89 func TestTCPPassthroughBackendDialFailure(t *testing.T) {88 func TestTCPPassthroughBackendDialFailure(t *testing.T) {
90 graph := tcpGraph(t, freeTCPAddress(t), freeTCPAddress(t))89 graph := tcpGraph(t, freeTCPAddress(t), freeTCPAddress(t))
91 graph.Options.TCP.DialTimeout = 50 * time.Millisecond90 graph.Options.TCP.DialTimeout = 50 * time.Millisecond
92- manager := NewManager(zerowrap.Default())91+ manager := NewManager()
93 require.NoError(t, manager.Apply(context.Background(), &graph))92 require.NoError(t, manager.Apply(context.Background(), &graph))
94 defer shutdownManager(t, manager)93 defer shutdownManager(t, manager)
95 94
@@ -105,7 +104,7 @@ func TestTCPPassthroughBackendDialFailure(t *testing.T) {
105 func TestTCPPassthroughShutdownStopsAccepting(t *testing.T) {104 func TestTCPPassthroughShutdownStopsAccepting(t *testing.T) {
106 backend := startTCPEchoServer(t, 0)105 backend := startTCPEchoServer(t, 0)
107 graph := tcpGraph(t, freeTCPAddress(t), backend.address)106 graph := tcpGraph(t, freeTCPAddress(t), backend.address)
108- manager := NewManager(zerowrap.Default())107+ manager := NewManager()
109 require.NoError(t, manager.Apply(context.Background(), &graph))108 require.NoError(t, manager.Apply(context.Background(), &graph))
110 109
111 require.NoError(t, manager.Shutdown(context.Background()))110 require.NoError(t, manager.Shutdown(context.Background()))
@@ -117,7 +116,7 @@ func TestTCPPassthroughIdleTimeoutClosesConnection(t *testing.T) {
117 backend := startTCPEchoServer(t, 0)116 backend := startTCPEchoServer(t, 0)
118 graph := tcpGraph(t, freeTCPAddress(t), backend.address)117 graph := tcpGraph(t, freeTCPAddress(t), backend.address)
119 graph.Options.TCP.IdleTimeout = 50 * time.Millisecond118 graph.Options.TCP.IdleTimeout = 50 * time.Millisecond
120- manager := NewManager(zerowrap.Default())119+ manager := NewManager()
121 require.NoError(t, manager.Apply(context.Background(), &graph))120 require.NoError(t, manager.Apply(context.Background(), &graph))
122 defer shutdownManager(t, manager)121 defer shutdownManager(t, manager)
123 122
@@ -132,7 +131,7 @@ func TestTCPPassthroughMaxConnectionsRejectsOverflow(t *testing.T) {
132 backend := startTCPEchoServer(t, 0)131 backend := startTCPEchoServer(t, 0)
133 graph := tcpGraph(t, freeTCPAddress(t), backend.address)132 graph := tcpGraph(t, freeTCPAddress(t), backend.address)
134 graph.Options.TCP.MaxConnections = 1133 graph.Options.TCP.MaxConnections = 1
135- manager := NewManager(zerowrap.Default())134+ manager := NewManager()
136 require.NoError(t, manager.Apply(context.Background(), &graph))135 require.NoError(t, manager.Apply(context.Background(), &graph))
137 defer shutdownManager(t, manager)136 defer shutdownManager(t, manager)
138 137
@@ -154,7 +153,7 @@ func TestTCPPassthroughDrainWaitsForActiveConnectionThenTimesOut(t *testing.T) {
154 backend := startTCPEchoServer(t, 0)153 backend := startTCPEchoServer(t, 0)
155 graph := tcpGraph(t, freeTCPAddress(t), backend.address)154 graph := tcpGraph(t, freeTCPAddress(t), backend.address)
156 graph.Options.TCP.DrainTimeout = 50 * time.Millisecond155 graph.Options.TCP.DrainTimeout = 50 * time.Millisecond
157- manager := NewManager(zerowrap.Default())156+ manager := NewManager()
158 require.NoError(t, manager.Apply(context.Background(), &graph))157 require.NoError(t, manager.Apply(context.Background(), &graph))
159 defer shutdownManager(t, manager)158 defer shutdownManager(t, manager)
160 159
@@ -179,7 +178,7 @@ func TestTCPPassthroughStatusCountersTrackAcceptedRefusedErrorsAndBytes(t *testi
179 backend := startTCPEchoServer(t, 0)178 backend := startTCPEchoServer(t, 0)
180 graph := tcpGraph(t, freeTCPAddress(t), backend.address)179 graph := tcpGraph(t, freeTCPAddress(t), backend.address)
181 graph.Options.TCP.MaxConnections = 1180 graph.Options.TCP.MaxConnections = 1
182- manager := NewManager(zerowrap.Default())181+ manager := NewManager()
183 require.NoError(t, manager.Apply(context.Background(), &graph))182 require.NoError(t, manager.Apply(context.Background(), &graph))
184 defer shutdownManager(t, manager)183 defer shutdownManager(t, manager)
185 184
M internal/adapters/in/traffic/tlsmux_test.go +4 -5
@@ -18,7 +18,6 @@ import (
1818 "testing"
1919 "time"
2020
21- "github.com/bnema/zerowrap"
2221 "github.com/stretchr/testify/assert"
2322 "github.com/stretchr/testify/require"
2423
@@ -80,7 +79,7 @@ func TestTLSPassthrough(t *testing.T) {
8079 {name: "wild", sni: "*.example.com", service: "wild", backend: backendB.address},
8180 {name: "exact", sni: "api.example.com", service: "exact", backend: backendExact.address},
8281 })
83- manager := NewManager(zerowrap.Default())
82+ manager := NewManager()
8483 require.NoError(t, manager.Apply(context.Background(), &graph))
8584 defer shutdownManager(t, manager)
8685
@@ -92,7 +91,7 @@ func TestTLSPassthrough(t *testing.T) {
9291 func TestTLSPassthroughUnknownCloses(t *testing.T) {
9392 backend := startTLSGreetingServer(t, "backend", "backend\n")
9493 graph := tlsGraph(t, freeTCPAddress(t), []tlsRoute{{name: "raw", sni: "raw.example.com", service: "raw", backend: backend.address}})
95- manager := NewManager(zerowrap.Default())
94+ manager := NewManager()
9695 require.NoError(t, manager.Apply(context.Background(), &graph))
9796 defer shutdownManager(t, manager)
9897
@@ -106,7 +105,7 @@ func TestTLSMuxIncompleteClientHelloDoesNotHangShutdown(t *testing.T) {
106105 Options: domain.TrafficOptions{TCP: domain.TCPOptions{DialTimeout: time.Second, IdleTimeout: time.Minute, DrainTimeout: 50 * time.Millisecond}},
107106 EntryPoints: []domain.EntryPoint{{Name: "websecure", Address: freeTCPAddress(t), Protocol: domain.EntryPointProtocolTLSMux}},
108107 }
109- manager := NewManager(zerowrap.Default())
108+ manager := NewManager()
110109 require.NoError(t, manager.Apply(context.Background(), &graph))
111110
112111 conn := dialTCP(t, graph.EntryPoints[0].Address)
@@ -124,7 +123,7 @@ func TestTLSMuxIncompleteClientHelloDoesNotHangShutdown(t *testing.T) {
124123 func TestTLSMuxHTTPSFallback(t *testing.T) {
125124 backend := startTLSGreetingServer(t, "raw-backend", "raw\n")
126125 graph := tlsGraph(t, freeTCPAddress(t), []tlsRoute{{name: "raw", sni: "raw.example.com", service: "raw", backend: backend.address}})
127- manager := NewManager(zerowrap.Default())
126+ manager := NewManager()
128127 fallbackCert := testTLSConfig(t, "fallback.example.com")
129128 manager.SetTLSFallback("websecure", func(ctx context.Context, conn net.Conn) {
130129 _ = ServeHTTPSFallback(ctx, conn, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
@@ -18,7 +18,6 @@ import (
18 "testing"18 "testing"
19 "time"19 "time"
20 20
21- "github.com/bnema/zerowrap"
22 "github.com/stretchr/testify/assert"21 "github.com/stretchr/testify/assert"
23 "github.com/stretchr/testify/require"22 "github.com/stretchr/testify/require"
24 23
@@ -80,7 +79,7 @@ func TestTLSPassthrough(t *testing.T) {
80 {name: "wild", sni: "*.example.com", service: "wild", backend: backendB.address},79 {name: "wild", sni: "*.example.com", service: "wild", backend: backendB.address},
81 {name: "exact", sni: "api.example.com", service: "exact", backend: backendExact.address},80 {name: "exact", sni: "api.example.com", service: "exact", backend: backendExact.address},
82 })81 })
83- manager := NewManager(zerowrap.Default())82+ manager := NewManager()
84 require.NoError(t, manager.Apply(context.Background(), &graph))83 require.NoError(t, manager.Apply(context.Background(), &graph))
85 defer shutdownManager(t, manager)84 defer shutdownManager(t, manager)
86 85
@@ -92,7 +91,7 @@ func TestTLSPassthrough(t *testing.T) {
92 func TestTLSPassthroughUnknownCloses(t *testing.T) {91 func TestTLSPassthroughUnknownCloses(t *testing.T) {
93 backend := startTLSGreetingServer(t, "backend", "backend\n")92 backend := startTLSGreetingServer(t, "backend", "backend\n")
94 graph := tlsGraph(t, freeTCPAddress(t), []tlsRoute{{name: "raw", sni: "raw.example.com", service: "raw", backend: backend.address}})93 graph := tlsGraph(t, freeTCPAddress(t), []tlsRoute{{name: "raw", sni: "raw.example.com", service: "raw", backend: backend.address}})
95- manager := NewManager(zerowrap.Default())94+ manager := NewManager()
96 require.NoError(t, manager.Apply(context.Background(), &graph))95 require.NoError(t, manager.Apply(context.Background(), &graph))
97 defer shutdownManager(t, manager)96 defer shutdownManager(t, manager)
98 97
@@ -106,7 +105,7 @@ func TestTLSMuxIncompleteClientHelloDoesNotHangShutdown(t *testing.T) {
106 Options: domain.TrafficOptions{TCP: domain.TCPOptions{DialTimeout: time.Second, IdleTimeout: time.Minute, DrainTimeout: 50 * time.Millisecond}},105 Options: domain.TrafficOptions{TCP: domain.TCPOptions{DialTimeout: time.Second, IdleTimeout: time.Minute, DrainTimeout: 50 * time.Millisecond}},
107 EntryPoints: []domain.EntryPoint{{Name: "websecure", Address: freeTCPAddress(t), Protocol: domain.EntryPointProtocolTLSMux}},106 EntryPoints: []domain.EntryPoint{{Name: "websecure", Address: freeTCPAddress(t), Protocol: domain.EntryPointProtocolTLSMux}},
108 }107 }
109- manager := NewManager(zerowrap.Default())108+ manager := NewManager()
110 require.NoError(t, manager.Apply(context.Background(), &graph))109 require.NoError(t, manager.Apply(context.Background(), &graph))
111 110
112 conn := dialTCP(t, graph.EntryPoints[0].Address)111 conn := dialTCP(t, graph.EntryPoints[0].Address)
@@ -124,7 +123,7 @@ func TestTLSMuxIncompleteClientHelloDoesNotHangShutdown(t *testing.T) {
124 func TestTLSMuxHTTPSFallback(t *testing.T) {123 func TestTLSMuxHTTPSFallback(t *testing.T) {
125 backend := startTLSGreetingServer(t, "raw-backend", "raw\n")124 backend := startTLSGreetingServer(t, "raw-backend", "raw\n")
126 graph := tlsGraph(t, freeTCPAddress(t), []tlsRoute{{name: "raw", sni: "raw.example.com", service: "raw", backend: backend.address}})125 graph := tlsGraph(t, freeTCPAddress(t), []tlsRoute{{name: "raw", sni: "raw.example.com", service: "raw", backend: backend.address}})
127- manager := NewManager(zerowrap.Default())126+ manager := NewManager()
128 fallbackCert := testTLSConfig(t, "fallback.example.com")127 fallbackCert := testTLSConfig(t, "fallback.example.com")
129 manager.SetTLSFallback("websecure", func(ctx context.Context, conn net.Conn) {128 manager.SetTLSFallback("websecure", func(ctx context.Context, conn net.Conn) {
130 _ = ServeHTTPSFallback(ctx, conn, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {129 _ = ServeHTTPSFallback(ctx, conn, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
M internal/adapters/in/traffic/udp.go +8 -2
@@ -40,8 +40,8 @@ type udpSession struct {
4040 once sync.Once
4141 }
4242
43-func newUDPEntryPointRuntime(manager *Manager, entryPoint domain.EntryPoint, packetConn net.PacketConn) *udpEntryPointRuntime {
44- ctx, cancel := context.WithCancel(context.Background())
43+func newUDPEntryPointRuntime(parentCtx context.Context, manager *Manager, entryPoint domain.EntryPoint, packetConn net.PacketConn) *udpEntryPointRuntime {
44+ ctx, cancel := context.WithCancel(context.WithoutCancel(parentCtx))
4545 return &udpEntryPointRuntime{
4646 manager: manager,
4747 entryPoint: entryPoint,
@@ -57,6 +57,7 @@ func (r *udpEntryPointRuntime) start() {
5757 if !r.started.CompareAndSwap(false, true) {
5858 return
5959 }
60+ trafficInfo(r.ctx).Str("entrypoint", r.entryPoint.Name).Str("address", r.entryPoint.Address).Str("protocol", string(r.entryPoint.Protocol)).Msg("started udp traffic entrypoint")
6061 go r.readLoop()
6162 go r.expireLoop()
6263 }
@@ -234,6 +235,7 @@ func (r *udpEntryPointRuntime) resolveUDPBackend() (domain.TrafficBackend, bool)
234235
235236 func (r *udpEntryPointRuntime) stop(ctx context.Context, drainTimeout time.Duration) {
236237 if r.closed.CompareAndSwap(false, true) {
238+ trafficInfo(ctx).Str("entrypoint", r.entryPoint.Name).Str("address", r.entryPoint.Address).Msg("stopping udp traffic entrypoint")
237239 r.cancel()
238240 _ = r.packetConn.Close()
239241 if !r.started.Load() {
@@ -246,16 +248,20 @@ func (r *udpEntryPointRuntime) stop(ctx context.Context, drainTimeout time.Durat
246248 return
247249 }
248250 if r.waitSessions(ctx, drainTimeout) {
251+ trafficInfo(ctx).Str("entrypoint", r.entryPoint.Name).Msg("stopped udp traffic entrypoint")
249252 return
250253 }
254+ trafficDebug(ctx).Str("entrypoint", r.entryPoint.Name).Dur("drain_timeout", drainTimeout).Msg("forcing udp traffic entrypoint drain")
251255 r.closeSessions()
252256 select {
253257 case <-r.sessionsDone():
258+ trafficInfo(ctx).Str("entrypoint", r.entryPoint.Name).Msg("stopped udp traffic entrypoint")
254259 case <-ctx.Done():
255260 }
256261 }
257262
258263 func (r *udpEntryPointRuntime) drainSessionsAfter(drainTimeout time.Duration) {
264+ trafficDebug(r.ctx).Str("entrypoint", r.entryPoint.Name).Dur("drain_timeout", drainTimeout).Msg("scheduled udp session drain after router removal")
259265 go func() {
260266 if drainTimeout <= 0 {
261267 drainTimeout = defaultUDPOptions().DrainTimeout
@@ -40,8 +40,8 @@ type udpSession struct {
40 once sync.Once40 once sync.Once
41 }41 }
42 42
43-func newUDPEntryPointRuntime(manager *Manager, entryPoint domain.EntryPoint, packetConn net.PacketConn) *udpEntryPointRuntime {43+func newUDPEntryPointRuntime(parentCtx context.Context, manager *Manager, entryPoint domain.EntryPoint, packetConn net.PacketConn) *udpEntryPointRuntime {
44- ctx, cancel := context.WithCancel(context.Background())44+ ctx, cancel := context.WithCancel(context.WithoutCancel(parentCtx))
45 return &udpEntryPointRuntime{45 return &udpEntryPointRuntime{
46 manager: manager,46 manager: manager,
47 entryPoint: entryPoint,47 entryPoint: entryPoint,
@@ -57,6 +57,7 @@ func (r *udpEntryPointRuntime) start() {
57 if !r.started.CompareAndSwap(false, true) {57 if !r.started.CompareAndSwap(false, true) {
58 return58 return
59 }59 }
60+ trafficInfo(r.ctx).Str("entrypoint", r.entryPoint.Name).Str("address", r.entryPoint.Address).Str("protocol", string(r.entryPoint.Protocol)).Msg("started udp traffic entrypoint")
60 go r.readLoop()61 go r.readLoop()
61 go r.expireLoop()62 go r.expireLoop()
62 }63 }
@@ -234,6 +235,7 @@ func (r *udpEntryPointRuntime) resolveUDPBackend() (domain.TrafficBackend, bool)
234 235
235 func (r *udpEntryPointRuntime) stop(ctx context.Context, drainTimeout time.Duration) {236 func (r *udpEntryPointRuntime) stop(ctx context.Context, drainTimeout time.Duration) {
236 if r.closed.CompareAndSwap(false, true) {237 if r.closed.CompareAndSwap(false, true) {
238+ trafficInfo(ctx).Str("entrypoint", r.entryPoint.Name).Str("address", r.entryPoint.Address).Msg("stopping udp traffic entrypoint")
237 r.cancel()239 r.cancel()
238 _ = r.packetConn.Close()240 _ = r.packetConn.Close()
239 if !r.started.Load() {241 if !r.started.Load() {
@@ -246,16 +248,20 @@ func (r *udpEntryPointRuntime) stop(ctx context.Context, drainTimeout time.Durat
246 return248 return
247 }249 }
248 if r.waitSessions(ctx, drainTimeout) {250 if r.waitSessions(ctx, drainTimeout) {
251+ trafficInfo(ctx).Str("entrypoint", r.entryPoint.Name).Msg("stopped udp traffic entrypoint")
249 return252 return
250 }253 }
254+ trafficDebug(ctx).Str("entrypoint", r.entryPoint.Name).Dur("drain_timeout", drainTimeout).Msg("forcing udp traffic entrypoint drain")
251 r.closeSessions()255 r.closeSessions()
252 select {256 select {
253 case <-r.sessionsDone():257 case <-r.sessionsDone():
258+ trafficInfo(ctx).Str("entrypoint", r.entryPoint.Name).Msg("stopped udp traffic entrypoint")
254 case <-ctx.Done():259 case <-ctx.Done():
255 }260 }
256 }261 }
257 262
258 func (r *udpEntryPointRuntime) drainSessionsAfter(drainTimeout time.Duration) {263 func (r *udpEntryPointRuntime) drainSessionsAfter(drainTimeout time.Duration) {
264+ trafficDebug(r.ctx).Str("entrypoint", r.entryPoint.Name).Dur("drain_timeout", drainTimeout).Msg("scheduled udp session drain after router removal")
259 go func() {265 go func() {
260 if drainTimeout <= 0 {266 if drainTimeout <= 0 {
261 drainTimeout = defaultUDPOptions().DrainTimeout267 drainTimeout = defaultUDPOptions().DrainTimeout
M internal/adapters/in/traffic/udp_test.go +8 -9
@@ -6,7 +6,6 @@ import (
66 "testing"
77 "time"
88
9- "github.com/bnema/zerowrap"
109 "github.com/stretchr/testify/assert"
1110 "github.com/stretchr/testify/require"
1211
@@ -16,7 +15,7 @@ import (
1615 func TestUDPPassthroughEcho(t *testing.T) {
1716 backend := startUDPEchoServer(t)
1817 graph := udpGraph(t, freeUDPAddress(t), backend.address)
19- manager := NewManager(zerowrap.Default())
18+ manager := NewManager()
2019 require.NoError(t, manager.Apply(context.Background(), &graph))
2120 defer shutdownManager(t, manager)
2221
@@ -28,7 +27,7 @@ func TestUDPPassthroughEcho(t *testing.T) {
2827 func TestUDPTwoClientsGetIsolatedSessions(t *testing.T) {
2928 backend := startUDPEchoServer(t)
3029 graph := udpGraph(t, freeUDPAddress(t), backend.address)
31- manager := NewManager(zerowrap.Default())
30+ manager := NewManager()
3231 require.NoError(t, manager.Apply(context.Background(), &graph))
3332 defer shutdownManager(t, manager)
3433
@@ -45,7 +44,7 @@ func TestUDPIdleSessionExpires(t *testing.T) {
4544 backend := startUDPEchoServer(t)
4645 graph := udpGraph(t, freeUDPAddress(t), backend.address)
4746 graph.Options.UDP.IdleTimeout = 40 * time.Millisecond
48- manager := NewManager(zerowrap.Default())
47+ manager := NewManager()
4948 require.NoError(t, manager.Apply(context.Background(), &graph))
5049 defer shutdownManager(t, manager)
5150
@@ -59,7 +58,7 @@ func TestUDPMaxSessionsRejectsOverflow(t *testing.T) {
5958 backend := startUDPEchoServer(t)
6059 graph := udpGraph(t, freeUDPAddress(t), backend.address)
6160 graph.Options.UDP.MaxSessions = 1
62- manager := NewManager(zerowrap.Default())
61+ manager := NewManager()
6362 require.NoError(t, manager.Apply(context.Background(), &graph))
6463 defer shutdownManager(t, manager)
6564
@@ -82,7 +81,7 @@ func TestUDPRemovedRouterWithRetainedEntryPointDrainsSession(t *testing.T) {
8281 backend := startUDPEchoServer(t)
8382 graph := udpGraph(t, freeUDPAddress(t), backend.address)
8483 graph.Options.UDP.DrainTimeout = 50 * time.Millisecond
85- manager := NewManager(zerowrap.Default())
84+ manager := NewManager()
8685 require.NoError(t, manager.Apply(context.Background(), &graph))
8786 defer shutdownManager(t, manager)
8887
@@ -108,7 +107,7 @@ func TestUDPRemovedRouterDrainsThenClosesSessions(t *testing.T) {
108107 backend := startUDPEchoServer(t)
109108 graph := udpGraph(t, freeUDPAddress(t), backend.address)
110109 graph.Options.UDP.DrainTimeout = 50 * time.Millisecond
111- manager := NewManager(zerowrap.Default())
110+ manager := NewManager()
112111 require.NoError(t, manager.Apply(context.Background(), &graph))
113112 defer shutdownManager(t, manager)
114113
@@ -127,7 +126,7 @@ func TestUDPRemovedRouterDrainsThenClosesSessions(t *testing.T) {
127126 func TestUDPShutdownClosesSocket(t *testing.T) {
128127 backend := startUDPEchoServer(t)
129128 graph := udpGraph(t, freeUDPAddress(t), backend.address)
130- manager := NewManager(zerowrap.Default())
129+ manager := NewManager()
131130 require.NoError(t, manager.Apply(context.Background(), &graph))
132131 require.NoError(t, manager.Shutdown(context.Background()))
133132
@@ -144,7 +143,7 @@ func TestUDPShutdownClosesSocket(t *testing.T) {
144143 func TestUDPStatusCountersTrackDatagrams(t *testing.T) {
145144 backend := startUDPEchoServer(t)
146145 graph := udpGraph(t, freeUDPAddress(t), backend.address)
147- manager := NewManager(zerowrap.Default())
146+ manager := NewManager()
148147 require.NoError(t, manager.Apply(context.Background(), &graph))
149148 defer shutdownManager(t, manager)
150149
@@ -6,7 +6,6 @@ import (
6 "testing"6 "testing"
7 "time"7 "time"
8 8
9- "github.com/bnema/zerowrap"
10 "github.com/stretchr/testify/assert"9 "github.com/stretchr/testify/assert"
11 "github.com/stretchr/testify/require"10 "github.com/stretchr/testify/require"
12 11
@@ -16,7 +15,7 @@ import (
16 func TestUDPPassthroughEcho(t *testing.T) {15 func TestUDPPassthroughEcho(t *testing.T) {
17 backend := startUDPEchoServer(t)16 backend := startUDPEchoServer(t)
18 graph := udpGraph(t, freeUDPAddress(t), backend.address)17 graph := udpGraph(t, freeUDPAddress(t), backend.address)
19- manager := NewManager(zerowrap.Default())18+ manager := NewManager()
20 require.NoError(t, manager.Apply(context.Background(), &graph))19 require.NoError(t, manager.Apply(context.Background(), &graph))
21 defer shutdownManager(t, manager)20 defer shutdownManager(t, manager)
22 21
@@ -28,7 +27,7 @@ func TestUDPPassthroughEcho(t *testing.T) {
28 func TestUDPTwoClientsGetIsolatedSessions(t *testing.T) {27 func TestUDPTwoClientsGetIsolatedSessions(t *testing.T) {
29 backend := startUDPEchoServer(t)28 backend := startUDPEchoServer(t)
30 graph := udpGraph(t, freeUDPAddress(t), backend.address)29 graph := udpGraph(t, freeUDPAddress(t), backend.address)
31- manager := NewManager(zerowrap.Default())30+ manager := NewManager()
32 require.NoError(t, manager.Apply(context.Background(), &graph))31 require.NoError(t, manager.Apply(context.Background(), &graph))
33 defer shutdownManager(t, manager)32 defer shutdownManager(t, manager)
34 33
@@ -45,7 +44,7 @@ func TestUDPIdleSessionExpires(t *testing.T) {
45 backend := startUDPEchoServer(t)44 backend := startUDPEchoServer(t)
46 graph := udpGraph(t, freeUDPAddress(t), backend.address)45 graph := udpGraph(t, freeUDPAddress(t), backend.address)
47 graph.Options.UDP.IdleTimeout = 40 * time.Millisecond46 graph.Options.UDP.IdleTimeout = 40 * time.Millisecond
48- manager := NewManager(zerowrap.Default())47+ manager := NewManager()
49 require.NoError(t, manager.Apply(context.Background(), &graph))48 require.NoError(t, manager.Apply(context.Background(), &graph))
50 defer shutdownManager(t, manager)49 defer shutdownManager(t, manager)
51 50
@@ -59,7 +58,7 @@ func TestUDPMaxSessionsRejectsOverflow(t *testing.T) {
59 backend := startUDPEchoServer(t)58 backend := startUDPEchoServer(t)
60 graph := udpGraph(t, freeUDPAddress(t), backend.address)59 graph := udpGraph(t, freeUDPAddress(t), backend.address)
61 graph.Options.UDP.MaxSessions = 160 graph.Options.UDP.MaxSessions = 1
62- manager := NewManager(zerowrap.Default())61+ manager := NewManager()
63 require.NoError(t, manager.Apply(context.Background(), &graph))62 require.NoError(t, manager.Apply(context.Background(), &graph))
64 defer shutdownManager(t, manager)63 defer shutdownManager(t, manager)
65 64
@@ -82,7 +81,7 @@ func TestUDPRemovedRouterWithRetainedEntryPointDrainsSession(t *testing.T) {
82 backend := startUDPEchoServer(t)81 backend := startUDPEchoServer(t)
83 graph := udpGraph(t, freeUDPAddress(t), backend.address)82 graph := udpGraph(t, freeUDPAddress(t), backend.address)
84 graph.Options.UDP.DrainTimeout = 50 * time.Millisecond83 graph.Options.UDP.DrainTimeout = 50 * time.Millisecond
85- manager := NewManager(zerowrap.Default())84+ manager := NewManager()
86 require.NoError(t, manager.Apply(context.Background(), &graph))85 require.NoError(t, manager.Apply(context.Background(), &graph))
87 defer shutdownManager(t, manager)86 defer shutdownManager(t, manager)
88 87
@@ -108,7 +107,7 @@ func TestUDPRemovedRouterDrainsThenClosesSessions(t *testing.T) {
108 backend := startUDPEchoServer(t)107 backend := startUDPEchoServer(t)
109 graph := udpGraph(t, freeUDPAddress(t), backend.address)108 graph := udpGraph(t, freeUDPAddress(t), backend.address)
110 graph.Options.UDP.DrainTimeout = 50 * time.Millisecond109 graph.Options.UDP.DrainTimeout = 50 * time.Millisecond
111- manager := NewManager(zerowrap.Default())110+ manager := NewManager()
112 require.NoError(t, manager.Apply(context.Background(), &graph))111 require.NoError(t, manager.Apply(context.Background(), &graph))
113 defer shutdownManager(t, manager)112 defer shutdownManager(t, manager)
114 113
@@ -127,7 +126,7 @@ func TestUDPRemovedRouterDrainsThenClosesSessions(t *testing.T) {
127 func TestUDPShutdownClosesSocket(t *testing.T) {126 func TestUDPShutdownClosesSocket(t *testing.T) {
128 backend := startUDPEchoServer(t)127 backend := startUDPEchoServer(t)
129 graph := udpGraph(t, freeUDPAddress(t), backend.address)128 graph := udpGraph(t, freeUDPAddress(t), backend.address)
130- manager := NewManager(zerowrap.Default())129+ manager := NewManager()
131 require.NoError(t, manager.Apply(context.Background(), &graph))130 require.NoError(t, manager.Apply(context.Background(), &graph))
132 require.NoError(t, manager.Shutdown(context.Background()))131 require.NoError(t, manager.Shutdown(context.Background()))
133 132
@@ -144,7 +143,7 @@ func TestUDPShutdownClosesSocket(t *testing.T) {
144 func TestUDPStatusCountersTrackDatagrams(t *testing.T) {143 func TestUDPStatusCountersTrackDatagrams(t *testing.T) {
145 backend := startUDPEchoServer(t)144 backend := startUDPEchoServer(t)
146 graph := udpGraph(t, freeUDPAddress(t), backend.address)145 graph := udpGraph(t, freeUDPAddress(t), backend.address)
147- manager := NewManager(zerowrap.Default())146+ manager := NewManager()
148 require.NoError(t, manager.Apply(context.Background(), &graph))147 require.NoError(t, manager.Apply(context.Background(), &graph))
149 defer shutdownManager(t, manager)148 defer shutdownManager(t, manager)
150 149
M internal/app/run.go +2 -2
@@ -716,7 +716,7 @@ func (si *serviceInit) initRuntimeProxyAndTraffic() error {
716716 if err := si.initRuntimeAndProxy(); err != nil {
717717 return err
718718 }
719- si.svc.trafficManager = trafficadapter.NewManager(si.log)
719+ si.svc.trafficManager = trafficadapter.NewManager()
720720 if err := applyTrafficRuntimeConfig(si.ctx, si.svc.trafficManager, si.cfg, si.svc.configSvc); err != nil {
721721 return si.log.WrapErr(err, "failed to apply traffic configuration")
722722 }
@@ -793,7 +793,7 @@ func (si *serviceInit) initHandlers() {
793793
794794 initPreviewService(si.ctx, si.cfg, si.svc, si.log)
795795 if si.svc.trafficManager == nil {
796- si.svc.trafficManager = trafficadapter.NewManager(si.log)
796+ si.svc.trafficManager = trafficadapter.NewManager()
797797 }
798798
799799 si.svc.adminHandler = admin.NewHandler(admin.HandlerDeps{
@@ -716,7 +716,7 @@ func (si *serviceInit) initRuntimeProxyAndTraffic() error {
716 if err := si.initRuntimeAndProxy(); err != nil {716 if err := si.initRuntimeAndProxy(); err != nil {
717 return err717 return err
718 }718 }
719- si.svc.trafficManager = trafficadapter.NewManager(si.log)719+ si.svc.trafficManager = trafficadapter.NewManager()
720 if err := applyTrafficRuntimeConfig(si.ctx, si.svc.trafficManager, si.cfg, si.svc.configSvc); err != nil {720 if err := applyTrafficRuntimeConfig(si.ctx, si.svc.trafficManager, si.cfg, si.svc.configSvc); err != nil {
721 return si.log.WrapErr(err, "failed to apply traffic configuration")721 return si.log.WrapErr(err, "failed to apply traffic configuration")
722 }722 }
@@ -793,7 +793,7 @@ func (si *serviceInit) initHandlers() {
793 793
794 initPreviewService(si.ctx, si.cfg, si.svc, si.log)794 initPreviewService(si.ctx, si.cfg, si.svc, si.log)
795 if si.svc.trafficManager == nil {795 if si.svc.trafficManager == nil {
796- si.svc.trafficManager = trafficadapter.NewManager(si.log)796+ si.svc.trafficManager = trafficadapter.NewManager()
797 }797 }
798 798
799 si.svc.adminHandler = admin.NewHandler(admin.HandlerDeps{799 si.svc.adminHandler = admin.NewHandler(admin.HandlerDeps{
M internal/app/traffic.go +2 -4
@@ -5,14 +5,12 @@ import (
55 "fmt"
66
77 trafficadapter "github.com/bnema/gordon/internal/adapters/in/traffic"
8+ "github.com/bnema/gordon/internal/boundaries/in"
89 "github.com/bnema/gordon/internal/domain"
910 trafficbuilder "github.com/bnema/gordon/internal/usecase/traffic"
1011 )
1112
12-func applyTrafficRuntimeConfig(ctx context.Context, manager *trafficadapter.Manager, cfg Config, configSvc interface {
13- GetRoutes(context.Context) []domain.Route
14- GetExternalRoutes() map[string]string
15-}) error {
13+func applyTrafficRuntimeConfig(ctx context.Context, manager *trafficadapter.Manager, cfg Config, configSvc in.ConfigService) error {
1614 if manager == nil || configSvc == nil {
1715 return nil
1816 }
@@ -5,14 +5,12 @@ import (
5 "fmt"5 "fmt"
6 6
7 trafficadapter "github.com/bnema/gordon/internal/adapters/in/traffic"7 trafficadapter "github.com/bnema/gordon/internal/adapters/in/traffic"
8+ "github.com/bnema/gordon/internal/boundaries/in"
8 "github.com/bnema/gordon/internal/domain"9 "github.com/bnema/gordon/internal/domain"
9 trafficbuilder "github.com/bnema/gordon/internal/usecase/traffic"10 trafficbuilder "github.com/bnema/gordon/internal/usecase/traffic"
10 )11 )
11 12
12-func applyTrafficRuntimeConfig(ctx context.Context, manager *trafficadapter.Manager, cfg Config, configSvc interface {13+func applyTrafficRuntimeConfig(ctx context.Context, manager *trafficadapter.Manager, cfg Config, configSvc in.ConfigService) error {
13- GetRoutes(context.Context) []domain.Route
14- GetExternalRoutes() map[string]string
15-}) error {
16 if manager == nil || configSvc == nil {14 if manager == nil || configSvc == nil {
17 return nil15 return nil
18 }16 }
M internal/app/traffic_test.go +7 -8
@@ -5,11 +5,11 @@ import (
55 "net"
66 "testing"
77
8- "github.com/bnema/zerowrap"
98 "github.com/stretchr/testify/assert"
109 "github.com/stretchr/testify/require"
1110
1211 trafficadapter "github.com/bnema/gordon/internal/adapters/in/traffic"
12+ inmocks "github.com/bnema/gordon/internal/boundaries/in/mocks"
1313 "github.com/bnema/gordon/internal/domain"
1414 "github.com/bnema/gordon/internal/usecase/traffic"
1515 )
@@ -53,7 +53,7 @@ func TestTrafficRuntimeGraphRejectsL4RouterOnLegacyEntrypoint(t *testing.T) {
5353 }
5454
5555 func TestApplyTrafficRuntimeConfigAppliesCustomL4Entrypoint(t *testing.T) {
56- manager := trafficadapter.NewManager(zerowrap.Default())
56+ manager := trafficadapter.NewManager()
5757 defer func() { require.NoError(t, manager.Shutdown(context.Background())) }()
5858 cfg := Config{
5959 EntryPoints: map[string]traffic.EntryPointConfig{
@@ -66,18 +66,17 @@ func TestApplyTrafficRuntimeConfigAppliesCustomL4Entrypoint(t *testing.T) {
6666 }
6767 cfg.Traffic.TCP.Routers = []traffic.RouterConfig{{Name: "postgres", EntryPoint: "postgres", Service: "network_service:postgres:db"}}
6868
69- require.NoError(t, applyTrafficRuntimeConfig(context.Background(), manager, cfg, fakeTrafficConfigService{}))
69+ configSvc := inmocks.NewMockConfigService(t)
70+ configSvc.EXPECT().GetRoutes(context.Background()).Return(nil)
71+ configSvc.EXPECT().GetExternalRoutes().Return(nil)
72+
73+ require.NoError(t, applyTrafficRuntimeConfig(context.Background(), manager, cfg, configSvc))
7074 status := manager.Status()
7175 require.Len(t, status.EntryPoints, 1)
7276 assert.Equal(t, "postgres", status.EntryPoints[0].Name)
7377 assert.True(t, status.EntryPoints[0].Active)
7478 }
7579
76-type fakeTrafficConfigService struct{}
77-
78-func (fakeTrafficConfigService) GetRoutes(context.Context) []domain.Route { return nil }
79-func (fakeTrafficConfigService) GetExternalRoutes() map[string]string { return nil }
80-
8180 func freeTCPAddress(t *testing.T) string {
8281 t.Helper()
8382 listener, err := net.Listen("tcp", "127.0.0.1:0")
@@ -5,11 +5,11 @@ import (
5 "net"5 "net"
6 "testing"6 "testing"
7 7
8- "github.com/bnema/zerowrap"
9 "github.com/stretchr/testify/assert"8 "github.com/stretchr/testify/assert"
10 "github.com/stretchr/testify/require"9 "github.com/stretchr/testify/require"
11 10
12 trafficadapter "github.com/bnema/gordon/internal/adapters/in/traffic"11 trafficadapter "github.com/bnema/gordon/internal/adapters/in/traffic"
12+ inmocks "github.com/bnema/gordon/internal/boundaries/in/mocks"
13 "github.com/bnema/gordon/internal/domain"13 "github.com/bnema/gordon/internal/domain"
14 "github.com/bnema/gordon/internal/usecase/traffic"14 "github.com/bnema/gordon/internal/usecase/traffic"
15 )15 )
@@ -53,7 +53,7 @@ func TestTrafficRuntimeGraphRejectsL4RouterOnLegacyEntrypoint(t *testing.T) {
53 }53 }
54 54
55 func TestApplyTrafficRuntimeConfigAppliesCustomL4Entrypoint(t *testing.T) {55 func TestApplyTrafficRuntimeConfigAppliesCustomL4Entrypoint(t *testing.T) {
56- manager := trafficadapter.NewManager(zerowrap.Default())56+ manager := trafficadapter.NewManager()
57 defer func() { require.NoError(t, manager.Shutdown(context.Background())) }()57 defer func() { require.NoError(t, manager.Shutdown(context.Background())) }()
58 cfg := Config{58 cfg := Config{
59 EntryPoints: map[string]traffic.EntryPointConfig{59 EntryPoints: map[string]traffic.EntryPointConfig{
@@ -66,18 +66,17 @@ func TestApplyTrafficRuntimeConfigAppliesCustomL4Entrypoint(t *testing.T) {
66 }66 }
67 cfg.Traffic.TCP.Routers = []traffic.RouterConfig{{Name: "postgres", EntryPoint: "postgres", Service: "network_service:postgres:db"}}67 cfg.Traffic.TCP.Routers = []traffic.RouterConfig{{Name: "postgres", EntryPoint: "postgres", Service: "network_service:postgres:db"}}
68 68
69- require.NoError(t, applyTrafficRuntimeConfig(context.Background(), manager, cfg, fakeTrafficConfigService{}))69+ configSvc := inmocks.NewMockConfigService(t)
70+ configSvc.EXPECT().GetRoutes(context.Background()).Return(nil)
71+ configSvc.EXPECT().GetExternalRoutes().Return(nil)
72+
73+ require.NoError(t, applyTrafficRuntimeConfig(context.Background(), manager, cfg, configSvc))
70 status := manager.Status()74 status := manager.Status()
71 require.Len(t, status.EntryPoints, 1)75 require.Len(t, status.EntryPoints, 1)
72 assert.Equal(t, "postgres", status.EntryPoints[0].Name)76 assert.Equal(t, "postgres", status.EntryPoints[0].Name)
73 assert.True(t, status.EntryPoints[0].Active)77 assert.True(t, status.EntryPoints[0].Active)
74 }78 }
75 79
76-type fakeTrafficConfigService struct{}
77-
78-func (fakeTrafficConfigService) GetRoutes(context.Context) []domain.Route { return nil }
79-func (fakeTrafficConfigService) GetExternalRoutes() map[string]string { return nil }
80-
81 func freeTCPAddress(t *testing.T) string {80 func freeTCPAddress(t *testing.T) string {
82 t.Helper()81 t.Helper()
83 listener, err := net.Listen("tcp", "127.0.0.1:0")82 listener, err := net.Listen("tcp", "127.0.0.1:0")
A internal/boundaries/in/mocks/mock_traffic_status_service.go +81 -0
@@ -0,0 +1,81 @@
1+// Code generated by mockery; DO NOT EDIT.
2+// github.com/vektra/mockery
3+// template: testify
4+
5+package mocks
6+
7+import (
8+ "github.com/bnema/gordon/internal/domain"
9+ mock "github.com/stretchr/testify/mock"
10+)
11+
12+// NewMockTrafficStatusService creates a new instance of MockTrafficStatusService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
13+// The first argument is typically a *testing.T value.
14+func NewMockTrafficStatusService(t interface {
15+ mock.TestingT
16+ Cleanup(func())
17+}) *MockTrafficStatusService {
18+ mock := &MockTrafficStatusService{}
19+ mock.Mock.Test(t)
20+
21+ t.Cleanup(func() { mock.AssertExpectations(t) })
22+
23+ return mock
24+}
25+
26+// MockTrafficStatusService is an autogenerated mock type for the TrafficStatusService type
27+type MockTrafficStatusService struct {
28+ mock.Mock
29+}
30+
31+type MockTrafficStatusService_Expecter struct {
32+ mock *mock.Mock
33+}
34+
35+func (_m *MockTrafficStatusService) EXPECT() *MockTrafficStatusService_Expecter {
36+ return &MockTrafficStatusService_Expecter{mock: &_m.Mock}
37+}
38+
39+// Status provides a mock function for the type MockTrafficStatusService
40+func (_mock *MockTrafficStatusService) Status() domain.TrafficStatus {
41+ ret := _mock.Called()
42+
43+ if len(ret) == 0 {
44+ panic("no return value specified for Status")
45+ }
46+
47+ var r0 domain.TrafficStatus
48+ if returnFunc, ok := ret.Get(0).(func() domain.TrafficStatus); ok {
49+ r0 = returnFunc()
50+ } else {
51+ r0 = ret.Get(0).(domain.TrafficStatus)
52+ }
53+ return r0
54+}
55+
56+// MockTrafficStatusService_Status_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Status'
57+type MockTrafficStatusService_Status_Call struct {
58+ *mock.Call
59+}
60+
61+// Status is a helper method to define mock.On call
62+func (_e *MockTrafficStatusService_Expecter) Status() *MockTrafficStatusService_Status_Call {
63+ return &MockTrafficStatusService_Status_Call{Call: _e.mock.On("Status")}
64+}
65+
66+func (_c *MockTrafficStatusService_Status_Call) Run(run func()) *MockTrafficStatusService_Status_Call {
67+ _c.Call.Run(func(args mock.Arguments) {
68+ run()
69+ })
70+ return _c
71+}
72+
73+func (_c *MockTrafficStatusService_Status_Call) Return(trafficStatus domain.TrafficStatus) *MockTrafficStatusService_Status_Call {
74+ _c.Call.Return(trafficStatus)
75+ return _c
76+}
77+
78+func (_c *MockTrafficStatusService_Status_Call) RunAndReturn(run func() domain.TrafficStatus) *MockTrafficStatusService_Status_Call {
79+ _c.Call.Return(run)
80+ return _c
81+}
@@ -0,0 +1,81 @@
1+// Code generated by mockery; DO NOT EDIT.
2+// github.com/vektra/mockery
3+// template: testify
4+
5+package mocks
6+
7+import (
8+ "github.com/bnema/gordon/internal/domain"
9+ mock "github.com/stretchr/testify/mock"
10+)
11+
12+// NewMockTrafficStatusService creates a new instance of MockTrafficStatusService. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
13+// The first argument is typically a *testing.T value.
14+func NewMockTrafficStatusService(t interface {
15+ mock.TestingT
16+ Cleanup(func())
17+}) *MockTrafficStatusService {
18+ mock := &MockTrafficStatusService{}
19+ mock.Mock.Test(t)
20+
21+ t.Cleanup(func() { mock.AssertExpectations(t) })
22+
23+ return mock
24+}
25+
26+// MockTrafficStatusService is an autogenerated mock type for the TrafficStatusService type
27+type MockTrafficStatusService struct {
28+ mock.Mock
29+}
30+
31+type MockTrafficStatusService_Expecter struct {
32+ mock *mock.Mock
33+}
34+
35+func (_m *MockTrafficStatusService) EXPECT() *MockTrafficStatusService_Expecter {
36+ return &MockTrafficStatusService_Expecter{mock: &_m.Mock}
37+}
38+
39+// Status provides a mock function for the type MockTrafficStatusService
40+func (_mock *MockTrafficStatusService) Status() domain.TrafficStatus {
41+ ret := _mock.Called()
42+
43+ if len(ret) == 0 {
44+ panic("no return value specified for Status")
45+ }
46+
47+ var r0 domain.TrafficStatus
48+ if returnFunc, ok := ret.Get(0).(func() domain.TrafficStatus); ok {
49+ r0 = returnFunc()
50+ } else {
51+ r0 = ret.Get(0).(domain.TrafficStatus)
52+ }
53+ return r0
54+}
55+
56+// MockTrafficStatusService_Status_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Status'
57+type MockTrafficStatusService_Status_Call struct {
58+ *mock.Call
59+}
60+
61+// Status is a helper method to define mock.On call
62+func (_e *MockTrafficStatusService_Expecter) Status() *MockTrafficStatusService_Status_Call {
63+ return &MockTrafficStatusService_Status_Call{Call: _e.mock.On("Status")}
64+}
65+
66+func (_c *MockTrafficStatusService_Status_Call) Run(run func()) *MockTrafficStatusService_Status_Call {
67+ _c.Call.Run(func(args mock.Arguments) {
68+ run()
69+ })
70+ return _c
71+}
72+
73+func (_c *MockTrafficStatusService_Status_Call) Return(trafficStatus domain.TrafficStatus) *MockTrafficStatusService_Status_Call {
74+ _c.Call.Return(trafficStatus)
75+ return _c
76+}
77+
78+func (_c *MockTrafficStatusService_Status_Call) RunAndReturn(run func() domain.TrafficStatus) *MockTrafficStatusService_Status_Call {
79+ _c.Call.Return(run)
80+ return _c
81+}
A internal/boundaries/in/traffic.go +8 -0
@@ -0,0 +1,8 @@
1+package in
2+
3+import "github.com/bnema/gordon/internal/domain"
4+
5+// TrafficStatusService exposes runtime traffic-plane status.
6+type TrafficStatusService interface {
7+ Status() domain.TrafficStatus
8+}
@@ -0,0 +1,8 @@
1+package in
2+
3+import "github.com/bnema/gordon/internal/domain"
4+
5+// TrafficStatusService exposes runtime traffic-plane status.
6+type TrafficStatusService interface {
7+ Status() domain.TrafficStatus
8+}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 7

Tags 106

↑↓ moveenter openesc close