Skip to content

server, e2e: auto-promote learners that have caught up with the leader#21744

Draft
john7doe wants to merge 10 commits into
etcd-io:mainfrom
john7doe:auto-promote-learners
Draft

server, e2e: auto-promote learners that have caught up with the leader#21744
john7doe wants to merge 10 commits into
etcd-io:mainfrom
john7doe:auto-promote-learners

Conversation

@john7doe
Copy link
Copy Markdown

@john7doe john7doe commented May 12, 2026

Fixes #21611.

Motivation

Adding a member as a learner is the recommended way to grow an etcd cluster, but turning that learner into a voting member still requires an operator to issue etcdctl member promote (or call MemberPromote). That manual step is the only thing standing between "learner is ready" and "learner is a voting member", and the readiness check already lives in the server — it is the same one the manual path uses.

This change adds an opt-in feature gate, AutoPromoteLearners, that makes the leader perform the promotion automatically once the learner has caught up.

Design

  • Opt-in feature gate. AutoPromoteLearners (Alpha, default false). With the gate off, behavior is unchanged.
  • Leader-only loop. A new monitorAutoPromoteLearners goroutine is launched from EtcdServer.Start only when the gate is enabled. It wakes on firstCommitInTerm (so a newly elected leader picks up any pending learners promptly) or every monitorVersionInterval (~4s), short-circuits on non-leaders, and delegates to tryAutoPromoteLearners.
  • Same readiness check as the manual path. tryAutoPromoteLearners iterates s.cluster.Members(), filters for learners, and calls the existing promoteMember. That goes through isLearnerReady (the same check used by etcdctl member promote) and the standard raft conf-change proposal, so the WAL audit trail is identical to a manual promotion.
  • Quiet handling of transient errors. Expected pending states (ErrLearnerNotReady, ErrNotEnoughStartedMembers, ErrNotLeader, ErrLeaderChanged, ErrTimeout, ErrTimeoutWaitAppliedIndex, ErrStopped, ErrCanceled, ErrMemberNotLearner, ErrIDNotFound) are logged at Debug; only unexpected errors are logged at Warn and bumped on the learnerPromoteFailed metric.
  • No new metrics. Successes and unexpected failures reuse the existing learnerPromoteSucceed / learnerPromoteFailed counters.

Behavior change

  • With --feature-gates=AutoPromoteLearners=true on the prospective leader, a learner is promoted automatically once it has caught up.
  • With the gate off (default), there is no behavior change.

The gate must be enabled on every member that may become leader for auto-promotion to take effect cluster-wide.

Tests

  • UnitTestMonitorAutoPromoteLearners_ExitsCleanly verifies the monitor wakes on firstCommitInTerm, short-circuits when the local member is not the leader, and exits promptly on stopping.
  • IntegrationTestAutoPromoteLearner in tests/integration/clientv3 brings up a 3-node cluster with the gate enabled, adds a learner via the client API, and asserts that MemberList reports the learner promoted within 30s.
  • E2ETestAutoPromoteLearner in tests/e2e brings up a full 3-node etcd process cluster with --feature-gates=AutoPromoteLearners=true, adds a learner via etcdctl member add --learner, and asserts auto-promotion within 45s plus that the new member is healthy.
  • Existing TestMemberPromote* tests continue to pass.

Verification

  • make verify — green (lint, dep, shellcheck, mod-tidy, shellws, proto-annotations, genproto, gomodguard, go-workspace, go-versions, grpc-experimental, bom).
  • make build — green.
  • New unit, integration, and e2e tests pass under -race -count=1.

Open questions for reviewers

  • Log-spam policy. A permanently-stuck learner will emit one Debug line per tick today. Would you prefer per-learner rate-limiting on the Warn path before the Beta cut?
  • Commit history. The change is 9 logical commits (feature gate, server logic, unit test, integration test, error-class fixup, gate doc, CHANGELOG, e2e test, plus a bill-of-materials.json refresh so make verify-bom is green). Happy to squash, or drop the BOM refresh if a parallel fix is already in flight.

CHANGELOG

CHANGELOG/CHANGELOG-3.7.md updated under "etcd server".

@k8s-ci-robot
Copy link
Copy Markdown

Hi @john7doe. Thanks for your PR.

I'm waiting for a etcd-io member to verify that this patch is reasonable to test. If it is, they should reply with /ok-to-test on its own line. Until that is done, I will not automatically test new commits in this PR, but the usual testing commands by org members will still work.

Regular contributors should join the org to skip this step.

Once the patch is verified, the new status will be reflected by the ok-to-test label.

I understand the commands that are listed here.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

@k8s-ci-robot
Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: john7doe
Once this PR has been reviewed and has the lgtm label, please assign ahrtr for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@john7doe john7doe force-pushed the auto-promote-learners branch 2 times, most recently from ac7ab62 to 23a750e Compare May 12, 2026 08:47
@john7doe john7doe force-pushed the auto-promote-learners branch from 23a750e to 5f708ae Compare May 12, 2026 08:48
john7doe and others added 10 commits May 12, 2026 10:49
Introduce a new alpha feature gate that, when enabled, will let the
leader automatically promote a learner member once it has caught up
with the leader's log. This commit only registers the gate; the
behaviour is wired up in a follow-up commit.

