Skip to content

Commit 20cb87e

Browse files
committed
wip
1 parent de641c5 commit 20cb87e

14 files changed

+676
-98
lines changed

adapters.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ var ErrInvalidCommand = errors.New("invalid command type")
1919
var ErrInvalidPool = errors.New("invalid pool type")
2020

2121
// NewClientAdapter creates a new client adapter for regular Redis clients.
22-
func NewClientAdapter(client *Client) interfaces.ClientInterface {
22+
func NewClientAdapter(client *baseClient) interfaces.ClientInterface {
2323
return &clientAdapter{client: client}
2424
}
2525

@@ -30,7 +30,7 @@ func NewClusterClientAdapter(client interface{}) interfaces.ClientInterface {
3030

3131
// clientAdapter adapts a Redis client to implement interfaces.ClientInterface.
3232
type clientAdapter struct {
33-
client *Client
33+
client *baseClient
3434
}
3535

3636
// GetOptions returns the client options.

example/pubsub/main.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"time"
99

1010
"github.com/redis/go-redis/v9"
11+
"github.com/redis/go-redis/v9/hitless"
1112
)
1213

1314
var ctx = context.Background()
@@ -19,7 +20,9 @@ func main() {
1920
wg := &sync.WaitGroup{}
2021
rdb := redis.NewClient(&redis.Options{
2122
Addr: ":6379",
22-
HitlessUpgrades: true,
23+
HitlessUpgradeConfig: &redis.HitlessUpgradeConfig{
24+
Enabled: hitless.MaintNotificationsEnabled,
25+
},
2326
})
2427
_ = rdb.FlushDB(ctx).Err()
2528

hitless/config.go

Lines changed: 73 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,43 @@ import (
44
"time"
55
)
66

7+
// Constants for maintenance push notifications modes
8+
const (
9+
MaintNotificationsDisabled = "disabled" // Client doesn't send CLIENT MAINT_NOTIFICATIONS ON command
10+
MaintNotificationsEnabled = "enabled" // Client forcefully sends command, interrupts connection on error
11+
MaintNotificationsAuto = "auto" // Client tries to send command, disables feature on error
12+
)
13+
14+
// Constants for endpoint types
15+
const (
16+
EndpointTypeAuto = "auto" // Auto-detect based on connection
17+
EndpointTypeInternalIP = "internal-ip" // Internal IP address
18+
EndpointTypeInternalFQDN = "internal-fqdn" // Internal FQDN
19+
EndpointTypeExternalIP = "external-ip" // External IP address
20+
EndpointTypeExternalFQDN = "external-fqdn" // External FQDN
21+
EndpointTypeNone = "none" // No endpoint (reconnect with current config)
22+
)
23+
724
// Config provides configuration options for hitless upgrades.
825
type Config struct {
9-
// Enabled controls whether hitless upgrades are enabled.
10-
// Requires RESP3 protocol for push notifications.
11-
// Default: false
12-
Enabled bool
26+
// Enabled controls how client maintenance notifications are handled.
27+
// Valid values: MaintNotificationsDisabled, MaintNotificationsEnabled, MaintNotificationsAuto
28+
// - disabled: The client doesn't send CLIENT MAINT_NOTIFICATIONS ON command
29+
// - enabled: The client forcefully sends the command, interrupts connection on error
30+
// - auto: The client tries to send the command, disables feature on error
31+
// Default: MaintNotificationsAuto ("auto")
32+
Enabled string
1333

1434
// EndpointType specifies the type of endpoint to request in MOVING notifications.
15-
// Valid values: "internal-ip", "internal-fqdn", "external-ip", "external-fqdn", "none"
16-
// If empty, the client will auto-detect based on connection settings.
17-
// Default: "" (auto-detect)
35+
// Valid values: EndpointTypeAuto, EndpointTypeInternalIP, EndpointTypeInternalFQDN,
36+
// EndpointTypeExternalIP, EndpointTypeExternalFQDN, EndpointTypeNone
37+
// - auto: Auto-detect based on connection (private IP → internal, TLS → FQDN)
38+
// - internal-ip: Enforce requesting the internal IP
39+
// - internal-fqdn: Enforce requesting the internal FQDN
40+
// - external-ip: Enforce requesting the external IP address
41+
// - external-fqdn: Enforce requesting the external FQDN
42+
// - none: Request null endpoint (reconnect based on current config)
43+
// Default: EndpointTypeAuto ("auto")
1844
EndpointType string
1945

2046
// RelaxedTimeout is the concrete timeout value to use during
@@ -70,17 +96,17 @@ type Config struct {
7096
// DefaultConfig returns a Config with sensible defaults.
7197
func DefaultConfig() *Config {
7298
return &Config{
73-
Enabled: false,
74-
EndpointType: "", // Auto-detect
75-
RelaxedTimeout: 30 * time.Second,
76-
RelaxedTimeoutDuration: 30 * time.Second,
77-
HandoffTimeout: 15 * time.Second,
78-
MinWorkers: 0, // Auto-calculated based on pool size
79-
MaxWorkers: 0, // Auto-calculated based on pool size
80-
HandoffQueueSize: 0, // Auto-calculated based on max workers
99+
Enabled: MaintNotificationsAuto, // Enable by default for Redis Cloud
100+
EndpointType: EndpointTypeAuto, // Auto-detect based on connection
101+
RelaxedTimeout: 30 * time.Second,
102+
RelaxedTimeoutDuration: 30 * time.Second,
103+
HandoffTimeout: 15 * time.Second,
104+
MinWorkers: 0, // Auto-calculated based on pool size
105+
MaxWorkers: 0, // Auto-calculated based on pool size
106+
HandoffQueueSize: 0, // Auto-calculated based on max workers
81107
PostHandoffRelaxedDuration: 10 * time.Second,
82-
ScaleDownDelay: 2 * time.Second,
83-
LogLevel: 1,
108+
ScaleDownDelay: 2 * time.Second,
109+
LogLevel: 1,
84110
}
85111
}
86112

@@ -111,15 +137,25 @@ func (c *Config) Validate() error {
111137
return ErrInvalidLogLevel
112138
}
113139

140+
// Validate Enabled (maintenance notifications mode)
141+
validMaintNotifications := map[string]bool{
142+
MaintNotificationsDisabled: true,
143+
MaintNotificationsEnabled: true,
144+
MaintNotificationsAuto: true,
145+
}
146+
if !validMaintNotifications[c.Enabled] {
147+
return ErrInvalidMaintNotifications
148+
}
149+
150+
// Validate EndpointType
114151
validEndpointTypes := map[string]bool{
115-
"": true, // Auto-detect
116-
"internal-ip": true,
117-
"internal-fqdn": true,
118-
"external-ip": true,
119-
"external-fqdn": true,
120-
"none": true,
152+
EndpointTypeAuto: true,
153+
EndpointTypeInternalIP: true,
154+
EndpointTypeInternalFQDN: true,
155+
EndpointTypeExternalIP: true,
156+
EndpointTypeExternalFQDN: true,
157+
EndpointTypeNone: true,
121158
}
122-
123159
if !validEndpointTypes[c.EndpointType] {
124160
return ErrInvalidEndpointType
125161
}
@@ -142,9 +178,19 @@ func (c *Config) ApplyDefaultsWithPoolSize(poolSize int) *Config {
142178
}
143179

144180
defaults := DefaultConfig()
145-
result := &Config{
146-
Enabled: c.Enabled, // boolean, no default needed
147-
EndpointType: c.EndpointType, // string, empty is valid (auto-detect)
181+
result := &Config{}
182+
183+
// Apply defaults for string fields (empty means not set)
184+
if c.Enabled == "" {
185+
result.Enabled = defaults.Enabled
186+
} else {
187+
result.Enabled = c.Enabled
188+
}
189+
190+
if c.EndpointType == "" {
191+
result.EndpointType = defaults.EndpointType
192+
} else {
193+
result.EndpointType = c.EndpointType
148194
}
149195

150196
// Apply defaults for duration fields (zero means not set)

hitless/errors.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@ var (
1313
ErrInvalidWorkerRange = errors.New("hitless: MaxWorkers must be greater than or equal to MinWorkers")
1414
ErrInvalidHandoffQueueSize = errors.New("hitless: handoff queue size must be greater than 0")
1515
ErrInvalidPostHandoffRelaxedDuration = errors.New("hitless: post-handoff relaxed duration must be greater than or equal to 0")
16-
ErrInvalidLogLevel = errors.New("hitless: log level must be between 0 and 3")
17-
ErrInvalidEndpointType = errors.New("hitless: invalid endpoint type")
16+
ErrInvalidLogLevel = errors.New("hitless: log level must be between 0 and 3")
17+
ErrInvalidEndpointType = errors.New("hitless: invalid endpoint type")
18+
ErrInvalidMaintNotifications = errors.New("hitless: invalid maintenance notifications setting (must be 'disabled', 'enabled', or 'auto')")
1819
)
1920

2021
// Integration errors

hitless/example_config_usage.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ import (
77
// ExampleCustomConfig shows how to create a custom hitless configuration
88
func ExampleCustomConfig() *Config {
99
return &Config{
10-
Enabled: true,
11-
EndpointType: "internal-ip",
10+
Enabled: MaintNotificationsEnabled,
11+
EndpointType: EndpointTypeInternalIP,
1212
RelaxedTimeout: 45 * time.Second,
1313
RelaxedTimeoutDuration: 45 * time.Second,
1414
HandoffTimeout: 20 * time.Second,
@@ -22,8 +22,8 @@ func ExampleCustomConfig() *Config {
2222
// ExampleLowResourceConfig shows a configuration for resource-constrained environments
2323
func ExampleLowResourceConfig() *Config {
2424
return &Config{
25-
Enabled: true,
26-
EndpointType: "internal-ip",
25+
Enabled: MaintNotificationsEnabled,
26+
EndpointType: EndpointTypeInternalIP,
2727
RelaxedTimeout: 20 * time.Second,
2828
RelaxedTimeoutDuration: 20 * time.Second,
2929
HandoffTimeout: 10 * time.Second,
@@ -37,8 +37,8 @@ func ExampleLowResourceConfig() *Config {
3737
// ExampleHighThroughputConfig shows a configuration for high-throughput scenarios
3838
func ExampleHighThroughputConfig() *Config {
3939
return &Config{
40-
Enabled: true,
41-
EndpointType: "internal-ip",
40+
Enabled: MaintNotificationsEnabled,
41+
EndpointType: EndpointTypeInternalIP,
4242
RelaxedTimeout: 60 * time.Second,
4343
RelaxedTimeoutDuration: 60 * time.Second,
4444
HandoffTimeout: 30 * time.Second,
@@ -54,7 +54,7 @@ func ExamplePartialConfig() *Config {
5454
// Only specify the fields you want to customize
5555
// Other fields will automatically get default values when ApplyDefaults() is called
5656
return &Config{
57-
Enabled: true,
57+
Enabled: MaintNotificationsEnabled,
5858
MinWorkers: 3, // Custom minimum worker count
5959
MaxWorkers: 15, // Custom maximum worker count
6060
LogLevel: 2, // Info level logging
@@ -69,7 +69,7 @@ func ExamplePartialConfig() *Config {
6969
func ExampleMinimalConfig() *Config {
7070
// Just enable hitless upgrades, everything else gets defaults
7171
return &Config{
72-
Enabled: true,
72+
Enabled: MaintNotificationsEnabled,
7373
// All other fields will get default values automatically
7474
}
7575
}

0 commit comments

Comments
 (0)