feat(home): add support for disabling cluster discovery in Redis configuration

This commit is contained in:
hkfires
2026-05-16 20:25:29 +08:00
parent 48104abf51
commit 644d5ea618
8 changed files with 96 additions and 6 deletions
+2 -1
View File
@@ -76,10 +76,11 @@ func parseHomeURLConfig(rawAddr string, password string) (config.HomeConfig, err
Port: port, Port: port,
Password: password, Password: password,
} }
query := parsed.Query()
homeCfg.DisableClusterDiscovery = parseHomeBoolQuery(query, "disable-cluster-discovery", "disable_cluster_discovery")
if scheme == "rediss" { if scheme == "rediss" {
homeCfg.TLS.Enable = true homeCfg.TLS.Enable = true
query := parsed.Query()
homeCfg.TLS.ServerName = strings.TrimSpace(firstHomeQueryValue(query, "server-name", "server_name")) homeCfg.TLS.ServerName = strings.TrimSpace(firstHomeQueryValue(query, "server-name", "server_name"))
homeCfg.TLS.InsecureSkipVerify = parseHomeBoolQuery(query, "insecure-skip-verify", "insecure_skip_verify", "skip_verify") homeCfg.TLS.InsecureSkipVerify = parseHomeBoolQuery(query, "insecure-skip-verify", "insecure_skip_verify", "skip_verify")
homeCfg.TLS.CACert = strings.TrimSpace(firstHomeQueryValue(query, "ca-cert", "ca_cert")) homeCfg.TLS.CACert = strings.TrimSpace(firstHomeQueryValue(query, "ca-cert", "ca_cert"))
+11
View File
@@ -64,3 +64,14 @@ func TestParseHomeFlagConfigPasswordFlagOverridesURLPassword(t *testing.T) {
t.Fatalf("Password = %q, want flag-secret", cfg.Password) t.Fatalf("Password = %q, want flag-secret", cfg.Password)
} }
} }
func TestParseHomeFlagConfigDisableClusterDiscovery(t *testing.T) {
cfg, err := parseHomeFlagConfig("redis://home.example.com:8327?disable-cluster-discovery=true", "")
if err != nil {
t.Fatalf("parseHomeFlagConfig() error = %v", err)
}
if !cfg.DisableClusterDiscovery {
t.Fatal("DisableClusterDiscovery = false, want true")
}
}
+5
View File
@@ -73,6 +73,7 @@ func main() {
var password string var password string
var homeAddr string var homeAddr string
var homePassword string var homePassword string
var homeDisableClusterDiscovery bool
var tuiMode bool var tuiMode bool
var standalone bool var standalone bool
var localModel bool var localModel bool
@@ -93,6 +94,7 @@ func main() {
flag.StringVar(&password, "password", "", "") flag.StringVar(&password, "password", "", "")
flag.StringVar(&homeAddr, "home", "", "Home control plane address in host:port, redis://host:port, or rediss://host:port format (loads config from home and skips local config file)") flag.StringVar(&homeAddr, "home", "", "Home control plane address in host:port, redis://host:port, or rediss://host:port format (loads config from home and skips local config file)")
flag.StringVar(&homePassword, "home-password", "", "Home control plane password (Redis AUTH)") flag.StringVar(&homePassword, "home-password", "", "Home control plane password (Redis AUTH)")
flag.BoolVar(&homeDisableClusterDiscovery, "home-disable-cluster-discovery", false, "Disable Home CLUSTER NODES discovery and keep using the configured -home address")
flag.BoolVar(&tuiMode, "tui", false, "Start with terminal management UI") flag.BoolVar(&tuiMode, "tui", false, "Start with terminal management UI")
flag.BoolVar(&standalone, "standalone", false, "In TUI mode, start an embedded local server") flag.BoolVar(&standalone, "standalone", false, "In TUI mode, start an embedded local server")
flag.BoolVar(&localModel, "local-model", false, "Use embedded model catalog only, skip remote model fetching") flag.BoolVar(&localModel, "local-model", false, "Use embedded model catalog only, skip remote model fetching")
@@ -250,6 +252,9 @@ func main() {
log.Errorf("invalid -home address %q: %v", homeAddr, errHomeCfg) log.Errorf("invalid -home address %q: %v", homeAddr, errHomeCfg)
return return
} }
if homeDisableClusterDiscovery {
homeCfg.DisableClusterDiscovery = true
}
homeClient := home.New(homeCfg) homeClient := home.New(homeCfg)
defer homeClient.Close() defer homeClient.Close()
+3
View File
@@ -17,6 +17,9 @@ home:
host: "127.0.0.1" host: "127.0.0.1"
port: 6379 port: 6379
password: "" password: ""
# Keep CPA pinned to the configured home address instead of switching to CLUSTER NODES entries.
# Useful when Home is behind NAT, Docker networking, or a reverse proxy.
disable-cluster-discovery: false
# Optional TLS for the outbound Redis connection to the home control plane. # Optional TLS for the outbound Redis connection to the home control plane.
# Enable this when connecting through rediss:// or an SSL stream proxy. # Enable this when connecting through rediss:// or an SSL stream proxy.
tls: tls:
+6 -5
View File
@@ -2,11 +2,12 @@ package config
// HomeConfig configures the optional "home" control plane integration over Redis protocol. // HomeConfig configures the optional "home" control plane integration over Redis protocol.
type HomeConfig struct { type HomeConfig struct {
Enabled bool `yaml:"enabled" json:"enabled"` Enabled bool `yaml:"enabled" json:"enabled"`
Host string `yaml:"host" json:"-"` Host string `yaml:"host" json:"-"`
Port int `yaml:"port" json:"-"` Port int `yaml:"port" json:"-"`
Password string `yaml:"password" json:"-"` Password string `yaml:"password" json:"-"`
TLS HomeTLSConfig `yaml:"tls" json:"-"` DisableClusterDiscovery bool `yaml:"disable-cluster-discovery" json:"-"`
TLS HomeTLSConfig `yaml:"tls" json:"-"`
} }
// HomeTLSConfig configures client-side TLS for the home Redis connection. // HomeTLSConfig configures client-side TLS for the home Redis connection.
+4
View File
@@ -9,6 +9,7 @@ home:
host: home.example.com host: home.example.com
port: 444 port: 444
password: secret password: secret
disable-cluster-discovery: true
tls: tls:
enable: true enable: true
server-name: home.example.com server-name: home.example.com
@@ -31,6 +32,9 @@ home:
if cfg.Home.Password != "secret" { if cfg.Home.Password != "secret" {
t.Fatalf("Home.Password = %q, want secret", cfg.Home.Password) t.Fatalf("Home.Password = %q, want secret", cfg.Home.Password)
} }
if !cfg.Home.DisableClusterDiscovery {
t.Fatal("Home.DisableClusterDiscovery = false, want true")
}
if !cfg.Home.TLS.Enable { if !cfg.Home.TLS.Enable {
t.Fatal("Home.TLS.Enable = false, want true") t.Fatal("Home.TLS.Enable = false, want true")
} }
+23
View File
@@ -265,7 +265,23 @@ func (c *Client) Ping(ctx context.Context) error {
return cmd.Ping(ctx).Err() return cmd.Ping(ctx).Err()
} }
func (c *Client) clusterDiscoveryEnabled() bool {
if c == nil {
return false
}
c.mu.Lock()
defer c.mu.Unlock()
return c.clusterDiscoveryEnabledLocked()
}
func (c *Client) clusterDiscoveryEnabledLocked() bool {
return !c.homeCfg.DisableClusterDiscovery
}
func (c *Client) refreshBestClusterNode(ctx context.Context) { func (c *Client) refreshBestClusterNode(ctx context.Context) {
if !c.clusterDiscoveryEnabled() {
return
}
switched, errRefresh := c.refreshClusterNodes(ctx) switched, errRefresh := c.refreshClusterNodes(ctx)
if errRefresh != nil { if errRefresh != nil {
log.Debugf("home cluster nodes unavailable: %v", errRefresh) log.Debugf("home cluster nodes unavailable: %v", errRefresh)
@@ -279,6 +295,9 @@ func (c *Client) refreshBestClusterNode(ctx context.Context) {
} }
func (c *Client) refreshClusterNodes(ctx context.Context) (bool, error) { func (c *Client) refreshClusterNodes(ctx context.Context) (bool, error) {
if !c.clusterDiscoveryEnabled() {
return false, nil
}
if ctx == nil { if ctx == nil {
ctx = context.Background() ctx = context.Background()
} }
@@ -353,6 +372,10 @@ func (c *Client) failoverAfterReconnectFailure() (bool, string) {
c.mu.Lock() c.mu.Lock()
defer c.mu.Unlock() defer c.mu.Unlock()
if !c.clusterDiscoveryEnabledLocked() {
c.reconnectFailures = 0
return false, ""
}
c.reconnectFailures++ c.reconnectFailures++
if c.reconnectFailures < homeReconnectFailoverThreshold { if c.reconnectFailures < homeReconnectFailoverThreshold {
return false, "" return false, ""
+42
View File
@@ -1,6 +1,7 @@
package home package home
import ( import (
"context"
"crypto/tls" "crypto/tls"
"encoding/json" "encoding/json"
"net/http" "net/http"
@@ -115,3 +116,44 @@ func TestRedisOptionsHomeTLSEnabledUsesExplicitServerName(t *testing.T) {
t.Fatal("InsecureSkipVerify = false, want true") t.Fatal("InsecureSkipVerify = false, want true")
} }
} }
func TestRefreshClusterNodesDisabledSkipsRedisCommand(t *testing.T) {
client := New(config.HomeConfig{
Enabled: true,
Host: "127.0.0.1",
Port: 1,
DisableClusterDiscovery: true,
})
switched, err := client.refreshClusterNodes(context.Background())
if err != nil {
t.Fatalf("refreshClusterNodes() error = %v", err)
}
if switched {
t.Fatal("refreshClusterNodes() switched = true, want false")
}
if client.cmd != nil || client.sub != nil {
t.Fatalf("redis clients were initialized when cluster discovery was disabled")
}
}
func TestFailoverAfterReconnectFailureDisabledDoesNotSwitchToClusterNode(t *testing.T) {
client := New(config.HomeConfig{
Enabled: true,
Host: "seed.example.com",
Port: 8327,
DisableClusterDiscovery: true,
})
client.mu.Lock()
client.clusterNodes = []clusterNode{{IP: "other.example.com", Port: 8327}}
client.reconnectFailures = homeReconnectFailoverThreshold - 1
client.mu.Unlock()
switched, addr := client.failoverAfterReconnectFailure()
if switched {
t.Fatalf("failoverAfterReconnectFailure() switched to %s, want no switch", addr)
}
if got, _ := client.addr(); got != "seed.example.com:8327" {
t.Fatalf("addr() = %q, want seed.example.com:8327", got)
}
}