Skip to content

Commit 30fceb8

Browse files
committed
fix hooks and add logging, logging will be removed before merge
1 parent b2228f4 commit 30fceb8

File tree

5 files changed

+104
-48
lines changed

5 files changed

+104
-48
lines changed

adapters.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,8 @@ func (ca *connectionAdapter) IsUsable() bool {
103103
return ca.conn.IsUsable()
104104
}
105105

106-
// GetPoolConnection returns the underlying pool connection.
107-
func (ca *connectionAdapter) GetPoolConnection() *pool.Conn {
106+
// GetPoolConn returns the underlying pool connection.
107+
func (ca *connectionAdapter) GetPoolConn() *pool.Conn {
108108
return ca.conn
109109
}
110110

hitless/config.go

Lines changed: 71 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package hitless
33
import (
44
"net"
55
"runtime"
6+
"strings"
67
"time"
78

89
"github.com/redis/go-redis/v9/internal/util"
@@ -183,8 +184,6 @@ func (c *Config) Validate() error {
183184
return ErrInvalidHandoffRetries
184185
}
185186

186-
187-
188187
return nil
189188
}
190189

@@ -284,8 +283,6 @@ func (c *Config) ApplyDefaultsWithPoolSize(poolSize int) *Config {
284283
result.MaxHandoffRetries = c.MaxHandoffRetries
285284
}
286285

287-
288-
289286
return result
290287
}
291288