Refs: etcd-io#21611
Signed-off-by: Simon Scharf <john7doe@gmail.com>
Add a leader-side monitor goroutine that periodically scans cluster
members for learners and promotes any that have caught up. Readiness
is decided by the existing isLearnerReady check (>=90% Match against
the leader), so behaviour matches a manual 'etcdctl member promote'
issued at the leader.

The loop is gated by the AutoPromoteLearners feature gate (alpha,
default off, added in the previous commit) and reuses the existing
promoteMember -> mayPromoteMember -> configure pipeline, including
StrictReconfigCheck quorum-safety and the
learnerPromoteSucceed / learnerPromoteFailed metrics.

The internal call uses authStore.WithRoot to bypass the user-level
admin check for the server's own goroutine, mirroring the existing
lease-revocation pattern.

Trigger cadence reuses monitorVersionInterval and additionally wakes
on firstCommitInTerm so a freshly-elected leader runs the check
promptly. Expected-pending errors (learner not ready, quorum unsafe,
race with manual promote, leadership lost) are logged at DEBUG to
avoid log spam; unexpected errors are surfaced at WARN.

Refs: etcd-io#21611
Signed-off-by: Simon Scharf <john7doe@gmail.com>
Add TestMonitorAutoPromoteLearners_ExitsCleanly which verifies that
monitorAutoPromoteLearners:

  - wakes on firstCommitInTerm without touching cluster / authStore
    when the local member is not the leader (i.e. the leader gate
    short-circuits before tryAutoPromoteLearners runs);
  - exits promptly when the stopping channel is closed.

A regression here would either deadlock the goroutine or surface a
nil-pointer dereference once the leader gate is removed, so this is
a cheap guard for the contract enforced by the loop's scaffolding.

Refs: etcd-io#21611
Signed-off-by: Simon Scharf <john7doe@gmail.com>
Add an integration test that verifies a learner is auto-promoted to a
voting member without an explicit MemberPromote call when the
AutoPromoteLearners feature gate is enabled on every cluster member.

To enable the feature gate from integration tests, plumb a new
EnableAutoPromoteLearners field through ClusterConfig and
MemberConfig, mirroring the existing EnableLeaseCheckpoint pattern in
MustNewMember (it is appended to the per-member feature-gate string).

The test mirrors the setup of TestMemberPromote (add learner, launch
it) but instead of calling MemberPromote it polls MemberList until the
learner transitions to a voter; the auto-promote loop ticks at
monitorVersionInterval (~4s), so a 30s deadline gives several
iterations of headroom on slow runners.

Refs: etcd-io#21611
Signed-off-by: Simon Scharf <john7doe@gmail.com>
Suppress transient errors that promoteMember can return during normal
operation under the leader-side auto-promote loop, so the Warn log
fires only on truly unexpected failures:

  - ErrLeaderChanged: leadership flips between isLeader() and the
    raft proposal.
  - ErrTimeout / ErrTimeoutWaitAppliedIndex: backpressure or proposal
    timeout; the next monitor tick retries.
  - ErrStopped / ErrCanceled: server is shutting down; the monitor's
    stopping select exits on the next iteration.

The learnerPromoteFailed metric is no longer incremented for these
transient states.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Simon Scharf <john7doe@gmail.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Simon Scharf <john7doe@gmail.com>
Clarify behavior:
  - Readiness reuses the same isLearnerReady check as the manual
    etcdctl member promote path.
  - Only the leader scans; followers short-circuit.
  - The gate must be set on every member that may become leader for
    auto-promotion to take effect cluster-wide.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Simon Scharf <john7doe@gmail.com>
End-to-end coverage for the AutoPromoteLearners feature gate using a
real 3-node etcd process cluster. The test starts the cluster with
--feature-gates=AutoPromoteLearners=true, adds a fourth member as a
learner, and asserts the leader auto-promotes it to a voting member
without an explicit etcdctl member promote invocation. The deadline
gives ~10 ticks of the auto-promote monitor (monitorVersionInterval,
~4s) to keep the test reliable on slow runners.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Simon Scharf <john7doe@gmail.com>
Regenerated via 'make fix' to drop the stale
github.com/antithesishq/antithesis-sdk-go entry so 'make verify-bom'
passes locally. No source dependency changes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: Simon Scharf <john7doe@gmail.com>
monitorAutoPromoteLearners/tryAutoPromoteLearners adjust comment

remove test

Signed-off-by: Simon Scharf <john7doe@gmail.com>
// owner: @john7doe
// alpha: v3.7
// issue: https://github.com/etcd-io/etcd/issues/21611
AutoPromoteLearners featuregate.Feature = "AutoPromoteLearners"
Copy link
Copy Markdown
Member

@serathius serathius May 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might be a good candidate for first cluster level flag, what do you think @siyuanfoundation ? Context: kubernetes/enhancements#5189

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

Development

Successfully merging this pull request may close these issues.

auto promote learner once it caught up with leader

3 participants