Skip to content

Commit 2b9598b

Browse files
authored
Merge pull request #19563 from ahrtr/3.5_learner_20250310
[release-3.5] Fix the learner promotion changes not being persisted into v3store (bbolt)
2 parents c8a9037 + a1cb13b commit 2b9598b

File tree

7 files changed

+85
-6
lines changed

7 files changed

+85
-6
lines changed

server/etcdserver/api/membership/cluster.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -696,6 +696,10 @@ func (c *RaftCluster) IsReadyToPromoteMember(id uint64) bool {
696696
return true
697697
}
698698

699+
func (c *RaftCluster) MembersFromStore() (map[types.ID]*Member, map[types.ID]bool) {
700+
return membersFromStore(c.lg, c.v2store)
701+
}
702+
699703
func membersFromStore(lg *zap.Logger, st v2store.Store) (map[types.ID]*Member, map[types.ID]bool) {
700704
members := make(map[types.ID]*Member)
701705
removed := make(map[types.ID]bool)
@@ -732,6 +736,10 @@ func membersFromStore(lg *zap.Logger, st v2store.Store) (map[types.ID]*Member, m
732736
return members, removed
733737
}
734738

739+
func (c *RaftCluster) MembersFromBackend() (map[types.ID]*Member, map[types.ID]bool) {
740+
return membersFromBackend(c.lg, c.be)
741+
}
742+
735743
func membersFromBackend(lg *zap.Logger, be backend.Backend) (map[types.ID]*Member, map[types.ID]bool) {
736744
return mustReadMembersFromBackend(lg, be)
737745
}

server/etcdserver/api/membership/cluster_test.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1049,10 +1049,9 @@ func TestAddMemberSyncsBackendAndStoreV2(t *testing.T) {
10491049
backendMembers: []*Member{alice},
10501050
},
10511051
{
1052-
name: "Adding member should fail if it exists in both",
1052+
name: "Adding member should success if it exists in both",
10531053
storeV2Members: []*Member{alice},
10541054
backendMembers: []*Member{alice},
1055-
expectPanics: true,
10561055
},
10571056
{
10581057
name: "Adding member should fail if it exists in storeV2 and backend is nil",

server/etcdserver/api/membership/store.go

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,6 @@ func unsafeSaveMemberToBackend(lg *zap.Logger, be backend.Backend, m *Member) er
5454
tx := be.BatchTx()
5555
tx.LockInsideApply()
5656
defer tx.Unlock()
57-
if unsafeMemberExists(tx, mkey) {
58-
return errMemberAlreadyExist
59-
}
6057
tx.UnsafePut(buckets.Members, mkey, mvalue)
6158
return nil
6259
}

server/etcdserver/server.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"net/http"
2525
"os"
2626
"path"
27+
"reflect"
2728
"regexp"
2829
"strconv"
2930
"strings"
@@ -67,6 +68,7 @@ import (
6768
"go.etcd.io/etcd/server/v3/lease/leasehttp"
6869
"go.etcd.io/etcd/server/v3/mvcc"
6970
"go.etcd.io/etcd/server/v3/mvcc/backend"
71+
"go.etcd.io/etcd/server/v3/verify"
7072
"go.etcd.io/etcd/server/v3/wal"
7173
)
7274

@@ -2449,9 +2451,51 @@ func (s *EtcdServer) applyConfChange(cc raftpb.ConfChange, confState *raftpb.Con
24492451
s.r.transport.UpdatePeer(m.ID, m.PeerURLs)
24502452
}
24512453
}
2454+
2455+
s.verifyV3StoreInSyncWithV2Store(shouldApplyV3)
2456+
24522457
return false, nil
24532458
}
24542459

