github.com/ethersphere/bee/v2@v2.2.0/pkg/sharky/recovery_test.go (about)

     1  // Copyright 2021 The Swarm Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package sharky_test
     6  
     7  import (
     8  	"bytes"
     9  	"context"
    10  	"encoding/binary"
    11  	"errors"
    12  	"math/rand"
    13  	"testing"
    14  	"time"
    15  
    16  	"github.com/ethersphere/bee/v2/pkg/sharky"
    17  )
    18  
    19  func TestMissingShard(t *testing.T) {
    20  	t.Parallel()
    21  
    22  	_, err := sharky.NewRecovery(t.TempDir(), 1, 8)
    23  	if !errors.Is(err, sharky.ErrShardNotFound) {
    24  		t.Fatalf("want %v, got %v", sharky.ErrShardNotFound, err)
    25  	}
    26  }
    27  
    28  // nolint:paralleltest
    29  func TestRecovery(t *testing.T) {
    30  	datasize := 4
    31  	shards := 8
    32  	shardSize := uint32(16)
    33  	limitInChunks := shards * int(shardSize)
    34  
    35  	dir := t.TempDir()
    36  	ctx := context.Background()
    37  	size := limitInChunks / 2
    38  	data := make([]byte, 4)
    39  	locs := make([]sharky.Location, size)
    40  	preserved := make(map[uint32]bool)
    41  
    42  	s := newSharky(t, dir, shards, datasize)
    43  	for i := range locs {
    44  		binary.BigEndian.PutUint32(data, uint32(i))
    45  		loc, err := s.Write(ctx, data)
    46  		if err != nil {
    47  			t.Fatal(err)
    48  		}
    49  		locs[i] = loc
    50  	}
    51  	// extract locations to preserve / free in map
    52  	indexes := make([]uint32, size)
    53  	for i := range indexes {
    54  		indexes[i] = uint32(i)
    55  	}
    56  	rest := indexes[:]
    57  	for n := size; n > size/2; n-- {
    58  		i := rand.Intn(n)
    59  		preserved[rest[i]] = false
    60  		rest = append(rest[:i], rest[i+1:]...)
    61  	}
    62  	if len(rest) != len(preserved) {
    63  		t.Fatalf("incorrect set sizes: %d <> %d", len(rest), len(preserved))
    64  	}
    65  	for _, i := range rest {
    66  		preserved[i] = true
    67  	}
    68  
    69  	t.Run("recover based on preserved map", func(t *testing.T) {
    70  		r, err := sharky.NewRecovery(dir, shards, datasize)
    71  		if err != nil {
    72  			t.Fatal(err)
    73  		}
    74  		t.Cleanup(func() {
    75  			if err := r.Close(); err != nil {
    76  				t.Fatal(err)
    77  			}
    78  		})
    79  		for i, add := range preserved {
    80  			if add {
    81  				if err := r.Add(locs[i]); err != nil {
    82  					t.Fatal(err)
    83  				}
    84  			}
    85  		}
    86  		if err := r.Save(); err != nil {
    87  			t.Fatal(err)
    88  		}
    89  	})
    90  
    91  	payload := []byte{0xff}
    92  
    93  	t.Run("check integrity of recovered sharky", func(t *testing.T) {
    94  		s := newSharky(t, dir, shards, datasize)
    95  		buf := make([]byte, datasize)
    96  		t.Run("preserved are found", func(t *testing.T) {
    97  			for i := range preserved {
    98  				loc := locs[i]
    99  				if err := s.Read(ctx, loc, buf); err != nil {
   100  					t.Fatal(err)
   101  				}
   102  				j := binary.BigEndian.Uint32(buf)
   103  				if i != j {
   104  					t.Fatalf("data not preserved at location %v: want %d; got %d", loc, i, j)
   105  				}
   106  			}
   107  		})
   108  
   109  		var freelocs []sharky.Location
   110  
   111  		t.Run("correct number of free slots", func(t *testing.T) {
   112  			s := newSharky(t, dir, 1, datasize)
   113  			cctx, cancel := context.WithTimeout(ctx, 800*time.Millisecond)
   114  			defer cancel()
   115  
   116  			runs := 96
   117  			for i := 0; i < runs; i++ {
   118  				loc, err := s.Write(cctx, payload)
   119  				if err != nil {
   120  					if errors.Is(err, context.DeadlineExceeded) {
   121  						break
   122  					}
   123  					t.Fatal(err)
   124  				}
   125  				freelocs = append(freelocs, loc)
   126  			}
   127  			if len(freelocs) != runs {
   128  				t.Fatalf("incorrect number of free slots: wanted %d; got %d", runs, len(freelocs))
   129  			}
   130  		})
   131  		t.Run("added locs are still preserved", func(t *testing.T) {
   132  			for i, added := range preserved {
   133  				if !added {
   134  					continue
   135  				}
   136  				if err := s.Read(ctx, locs[int(i)], buf); err != nil {
   137  					t.Fatal(err)
   138  				}
   139  				j := binary.BigEndian.Uint32(buf)
   140  				if i != j {
   141  					t.Fatalf("data not preserved at location %v: want %d; got %d", locs[int(j)], i, j)
   142  				}
   143  			}
   144  		})
   145  		t.Run("all other slots also overwritten", func(t *testing.T) {
   146  			for _, loc := range freelocs {
   147  				if err := s.Read(ctx, loc, buf); err != nil {
   148  					t.Fatal(err)
   149  				}
   150  				data := buf[:len(payload)]
   151  				if !bytes.Equal(data, payload) {
   152  					t.Fatalf("incorrect data on freed location %v: want %x; got %x", loc, payload, data)
   153  				}
   154  			}
   155  		})
   156  	})
   157  }
   158  
   159  func newSharky(t *testing.T, dir string, shards, datasize int) *sharky.Store {
   160  	t.Helper()
   161  	s, err := sharky.New(&dirFS{basedir: dir}, shards, datasize)
   162  	if err != nil {
   163  		t.Fatal(err)
   164  	}
   165  	t.Cleanup(func() {
   166  		if err := s.Close(); err != nil {
   167  			t.Fatal(err)
   168  		}
   169  	})
   170  
   171  	return s
   172  }