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 }