Skip to content

fix: SentinelCheckQuorum NOQUORUM dead code + MakeSlaveOfWithPort wrong port - #128

Open
Saremox wants to merge 1 commit into
mainfrom
claude/fix-sentinel-quorum-and-slaveof-port
Open

fix: SentinelCheckQuorum NOQUORUM dead code + MakeSlaveOfWithPort wrong port#128
Saremox wants to merge 1 commit into
mainfrom
claude/fix-sentinel-quorum-and-slaveof-port

Conversation

@Saremox

@Saremox Saremox commented Aug 23, 2026

Copy link
Copy Markdown
Owner

Summary

Two real bugs found and fixed in service/redis/client.go, both confirmed against real Redis/Sentinel 7.0.15 (and cross-checked against Valkey 7.2.12 in the investigation that surfaced them).

1. SentinelCheckQuorum's NOQUORUM handling was dead code

Real Sentinel's SENTINEL CKQUORUM reply for the NOQUORUM case comes back as a genuine RESP error reply, not a successful string reply whose text happens to start with "(error)". Since go-redis's CkQuorum surfaces that as cmd.Err(), the function's if err != nil { return err } branch fired immediately — the subsequent string-parsing code that checked for status == "(error)" && quorum == "NOQUORUM" could never execute, since res is only non-empty when err is nil. Callers still correctly saw a non-nil error (the raw driver error happens to mention NOQUORUM too), but the intended "quorum Not available" message and NOQUORUM metrics tag never fired.

Fix: NOQUORUM is now classified directly from err.Error() (which real Sentinel prefixes with "NOQUORUM ...") before the now-simplified success-path parsing. Verified against a real Sentinel process — TestSentinelCheckQuorum_NoQuorum now asserts the exact restored message.

2. MakeSlaveOfWithPort connected to the target using the master's port

MakeSlaveOfWithPort(ip, masterIP, masterPort, password) dialed the target Redis instance at net.JoinHostPort(ip, masterPort) — using the master's port to reach the target, with no parameter for the target's own port. This only worked by coincidence: every caller except SetExternalMasterOnAll passes the same port for both target and master (both come from spec.redis.port on the same RedisFailover). SetExternalMasterOnAll's externally-supplied Bootstrapping master port (spec.bootstrapNode.port) can genuinely differ from spec.redis.port — in which case the client silently connected to the wrong port (or, if target and master share an IP, ended up issuing SLAVEOF to the master itself, which Redis accepts without error) instead of failing loudly.

Fix: MakeSlaveOfWithPort now takes an explicit port for the target, separate from masterPort. Updated the interface, all four call sites in heal.go (SetOldestAsMaster, SetMasterOnAll, SetExternalMasterOnAll, PromoteBestReplica), the MakeSlaveOf convenience wrapper, the generated mock, and all affected tests. Verified against two real, independently-addressable Redis instances on different ports — TestMakeSlaveOfWithPort_MismatchedTargetPort now asserts the target is correctly reconfigured and the master is left untouched.

Test plan

  • go build ./...
  • go vet ./...
  • go test ./... (full repo, including real redis-server/sentinel subprocess tests, ~17s)
  • golangci-lint run --no-config ./service/redis/... ./operator/redisfailover/... ./mocks/... — 0 issues
  • gofmt -l on all changed files — clean

Generated by Claude Code

…ng port

SentinelCheckQuorum: real Sentinel's CKQUORUM NOQUORUM outcome comes
back as a RESP error, not a successful string reply - so the intended
NOQUORUM handling (parsing "(error)"/"NOQUORUM" out of a successful
result) could never execute; callers only ever saw the raw driver
error. NOQUORUM is now classified from err.Error() directly, restoring
the intended "quorum Not available" message and NOQUORUM metrics tag.

MakeSlaveOfWithPort(ip, masterIP, masterPort, password) connected to
the *target* redis instance at (ip, masterPort) - using the master's
port to reach the target, with no parameter for the target's own port.
This only worked because every caller except SetExternalMasterOnAll
happens to pass the same port for target and master (both come from
this RedisFailover's own spec.redis.port). SetExternalMasterOnAll's
externally-supplied bootstrap master port can genuinely differ,
in which case the client silently connected to the wrong instance
(or, when target and master share an IP, issued SLAVEOF to the master
itself) instead of failing loudly. MakeSlaveOfWithPort now takes an
explicit port for the target, separate from masterPort; all call
sites, the mock, and MakeSlaveOf updated accordingly.

Both found and confirmed against real Redis/Sentinel 7.0.15 while
writing coverage tests for this file.
@codecov

codecov Bot commented Aug 23, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 92.30769% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
service/redis/client.go 87.50% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants