github.com/hernad/nomad@v1.6.112/nomad/state/state_changes.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package state
     5  
     6  import (
     7  	"github.com/hashicorp/go-memdb"
     8  	"github.com/hernad/nomad/nomad/stream"
     9  	"github.com/hernad/nomad/nomad/structs"
    10  )
    11  
    12  // ReadTxn is implemented by memdb.Txn to perform read operations.
    13  type ReadTxn interface {
    14  	Get(table, index string, args ...interface{}) (memdb.ResultIterator, error)
    15  	First(table, index string, args ...interface{}) (interface{}, error)
    16  	FirstWatch(table, index string, args ...interface{}) (<-chan struct{}, interface{}, error)
    17  	Abort()
    18  }
    19  
    20  // Changes wraps a memdb.Changes to include the index at which these changes
    21  // were made.
    22  type Changes struct {
    23  	// Index is the latest index at the time these changes were committed.
    24  	Index   uint64
    25  	Changes memdb.Changes
    26  	MsgType structs.MessageType
    27  }
    28  
    29  // changeTrackerDB is a thin wrapper around memdb.DB which enables TrackChanges on
    30  // all write transactions. When the transaction is committed the changes are
    31  // sent to the EventBroker which will create and emit change events.
    32  type changeTrackerDB struct {
    33  	memdb          *memdb.MemDB
    34  	publisher      *stream.EventBroker
    35  	processChanges changeProcessor
    36  }
    37  
    38  func NewChangeTrackerDB(db *memdb.MemDB, publisher *stream.EventBroker, changesFn changeProcessor) *changeTrackerDB {
    39  	return &changeTrackerDB{
    40  		memdb:          db,
    41  		publisher:      publisher,
    42  		processChanges: changesFn,
    43  	}
    44  }
    45  
    46  type changeProcessor func(ReadTxn, Changes) *structs.Events
    47  
    48  func noOpProcessChanges(ReadTxn, Changes) *structs.Events { return nil }
    49  
    50  // ReadTxn returns a read-only transaction which behaves exactly the same as
    51  // memdb.Txn
    52  //
    53  // TODO: this could return a regular memdb.Txn if all the state functions accepted
    54  // the ReadTxn interface
    55  func (c *changeTrackerDB) ReadTxn() *txn {
    56  	return &txn{Txn: c.memdb.Txn(false)}
    57  }
    58  
    59  // WriteTxn returns a wrapped memdb.Txn suitable for writes to the state store.
    60  // It will track changes and publish events for the changes when Commit
    61  // is called.
    62  //
    63  // The idx argument must be the index of the current Raft operation. Almost
    64  // all mutations to state should happen as part of a raft apply so the index of
    65  // the log being applied can be passed to WriteTxn.
    66  // The exceptional cases are transactions that are executed on an empty
    67  // memdb.DB as part of Restore, and those executed by tests where we insert
    68  // data directly into the DB. These cases may use WriteTxnRestore.
    69  func (c *changeTrackerDB) WriteTxn(idx uint64) *txn {
    70  	t := &txn{
    71  		Txn:     c.memdb.Txn(true),
    72  		Index:   idx,
    73  		publish: c.publish,
    74  		msgType: structs.IgnoreUnknownTypeFlag, // The zero value of structs.MessageType is noderegistration.
    75  	}
    76  	t.Txn.TrackChanges()
    77  	return t
    78  }
    79  
    80  func (c *changeTrackerDB) WriteTxnMsgT(msgType structs.MessageType, idx uint64) *txn {
    81  	t := &txn{
    82  		msgType: msgType,
    83  		Txn:     c.memdb.Txn(true),
    84  		Index:   idx,
    85  		publish: c.publish,
    86  	}
    87  	t.Txn.TrackChanges()
    88  	return t
    89  }
    90  
    91  func (c *changeTrackerDB) publish(changes Changes) (*structs.Events, error) {
    92  	readOnlyTx := c.memdb.Txn(false)
    93  	defer readOnlyTx.Abort()
    94  
    95  	events := c.processChanges(readOnlyTx, changes)
    96  	if events != nil {
    97  		c.publisher.Publish(events)
    98  	}
    99  
   100  	return events, nil
   101  }
   102  
   103  // WriteTxnRestore returns a wrapped RW transaction that does NOT have change
   104  // tracking enabled. This should only be used in Restore where we need to
   105  // replace the entire contents of the Store without a need to track the changes.
   106  // WriteTxnRestore uses a zero index since the whole restore doesn't really occur
   107  // at one index - the effect is to write many values that were previously
   108  // written across many indexes.
   109  func (c *changeTrackerDB) WriteTxnRestore() *txn {
   110  	return &txn{
   111  		Txn:   c.memdb.Txn(true),
   112  		Index: 0,
   113  	}
   114  }
   115  
   116  // txn wraps a memdb.Txn to capture changes and send them to the EventBroker.
   117  //
   118  // This can not be done with txn.Defer because the callback passed to Defer is
   119  // invoked after commit completes, and because the callback can not return an
   120  // error. Any errors from the callback would be lost,  which would result in a
   121  // missing change event, even though the state store had changed.
   122  type txn struct {
   123  	// msgType is used to inform event sourcing which type of event to create
   124  	msgType structs.MessageType
   125  
   126  	*memdb.Txn
   127  	// Index in raft where the write is occurring. The value is zero for a
   128  	// read-only, or WriteTxnRestore transaction.
   129  	// Index is stored so that it may be passed along to any subscribers as part
   130  	// of a change event.
   131  	Index   uint64
   132  	publish func(changes Changes) (*structs.Events, error)
   133  }
   134  
   135  // Commit first pushes changes to EventBroker, then calls Commit on the
   136  // underlying transaction.
   137  //
   138  // Note that this function, unlike memdb.Txn, returns an error which must be checked
   139  // by the caller. A non-nil error indicates that a commit failed and was not
   140  // applied.
   141  func (tx *txn) Commit() error {
   142  	// publish may be nil if this is a read-only or WriteTxnRestore transaction.
   143  	// In those cases changes should also be empty, and there will be nothing
   144  	// to publish.
   145  	if tx.publish != nil {
   146  		changes := Changes{
   147  			Index:   tx.Index,
   148  			Changes: tx.Txn.Changes(),
   149  			MsgType: tx.MsgType(),
   150  		}
   151  		_, err := tx.publish(changes)
   152  		if err != nil {
   153  			return err
   154  		}
   155  	}
   156  
   157  	tx.Txn.Commit()
   158  	return nil
   159  }
   160  
   161  // MsgType returns a MessageType from the txn's context.
   162  // If the context is empty or the value isn't set IgnoreUnknownTypeFlag will
   163  // be returned to signal that the MsgType is unknown.
   164  func (tx *txn) MsgType() structs.MessageType {
   165  	return tx.msgType
   166  }