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 }