github.com/ethersphere/bee/v2@v2.2.0/pkg/storage/migration/migration.go (about)

     1  // Copyright 2022 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 migration
     6  
     7  import (
     8  	"encoding/binary"
     9  	"errors"
    10  	"fmt"
    11  	"sort"
    12  
    13  	storage "github.com/ethersphere/bee/v2/pkg/storage"
    14  	"github.com/ethersphere/bee/v2/pkg/storage/storageutil"
    15  )
    16  
    17  type (
    18  	// StepFn is a function that migrates the storage to the next version
    19  	StepFn func() error
    20  	// Steps is a map of versions and their migration functions
    21  	Steps = map[uint64]StepFn
    22  )
    23  
    24  var (
    25  	// errStorageVersionItemUnmarshalInvalidSize is returned when trying
    26  	// to unmarshal buffer that is not of size storageVersionItemSize.
    27  	errStorageVersionItemUnmarshalInvalidSize = errors.New("unmarshal StorageVersionItem: invalid size")
    28  )
    29  
    30  // Migrate migrates the storage to the latest version.
    31  // The steps are separated by groups so different lists of steps can run individually, for example,
    32  // two groups of migrations that run before and after the storer is initialized.
    33  func Migrate(s storage.IndexStore, group string, sm Steps) error {
    34  	if err := ValidateVersions(sm); err != nil {
    35  		return err
    36  	}
    37  
    38  	currentVersion, err := Version(s, group)
    39  	if err != nil {
    40  		return err
    41  	}
    42  
    43  	for nextVersion := currentVersion + 1; ; nextVersion++ {
    44  		stepFn, ok := sm[nextVersion]
    45  		if !ok {
    46  			return nil
    47  		}
    48  		err := stepFn()
    49  		if err != nil {
    50  			return err
    51  		}
    52  		err = setVersion(s, nextVersion, group)
    53  		if err != nil {
    54  			return err
    55  		}
    56  	}
    57  }
    58  
    59  // ValidateVersions checks versions if they are in order n (where n min version value), n+1, n+2, n+3... (all values are increasing orders)
    60  func ValidateVersions(sm Steps) error {
    61  	if len(sm) == 0 {
    62  		return fmt.Errorf("steps map is empty")
    63  	}
    64  	versions := make([]int, len(sm))
    65  	i := 0
    66  	for version := range sm {
    67  		versions[i] = int(version)
    68  		i++
    69  	}
    70  	sort.Ints(versions)
    71  
    72  	if (versions[i-1] - versions[0]) == i-1 {
    73  		return nil
    74  	}
    75  	return fmt.Errorf("missing versions")
    76  }
    77  
    78  var _ storage.Item = (*StorageVersionItem)(nil)
    79  
    80  // storageVersionItemSize is the size of the marshaled storage version item.
    81  const storageVersionItemSize = 8
    82  
    83  type StorageVersionItem struct {
    84  	Version uint64
    85  	Group   string
    86  }
    87  
    88  // ID implements the storage.Item interface.
    89  func (s *StorageVersionItem) ID() string {
    90  	return "storage_version"
    91  }
    92  
    93  // Namespace implements the storage.Item interface.
    94  func (s StorageVersionItem) Namespace() string {
    95  	return s.Group
    96  }
    97  
    98  // Marshal implements the storage.Item interface.
    99  func (s *StorageVersionItem) Marshal() ([]byte, error) {
   100  	buf := make([]byte, storageVersionItemSize)
   101  	binary.LittleEndian.PutUint64(buf, s.Version)
   102  	return buf, nil
   103  }
   104  
   105  // Unmarshal implements the storage.Item interface.
   106  func (s *StorageVersionItem) Unmarshal(bytes []byte) error {
   107  	if len(bytes) != storageVersionItemSize {
   108  		return errStorageVersionItemUnmarshalInvalidSize
   109  	}
   110  	s.Version = binary.LittleEndian.Uint64(bytes)
   111  	return nil
   112  }
   113  
   114  // Clone implements the storage.Item interface.
   115  func (s *StorageVersionItem) Clone() storage.Item {
   116  	if s == nil {
   117  		return nil
   118  	}
   119  	return &StorageVersionItem{
   120  		Version: s.Version,
   121  	}
   122  }
   123  
   124  // Clone implements the storage.Item interface.
   125  func (s StorageVersionItem) String() string {
   126  	return storageutil.JoinFields(s.Namespace(), s.ID())
   127  }
   128  
   129  // Version returns the current version of the storage
   130  func Version(s storage.Reader, group string) (uint64, error) {
   131  	item := StorageVersionItem{Group: group}
   132  	err := s.Get(&item)
   133  	if err != nil {
   134  		if errors.Is(err, storage.ErrNotFound) {
   135  			return 0, nil
   136  		}
   137  		return 0, err
   138  	}
   139  	return item.Version, nil
   140  }
   141  
   142  // setVersion sets the current version of the storage
   143  func setVersion(s storage.Writer, v uint64, g string) error {
   144  	return s.Put(&StorageVersionItem{Version: v, Group: g})
   145  }
   146  
   147  // LatestVersion returns latest version from supplied migration steps.
   148  func LatestVersion(sm Steps) uint64 {
   149  	var latest uint64
   150  
   151  	for version := range sm {
   152  		if version > latest {
   153  			latest = version
   154  		}
   155  	}
   156  
   157  	return latest
   158  }