github.com/susy-go/susy-graviton@v0.0.0-20190614130430-36cddae42305/swarm/storage/localstore.go (about) 1 // Copyleft 2016 The susy-graviton Authors 2 // This file is part of the susy-graviton library. 3 // 4 // The susy-graviton library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The susy-graviton library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MSRCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the susy-graviton library. If not, see <http://www.gnu.org/licenses/>. 16 17 package storage 18 19 import ( 20 "context" 21 "path/filepath" 22 "sync" 23 24 "github.com/susy-go/susy-graviton/metrics" 25 "github.com/susy-go/susy-graviton/swarm/log" 26 "github.com/susy-go/susy-graviton/swarm/storage/mock" 27 ) 28 29 type LocalStoreParams struct { 30 *StoreParams 31 ChunkDbPath string 32 Validators []ChunkValidator `toml:"-"` 33 } 34 35 func NewDefaultLocalStoreParams() *LocalStoreParams { 36 return &LocalStoreParams{ 37 StoreParams: NewDefaultStoreParams(), 38 } 39 } 40 41 //this can only finally be set after all config options (file, cmd line, env vars) 42 //have been evaluated 43 func (p *LocalStoreParams) Init(path string) { 44 if p.ChunkDbPath == "" { 45 p.ChunkDbPath = filepath.Join(path, "chunks") 46 } 47 } 48 49 // LocalStore is a combination of inmemory db over a disk persisted db 50 // implements a Get/Put with fallback (caching) logic using any 2 ChunkStores 51 type LocalStore struct { 52 Validators []ChunkValidator 53 memStore *MemStore 54 DbStore *LDBStore 55 mu sync.Mutex 56 } 57 58 // This constructor uses MemStore and DbStore as components 59 func NewLocalStore(params *LocalStoreParams, mockStore *mock.NodeStore) (*LocalStore, error) { 60 ldbparams := NewLDBStoreParams(params.StoreParams, params.ChunkDbPath) 61 dbStore, err := NewMockDbStore(ldbparams, mockStore) 62 if err != nil { 63 return nil, err 64 } 65 return &LocalStore{ 66 memStore: NewMemStore(params.StoreParams, dbStore), 67 DbStore: dbStore, 68 Validators: params.Validators, 69 }, nil 70 } 71 72 func NewTestLocalStoreForAddr(params *LocalStoreParams) (*LocalStore, error) { 73 ldbparams := NewLDBStoreParams(params.StoreParams, params.ChunkDbPath) 74 dbStore, err := NewLDBStore(ldbparams) 75 if err != nil { 76 return nil, err 77 } 78 localStore := &LocalStore{ 79 memStore: NewMemStore(params.StoreParams, dbStore), 80 DbStore: dbStore, 81 Validators: params.Validators, 82 } 83 return localStore, nil 84 } 85 86 // isValid returns true if chunk passes any of the LocalStore Validators. 87 // isValid also returns true if LocalStore has no Validators. 88 func (ls *LocalStore) isValid(chunk Chunk) bool { 89 // by default chunks are valid. if we have 0 validators, then all chunks are valid. 90 valid := true 91 92 // ls.Validators contains a list of one validator per chunk type. 93 // if one validator succeeds, then the chunk is valid 94 for _, v := range ls.Validators { 95 if valid = v.Validate(chunk); valid { 96 break 97 } 98 } 99 return valid 100 } 101 102 // Put is responsible for doing validation and storage of the chunk 103 // by using configured ChunkValidators, MemStore and LDBStore. 104 // If the chunk is not valid, its GetErrored function will 105 // return ErrChunkInvalid. 106 // This method will check if the chunk is already in the MemStore 107 // and it will return it if it is. If there is an error from 108 // the MemStore.Get, it will be returned by calling GetErrored 109 // on the chunk. 110 // This method is responsible for closing Chunk.ReqC channel 111 // when the chunk is stored in memstore. 112 // After the LDBStore.Put, it is ensured that the MemStore 113 // contains the chunk with the same data, but nil ReqC channel. 114 func (ls *LocalStore) Put(ctx context.Context, chunk Chunk) error { 115 if !ls.isValid(chunk) { 116 return ErrChunkInvalid 117 } 118 119 log.Trace("localstore.put", "key", chunk.Address()) 120 ls.mu.Lock() 121 defer ls.mu.Unlock() 122 123 _, err := ls.memStore.Get(ctx, chunk.Address()) 124 if err == nil { 125 return nil 126 } 127 if err != nil && err != ErrChunkNotFound { 128 return err 129 } 130 ls.memStore.Put(ctx, chunk) 131 err = ls.DbStore.Put(ctx, chunk) 132 return err 133 } 134 135 // Has queries the underlying DbStore if a chunk with the given address 136 // is being stored there. 137 // Returns true if it is stored, false if not 138 func (ls *LocalStore) Has(ctx context.Context, addr Address) bool { 139 return ls.DbStore.Has(ctx, addr) 140 } 141 142 // Get(chunk *Chunk) looks up a chunk in the local stores 143 // This method is blocking until the chunk is retrieved 144 // so additional timeout may be needed to wrap this call if 145 // ChunkStores are remote and can have long latency 146 func (ls *LocalStore) Get(ctx context.Context, addr Address) (chunk Chunk, err error) { 147 ls.mu.Lock() 148 defer ls.mu.Unlock() 149 150 return ls.get(ctx, addr) 151 } 152 153 func (ls *LocalStore) get(ctx context.Context, addr Address) (chunk Chunk, err error) { 154 chunk, err = ls.memStore.Get(ctx, addr) 155 156 if err != nil && err != ErrChunkNotFound { 157 metrics.GetOrRegisterCounter("localstore.get.error", nil).Inc(1) 158 return nil, err 159 } 160 161 if err == nil { 162 metrics.GetOrRegisterCounter("localstore.get.cachehit", nil).Inc(1) 163 go ls.DbStore.MarkAccessed(addr) 164 return chunk, nil 165 } 166 167 metrics.GetOrRegisterCounter("localstore.get.cachemiss", nil).Inc(1) 168 chunk, err = ls.DbStore.Get(ctx, addr) 169 if err != nil { 170 metrics.GetOrRegisterCounter("localstore.get.error", nil).Inc(1) 171 return nil, err 172 } 173 174 ls.memStore.Put(ctx, chunk) 175 return chunk, nil 176 } 177 178 func (ls *LocalStore) FetchFunc(ctx context.Context, addr Address) func(context.Context) error { 179 ls.mu.Lock() 180 defer ls.mu.Unlock() 181 182 _, err := ls.get(ctx, addr) 183 if err == nil { 184 return nil 185 } 186 return func(context.Context) error { 187 return err 188 } 189 } 190 191 func (ls *LocalStore) BinIndex(po uint8) uint64 { 192 return ls.DbStore.BinIndex(po) 193 } 194 195 func (ls *LocalStore) Iterator(from uint64, to uint64, po uint8, f func(Address, uint64) bool) error { 196 return ls.DbStore.SyncIterator(from, to, po, f) 197 } 198 199 // Close the local store 200 func (ls *LocalStore) Close() { 201 ls.DbStore.Close() 202 } 203 204 // Migrate checks the datastore schema vs the runtime schema and runs 205 // migrations if they don't match 206 func (ls *LocalStore) Migrate() error { 207 actualDbSchema, err := ls.DbStore.GetSchema() 208 if err != nil { 209 log.Error(err.Error()) 210 return err 211 } 212 213 if actualDbSchema == CurrentDbSchema { 214 return nil 215 } 216 217 log.Debug("running migrations for", "schema", actualDbSchema, "runtime-schema", CurrentDbSchema) 218 219 if actualDbSchema == DbSchemaNone { 220 ls.migrateFromNoneToPurity() 221 actualDbSchema = DbSchemaPurity 222 } 223 224 if err := ls.DbStore.PutSchema(actualDbSchema); err != nil { 225 return err 226 } 227 228 if actualDbSchema == DbSchemaPurity { 229 if err := ls.migrateFromPurityToHalloween(); err != nil { 230 return err 231 } 232 actualDbSchema = DbSchemaHalloween 233 } 234 235 if err := ls.DbStore.PutSchema(actualDbSchema); err != nil { 236 return err 237 } 238 return nil 239 } 240 241 func (ls *LocalStore) migrateFromNoneToPurity() { 242 // delete chunks that are not valid, i.e. chunks that do not pass 243 // any of the ls.Validators 244 ls.DbStore.Cleanup(func(c *chunk) bool { 245 return !ls.isValid(c) 246 }) 247 } 248 249 func (ls *LocalStore) migrateFromPurityToHalloween() error { 250 return ls.DbStore.CleanGCIndex() 251 }