MOD-17518 Don't register internal commands as internal on enterprise - #122
Open
gabsow wants to merge 5 commits into
Open
MOD-17518 Don't register internal commands as internal on enterprise#122gabsow wants to merge 5 commits into
internal on enterprise#122gabsow wants to merge 5 commits into
Conversation
An enterprise shard receives its topology, and its credentials, from DMC through CLUSTERSET, and authenticates its inter-shard connections with the password carried in the `ADDR <pwd>@<ip>:<port>` entry. That produces a regular, non-internal connection. Since #98 an enterprise binary running with cluster-enabled=yes - which is every ASM-enabled database - is classified as OSS, so we also registered `<module>.HELLO` and friends with the `internal` command flag. Those two decisions contradict each other: the commands are then hidden from our own inter-shard connection, the HELLO handshake fails with Got bad hello response from <id> (<ip>:<port>), will try again in 1 second, ERR unknown command '<module>.HELLO'. and retries at 1Hz forever, so the cluster never forms and every coordinated command fails on the max-idle timeout. Observed on RedisTimeSeries ASM databases for ~30 minutes at a time, including HELLOs the shard sends to itself - proof the command is hidden rather than missing. `isOss` was answering two different questions. Keep it for the topology source, and decide the connection/visibility model separately from whether this is an enterprise binary: - register the commands as `_proxy-filtered` whenever rlec_version is present, regardless of the cluster mode, and as `internal` only when we will actually authenticate as an internal connection; - add `clusterCtx.useInternalConn` and use it for the auth mode in SendAuthCommandIfNeeded() and for whether MR_ClusterGetPassword() may drop the password, so the flags and the auth can no longer disagree; - prefer the internal secret over a CLUSTERSET password when the commands are internal, since a password cannot make the connection internal. The AUTH was also fire-and-forget, which is why half an hour of a broken handshake produced no diagnostic beyond `unknown command`; log a failed AUTH.
cluster-enabled is not a property of an enterprise deployment; it is set per database, for the OSS cluster API and/or for ASM. ASM is one way to reach the broken combination, not the only one - a database with the OSS cluster API enabled reaches it too, without any ASM involved. No behaviour change.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 86c8e8a. Configure here.
…sword Bugbot caught a regression in the previous commit: keying the auth mode off the same flag as the command flags meant an enterprise binary never sent the internal-secret AUTH, and with no CLUSTERSET password it sent no AUTH at all. That is exactly the env0 case #98 added (rlec_version present with cluster-enabled=yes, no DMC), which used to authenticate with the internal secret; under requirepass it would now fail the handshake with NOAUTH and never recover. Only the contradictory combination needed changing, not the credential preference. Restore the original order and add the reason it exists: - commands registered `internal` -> internal secret, since a password cannot make the connection internal; - otherwise a CLUSTERSET password if we have one; - otherwise the internal secret when available, because the server may still require us to authenticate and it is the one credential we always have. Rename the flag to `commandsAreInternal`, which is what it actually decides - the previous name invited exactly this conflation. Drop the auth mode from the failed-AUTH log line, which can no longer be derived from the flag alone.
Half the added lines were comment. Keep only what is not evident from the code - why the flag is not keyed off isOss, and why a password is no use on an internal-only command set - and drop the prose around it. No behaviour change.
LibMR's own test suite crashed the module on redis unstable:
# === ASSERTION FAILED ===
# ==> cluster.c:441 'secret' is not true
RedisModule_GetInternalSecret can be present and still return NULL - a cluster
wired up by CLUSTERSET alone, which is exactly what the test suite and a Redis
Enterprise database both are, never gets a secret to share. The previous commit
made that path reachable whenever the commands are internal, where before it
was only reached with no password, so the pre-existing assert started firing.
Preferring the internal secret is still right when our commands are internal,
but it has to be able to fail: TrySendInternalSecretAuth() returns false when
there is no secret and we fall through to the password, which is the best
credential we have left. That also removes the assert as a crash vector on a
secretless deployment, which it was on master too.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Problem
On an Enterprise database whose shards run in cluster mode, the libmr cluster never forms. Both masters sit in this loop at 1 Hz for as long as the database lives:
Every coordinated command then dies on the max-idle timeout, which is what fails
test_asm_scale_in_out_with_workload(see MOD-17016).The command is hidden, not missing. In the support packages the initiator gets
unknown commandfrom its own ip:port — LibMR deliberately connects to its own node for internal-function fan-out (cluster.c:281+ the lazy connect atcluster.c:252), so the process replying "unknown command" is the process that registered the command.Cause
isOssis answering two unrelated questions at once — where does the topology come from and how do we authenticate / which commands are visible to us.Since #98, an Enterprise binary running with
cluster-enabled=yesis classified as OSS. That is correct for the topology source (it must read the native cluster view), but it also selected theinternalcommand flag atcluster.c:1902. Meanwhile an Enterprise shard authenticates with the password DMC puts in the long-form CLUSTERSET (ADDR <pwd>@<ip>:<port>), andSendAuthCommandIfNeeded()returns as soon as it has used it — so the connection is a regular one, andinternal-flagged commands are invisible on it. The retry path re-sends the same password, so it never recovers. LibMR's own comment atcluster.c:458-461already describes this outcome.Which databases are affected
cluster-enabledis per database on Enterprise, not a property of the deployment: RE sets it for the OSS cluster API and/or for ASM. So the trigger is "this database's shards run in cluster mode", and ASM is one route to it, not the only one — a database with the OSS cluster API enabled reaches the same broken combination with no ASM involved.The support packages I have only contain the ASM route (
oss_cluster=disabled,redis_cluster_enabled=enabled), so that is the only one directly observed; the OSS-cluster-API route follows from the same code path and is worth an explicit test.The correlation in one package is exact: 4 of 344 shard processes log
Running mode=cluster→ exactly the ASM database's shards → exactly the only ones loggingDetected redis oss (cluster-enabled=yes)→ exactly the only ones with the HELLO storm. The other 340 logDetected redis enterprise (cluster-enabled=no)and are healthy.Affected pins:
v8.8.2(a37b672) andmaster(c6a5ed6) include #98;v8.8.1(44d5025) does not and is clean.Change
Keep
isOssfor the topology source, and decide the connection/visibility model from whether this is an Enterprise binary:_proxy-filteredwheneverrlec_versionis present, regardless of cluster mode;internalonly when we will actually authenticate as an internal connection.clusterCtx.useInternalConn, used for the auth mode inSendAuthCommandIfNeeded()and for whetherMR_ClusterGetPassword()may drop the password — so the flags and the auth can no longer disagree.NULLcallback, which is why half an hour of broken handshake produced no diagnostic beyondunknown command.CLUSTERSETitself also moves to_proxy-filteredon any Enterprise binary; previously a cluster-mode database exposed it to clients through the proxy.Because the decision is keyed off the binary rather than the cluster mode, it covers both routes into cluster mode identically.
Effect
On the failing run this is sufficient: the long-form CLUSTERSET already supplies the password, the flags become
_proxy-filtered(visible on a password-authenticated connection), HELLO succeeds, the fan-out forms.Cost: #98's target scenario (an Enterprise binary running a plain OSS cluster in env0) loses the
internalhiding — it gets_proxy-filteredand keeps internal-secret AUTH via then->password == NULLpath. Functionally unchanged, slightly less hardened.Verification
cluster.ccompiles clean. The behaviour needs a live cluster-mode Enterprise database to verify: a master shard log should showDetected redis oss (cluster-enabled=yes)and zeroGot bad hello response. Today those two are mutually exclusive.Two things I could not verify locally and would like a second opinion on:
_proxy-filteredwhile running withcluster-enabled=yes. If it does not, that branch should fall back to plain"readonly deny-script".MR_ClusterGetPassword()returnedNULL, the internal-secret AUTH was sent, and it still failed. If RE can share it, the opposite fix becomes viable (always prefer internal-secret AUTH and keepinternaleverywhere).🤖 Generated with Claude Code
Note
Medium Risk
Changes cluster handshake, command ACL flags, and AUTH selection across OSS vs Enterprise and cluster-enabled combinations; wrong classification could still break HELLO or expose commands differently on proxy paths.
Overview
Fixes LibMR cluster formation on Enterprise shards with
cluster-enabled=yes, where inter-shardHELLOfailed withunknown commandbecause commands were registered asinternalwhile shard links authenticated with the CLUSTERSET password (a regular, non-internal connection).isOssstill only picks the topology source (native cluster vs DMC CLUSTERSET). A newcommandsAreInternalflag decides whether module commands are registeredinternaland whether password-based auth is appropriate—true only for plain OSS binaries with internal-secret support, not whenrlec_versionis present.On Enterprise binaries, cluster commands (including
CLUSTERSET) now use_proxy-filteredinstead ofinternal, so they remain visible on password-authenticated shard connections.SendAuthCommandIfNeededprefers internal-secret AUTH when commands are internal; internal secret is optional (no assert). Failed AUTH responses are logged viaMR_AuthResponseArrived.MR_ClusterGetPasswordomits the stored password only when internal connections are required.Reviewed by Cursor Bugbot for commit f7ff12c. Bugbot is set up for automated code reviews on this repo. Configure here.