open-vault/sdk/physical/transactions.go

151 lines
3.7 KiB
Go
Raw Normal View History

2017-02-17 14:15:35 +00:00
package physical
2017-10-23 20:42:56 +00:00
import (
"context"
2022-09-13 17:03:19 +00:00
"fmt"
2022-09-13 17:03:19 +00:00
"github.com/hashicorp/go-multierror"
2017-10-23 20:42:56 +00:00
)
2017-02-17 14:15:35 +00:00
// 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
}
2022-09-13 17:03:19 +00:00
func (t *TxnEntry) String() string {
return fmt.Sprintf("Operation: %s. Entry: %s", t.Operation, t.Entry)
}
2017-02-17 14:15:35 +00:00
// 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
2017-02-17 14:15:35 +00:00
}
2018-09-18 03:03:00 +00:00
type TransactionalBackend interface {
Backend
Transactional
}
2017-02-17 14:15:35 +00:00
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
2017-02-17 14:15:35 +00:00
}
// Implements the transaction interface
func GenericTransactionHandler(ctx context.Context, t PseudoTransactional, txns []*TxnEntry) (retErr error) {
2017-10-23 20:42:56 +00:00
rollbackStack := make([]*TxnEntry, 0, len(txns))
2017-02-17 14:15:35 +00:00
var dirty bool
2022-09-13 17:03:19 +00:00
// 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
}
}
}
2017-02-17 14:15:35 +00:00
// 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)
2017-02-17 14:15:35 +00:00
if err != nil {
retErr = multierror.Append(retErr, err)
dirty = true
break TxnWalk
}
if entry == nil {
// Nothing to delete or roll back
continue
}
2017-10-23 20:42:56 +00:00
rollbackEntry := &TxnEntry{
2017-02-17 14:15:35 +00:00
Operation: PutOperation,
Entry: &Entry{
2017-11-30 14:43:07 +00:00
Key: entry.Key,
Value: entry.Value,
2017-02-17 14:15:35 +00:00
},
}
err = t.DeleteInternal(ctx, txn.Entry.Key)
2017-02-17 14:15:35 +00:00
if err != nil {
retErr = multierror.Append(retErr, err)
dirty = true
break TxnWalk
}
2017-10-23 20:42:56 +00:00
rollbackStack = append([]*TxnEntry{rollbackEntry}, rollbackStack...)
2017-02-17 14:15:35 +00:00
case PutOperation:
entry, err := t.GetInternal(ctx, txn.Entry.Key)
2017-02-17 14:15:35 +00:00
if err != nil {
retErr = multierror.Append(retErr, err)
dirty = true
break TxnWalk
}
2022-09-13 17:03:19 +00:00
2017-02-17 14:15:35 +00:00
// Nothing existed so in fact rolling back requires a delete
2017-10-23 20:42:56 +00:00
var rollbackEntry *TxnEntry
2017-02-17 14:15:35 +00:00
if entry == nil {
2017-10-23 20:42:56 +00:00
rollbackEntry = &TxnEntry{
2017-02-17 14:15:35 +00:00
Operation: DeleteOperation,
Entry: &Entry{
Key: txn.Entry.Key,
},
}
} else {
2017-10-23 20:42:56 +00:00
rollbackEntry = &TxnEntry{
2017-02-17 14:15:35 +00:00
Operation: PutOperation,
Entry: &Entry{
2017-11-30 14:43:07 +00:00
Key: entry.Key,
Value: entry.Value,
2017-02-17 14:15:35 +00:00
},
}
}
2017-10-23 20:42:56 +00:00
err = t.PutInternal(ctx, txn.Entry)
2017-02-17 14:15:35 +00:00
if err != nil {
retErr = multierror.Append(retErr, err)
dirty = true
break TxnWalk
}
2017-10-23 20:42:56 +00:00
rollbackStack = append([]*TxnEntry{rollbackEntry}, rollbackStack...)
2017-02-17 14:15:35 +00:00
}
}
// 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)
2017-02-17 14:15:35 +00:00
if err != nil {
retErr = multierror.Append(retErr, err)
}
case PutOperation:
err := t.PutInternal(ctx, txn.Entry)
2017-02-17 14:15:35 +00:00
if err != nil {
retErr = multierror.Append(retErr, err)
}
}
}
}
2017-02-20 16:40:36 +00:00
return
2017-02-17 14:15:35 +00:00
}