@@ -334,44 +331,92 @@ func (c *Config) applyWorkerDefaults(poolSize int) {
334331

335332
// DetectEndpointType automatically detects the appropriate endpoint type
336333
// based on the connection address and TLS configuration.
334+
//
335+
// For IP addresses:
336+
// - If TLS is enabled: requests FQDN for proper certificate validation
337+
// - If TLS is disabled: requests IP for better performance
338+
//
339+
// For hostnames:
340+
// - If TLS is enabled: always requests FQDN for proper certificate validation
341+
// - If TLS is disabled: requests IP for better performance
342+
//
343+
// Internal vs External detection:
344+
// - For IPs: uses private IP range detection
345+
// - For hostnames: uses heuristics based on common internal naming patterns
337346
func DetectEndpointType(addr string, tlsEnabled bool) EndpointType {
338-
// Parse the address to determine if it's an IP or hostname
339-
isPrivate := isPrivateIP(addr)
347+
// Extract host from "host:port" format
348+
host, _, err := net.SplitHostPort(addr)
349+
if err != nil {
350+
host = addr // Assume no port
351+
}
340352

353+
// Check if the host is an IP address or hostname
354+
ip := net.ParseIP(host)
355+
isIPAddress := ip != nil
341356
var endpointType EndpointType
342357

343-
if tlsEnabled {
344-
// TLS requires FQDN for certificate validation
345-
if isPrivate {
346-
endpointType = EndpointTypeInternalFQDN
358+
if isIPAddress {
359+
// Address is an IP - determine if it's private or public
360+
isPrivate := ip.IsPrivate() || ip.IsLoopback() || ip.IsLinkLocalUnicast()
361+
362+
if tlsEnabled {
363+
// TLS with IP addresses - still prefer FQDN for certificate validation
364+
if isPrivate {
365+
endpointType = EndpointTypeInternalFQDN
366+
} else {
367+
endpointType = EndpointTypeExternalFQDN
368+
}
347369
} else {
348-
endpointType = EndpointTypeExternalFQDN
370+
// No TLS - can use IP addresses directly
371+
if isPrivate {
372+
endpointType = EndpointTypeInternalIP
373+
} else {
374+
endpointType = EndpointTypeExternalIP
375+
}
349376
}
350377
} else {
351-
// No TLS, can use IP addresses
352-
if isPrivate {
353-
endpointType = EndpointTypeInternalIP
378+
// Address is a hostname
379+
isInternalHostname := isInternalHostname(host)
380+
if isInternalHostname {
381+
endpointType = EndpointTypeInternalFQDN
354382
} else {
355-
endpointType = EndpointTypeExternalIP
383+
endpointType = EndpointTypeExternalFQDN
356384
}
357385
}
358386

359387
return endpointType
360388
}
361389

362-
// isPrivateIP checks if the given address is in a private IP range.
363-
func isPrivateIP(addr string) bool {
364-
// Extract host from "host:port" format
365-
host, _, err := net.SplitHostPort(addr)
366-
if err != nil {
367-
host = addr // Assume no port
390+
// isInternalHostname determines if a hostname appears to be internal/private.
391+
// This is a heuristic based on common naming patterns.
392+
func isInternalHostname(hostname string) bool {
393+
// Convert to lowercase for comparison
394+
hostname = strings.ToLower(hostname)
395+
396+
// Common internal hostname patterns
397+
internalPatterns := []string{
398+
"localhost",
399+
".local",
400+
".internal",
401+
".corp",
402+
".lan",
403+
".intranet",
404+
".private",
368405
}
369406

370-
ip := net.ParseIP(host)
371-
if ip == nil {
372-
return false // Not an IP address (likely hostname)
407+
// Check for exact match or suffix match
408+
for _, pattern := range internalPatterns {
409+
if hostname == pattern || strings.HasSuffix(hostname, pattern) {
410+
return true
411+
}
412+
}
413+
414+
// Check for RFC 1918 style hostnames (e.g., redis-1, db-server, etc.)
415+
// If hostname doesn't contain dots, it's likely internal
416+
if !strings.Contains(hostname, ".") {
417+
return true
373418
}
374419

375-
// Check for private/loopback ranges
376-
return ip.IsPrivate() || ip.IsLoopback() || ip.IsLinkLocalUnicast()
420+
// Default to external for fully qualified domain names
421+
return false
377422
}

hitless/hitless_manager.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@ import (
1313
"github.com/redis/go-redis/v9/internal/pool"
1414
)
1515

16-
17-
1816
// Push notification type constants for hitless upgrades
1917
const (
2018
NotificationMoving = "MOVING"
@@ -297,3 +295,9 @@ func (hm *HitlessManager) createPoolHook(baseDialer func(context.Context, string
297295

298296
return hm.poolHooksRef
299297
}
298+
299+
func (hm *HitlessManager) AddNotificationHook(notificationHook NotificationHook) {
300+
hm.hooksMu.Lock()
301+
defer hm.hooksMu.Unlock()
302+
hm.hooks = append(hm.hooks, notificationHook)
303+
}

hitless/hooks.go

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -22,27 +22,14 @@ func (lh *LoggingHook) PreHook(ctx context.Context, notificationType string, not
2222
// PostHook logs the result after processing.
2323
func (lh *LoggingHook) PostHook(ctx context.Context, notificationType string, notification []interface{}, result error) {
2424
if result != nil && lh.LogLevel >= 1 { // Warning level
25-
internal.Logger.Printf(ctx, "hitless: %s notification processing failed: %v", notificationType, result)
25+
internal.Logger.Printf(ctx, "hitless: %s notification processing failed: %v - %v", notificationType, result, notification)
2626
} else if lh.LogLevel >= 3 { // Debug level
2727
internal.Logger.Printf(ctx, "hitless: %s notification processed successfully", notificationType)
2828
}
2929
}
3030

31-
// FilterHook is an example hook that can filter out certain notifications.
32-
type FilterHook struct {
33-
BlockedTypes map[string]bool
34-
}
35-
36-
// PreHook filters notifications based on type.
37-
func (fh *FilterHook) PreHook(ctx context.Context, notificationType string, notification []interface{}) ([]interface{}, bool) {
38-
if fh.BlockedTypes[notificationType] {
39-
internal.Logger.Printf(ctx, "hitless: filtering out %s notification", notificationType)
40-
return notification, false // Skip processing
41-
}
42-
return notification, true
43-
}
44-
45-
// PostHook does nothing for filter hook.
46-
func (fh *FilterHook) PostHook(ctx context.Context, notificationType string, notification []interface{}, result error) {
47-
// No post-processing needed for filter hook
31+
// NewLoggingHook creates a new logging hook with the specified log level.
32+
// Log levels: 0=errors, 1=warnings, 2=info, 3=debug
33+
func NewLoggingHook(logLevel int) *LoggingHook {
34+
return &LoggingHook{LogLevel: logLevel}
4835
}

hitless/notification_handler.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,13 @@ type NotificationHandler struct {
1919
// HandlePushNotification processes push notifications with hook support.
2020
func (snh *NotificationHandler) HandlePushNotification(ctx context.Context, handlerCtx push.NotificationHandlerContext, notification []interface{}) error {
2121
if len(notification) == 0 {
22+
internal.Logger.Printf(ctx, "hitless: invalid notification format: %v", notification)
2223
return ErrInvalidNotification
2324
}
2425

2526
notificationType, ok := notification[0].(string)
2627
if !ok {
28+
internal.Logger.Printf(ctx, "hitless: invalid notification type format: %v", notification[0])
2729
return ErrInvalidNotification
2830
}
2931

@@ -60,16 +62,19 @@ func (snh *NotificationHandler) HandlePushNotification(ctx context.Context, hand
6062
// ["MOVING", seqNum, timeS, endpoint] - per-connection handoff
6163
func (snh *NotificationHandler) handleMoving(ctx context.Context, handlerCtx push.NotificationHandlerContext, notification []interface{}) error {
6264
if len(notification) < 3 {
65+
internal.Logger.Printf(ctx, "hitless: invalid MOVING notification: %v", notification)
6366
return ErrInvalidNotification
6467
}
6568
seqID, ok := notification[1].(int64)
6669
if !ok {
70+
internal.Logger.Printf(ctx, "hitless: invalid seqID in MOVING notification: %v", notification[1])
6771
return ErrInvalidNotification
6872
}
6973

7074
// Extract timeS
7175
timeS, ok := notification[2].(int64)
7276
if !ok {
77+
internal.Logger.Printf(ctx, "hitless: invalid timeS in MOVING notification: %v", notification[2])
7378
return ErrInvalidNotification
7479
}
7580

@@ -78,13 +83,15 @@ func (snh *NotificationHandler) handleMoving(ctx context.Context, handlerCtx pus
7883
// Extract new endpoint
7984
newEndpoint, ok = notification[3].(string)
8085
if !ok {
86+
internal.Logger.Printf(ctx, "hitless: invalid newEndpoint in MOVING notification: %v", notification[3])
8187
return ErrInvalidNotification
8288
}
8389
}
8490

8591
// Get the connection that received this notification
8692
conn := handlerCtx.Conn
8793
if conn == nil {
94+
internal.Logger.Printf(ctx, "hitless: no connection in handler context for MOVING notification")
8895
return ErrInvalidNotification
8996
}
9097

@@ -95,6 +102,7 @@ func (snh *NotificationHandler) handleMoving(ctx context.Context, handlerCtx pus
95102
} else if pc, ok := conn.(*pool.Conn); ok {
96103
poolConn = pc
97104
} else {
105+
internal.Logger.Printf(ctx, "hitless: invalid connection type in handler context for MOVING notification - %T %#v", conn, handlerCtx)
98106
return ErrInvalidNotification
99107
}
100108

@@ -145,17 +153,20 @@ func (snh *NotificationHandler) handleMigrating(ctx context.Context, handlerCtx
145153
// MIGRATING notifications indicate that a connection is about to be migrated
146154
// Apply relaxed timeouts to the specific connection that received this notification
147155
if len(notification) < 2 {
156+
internal.Logger.Printf(ctx, "hitless: invalid MIGRATING notification: %v", notification)
148157
return ErrInvalidNotification
149158
}
150159

151160
// Get the connection from handler context and type assert to connectionAdapter
152161
if handlerCtx.Conn == nil {
162+
internal.Logger.Printf(ctx, "hitless: no connection in handler context for MIGRATING notification")
153163
return ErrInvalidNotification
154164
}
155165

156166
// Type assert to connectionAdapter which implements ConnectionWithRelaxedTimeout
157167
connAdapter, ok := handlerCtx.Conn.(interfaces.ConnectionWithRelaxedTimeout)
158168
if !ok {
169+
internal.Logger.Printf(ctx, "hitless: invalid connection type in handler context for MIGRATING notification")
159170
return ErrInvalidNotification
160171
}
161172

@@ -169,17 +180,20 @@ func (snh *NotificationHandler) handleMigrated(ctx context.Context, handlerCtx p
169180
// MIGRATED notifications indicate that a connection migration has completed
170181
// Restore normal timeouts for the specific connection that received this notification
171182
if len(notification) < 2 {
183+
internal.Logger.Printf(ctx, "hitless: invalid MIGRATED notification: %v", notification)
172184
return ErrInvalidNotification
173185
}
174186

175187
// Get the connection from handler context and type assert to connectionAdapter
176188
if handlerCtx.Conn == nil {
189+
internal.Logger.Printf(ctx, "hitless: no connection in handler context for MIGRATED notification")
177190
return ErrInvalidNotification
178191
}
179192

180193
// Type assert to connectionAdapter which implements ConnectionWithRelaxedTimeout
181194
connAdapter, ok := handlerCtx.Conn.(interfaces.ConnectionWithRelaxedTimeout)
182195
if !ok {
196+
internal.Logger.Printf(ctx, "hitless: invalid connection type in handler context for MIGRATED notification")
183197
return ErrInvalidNotification
184198
}
185199

@@ -193,17 +207,20 @@ func (snh *NotificationHandler) handleFailingOver(ctx context.Context, handlerCt
193207
// FAILING_OVER notifications indicate that a connection is about to failover
194208
// Apply relaxed timeouts to the specific connection that received this notification
195209
if len(notification) < 2 {
210+
internal.Logger.Printf(ctx, "hitless: invalid FAILING_OVER notification: %v", notification)
196211
return ErrInvalidNotification
197212
}
198213

199214
// Get the connection from handler context and type assert to connectionAdapter
200215
if handlerCtx.Conn == nil {
216+
internal.Logger.Printf(ctx, "hitless: no connection in handler context for FAILING_OVER notification")
201217
return ErrInvalidNotification
202218
}
203219

204220
// Type assert to connectionAdapter which implements ConnectionWithRelaxedTimeout
205221
connAdapter, ok := handlerCtx.Conn.(interfaces.ConnectionWithRelaxedTimeout)
206222
if !ok {
223+
internal.Logger.Printf(ctx, "hitless: invalid connection type in handler context for FAILING_OVER notification")
207224
return ErrInvalidNotification
208225
}
209226

@@ -217,17 +234,20 @@ func (snh *NotificationHandler) handleFailedOver(ctx context.Context, handlerCtx
217234
// FAILED_OVER notifications indicate that a connection failover has completed
218235
// Restore normal timeouts for the specific connection that received this notification
219236
if len(notification) < 2 {
237+
internal.Logger.Printf(ctx, "hitless: invalid FAILED_OVER notification: %v", notification)
220238
return ErrInvalidNotification
221239
}
222240

223241
// Get the connection from handler context and type assert to connectionAdapter
224242
if handlerCtx.Conn == nil {
243+
internal.Logger.Printf(ctx, "hitless: no connection in handler context for FAILED_OVER notification")
225244
return ErrInvalidNotification
226245
}
227246

228247
// Type assert to connectionAdapter which implements ConnectionWithRelaxedTimeout
229248
connAdapter, ok := handlerCtx.Conn.(interfaces.ConnectionWithRelaxedTimeout)
230249
if !ok {
250+
internal.Logger.Printf(ctx, "hitless: invalid connection type in handler context for FAILED_OVER notification")
231251
return ErrInvalidNotification
232252
}
233253

0 commit comments

Comments
 (0)