2460+
func (s *EtcdServer) verifyV3StoreInSyncWithV2Store(shouldApplyV3 membership.ShouldApplyV3) {
2461+
if !verify.VerifyEnabled() {
2462+
return
2463+
}
2464+
2465+
// If shouldApplyV3 == false, then it means v2store hasn't caught up with v3store.
2466+
if !shouldApplyV3 {
2467+
return
2468+
}
2469+
2470+
// clean up the Attributes, and we only care about the RaftAttributes
2471+
cleanAttributesFunc := func(members map[types.ID]*membership.Member) map[types.ID]*membership.Member {
2472+
processedMembers := make(map[types.ID]*membership.Member)
2473+
for id, m := range members {
2474+
clonedMember := m.Clone()
2475+
clonedMember.Attributes = membership.Attributes{}
2476+
processedMembers[id] = clonedMember
2477+
}
2478+
2479+
return processedMembers
2480+
}
2481+
2482+
v2Members, _ := s.cluster.MembersFromStore()
2483+
v3Members, _ := s.cluster.MembersFromBackend()
2484+
2485+
processedV2Members := cleanAttributesFunc(v2Members)
2486+
processedV3Members := cleanAttributesFunc(v3Members)
2487+
2488+
if match := reflect.DeepEqual(processedV2Members, processedV3Members); !match {
2489+
v2Data, v2Err := json.Marshal(processedV2Members)
2490+
v3Data, v3Err := json.Marshal(processedV3Members)
2491+
2492+
if v2Err != nil || v3Err != nil {
2493+
panic("members in v2store doesn't match v3store")
2494+
}
2495+
panic(fmt.Sprintf("members in v2store doesn't match v3store, v2store: %s, v3store: %s", string(v2Data), string(v3Data)))
2496+
}
2497+
}
2498+
24552499
// TODO: non-blocking snapshot
24562500
func (s *EtcdServer) snapshot(snapi uint64, confState raftpb.ConfState) {
24572501
clone := s.v2store.Clone()

server/etcdserver/server_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -655,6 +655,7 @@ func TestApplyConfigChangeUpdatesConsistIndex(t *testing.T) {
655655

656656
be, _ := betesting.NewDefaultTmpBackend(t)
657657
defer betesting.Close(t, be)
658+
cl.SetBackend(be)
658659
cindex.CreateMetaBucket(be.BatchTx())
659660

660661
cl.AddMember(&membership.Member{ID: types.ID(1)}, true)

server/verify/verify.go

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,12 +90,37 @@ func Verify(cfg Config) error {
9090
// VerifyIfEnabled performs verification according to ETCD_VERIFY env settings.
9191
// See Verify for more information.
9292
func VerifyIfEnabled(cfg Config) error {
93-
if os.Getenv(ENV_VERIFY) == ENV_VERIFY_ALL_VALUE {
93+
if VerifyEnabled() {
9494
return Verify(cfg)
9595
}
9696
return nil
9797
}
9898

99+
// VerifyEnabled returns `true` if verification is enabled.
100+
func VerifyEnabled() bool {
101+
return os.Getenv(ENV_VERIFY) == ENV_VERIFY_ALL_VALUE
102+
}
103+
104+
// EnableVerification enables the verification and returns a function that
105+
// can be used to bring the original settings.
106+
func EnableVerification() func() {
107+
previousEnv := os.Getenv(ENV_VERIFY)
108+
os.Setenv(ENV_VERIFY, ENV_VERIFY_ALL_VALUE)
109+
return func() {
110+
os.Setenv(ENV_VERIFY, previousEnv)
111+
}
112+
}
113+
114+
// DisableVerification disables the verification and returns a function that
115+
// can be used to bring the original settings.
116+
func DisableVerification() func() {
117+
previousEnv := os.Getenv(ENV_VERIFY)
118+
os.Unsetenv(ENV_VERIFY)
119+
return func() {
120+
os.Setenv(ENV_VERIFY, previousEnv)
121+
}
122+
}
123+
99124
// MustVerifyIfEnabled performs verification according to ETCD_VERIFY env settings
100125
// and exits in case of found problems.
101126
// See Verify for more information.

tests/e2e/ctl_v2_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"testing"
2323
"time"
2424

25+
"go.etcd.io/etcd/server/v3/verify"
2526
"go.etcd.io/etcd/tests/v3/framework/e2e"
2627
)
2728

@@ -229,6 +230,10 @@ func TestUtlCtlV2Backup(t *testing.T) {
229230
func testUtlCtlV2Backup(t *testing.T, snapCount int, v3 bool, utl bool) {
230231
BeforeTestV2(t)
231232

233+
// disable the verification because this case updated the db offline
234+
revertFunc := verify.DisableVerification()
235+
defer revertFunc()
236+
232237
backupDir, err := ioutil.TempDir(t.TempDir(), "testbackup0.etcd")
233238
if err != nil {
234239
t.Fatal(err)

0 commit comments

Comments
 (0)