mirror of
				https://github.com/optim-enterprises-bv/vault.git
				synced 2025-11-03 20:17:59 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			154 lines
		
	
	
		
			3.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			154 lines
		
	
	
		
			3.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// Copyright (c) HashiCorp, Inc.
 | 
						|
// SPDX-License-Identifier: MPL-2.0
 | 
						|
 | 
						|
package physical
 | 
						|
 | 
						|
import (
 | 
						|
	"context"
 | 
						|
	"fmt"
 | 
						|
 | 
						|
	"github.com/hashicorp/go-multierror"
 | 
						|
)
 | 
						|
 | 
						|
// TxnEntry is an operation that takes atomically as part of
 | 
						|
// a transactional update. Only supported by Transactional backends.
 | 
						|
type TxnEntry struct {
 | 
						|
	Operation Operation
 | 
						|
	Entry     *Entry
 | 
						|
}
 | 
						|
 | 
						|
func (t *TxnEntry) String() string {
 | 
						|
	return fmt.Sprintf("Operation: %s. Entry: %s", t.Operation, t.Entry)
 | 
						|
}
 | 
						|
 | 
						|
// Transactional is an optional interface for backends that
 | 
						|
// support doing transactional updates of multiple keys. This is
 | 
						|
// required for some features such as replication.
 | 
						|
type Transactional interface {
 | 
						|
	// The function to run a transaction
 | 
						|
	Transaction(context.Context, []*TxnEntry) error
 | 
						|
}
 | 
						|
 | 
						|
type TransactionalBackend interface {
 | 
						|
	Backend
 | 
						|
	Transactional
 | 
						|
}
 | 
						|
 | 
						|
type PseudoTransactional interface {
 | 
						|
	// An internal function should do no locking or permit pool acquisition.
 | 
						|
	// Depending on the backend and if it natively supports transactions, these
 | 
						|
	// may simply chain to the normal backend functions.
 | 
						|
	GetInternal(context.Context, string) (*Entry, error)
 | 
						|
	PutInternal(context.Context, *Entry) error
 | 
						|
	DeleteInternal(context.Context, string) error
 | 
						|
}
 | 
						|
 | 
						|
// Implements the transaction interface
 | 
						|
func GenericTransactionHandler(ctx context.Context, t PseudoTransactional, txns []*TxnEntry) (retErr error) {
 | 
						|
	rollbackStack := make([]*TxnEntry, 0, len(txns))
 | 
						|
	var dirty bool
 | 
						|
 | 
						|
	// Update all of our GET transaction entries, so we can populate existing values back at the wal layer.
 | 
						|
	for _, txn := range txns {
 | 
						|
		if txn.Operation == GetOperation {
 | 
						|
			entry, err := t.GetInternal(ctx, txn.Entry.Key)
 | 
						|
			if err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
			if entry != nil {
 | 
						|
				txn.Entry.Value = entry.Value
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// We walk the transactions in order; each successful operation goes into a
 | 
						|
	// LIFO for rollback if we hit an error along the way
 | 
						|
TxnWalk:
 | 
						|
	for _, txn := range txns {
 | 
						|
		switch txn.Operation {
 | 
						|
		case DeleteOperation:
 | 
						|
			entry, err := t.GetInternal(ctx, txn.Entry.Key)
 | 
						|
			if err != nil {
 | 
						|
				retErr = multierror.Append(retErr, err)
 | 
						|
				dirty = true
 | 
						|
				break TxnWalk
 | 
						|
			}
 | 
						|
			if entry == nil {
 | 
						|
				// Nothing to delete or roll back
 | 
						|
				continue
 | 
						|
			}
 | 
						|
			rollbackEntry := &TxnEntry{
 | 
						|
				Operation: PutOperation,
 | 
						|
				Entry: &Entry{
 | 
						|
					Key:   entry.Key,
 | 
						|
					Value: entry.Value,
 | 
						|
				},
 | 
						|
			}
 | 
						|
			err = t.DeleteInternal(ctx, txn.Entry.Key)
 | 
						|
			if err != nil {
 | 
						|
				retErr = multierror.Append(retErr, err)
 | 
						|
				dirty = true
 | 
						|
				break TxnWalk
 | 
						|
			}
 | 
						|
			rollbackStack = append([]*TxnEntry{rollbackEntry}, rollbackStack...)
 | 
						|
 | 
						|
		case PutOperation:
 | 
						|
			entry, err := t.GetInternal(ctx, txn.Entry.Key)
 | 
						|
			if err != nil {
 | 
						|
				retErr = multierror.Append(retErr, err)
 | 
						|
				dirty = true
 | 
						|
				break TxnWalk
 | 
						|
			}
 | 
						|
 | 
						|
			// Nothing existed so in fact rolling back requires a delete
 | 
						|
			var rollbackEntry *TxnEntry
 | 
						|
			if entry == nil {
 | 
						|
				rollbackEntry = &TxnEntry{
 | 
						|
					Operation: DeleteOperation,
 | 
						|
					Entry: &Entry{
 | 
						|
						Key: txn.Entry.Key,
 | 
						|
					},
 | 
						|
				}
 | 
						|
			} else {
 | 
						|
				rollbackEntry = &TxnEntry{
 | 
						|
					Operation: PutOperation,
 | 
						|
					Entry: &Entry{
 | 
						|
						Key:   entry.Key,
 | 
						|
						Value: entry.Value,
 | 
						|
					},
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			err = t.PutInternal(ctx, txn.Entry)
 | 
						|
			if err != nil {
 | 
						|
				retErr = multierror.Append(retErr, err)
 | 
						|
				dirty = true
 | 
						|
				break TxnWalk
 | 
						|
			}
 | 
						|
			rollbackStack = append([]*TxnEntry{rollbackEntry}, rollbackStack...)
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// Need to roll back because we hit an error along the way
 | 
						|
	if dirty {
 | 
						|
		// While traversing this, if we get an error, we continue anyways in
 | 
						|
		// best-effort fashion
 | 
						|
		for _, txn := range rollbackStack {
 | 
						|
			switch txn.Operation {
 | 
						|
			case DeleteOperation:
 | 
						|
				err := t.DeleteInternal(ctx, txn.Entry.Key)
 | 
						|
				if err != nil {
 | 
						|
					retErr = multierror.Append(retErr, err)
 | 
						|
				}
 | 
						|
			case PutOperation:
 | 
						|
				err := t.PutInternal(ctx, txn.Entry)
 | 
						|
				if err != nil {
 | 
						|
					retErr = multierror.Append(retErr, err)
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return
 | 
						|
}
 |