github.com/true-sqn/fabric@v2.1.1+incompatible/orderer/consensus/etcdraft/storage_test.go (about)

     1  /*
     2  Copyright IBM Corp. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package etcdraft
     8  
     9  import (
    10  	"io/ioutil"
    11  	"os"
    12  	"path"
    13  	"path/filepath"
    14  	"sort"
    15  	"strings"
    16  	"testing"
    17  
    18  	"github.com/hyperledger/fabric/common/flogging"
    19  	"github.com/stretchr/testify/assert"
    20  	"github.com/stretchr/testify/require"
    21  	"go.etcd.io/etcd/pkg/fileutil"
    22  	"go.etcd.io/etcd/raft"
    23  	"go.etcd.io/etcd/raft/raftpb"
    24  	"go.etcd.io/etcd/wal"
    25  	"go.uber.org/zap"
    26  )
    27  
    28  var (
    29  	err    error
    30  	logger *flogging.FabricLogger
    31  
    32  	dataDir, walDir, snapDir string
    33  
    34  	ram   *raft.MemoryStorage
    35  	store *RaftStorage
    36  )
    37  
    38  func setup(t *testing.T) {
    39  	logger = flogging.NewFabricLogger(zap.NewExample())
    40  	ram = raft.NewMemoryStorage()
    41  	dataDir, err = ioutil.TempDir("", "etcdraft-")
    42  	assert.NoError(t, err)
    43  	walDir, snapDir = path.Join(dataDir, "wal"), path.Join(dataDir, "snapshot")
    44  	store, err = CreateStorage(logger, walDir, snapDir, ram)
    45  	assert.NoError(t, err)
    46  }
    47  
    48  func clean(t *testing.T) {
    49  	err = store.Close()
    50  	assert.NoError(t, err)
    51  	err = os.RemoveAll(dataDir)
    52  	assert.NoError(t, err)
    53  }
    54  
    55  func fileCount(files []string, suffix string) (c int) {
    56  	for _, f := range files {
    57  		if strings.HasSuffix(f, suffix) {
    58  			c++
    59  		}
    60  	}
    61  	return
    62  }
    63  
    64  func assertFileCount(t *testing.T, wal, snap int) {
    65  	files, err := fileutil.ReadDir(walDir)
    66  	assert.NoError(t, err)
    67  	assert.Equal(t, wal, fileCount(files, ".wal"), "WAL file count mismatch")
    68  
    69  	files, err = fileutil.ReadDir(snapDir)
    70  	assert.NoError(t, err)
    71  	assert.Equal(t, snap, fileCount(files, ".snap"), "Snap file count mismatch")
    72  }
    73  
    74  func TestOpenWAL(t *testing.T) {
    75  	t.Run("Last WAL file is broken", func(t *testing.T) {
    76  		setup(t)
    77  		defer clean(t)
    78  
    79  		// create 10 new wal files
    80  		for i := 0; i < 10; i++ {
    81  			store.Store(
    82  				[]raftpb.Entry{{Index: uint64(i), Data: make([]byte, 10)}},
    83  				raftpb.HardState{},
    84  				raftpb.Snapshot{},
    85  			)
    86  		}
    87  		assertFileCount(t, 1, 0)
    88  		lasti, _ := store.ram.LastIndex() // it never returns err
    89  
    90  		// close current storage
    91  		err = store.Close()
    92  		assert.NoError(t, err)
    93  
    94  		// truncate wal file
    95  		w := func() string {
    96  			files, err := fileutil.ReadDir(walDir)
    97  			assert.NoError(t, err)
    98  			for _, f := range files {
    99  				if strings.HasSuffix(f, ".wal") {
   100  					return path.Join(walDir, f)
   101  				}
   102  			}
   103  			t.FailNow()
   104  			return ""
   105  		}()
   106  		err = os.Truncate(w, 200)
   107  		assert.NoError(t, err)
   108  
   109  		// create new storage
   110  		ram = raft.NewMemoryStorage()
   111  		store, err = CreateStorage(logger, walDir, snapDir, ram)
   112  		require.NoError(t, err)
   113  		lastI, _ := store.ram.LastIndex()
   114  		assert.True(t, lastI > 0)     // we are still able to read some entries
   115  		assert.True(t, lasti > lastI) // but less than before because some are broken
   116  	})
   117  }
   118  
   119  func TestTakeSnapshot(t *testing.T) {
   120  	// To make this test more understandable, here's a list
   121  	// of expected wal files:
   122  	// (wal file name format: seq-index.wal, index is the first index in this file)
   123  	//
   124  	// 0000000000000000-0000000000000000.wal (this is created initially by etcd/wal)
   125  	// 0000000000000001-0000000000000001.wal
   126  	// 0000000000000002-0000000000000002.wal
   127  	// 0000000000000003-0000000000000003.wal
   128  	// 0000000000000004-0000000000000004.wal
   129  	// 0000000000000005-0000000000000005.wal
   130  	// 0000000000000006-0000000000000006.wal
   131  	// 0000000000000007-0000000000000007.wal
   132  	// 0000000000000008-0000000000000008.wal
   133  	// 0000000000000009-0000000000000009.wal
   134  	// 000000000000000a-000000000000000a.wal
   135  
   136  	t.Run("Good", func(t *testing.T) {
   137  		t.Run("MaxSnapshotFiles==1", func(t *testing.T) {
   138  			backup := MaxSnapshotFiles
   139  			MaxSnapshotFiles = 1
   140  			defer func() { MaxSnapshotFiles = backup }()
   141  
   142  			setup(t)
   143  			defer clean(t)
   144  
   145  			// set SegmentSizeBytes to a small value so that
   146  			// every entry persisted to wal would result in
   147  			// a new wal being created.
   148  			oldSegmentSizeBytes := wal.SegmentSizeBytes
   149  			wal.SegmentSizeBytes = 10
   150  			defer func() {
   151  				wal.SegmentSizeBytes = oldSegmentSizeBytes
   152  			}()
   153  
   154  			// create 10 new wal files
   155  			for i := 0; i < 10; i++ {
   156  				store.Store(
   157  					[]raftpb.Entry{{Index: uint64(i), Data: make([]byte, 100)}},
   158  					raftpb.HardState{},
   159  					raftpb.Snapshot{},
   160  				)
   161  			}
   162  
   163  			assertFileCount(t, 11, 0)
   164  
   165  			err = store.TakeSnapshot(uint64(3), raftpb.ConfState{Nodes: []uint64{1}}, make([]byte, 10))
   166  			assert.NoError(t, err)
   167  			// Snapshot is taken at index 3, which releases lock up to 2 (excl.).
   168  			// This results in wal files with index [0, 1] being purged (2 files)
   169  			assertFileCount(t, 9, 1)
   170  
   171  			err = store.TakeSnapshot(uint64(5), raftpb.ConfState{Nodes: []uint64{1}}, make([]byte, 10))
   172  			assert.NoError(t, err)
   173  			// Snapshot is taken at index 5, which releases lock up to 4 (excl.).
   174  			// This results in wal files with index [2, 3] being purged (2 files)
   175  			assertFileCount(t, 7, 1)
   176  
   177  			t.Logf("Close the storage and create a new one based on existing files")
   178  
   179  			err = store.Close()
   180  			assert.NoError(t, err)
   181  			ram := raft.NewMemoryStorage()
   182  			store, err = CreateStorage(logger, walDir, snapDir, ram)
   183  			assert.NoError(t, err)
   184  
   185  			err = store.TakeSnapshot(uint64(7), raftpb.ConfState{Nodes: []uint64{1}}, make([]byte, 10))
   186  			assert.NoError(t, err)
   187  			// Snapshot is taken at index 7, which releases lock up to 6 (excl.).
   188  			// This results in wal files with index [4, 5] being purged (2 file)
   189  			assertFileCount(t, 5, 1)
   190  
   191  			err = store.TakeSnapshot(uint64(9), raftpb.ConfState{Nodes: []uint64{1}}, make([]byte, 10))
   192  			assert.NoError(t, err)
   193  			// Snapshot is taken at index 9, which releases lock up to 8 (excl.).
   194  			// This results in wal files with index [6, 7] being purged (2 file)
   195  			assertFileCount(t, 3, 1)
   196  		})
   197  
   198  		t.Run("MaxSnapshotFiles==2", func(t *testing.T) {
   199  			backup := MaxSnapshotFiles
   200  			MaxSnapshotFiles = 2
   201  			defer func() { MaxSnapshotFiles = backup }()
   202  
   203  			setup(t)
   204  			defer clean(t)
   205  
   206  			// set SegmentSizeBytes to a small value so that
   207  			// every entry persisted to wal would result in
   208  			// a new wal being created.
   209  			oldSegmentSizeBytes := wal.SegmentSizeBytes
   210  			wal.SegmentSizeBytes = 10
   211  			defer func() {
   212  				wal.SegmentSizeBytes = oldSegmentSizeBytes
   213  			}()
   214  
   215  			// create 10 new wal files
   216  			for i := 0; i < 10; i++ {
   217  				store.Store(
   218  					[]raftpb.Entry{{Index: uint64(i), Data: make([]byte, 100)}},
   219  					raftpb.HardState{},
   220  					raftpb.Snapshot{},
   221  				)
   222  			}
   223  
   224  			assertFileCount(t, 11, 0)
   225  
   226  			// Only one snapshot is taken, no wal pruning happened
   227  			err = store.TakeSnapshot(uint64(3), raftpb.ConfState{Nodes: []uint64{1}}, make([]byte, 10))
   228  			assert.NoError(t, err)
   229  			assertFileCount(t, 11, 1)
   230  
   231  			// Two snapshots at index 3, 5. And we keep one extra wal file prior to oldest snapshot.
   232  			// So we should have pruned wal file with index [0, 1]
   233  			err = store.TakeSnapshot(uint64(5), raftpb.ConfState{Nodes: []uint64{1}}, make([]byte, 10))
   234  			assert.NoError(t, err)
   235  			assertFileCount(t, 9, 2)
   236  
   237  			t.Logf("Close the storage and create a new one based on existing files")
   238  
   239  			err = store.Close()
   240  			assert.NoError(t, err)
   241  			ram := raft.NewMemoryStorage()
   242  			store, err = CreateStorage(logger, walDir, snapDir, ram)
   243  			assert.NoError(t, err)
   244  
   245  			// Two snapshots at index 5, 7. And we keep one extra wal file prior to oldest snapshot.
   246  			// So we should have pruned wal file with index [2, 3]
   247  			err = store.TakeSnapshot(uint64(7), raftpb.ConfState{Nodes: []uint64{1}}, make([]byte, 10))
   248  			assert.NoError(t, err)
   249  			assertFileCount(t, 7, 2)
   250  
   251  			// Two snapshots at index 7, 9. And we keep one extra wal file prior to oldest snapshot.
   252  			// So we should have pruned wal file with index [4, 5]
   253  			err = store.TakeSnapshot(uint64(9), raftpb.ConfState{Nodes: []uint64{1}}, make([]byte, 10))
   254  			assert.NoError(t, err)
   255  			assertFileCount(t, 5, 2)
   256  		})
   257  	})
   258  
   259  	t.Run("Bad", func(t *testing.T) {
   260  
   261  		t.Run("MaxSnapshotFiles==2", func(t *testing.T) {
   262  			// If latest snapshot file is corrupted, storage should be able
   263  			// to recover from an older one.
   264  
   265  			backup := MaxSnapshotFiles
   266  			MaxSnapshotFiles = 2
   267  			defer func() { MaxSnapshotFiles = backup }()
   268  
   269  			setup(t)
   270  			defer clean(t)
   271  
   272  			// set SegmentSizeBytes to a small value so that
   273  			// every entry persisted to wal would result in
   274  			// a new wal being created.
   275  			oldSegmentSizeBytes := wal.SegmentSizeBytes
   276  			wal.SegmentSizeBytes = 10
   277  			defer func() {
   278  				wal.SegmentSizeBytes = oldSegmentSizeBytes
   279  			}()
   280  
   281  			// create 10 new wal files
   282  			for i := 0; i < 10; i++ {
   283  				store.Store(
   284  					[]raftpb.Entry{{Index: uint64(i), Data: make([]byte, 100)}},
   285  					raftpb.HardState{},
   286  					raftpb.Snapshot{},
   287  				)
   288  			}
   289  
   290  			assertFileCount(t, 11, 0)
   291  
   292  			// Only one snapshot is taken, no wal pruning happened
   293  			err = store.TakeSnapshot(uint64(3), raftpb.ConfState{Nodes: []uint64{1}}, make([]byte, 10))
   294  			assert.NoError(t, err)
   295  			assertFileCount(t, 11, 1)
   296  
   297  			// Two snapshots at index 3, 5. And we keep one extra wal file prior to oldest snapshot.
   298  			// So we should have pruned wal file with index [0, 1]
   299  			err = store.TakeSnapshot(uint64(5), raftpb.ConfState{Nodes: []uint64{1}}, make([]byte, 10))
   300  			assert.NoError(t, err)
   301  			assertFileCount(t, 9, 2)
   302  
   303  			d, err := os.Open(snapDir)
   304  			assert.NoError(t, err)
   305  			defer d.Close()
   306  			names, err := d.Readdirnames(-1)
   307  			assert.NoError(t, err)
   308  			sort.Sort(sort.Reverse(sort.StringSlice(names)))
   309  
   310  			corrupted := filepath.Join(snapDir, names[0])
   311  			t.Logf("Corrupt latest snapshot file: %s", corrupted)
   312  			f, err := os.OpenFile(corrupted, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
   313  			assert.NoError(t, err)
   314  			_, err = f.WriteString("Corrupted Snapshot")
   315  			assert.NoError(t, err)
   316  			f.Close()
   317  
   318  			// Corrupted snapshot file should've been renamed by ListSnapshots
   319  			_ = ListSnapshots(logger, snapDir)
   320  			assertFileCount(t, 9, 1)
   321  
   322  			// Rollback the rename
   323  			broken := corrupted + ".broken"
   324  			err = os.Rename(broken, corrupted)
   325  			assert.NoError(t, err)
   326  			assertFileCount(t, 9, 2)
   327  
   328  			t.Logf("Close the storage and create a new one based on existing files")
   329  
   330  			err = store.Close()
   331  			assert.NoError(t, err)
   332  			ram := raft.NewMemoryStorage()
   333  			store, err = CreateStorage(logger, walDir, snapDir, ram)
   334  			assert.NoError(t, err)
   335  
   336  			// Corrupted snapshot file should've been renamed by CreateStorage
   337  			assertFileCount(t, 9, 1)
   338  
   339  			files, err := fileutil.ReadDir(snapDir)
   340  			assert.NoError(t, err)
   341  			assert.Equal(t, 1, fileCount(files, ".broken"))
   342  
   343  			err = store.TakeSnapshot(uint64(7), raftpb.ConfState{Nodes: []uint64{1}}, make([]byte, 10))
   344  			assert.NoError(t, err)
   345  			assertFileCount(t, 9, 2)
   346  
   347  			err = store.TakeSnapshot(uint64(9), raftpb.ConfState{Nodes: []uint64{1}}, make([]byte, 10))
   348  			assert.NoError(t, err)
   349  			assertFileCount(t, 5, 2)
   350  		})
   351  	})
   352  }
   353  
   354  func TestApplyOutOfDateSnapshot(t *testing.T) {
   355  	t.Run("Apply out of date snapshot", func(t *testing.T) {
   356  		setup(t)
   357  		defer clean(t)
   358  
   359  		// set SegmentSizeBytes to a small value so that
   360  		// every entry persisted to wal would result in
   361  		// a new wal being created.
   362  		oldSegmentSizeBytes := wal.SegmentSizeBytes
   363  		wal.SegmentSizeBytes = 10
   364  		defer func() {
   365  			wal.SegmentSizeBytes = oldSegmentSizeBytes
   366  		}()
   367  
   368  		// create 10 new wal files
   369  		for i := 0; i < 10; i++ {
   370  			store.Store(
   371  				[]raftpb.Entry{{Index: uint64(i), Data: make([]byte, 100)}},
   372  				raftpb.HardState{},
   373  				raftpb.Snapshot{},
   374  			)
   375  		}
   376  		assertFileCount(t, 11, 0)
   377  
   378  		err = store.TakeSnapshot(uint64(3), raftpb.ConfState{Nodes: []uint64{1}}, make([]byte, 10))
   379  		assert.NoError(t, err)
   380  		assertFileCount(t, 11, 1)
   381  
   382  		snapshot := store.Snapshot()
   383  		assert.NotNil(t, snapshot)
   384  
   385  		// Applying old snapshot should have no effect
   386  		store.ApplySnapshot(snapshot)
   387  
   388  		// Storing old snapshot gets no error
   389  		err := store.Store(
   390  			[]raftpb.Entry{{Index: uint64(10), Data: make([]byte, 100)}},
   391  			raftpb.HardState{},
   392  			snapshot,
   393  		)
   394  		assert.NoError(t, err)
   395  		assertFileCount(t, 12, 1)
   396  	})
   397  }