github.com/kaisenlinux/docker.io@v0.0.0-20230510090727-ea55db55fac7/swarmkit/manager/state/raft/storage/walwrap_test.go (about)

     1  package storage
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"os"
     9  	"path/filepath"
    10  	"testing"
    11  
    12  	"github.com/coreos/etcd/raft/raftpb"
    13  	"github.com/coreos/etcd/wal/walpb"
    14  	"github.com/docker/swarmkit/api"
    15  	"github.com/docker/swarmkit/manager/encryption"
    16  	"github.com/stretchr/testify/require"
    17  )
    18  
    19  var _ WALFactory = walCryptor{}
    20  
    21  // Generates a bunch of WAL test data
    22  func makeWALData(index uint64, term uint64) ([]byte, []raftpb.Entry, walpb.Snapshot) {
    23  	wsn := walpb.Snapshot{
    24  		Index: index,
    25  		Term:  term,
    26  	}
    27  
    28  	var entries []raftpb.Entry
    29  	for i := wsn.Index + 1; i < wsn.Index+6; i++ {
    30  		entries = append(entries, raftpb.Entry{
    31  			Term:  wsn.Term + 1,
    32  			Index: i,
    33  			Data:  []byte(fmt.Sprintf("Entry %d", i)),
    34  		})
    35  	}
    36  
    37  	return []byte("metadata"), entries, wsn
    38  }
    39  
    40  func createWithWAL(t *testing.T, w WALFactory, metadata []byte, startSnap walpb.Snapshot, entries []raftpb.Entry) string {
    41  	walDir, err := ioutil.TempDir("", "waltests")
    42  	require.NoError(t, err)
    43  	require.NoError(t, os.RemoveAll(walDir))
    44  
    45  	walWriter, err := w.Create(walDir, metadata)
    46  	require.NoError(t, err)
    47  
    48  	require.NoError(t, walWriter.SaveSnapshot(startSnap))
    49  	require.NoError(t, walWriter.Save(raftpb.HardState{}, entries))
    50  	require.NoError(t, walWriter.Close())
    51  
    52  	return walDir
    53  }
    54  
    55  // WAL can read entries are not wrapped, but not encrypted
    56  func TestReadAllWrappedNoEncryption(t *testing.T) {
    57  	metadata, entries, snapshot := makeWALData(1, 1)
    58  	wrappedEntries := make([]raftpb.Entry, len(entries))
    59  	for i, entry := range entries {
    60  		r := api.MaybeEncryptedRecord{Data: entry.Data}
    61  		data, err := r.Marshal()
    62  		require.NoError(t, err)
    63  		entry.Data = data
    64  		wrappedEntries[i] = entry
    65  	}
    66  
    67  	tempdir := createWithWAL(t, OriginalWAL, metadata, snapshot, wrappedEntries)
    68  	defer os.RemoveAll(tempdir)
    69  
    70  	c := NewWALFactory(encryption.NoopCrypter, encryption.NoopCrypter)
    71  	wrapped, err := c.Open(tempdir, snapshot)
    72  	require.NoError(t, err)
    73  	defer wrapped.Close()
    74  
    75  	metaW, _, entsW, err := wrapped.ReadAll()
    76  	require.NoError(t, err)
    77  	require.NoError(t, wrapped.Close())
    78  
    79  	require.Equal(t, metadata, metaW)
    80  	require.Equal(t, entries, entsW)
    81  }
    82  
    83  // When reading WAL, if the decrypter can't read the encryption type, errors
    84  func TestReadAllNoSupportedDecrypter(t *testing.T) {
    85  	metadata, entries, snapshot := makeWALData(1, 1)
    86  	for i, entry := range entries {
    87  		r := api.MaybeEncryptedRecord{Data: entry.Data, Algorithm: api.MaybeEncryptedRecord_Algorithm(-3)}
    88  		data, err := r.Marshal()
    89  		require.NoError(t, err)
    90  		entries[i].Data = data
    91  	}
    92  
    93  	tempdir := createWithWAL(t, OriginalWAL, metadata, snapshot, entries)
    94  	defer os.RemoveAll(tempdir)
    95  
    96  	c := NewWALFactory(encryption.NoopCrypter, encryption.NoopCrypter)
    97  	wrapped, err := c.Open(tempdir, snapshot)
    98  	require.NoError(t, err)
    99  	defer wrapped.Close()
   100  
   101  	_, _, _, err = wrapped.ReadAll()
   102  	require.Error(t, err)
   103  	defer wrapped.Close()
   104  }
   105  
   106  // When reading WAL, if a decrypter is available for the encryption type but any
   107  // entry is incorrectly encryptd, an error is returned
   108  func TestReadAllEntryIncorrectlyEncrypted(t *testing.T) {
   109  	crypter := &meowCrypter{}
   110  	metadata, entries, snapshot := makeWALData(1, 1)
   111  
   112  	// metadata is correctly encryptd, but entries are not meow-encryptd
   113  	for i, entry := range entries {
   114  		r := api.MaybeEncryptedRecord{Data: entry.Data, Algorithm: crypter.Algorithm()}
   115  		data, err := r.Marshal()
   116  		require.NoError(t, err)
   117  		entries[i].Data = data
   118  	}
   119  
   120  	tempdir := createWithWAL(t, OriginalWAL, metadata, snapshot, entries)
   121  	defer os.RemoveAll(tempdir)
   122  
   123  	c := NewWALFactory(encryption.NoopCrypter, crypter)
   124  	wrapped, err := c.Open(tempdir, snapshot)
   125  	require.NoError(t, err)
   126  
   127  	_, _, _, err = wrapped.ReadAll()
   128  	require.Error(t, err)
   129  	require.Contains(t, err.Error(), "not meowcoded")
   130  	require.NoError(t, wrapped.Close())
   131  }
   132  
   133  // The entry data and metadata are encryptd with the given encrypter, and a regular
   134  // WAL will see them as such.
   135  func TestSave(t *testing.T) {
   136  	metadata, entries, snapshot := makeWALData(1, 1)
   137  
   138  	crypter := &meowCrypter{}
   139  	c := NewWALFactory(crypter, encryption.NoopCrypter)
   140  	tempdir := createWithWAL(t, c, metadata, snapshot, entries)
   141  	defer os.RemoveAll(tempdir)
   142  
   143  	ogWAL, err := OriginalWAL.Open(tempdir, snapshot)
   144  	require.NoError(t, err)
   145  	defer ogWAL.Close()
   146  
   147  	meta, state, ents, err := ogWAL.ReadAll()
   148  	require.NoError(t, err)
   149  	require.Equal(t, metadata, meta)
   150  	require.Equal(t, state, state)
   151  	for _, ent := range ents {
   152  		var encrypted api.MaybeEncryptedRecord
   153  		require.NoError(t, encrypted.Unmarshal(ent.Data))
   154  
   155  		require.Equal(t, crypter.Algorithm(), encrypted.Algorithm)
   156  		require.True(t, bytes.HasSuffix(encrypted.Data, []byte("🐱")))
   157  	}
   158  }
   159  
   160  // If encryption fails, saving will fail
   161  func TestSaveEncryptionFails(t *testing.T) {
   162  	metadata, entries, snapshot := makeWALData(1, 1)
   163  
   164  	tempdir, err := ioutil.TempDir("", "waltests")
   165  	require.NoError(t, err)
   166  	os.RemoveAll(tempdir)
   167  	defer os.RemoveAll(tempdir)
   168  
   169  	// fail encrypting one of the entries, but not the first one
   170  	c := NewWALFactory(&meowCrypter{encryptFailures: map[string]struct{}{
   171  		"Entry 3": {},
   172  	}}, nil)
   173  	wrapped, err := c.Create(tempdir, metadata)
   174  	require.NoError(t, err)
   175  
   176  	require.NoError(t, wrapped.SaveSnapshot(snapshot))
   177  	err = wrapped.Save(raftpb.HardState{}, entries)
   178  	require.Error(t, err)
   179  	require.Contains(t, err.Error(), "refusing to encrypt")
   180  	require.NoError(t, wrapped.Close())
   181  
   182  	// no entries are written at all
   183  	ogWAL, err := OriginalWAL.Open(tempdir, snapshot)
   184  	require.NoError(t, err)
   185  	defer ogWAL.Close()
   186  
   187  	_, _, ents, err := ogWAL.ReadAll()
   188  	require.NoError(t, err)
   189  	require.Empty(t, ents)
   190  }
   191  
   192  // If the underlying WAL returns an error when opening or creating, the error
   193  // is propagated up.
   194  func TestCreateOpenInvalidDirFails(t *testing.T) {
   195  	c := NewWALFactory(encryption.NoopCrypter, encryption.NoopCrypter)
   196  
   197  	_, err := c.Create("/not/existing/directory", []byte("metadata"))
   198  	require.Error(t, err)
   199  
   200  	tempDir, err := ioutil.TempDir("", "test-migrate")
   201  	require.NoError(t, err)
   202  	defer os.RemoveAll(tempDir)
   203  
   204  	_, err = c.Open(tempDir, walpb.Snapshot{}) // invalid because no WAL file
   205  	require.Error(t, err)
   206  }
   207  
   208  // A WAL can read what it wrote so long as it has a corresponding decrypter
   209  func TestSaveAndRead(t *testing.T) {
   210  	crypter := &meowCrypter{}
   211  	metadata, entries, snapshot := makeWALData(1, 1)
   212  
   213  	c := NewWALFactory(crypter, crypter)
   214  	tempdir := createWithWAL(t, c, metadata, snapshot, entries)
   215  	defer os.RemoveAll(tempdir)
   216  
   217  	wrapped, err := c.Open(tempdir, snapshot)
   218  	require.NoError(t, err)
   219  
   220  	meta, _, ents, err := wrapped.ReadAll()
   221  	require.NoError(t, wrapped.Close())
   222  	require.NoError(t, err)
   223  	require.Equal(t, metadata, meta)
   224  	require.Equal(t, entries, ents)
   225  }
   226  
   227  func TestReadRepairWAL(t *testing.T) {
   228  	metadata, entries, snapshot := makeWALData(1, 1)
   229  	tempdir := createWithWAL(t, OriginalWAL, metadata, snapshot, entries)
   230  	defer os.RemoveAll(tempdir)
   231  
   232  	// there should only be one WAL file in there - corrupt it
   233  	files, err := ioutil.ReadDir(tempdir)
   234  	require.NoError(t, err)
   235  	require.Len(t, files, 1)
   236  
   237  	fName := filepath.Join(tempdir, files[0].Name())
   238  	fileContents, err := ioutil.ReadFile(fName)
   239  	require.NoError(t, err)
   240  	require.NoError(t, ioutil.WriteFile(fName, fileContents[:200], files[0].Mode()))
   241  
   242  	ogWAL, err := OriginalWAL.Open(tempdir, snapshot)
   243  	require.NoError(t, err)
   244  	_, _, _, err = ogWAL.ReadAll()
   245  	require.Error(t, err)
   246  	require.NoError(t, ogWAL.Close())
   247  
   248  	ogWAL, waldata, err := ReadRepairWAL(context.Background(), tempdir, snapshot, OriginalWAL)
   249  	require.NoError(t, err)
   250  	require.Equal(t, metadata, waldata.Metadata)
   251  	require.NoError(t, ogWAL.Close())
   252  }
   253  
   254  func TestMigrateWALs(t *testing.T) {
   255  	metadata, entries, snapshot := makeWALData(1, 1)
   256  	coder := &meowCrypter{}
   257  	c := NewWALFactory(coder, coder)
   258  
   259  	var (
   260  		err  error
   261  		dirs = make([]string, 2)
   262  	)
   263  
   264  	tempDir, err := ioutil.TempDir("", "test-migrate")
   265  	require.NoError(t, err)
   266  	defer os.RemoveAll(tempDir)
   267  
   268  	for i := range dirs {
   269  		dirs[i] = filepath.Join(tempDir, fmt.Sprintf("walDir%d", i))
   270  	}
   271  
   272  	origDir := createWithWAL(t, OriginalWAL, metadata, snapshot, entries)
   273  	defer os.RemoveAll(origDir)
   274  
   275  	// original to new
   276  	oldDir := origDir
   277  	newDir := dirs[0]
   278  
   279  	err = MigrateWALs(context.Background(), oldDir, newDir, OriginalWAL, c, snapshot)
   280  	require.NoError(t, err)
   281  
   282  	newWAL, err := c.Open(newDir, snapshot)
   283  	require.NoError(t, err)
   284  	meta, _, ents, err := newWAL.ReadAll()
   285  	require.NoError(t, err)
   286  	require.Equal(t, metadata, meta)
   287  	require.Equal(t, entries, ents)
   288  	require.NoError(t, newWAL.Close())
   289  
   290  	// new to original
   291  	oldDir = dirs[0]
   292  	newDir = dirs[1]
   293  
   294  	err = MigrateWALs(context.Background(), oldDir, newDir, c, OriginalWAL, snapshot)
   295  	require.NoError(t, err)
   296  
   297  	newWAL, err = OriginalWAL.Open(newDir, snapshot)
   298  	require.NoError(t, err)
   299  	meta, _, ents, err = newWAL.ReadAll()
   300  	require.NoError(t, err)
   301  	require.Equal(t, metadata, meta)
   302  	require.Equal(t, entries, ents)
   303  	require.NoError(t, newWAL.Close())
   304  
   305  	// If we can't read the old directory (for instance if it doesn't exist), a temp directory
   306  	// is not created
   307  	for _, dir := range dirs {
   308  		require.NoError(t, os.RemoveAll(dir))
   309  	}
   310  	oldDir = dirs[0]
   311  	newDir = dirs[1]
   312  
   313  	err = MigrateWALs(context.Background(), oldDir, newDir, OriginalWAL, c, walpb.Snapshot{})
   314  	require.Error(t, err)
   315  
   316  	subdirs, err := ioutil.ReadDir(tempDir)
   317  	require.NoError(t, err)
   318  	require.Empty(t, subdirs)
   319  }