github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/helper/boltdd/boltdd.go (about)

     1  // Package boltdd contains a wrapper around BBoltDB to deduplicate writes and encode
     2  // values using mgspack.  (dd stands for de-duplicate)
     3  package boltdd
     4  
     5  import (
     6  	"bytes"
     7  	"fmt"
     8  	"os"
     9  	"sync"
    10  
    11  	"github.com/hashicorp/go-msgpack/codec"
    12  	"github.com/hashicorp/nomad/nomad/structs"
    13  	"go.etcd.io/bbolt"
    14  	"golang.org/x/crypto/blake2b"
    15  )
    16  
    17  // ErrNotFound is returned when a key is not found.
    18  type ErrNotFound struct {
    19  	name string
    20  }
    21  
    22  func (e *ErrNotFound) Error() string {
    23  	return fmt.Sprintf("key not found: %s", e.name)
    24  }
    25  
    26  // NotFound returns a new error for a key that was not found.
    27  func NotFound(name string) error {
    28  	return &ErrNotFound{name}
    29  }
    30  
    31  // IsErrNotFound returns true if the error is an ErrNotFound error.
    32  func IsErrNotFound(e error) bool {
    33  	if e == nil {
    34  		return false
    35  	}
    36  	_, ok := e.(*ErrNotFound)
    37  	return ok
    38  }
    39  
    40  // DB wraps an underlying bolt.DB to create write de-duplicating buckets and
    41  // msgpack encoded values.
    42  type DB struct {
    43  	rootBuckets     map[string]*bucketMeta
    44  	rootBucketsLock sync.Mutex
    45  
    46  	boltDB *bbolt.DB
    47  }
    48  
    49  // Open a bolt.DB and wrap it in a write-de-duplicating msgpack-encoding
    50  // implementation.
    51  func Open(path string, mode os.FileMode, options *bbolt.Options) (*DB, error) {
    52  	bdb, err := bbolt.Open(path, mode, options)
    53  	if err != nil {
    54  		return nil, err
    55  	}
    56  
    57  	return New(bdb), nil
    58  }
    59  
    60  // New de-duplicating wrapper for the given bboltdb.
    61  func New(bdb *bbolt.DB) *DB {
    62  	return &DB{
    63  		rootBuckets: make(map[string]*bucketMeta),
    64  		boltDB:      bdb,
    65  	}
    66  }
    67  
    68  func (db *DB) bucket(btx *bbolt.Tx, name []byte) *Bucket {
    69  	bb := btx.Bucket(name)
    70  	if bb == nil {
    71  		return nil
    72  	}
    73  
    74  	db.rootBucketsLock.Lock()
    75  	defer db.rootBucketsLock.Unlock()
    76  
    77  	if db.isClosed() {
    78  		return nil
    79  	}
    80  
    81  	b, ok := db.rootBuckets[string(name)]
    82  	if !ok {
    83  		b = newBucketMeta()
    84  		db.rootBuckets[string(name)] = b
    85  	}
    86  
    87  	return newBucket(b, bb)
    88  }
    89  
    90  func (db *DB) createBucket(btx *bbolt.Tx, name []byte) (*Bucket, error) {
    91  	bb, err := btx.CreateBucket(name)
    92  	if err != nil {
    93  		return nil, err
    94  	}
    95  
    96  	db.rootBucketsLock.Lock()
    97  	defer db.rootBucketsLock.Unlock()
    98  
    99  	// While creating a bucket on a closed db would error, we must recheck
   100  	// after acquiring the lock to avoid races.
   101  	if db.isClosed() {
   102  		return nil, bbolt.ErrDatabaseNotOpen
   103  	}
   104  
   105  	// Always create a new Bucket since CreateBucket above fails if the
   106  	// bucket already exists.
   107  	b := newBucketMeta()
   108  	db.rootBuckets[string(name)] = b
   109  
   110  	return newBucket(b, bb), nil
   111  }
   112  
   113  func (db *DB) createBucketIfNotExists(btx *bbolt.Tx, name []byte) (*Bucket, error) {
   114  	bb, err := btx.CreateBucketIfNotExists(name)
   115  	if err != nil {
   116  		return nil, err
   117  	}
   118  
   119  	db.rootBucketsLock.Lock()
   120  	defer db.rootBucketsLock.Unlock()
   121  
   122  	// While creating a bucket on a closed db would error, we must recheck
   123  	// after acquiring the lock to avoid races.
   124  	if db.isClosed() {
   125  		return nil, bbolt.ErrDatabaseNotOpen
   126  	}
   127  
   128  	b, ok := db.rootBuckets[string(name)]
   129  	if !ok {
   130  		b = newBucketMeta()
   131  		db.rootBuckets[string(name)] = b
   132  	}
   133  
   134  	return newBucket(b, bb), nil
   135  }
   136  
   137  func (db *DB) Update(fn func(*Tx) error) error {
   138  	return db.boltDB.Update(func(btx *bbolt.Tx) error {
   139  		tx := newTx(db, btx)
   140  		return fn(tx)
   141  	})
   142  }
   143  
   144  func (db *DB) Batch(fn func(*Tx) error) error {
   145  	return db.boltDB.Batch(func(btx *bbolt.Tx) error {
   146  		tx := newTx(db, btx)
   147  		return fn(tx)
   148  	})
   149  }
   150  
   151  func (db *DB) View(fn func(*Tx) error) error {
   152  	return db.boltDB.View(func(btx *bbolt.Tx) error {
   153  		tx := newTx(db, btx)
   154  		return fn(tx)
   155  	})
   156  }
   157  
   158  // isClosed returns true if the database is closed and must be called while
   159  // db.rootBucketsLock is acquired.
   160  func (db *DB) isClosed() bool {
   161  	return db.rootBuckets == nil
   162  }
   163  
   164  // Close closes the underlying bolt.DB and clears all bucket hashes. DB is
   165  // unusable after closing.
   166  func (db *DB) Close() error {
   167  	db.rootBucketsLock.Lock()
   168  	db.rootBuckets = nil
   169  	db.rootBucketsLock.Unlock()
   170  	return db.boltDB.Close()
   171  }
   172  
   173  // BoltDB returns the underlying bolt.DB.
   174  func (db *DB) BoltDB() *bbolt.DB {
   175  	return db.boltDB
   176  }
   177  
   178  type Tx struct {
   179  	db  *DB
   180  	btx *bbolt.Tx
   181  }
   182  
   183  func newTx(db *DB, btx *bbolt.Tx) *Tx {
   184  	return &Tx{
   185  		db:  db,
   186  		btx: btx,
   187  	}
   188  }
   189  
   190  // Bucket returns a root bucket or nil if it doesn't exist.
   191  func (tx *Tx) Bucket(name []byte) *Bucket {
   192  	return tx.db.bucket(tx.btx, name)
   193  }
   194  
   195  func (tx *Tx) CreateBucket(name []byte) (*Bucket, error) {
   196  	return tx.db.createBucket(tx.btx, name)
   197  }
   198  
   199  // CreateBucketIfNotExists returns a root bucket or creates a new one if it
   200  // doesn't already exist.
   201  func (tx *Tx) CreateBucketIfNotExists(name []byte) (*Bucket, error) {
   202  	return tx.db.createBucketIfNotExists(tx.btx, name)
   203  }
   204  
   205  // Writable wraps boltdb Tx.Writable.
   206  func (tx *Tx) Writable() bool {
   207  	return tx.btx.Writable()
   208  }
   209  
   210  // BoltTx returns the underlying bolt.Tx.
   211  func (tx *Tx) BoltTx() *bbolt.Tx {
   212  	return tx.btx
   213  }
   214  
   215  // bucketMeta persists metadata -- such as key hashes and child buckets --
   216  // about boltdb Buckets across transactions.
   217  type bucketMeta struct {
   218  	// hashes holds all of the value hashes for keys in this bucket
   219  	hashes     map[string][]byte
   220  	hashesLock sync.Mutex
   221  
   222  	// buckets holds all of the child buckets
   223  	buckets     map[string]*bucketMeta
   224  	bucketsLock sync.Mutex
   225  }
   226  
   227  func newBucketMeta() *bucketMeta {
   228  	return &bucketMeta{
   229  		hashes:  make(map[string][]byte),
   230  		buckets: make(map[string]*bucketMeta),
   231  	}
   232  }
   233  
   234  // getHash of last value written to a key or nil if no hash exists.
   235  func (bm *bucketMeta) getHash(hashKey string) []byte {
   236  	bm.hashesLock.Lock()
   237  	lastHash := bm.hashes[hashKey]
   238  	bm.hashesLock.Unlock()
   239  	return lastHash
   240  }
   241  
   242  // setHash of last value written to key.
   243  func (bm *bucketMeta) setHash(hashKey string, hashVal []byte) {
   244  	bm.hashesLock.Lock()
   245  	bm.hashes[hashKey] = hashVal
   246  	bm.hashesLock.Unlock()
   247  }
   248  
   249  // delHash deletes a hash value or does nothing if the hash key does not exist.
   250  func (bm *bucketMeta) delHash(hashKey string) {
   251  	bm.hashesLock.Lock()
   252  	delete(bm.hashes, hashKey)
   253  	bm.hashesLock.Unlock()
   254  }
   255  
   256  // createBucket metadata entry for the given nested bucket. Overwrites any
   257  // existing entry so caller should ensure bucket does not already exist.
   258  func (bm *bucketMeta) createBucket(name []byte) *bucketMeta {
   259  	bm.bucketsLock.Lock()
   260  	defer bm.bucketsLock.Unlock()
   261  
   262  	// Always create a new Bucket since CreateBucket above fails if the
   263  	// bucket already exists.
   264  	b := newBucketMeta()
   265  	bm.buckets[string(name)] = b
   266  	return b
   267  }
   268  
   269  // deleteBucket metadata entry for the given nested bucket. Does nothing if
   270  // nested bucket metadata does not exist.
   271  func (bm *bucketMeta) deleteBucket(name []byte) {
   272  	bm.bucketsLock.Lock()
   273  	delete(bm.buckets, string(name))
   274  	bm.bucketsLock.Unlock()
   275  
   276  }
   277  
   278  // getOrCreateBucket metadata entry for the given nested bucket.
   279  func (bm *bucketMeta) getOrCreateBucket(name []byte) *bucketMeta {
   280  	bm.bucketsLock.Lock()
   281  	defer bm.bucketsLock.Unlock()
   282  
   283  	b, ok := bm.buckets[string(name)]
   284  	if !ok {
   285  		b = newBucketMeta()
   286  		bm.buckets[string(name)] = b
   287  	}
   288  	return b
   289  }
   290  
   291  type Bucket struct {
   292  	bm         *bucketMeta
   293  	boltBucket *bbolt.Bucket
   294  }
   295  
   296  // newBucket creates a new view into a bucket backed by a boltdb
   297  // transaction.
   298  func newBucket(b *bucketMeta, bb *bbolt.Bucket) *Bucket {
   299  	return &Bucket{
   300  		bm:         b,
   301  		boltBucket: bb,
   302  	}
   303  }
   304  
   305  // Put into boltdb iff it has changed since the last write.
   306  func (b *Bucket) Put(key []byte, val interface{}) error {
   307  	// buffer for writing serialized state to
   308  	var buf bytes.Buffer
   309  
   310  	// Serialize the object
   311  	if err := codec.NewEncoder(&buf, structs.MsgpackHandle).Encode(val); err != nil {
   312  		return fmt.Errorf("failed to encode passed object: %v", err)
   313  	}
   314  
   315  	// Hash for skipping unnecessary writes
   316  	hashKey := string(key)
   317  	hashVal := blake2b.Sum256(buf.Bytes())
   318  
   319  	// lastHash value or nil if it hasn't been hashed yet
   320  	lastHash := b.bm.getHash(hashKey)
   321  
   322  	// If the hashes are equal, skip the write
   323  	if bytes.Equal(hashVal[:], lastHash) {
   324  		return nil
   325  	}
   326  
   327  	// New value: write it to the underlying boltdb
   328  	if err := b.boltBucket.Put(key, buf.Bytes()); err != nil {
   329  		return fmt.Errorf("failed to write data at key %s: %v", key, err)
   330  	}
   331  
   332  	// New value written, store hash (bucket path map was created above)
   333  	b.bm.setHash(hashKey, hashVal[:])
   334  
   335  	return nil
   336  
   337  }
   338  
   339  // Get value by key from boltdb or return an ErrNotFound error if key not
   340  // found.
   341  func (b *Bucket) Get(key []byte, obj interface{}) error {
   342  	// Get the raw data from the underlying boltdb
   343  	data := b.boltBucket.Get(key)
   344  	if data == nil {
   345  		return NotFound(string(key))
   346  	}
   347  
   348  	// Deserialize the object
   349  	if err := codec.NewDecoderBytes(data, structs.MsgpackHandle).Decode(obj); err != nil {
   350  		return fmt.Errorf("failed to decode data into passed object: %v", err)
   351  	}
   352  
   353  	return nil
   354  }
   355  
   356  // Iterate iterates each key in Bucket b that starts with prefix. fn is called on
   357  // the key and msg-pack decoded value. If prefix is empty or nil, all keys in the
   358  // bucket are iterated.
   359  //
   360  // b must already exist.
   361  func Iterate[T any](b *Bucket, prefix []byte, fn func([]byte, T)) error {
   362  	c := b.boltBucket.Cursor()
   363  	for k, data := c.Seek(prefix); k != nil && bytes.HasPrefix(k, prefix); k, data = c.Next() {
   364  		var obj T
   365  		if err := codec.NewDecoderBytes(data, structs.MsgpackHandle).Decode(&obj); err != nil {
   366  			return fmt.Errorf("failed to decode data into passed object: %v", err)
   367  		}
   368  		fn(k, obj)
   369  	}
   370  	return nil
   371  }
   372  
   373  // DeletePrefix removes all keys starting with prefix from the bucket. If no keys
   374  // with prefix exist then nothing is done and a nil error is returned. Returns an
   375  // error if the bucket was created from a read-only transaction.
   376  //
   377  // b must already exist.
   378  func (b *Bucket) DeletePrefix(prefix []byte) error {
   379  	c := b.boltBucket.Cursor()
   380  	for k, _ := c.Seek(prefix); k != nil && bytes.HasPrefix(k, prefix); k, _ = c.Next() {
   381  		if err := c.Delete(); err != nil {
   382  			return err
   383  		}
   384  		b.bm.delHash(string(k))
   385  	}
   386  	return nil
   387  }
   388  
   389  // Delete removes a key from the bucket. If the key does not exist then nothing
   390  // is done and a nil error is returned. Returns an error if the bucket was
   391  // created from a read-only transaction.
   392  func (b *Bucket) Delete(key []byte) error {
   393  	err := b.boltBucket.Delete(key)
   394  	b.bm.delHash(string(key))
   395  	return err
   396  }
   397  
   398  // Bucket represents a boltdb Bucket and its associated metadata necessary for
   399  // write deduplication. Like bolt.Buckets it is only valid for the duration of
   400  // the transaction that created it.
   401  func (b *Bucket) Bucket(name []byte) *Bucket {
   402  	bb := b.boltBucket.Bucket(name)
   403  	if bb == nil {
   404  		return nil
   405  	}
   406  
   407  	bmeta := b.bm.getOrCreateBucket(name)
   408  	return newBucket(bmeta, bb)
   409  }
   410  
   411  // CreateBucket creates a new bucket at the given key and returns the new
   412  // bucket. Returns an error if the key already exists, if the bucket name is
   413  // blank, or if the bucket name is too long. The bucket instance is only valid
   414  // for the lifetime of the transaction.
   415  func (b *Bucket) CreateBucket(name []byte) (*Bucket, error) {
   416  	bb, err := b.boltBucket.CreateBucket(name)
   417  	if err != nil {
   418  		return nil, err
   419  	}
   420  
   421  	bmeta := b.bm.createBucket(name)
   422  	return newBucket(bmeta, bb), nil
   423  }
   424  
   425  // CreateBucketIfNotExists creates a new bucket if it doesn't already exist and
   426  // returns a reference to it. The bucket instance is only valid for the
   427  // lifetime of the transaction.
   428  func (b *Bucket) CreateBucketIfNotExists(name []byte) (*Bucket, error) {
   429  	bb, err := b.boltBucket.CreateBucketIfNotExists(name)
   430  	if err != nil {
   431  		return nil, err
   432  	}
   433  
   434  	bmeta := b.bm.getOrCreateBucket(name)
   435  	return newBucket(bmeta, bb), nil
   436  }
   437  
   438  // DeleteBucket deletes a child bucket. Returns an error if the bucket
   439  // corresponds to a non-bucket key or another error is encountered. No error is
   440  // returned if the bucket does not exist.
   441  func (b *Bucket) DeleteBucket(name []byte) error {
   442  	// Delete the bucket from the underlying boltdb
   443  	err := b.boltBucket.DeleteBucket(name)
   444  	if err == bbolt.ErrBucketNotFound {
   445  		err = nil
   446  	}
   447  
   448  	// Remove reference to child bucket
   449  	b.bm.deleteBucket(name)
   450  	return err
   451  }
   452  
   453  // BoltBucket returns the internal bolt.Bucket for this Bucket. Only valid
   454  // for the duration of the current transaction.
   455  func (b *Bucket) BoltBucket() *bbolt.Bucket {
   456  	return b.boltBucket
   457  }