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 }