github.com/Ilhicas/nomad@v1.0.4-0.20210304152020-e86851182bc3/nomad/state/state_changes.go (about)

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