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  }