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  }