github.com/rish1988/moby@v25.0.2+incompatible/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/docker/docker/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 // Put the key, value pair. index number metadata is prepended to the value 113 func (b *BoltDB) Put(key string, value []byte) error { 114 b.mu.Lock() 115 defer b.mu.Unlock() 116 117 db, err := b.getDBhandle() 118 if err != nil { 119 return err 120 } 121 defer b.releaseDBhandle() 122 123 return db.Update(func(tx *bolt.Tx) error { 124 bucket, err := tx.CreateBucketIfNotExists(b.boltBucket) 125 if err != nil { 126 return err 127 } 128 129 dbIndex := atomic.AddUint64(&b.dbIndex, 1) 130 dbval := make([]byte, libkvmetadatalen) 131 binary.LittleEndian.PutUint64(dbval, dbIndex) 132 dbval = append(dbval, value...) 133 134 return bucket.Put([]byte(key), dbval) 135 }) 136 } 137 138 // Exists checks if the key exists inside the store 139 func (b *BoltDB) Exists(key string) (bool, error) { 140 b.mu.Lock() 141 defer b.mu.Unlock() 142 143 db, err := b.getDBhandle() 144 if err != nil { 145 return false, err 146 } 147 defer b.releaseDBhandle() 148 149 var exists bool 150 err = db.View(func(tx *bolt.Tx) error { 151 bucket := tx.Bucket(b.boltBucket) 152 if bucket == nil { 153 return store.ErrKeyNotFound 154 } 155 156 exists = len(bucket.Get([]byte(key))) > 0 157 return nil 158 }) 159 if err != nil { 160 return false, err 161 } 162 if !exists { 163 return false, store.ErrKeyNotFound 164 } 165 return true, nil 166 } 167 168 // List returns the range of keys starting with the passed in prefix 169 func (b *BoltDB) List(keyPrefix string) ([]*store.KVPair, error) { 170 b.mu.Lock() 171 defer b.mu.Unlock() 172 173 db, err := b.getDBhandle() 174 if err != nil { 175 return nil, err 176 } 177 defer b.releaseDBhandle() 178 179 var kv []*store.KVPair 180 err = db.View(func(tx *bolt.Tx) error { 181 bucket := tx.Bucket(b.boltBucket) 182 if bucket == nil { 183 return store.ErrKeyNotFound 184 } 185 186 cursor := bucket.Cursor() 187 prefix := []byte(keyPrefix) 188 189 for key, v := cursor.Seek(prefix); bytes.HasPrefix(key, prefix); key, v = cursor.Next() { 190 dbIndex := binary.LittleEndian.Uint64(v[:libkvmetadatalen]) 191 v = v[libkvmetadatalen:] 192 val := make([]byte, len(v)) 193 copy(val, v) 194 195 kv = append(kv, &store.KVPair{ 196 Key: string(key), 197 Value: val, 198 LastIndex: dbIndex, 199 }) 200 } 201 return nil 202 }) 203 if err != nil { 204 return nil, err 205 } 206 if len(kv) == 0 { 207 return nil, store.ErrKeyNotFound 208 } 209 return kv, nil 210 } 211 212 // AtomicDelete deletes a value at "key" if the key 213 // has not been modified in the meantime, throws an 214 // error if this is the case 215 func (b *BoltDB) AtomicDelete(key string, previous *store.KVPair) error { 216 b.mu.Lock() 217 defer b.mu.Unlock() 218 219 if previous == nil { 220 return store.ErrPreviousNotSpecified 221 } 222 db, err := b.getDBhandle() 223 if err != nil { 224 return err 225 } 226 defer b.releaseDBhandle() 227 228 return db.Update(func(tx *bolt.Tx) error { 229 bucket := tx.Bucket(b.boltBucket) 230 if bucket == nil { 231 return store.ErrKeyNotFound 232 } 233 234 val := bucket.Get([]byte(key)) 235 if val == nil { 236 return store.ErrKeyNotFound 237 } 238 dbIndex := binary.LittleEndian.Uint64(val[:libkvmetadatalen]) 239 if dbIndex != previous.LastIndex { 240 return store.ErrKeyModified 241 } 242 return bucket.Delete([]byte(key)) 243 }) 244 } 245 246 // AtomicPut puts a value at "key" if the key has not been 247 // modified since the last Put, throws an error if this is the case 248 func (b *BoltDB) AtomicPut(key string, value []byte, previous *store.KVPair) (*store.KVPair, error) { 249 b.mu.Lock() 250 defer b.mu.Unlock() 251 252 db, err := b.getDBhandle() 253 if err != nil { 254 return nil, err 255 } 256 defer b.releaseDBhandle() 257 258 var dbIndex uint64 259 dbval := make([]byte, libkvmetadatalen) 260 err = db.Update(func(tx *bolt.Tx) error { 261 bucket := tx.Bucket(b.boltBucket) 262 if bucket == nil { 263 if previous != nil { 264 return store.ErrKeyNotFound 265 } 266 var err error 267 bucket, err = tx.CreateBucket(b.boltBucket) 268 if err != nil { 269 return err 270 } 271 } 272 // AtomicPut is equivalent to Put if previous is nil and the Ky 273 // doesn't exist in the DB. 274 val := bucket.Get([]byte(key)) 275 if previous == nil && len(val) != 0 { 276 return store.ErrKeyExists 277 } 278 if previous != nil { 279 if len(val) == 0 { 280 return store.ErrKeyNotFound 281 } 282 dbIndex = binary.LittleEndian.Uint64(val[:libkvmetadatalen]) 283 if dbIndex != previous.LastIndex { 284 return store.ErrKeyModified 285 } 286 } 287 dbIndex = atomic.AddUint64(&b.dbIndex, 1) 288 binary.LittleEndian.PutUint64(dbval, b.dbIndex) 289 dbval = append(dbval, value...) 290 return bucket.Put([]byte(key), dbval) 291 }) 292 if err != nil { 293 return nil, err 294 } 295 return &store.KVPair{Key: key, Value: value, LastIndex: dbIndex}, nil 296 } 297 298 // Close the db connection to the BoltDB 299 func (b *BoltDB) Close() { 300 b.mu.Lock() 301 defer b.mu.Unlock() 302 303 if !b.PersistConnection { 304 b.reset() 305 } else { 306 b.client.Close() 307 } 308 }