github.com/Prakhar-Agarwal-byte/moby@v0.0.0-20231027092010-a14e3e8ab87e/libnetwork/internal/kvstore/boltdb/boltdb.go (about) 1 package boltdb 2 3 import ( 4 "bytes" 5 "encoding/binary" 6 "errors" 7 "os" 8 "path/filepath" 9 "sync" 10 "sync/atomic" 11 "time" 12 13 store "github.com/Prakhar-Agarwal-byte/moby/libnetwork/internal/kvstore" 14 bolt "go.etcd.io/bbolt" 15 ) 16 17 var ( 18 // ErrMultipleEndpointsUnsupported is thrown when multiple endpoints specified for 19 // BoltDB. Endpoint has to be a local file path 20 ErrMultipleEndpointsUnsupported = errors.New("boltdb supports one endpoint and should be a file path") 21 // ErrBoltBucketOptionMissing is thrown when boltBcuket config option is missing 22 ErrBoltBucketOptionMissing = errors.New("boltBucket config option missing") 23 ) 24 25 const filePerm = 0o644 26 27 // BoltDB type implements the Store interface 28 type BoltDB struct { 29 mu sync.Mutex 30 client *bolt.DB 31 boltBucket []byte 32 dbIndex uint64 33 path string 34 timeout time.Duration 35 // By default libkv opens and closes the bolt DB connection for every 36 // get/put operation. This allows multiple apps to use a Bolt DB at the 37 // same time. 38 // PersistConnection flag provides an option to override ths behavior. 39 // ie: open the connection in New and use it till Close is called. 40 PersistConnection bool 41 } 42 43 const ( 44 libkvmetadatalen = 8 45 transientTimeout = time.Duration(10) * time.Second 46 ) 47 48 // New opens a new BoltDB connection to the specified path and bucket 49 func New(endpoints []string, options *store.Config) (store.Store, error) { 50 if len(endpoints) > 1 { 51 return nil, ErrMultipleEndpointsUnsupported 52 } 53 54 if (options == nil) || (len(options.Bucket) == 0) { 55 return nil, ErrBoltBucketOptionMissing 56 } 57 58 dir, _ := filepath.Split(endpoints[0]) 59 if err := os.MkdirAll(dir, 0o750); err != nil { 60 return nil, err 61 } 62 63 var db *bolt.DB 64 if options.PersistConnection { 65 var err error 66 db, err = bolt.Open(endpoints[0], filePerm, &bolt.Options{ 67 Timeout: options.ConnectionTimeout, 68 }) 69 if err != nil { 70 return nil, err 71 } 72 } 73 74 timeout := transientTimeout 75 if options.ConnectionTimeout != 0 { 76 timeout = options.ConnectionTimeout 77 } 78 79 b := &BoltDB{ 80 client: db, 81 path: endpoints[0], 82 boltBucket: []byte(options.Bucket), 83 timeout: timeout, 84 PersistConnection: options.PersistConnection, 85 } 86 87 return b, nil 88 } 89 90 func (b *BoltDB) reset() { 91 b.path = "" 92 b.boltBucket = []byte{} 93 } 94 95 func (b *BoltDB) getDBhandle() (*bolt.DB, error) { 96 if !b.PersistConnection { 97 db, err := bolt.Open(b.path, filePerm, &bolt.Options{Timeout: b.timeout}) 98 if err != nil { 99 return nil, err 100 } 101 b.client = db 102 } 103 return b.client, nil 104 } 105 106 func (b *BoltDB) releaseDBhandle() { 107 if !b.PersistConnection { 108 b.client.Close() 109 } 110 } 111 112 // Get the value at "key". BoltDB doesn't provide an inbuilt last modified index with every kv pair. Its implemented by 113 // by a atomic counter maintained by the libkv and appened to the value passed by the client. 114 func (b *BoltDB) Get(key string) (*store.KVPair, error) { 115 b.mu.Lock() 116 defer b.mu.Unlock() 117 118 db, err := b.getDBhandle() 119 if err != nil { 120 return nil, err 121 } 122 defer b.releaseDBhandle() 123 124 var val []byte 125 err = db.View(func(tx *bolt.Tx) error { 126 bucket := tx.Bucket(b.boltBucket) 127 if bucket == nil { 128 return store.ErrKeyNotFound 129 } 130 131 v := bucket.Get([]byte(key)) 132 val = make([]byte, len(v)) 133 copy(val, v) 134 135 return nil 136 }) 137 if err != nil { 138 return nil, err 139 } 140 if len(val) == 0 { 141 return nil, store.ErrKeyNotFound 142 } 143 144 dbIndex := binary.LittleEndian.Uint64(val[:libkvmetadatalen]) 145 val = val[libkvmetadatalen:] 146 147 return &store.KVPair{Key: key, Value: val, LastIndex: dbIndex}, nil 148 } 149 150 // Put the key, value pair. index number metadata is prepended to the value 151 func (b *BoltDB) Put(key string, value []byte) error { 152 b.mu.Lock() 153 defer b.mu.Unlock() 154 155 db, err := b.getDBhandle() 156 if err != nil { 157 return err 158 } 159 defer b.releaseDBhandle() 160 161 return db.Update(func(tx *bolt.Tx) error { 162 bucket, err := tx.CreateBucketIfNotExists(b.boltBucket) 163 if err != nil { 164 return err 165 } 166 167 dbIndex := atomic.AddUint64(&b.dbIndex, 1) 168 dbval := make([]byte, libkvmetadatalen) 169 binary.LittleEndian.PutUint64(dbval, dbIndex) 170 dbval = append(dbval, value...) 171 172 return bucket.Put([]byte(key), dbval) 173 }) 174 } 175 176 // Exists checks if the key exists inside the store 177 func (b *BoltDB) Exists(key string) (bool, error) { 178 b.mu.Lock() 179 defer b.mu.Unlock() 180 181 db, err := b.getDBhandle() 182 if err != nil { 183 return false, err 184 } 185 defer b.releaseDBhandle() 186 187 var exists bool 188 err = db.View(func(tx *bolt.Tx) error { 189 bucket := tx.Bucket(b.boltBucket) 190 if bucket == nil { 191 return store.ErrKeyNotFound 192 } 193 194 exists = len(bucket.Get([]byte(key))) > 0 195 return nil 196 }) 197 if err != nil { 198 return false, err 199 } 200 if !exists { 201 return false, store.ErrKeyNotFound 202 } 203 return true, nil 204 } 205 206 // List returns the range of keys starting with the passed in prefix 207 func (b *BoltDB) List(keyPrefix string) ([]*store.KVPair, error) { 208 b.mu.Lock() 209 defer b.mu.Unlock() 210 211 db, err := b.getDBhandle() 212 if err != nil { 213 return nil, err 214 } 215 defer b.releaseDBhandle() 216 217 var kv []*store.KVPair 218 err = db.View(func(tx *bolt.Tx) error { 219 bucket := tx.Bucket(b.boltBucket) 220 if bucket == nil { 221 return store.ErrKeyNotFound 222 } 223 224 cursor := bucket.Cursor() 225 prefix := []byte(keyPrefix) 226 227 for key, v := cursor.Seek(prefix); bytes.HasPrefix(key, prefix); key, v = cursor.Next() { 228 dbIndex := binary.LittleEndian.Uint64(v[:libkvmetadatalen]) 229 v = v[libkvmetadatalen:] 230 val := make([]byte, len(v)) 231 copy(val, v) 232 233 kv = append(kv, &store.KVPair{ 234 Key: string(key), 235 Value: val, 236 LastIndex: dbIndex, 237 }) 238 } 239 return nil 240 }) 241 if err != nil { 242 return nil, err 243 } 244 if len(kv) == 0 { 245 return nil, store.ErrKeyNotFound 246 } 247 return kv, nil 248 } 249 250 // AtomicDelete deletes a value at "key" if the key 251 // has not been modified in the meantime, throws an 252 // error if this is the case 253 func (b *BoltDB) AtomicDelete(key string, previous *store.KVPair) error { 254 b.mu.Lock() 255 defer b.mu.Unlock() 256 257 if previous == nil { 258 return store.ErrPreviousNotSpecified 259 } 260 db, err := b.getDBhandle() 261 if err != nil { 262 return err 263 } 264 defer b.releaseDBhandle() 265 266 return db.Update(func(tx *bolt.Tx) error { 267 bucket := tx.Bucket(b.boltBucket) 268 if bucket == nil { 269 return store.ErrKeyNotFound 270 } 271 272 val := bucket.Get([]byte(key)) 273 if val == nil { 274 return store.ErrKeyNotFound 275 } 276 dbIndex := binary.LittleEndian.Uint64(val[:libkvmetadatalen]) 277 if dbIndex != previous.LastIndex { 278 return store.ErrKeyModified 279 } 280 return bucket.Delete([]byte(key)) 281 }) 282 } 283 284 // AtomicPut puts a value at "key" if the key has not been 285 // modified since the last Put, throws an error if this is the case 286 func (b *BoltDB) AtomicPut(key string, value []byte, previous *store.KVPair) (*store.KVPair, error) { 287 b.mu.Lock() 288 defer b.mu.Unlock() 289 290 db, err := b.getDBhandle() 291 if err != nil { 292 return nil, err 293 } 294 defer b.releaseDBhandle() 295 296 var dbIndex uint64 297 dbval := make([]byte, libkvmetadatalen) 298 err = db.Update(func(tx *bolt.Tx) error { 299 bucket := tx.Bucket(b.boltBucket) 300 if bucket == nil { 301 if previous != nil { 302 return store.ErrKeyNotFound 303 } 304 var err error 305 bucket, err = tx.CreateBucket(b.boltBucket) 306 if err != nil { 307 return err 308 } 309 } 310 // AtomicPut is equivalent to Put if previous is nil and the Ky 311 // doesn't exist in the DB. 312 val := bucket.Get([]byte(key)) 313 if previous == nil && len(val) != 0 { 314 return store.ErrKeyExists 315 } 316 if previous != nil { 317 if len(val) == 0 { 318 return store.ErrKeyNotFound 319 } 320 dbIndex = binary.LittleEndian.Uint64(val[:libkvmetadatalen]) 321 if dbIndex != previous.LastIndex { 322 return store.ErrKeyModified 323 } 324 } 325 dbIndex = atomic.AddUint64(&b.dbIndex, 1) 326 binary.LittleEndian.PutUint64(dbval, b.dbIndex) 327 dbval = append(dbval, value...) 328 return bucket.Put([]byte(key), dbval) 329 }) 330 if err != nil { 331 return nil, err 332 } 333 return &store.KVPair{Key: key, Value: value, LastIndex: dbIndex}, nil 334 } 335 336 // Close the db connection to the BoltDB 337 func (b *BoltDB) Close() { 338 b.mu.Lock() 339 defer b.mu.Unlock() 340 341 if !b.PersistConnection { 342 b.reset() 343 } else { 344 b.client.Close() 345 } 346 }