Skip to content

Commit

Permalink
etcdserver: a non-empty raft log snapshot should always be available
Browse files Browse the repository at this point in the history
Signed-off-by: Clement <[email protected]>
  • Loading branch information
clement2026 committed Aug 25, 2024
1 parent 4d42c0f commit a0c0639
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 1 deletion.
5 changes: 4 additions & 1 deletion server/etcdserver/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -1210,7 +1210,10 @@ func (s *EtcdServer) triggerSnapshot(ep *etcdProgress) {
}

func (s *EtcdServer) shouldSnapshot(ep *etcdProgress) bool {
return (s.forceSnapshot && ep.appliedi != ep.snapi) || (ep.appliedi-ep.snapi > s.Cfg.SnapshotCount)
return (s.forceSnapshot && ep.appliedi != ep.snapi) ||
(ep.appliedi-ep.snapi > s.Cfg.SnapshotCount) ||
// ensures a non-empty snapshot always exists
(ep.snapi == 0 && ep.appliedi > ep.snapi)
}

func (s *EtcdServer) hasMultipleVotingMembers() bool {
Expand Down
78 changes: 78 additions & 0 deletions tests/integration/raft_log_snapshot_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// Copyright 2024 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package integration

import (
"context"
"errors"
"testing"
"time"

pb "go.etcd.io/etcd/api/v3/etcdserverpb"
"go.etcd.io/etcd/tests/v3/framework/integration"
)

// TestRaftLogSnapshotExistsPostStartUp ensures a non-empty raft log snapshot exists after startup
func TestRaftLogSnapshotExistsPostStartUp(t *testing.T) {
integration.BeforeTest(t)

clus := integration.NewCluster(t, &integration.ClusterConfig{
Size: 1,
SnapshotCount: 100,
SnapshotCatchUpEntries: 10,
})
defer clus.Terminate(t)

m := clus.Members[0]

ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Second)
defer cancel()

_, err := m.LogObserver.Expect(ctx, "saved snapshot", 1)
if err != nil {
t.Fatalf("failed to expect (log:%s, count:%v): %v", "saved snapshot", 1, err)
}

kvc := integration.ToGRPC(clus.RandClient()).KV

// In order to trigger another snapshot, we should increase applied index from 1 to 102.
//
// NOTE: When starting a new cluster with 1 member, the member will
// apply 3 ConfChange directly at the beginning, meaning its applied
// index is 4.
for i := 0; i < 102-4; i++ {
_, err := kvc.Put(context.TODO(), &pb.PutRequest{Key: []byte("foo"), Value: []byte("bar")})
if err != nil {
t.Fatalf("#%d: couldn't put key (%v)", i, err)
}
}

ctx2, cancel2 := context.WithTimeout(context.TODO(), 5*time.Second)
defer cancel2()

_, err = m.LogObserver.Expect(ctx2, "saved snapshot", 2)
if err != nil {
t.Fatalf("failed to expect (log:%s, count:%v): %v", "saved snapshot", 1, err)
}

ctx3, cancel3 := context.WithTimeout(context.TODO(), 5*time.Second)
defer cancel3()

// Expect function should return a DeadlineExceeded error to ensure no more snapshots are present
_, err = m.LogObserver.Expect(ctx3, "saved snapshot", 3)
if !errors.Is(err, context.DeadlineExceeded) {
t.Fatalf("unexpected error, max snapshots allowed is %d: %v", 2, err)
}
}

0 comments on commit a0c0639

Please sign in to comment.