github.com/criteo-forks/consul@v1.4.5-criteonogrpc/agent/consul/state/state_store.go (about) 1 package state 2 3 import ( 4 "errors" 5 "fmt" 6 "log" 7 "os" 8 9 "github.com/hashicorp/consul/types" 10 "github.com/hashicorp/go-memdb" 11 ) 12 13 var ( 14 // ErrMissingNode is the error returned when trying an operation 15 // which requires a node registration but none exists. 16 ErrMissingNode = errors.New("Missing node registration") 17 18 // ErrMissingService is the error we return if trying an 19 // operation which requires a service but none exists. 20 ErrMissingService = errors.New("Missing service registration") 21 22 // ErrMissingSessionID is returned when a session registration 23 // is attempted with an empty session ID. 24 ErrMissingSessionID = errors.New("Missing session ID") 25 26 // ErrMissingACLTokenSecret is returned when a token set is called on a 27 // token with an empty SecretID. 28 ErrMissingACLTokenSecret = errors.New("Missing ACL Token SecretID") 29 30 // ErrMissingACLTokenAccessor is returned when a token set is called on a 31 // token with an empty AccessorID. 32 ErrMissingACLTokenAccessor = errors.New("Missing ACL Token AccessorID") 33 34 // ErrMissingACLPolicyID is returned when a policy set is called on a 35 // policy with an empty ID. 36 ErrMissingACLPolicyID = errors.New("Missing ACL Policy ID") 37 38 // ErrMissingACLPolicyName is returned when a policy set is called on a 39 // policy with an empty Name. 40 ErrMissingACLPolicyName = errors.New("Missing ACL Policy Name") 41 42 // ErrMissingQueryID is returned when a Query set is called on 43 // a Query with an empty ID. 44 ErrMissingQueryID = errors.New("Missing Query ID") 45 46 // ErrMissingCARootID is returned when an CARoot set is called 47 // with an CARoot with an empty ID. 48 ErrMissingCARootID = errors.New("Missing CA Root ID") 49 50 // ErrMissingIntentionID is returned when an Intention set is called 51 // with an Intention with an empty ID. 52 ErrMissingIntentionID = errors.New("Missing Intention ID") 53 ) 54 55 // Store is where we store all of Consul's state, including 56 // records of node registrations, services, checks, key/value 57 // pairs and more. The DB is entirely in-memory and is constructed 58 // from the Raft log through the FSM. 59 type Store struct { 60 schema *memdb.DBSchema 61 db *memdb.MemDB 62 63 // abandonCh is used to signal watchers that this state store has been 64 // abandoned (usually during a restore). This is only ever closed. 65 abandonCh chan struct{} 66 67 // kvsGraveyard manages tombstones for the key value store. 68 kvsGraveyard *Graveyard 69 70 // lockDelay holds expiration times for locks associated with keys. 71 lockDelay *Delay 72 73 // watchLimit is used as a soft limit to cap how many watches we allow 74 // for a given blocking query. If this is exceeded, then we will use a 75 // higher-level watch that's less fine-grained. This isn't as bad as it 76 // seems since we have made the main culprits (nodes and services) more 77 // efficient by diffing before we update via register requests. 78 // 79 // Given the current size of aFew == 32 in memdb's watch_few.go, 80 // this will allow for up to ~ watchLimit/32 goroutines per blocking query. 81 watchLimit int 82 83 watchLimitWarnCounter uint 84 85 logger *log.Logger 86 } 87 88 // Snapshot is used to provide a point-in-time snapshot. It 89 // works by starting a read transaction against the whole state store. 90 type Snapshot struct { 91 store *Store 92 tx *memdb.Txn 93 lastIndex uint64 94 } 95 96 // Restore is used to efficiently manage restoring a large amount of 97 // data to a state store. 98 type Restore struct { 99 store *Store 100 tx *memdb.Txn 101 } 102 103 // IndexEntry keeps a record of the last index per-table. 104 type IndexEntry struct { 105 Key string 106 Value uint64 107 } 108 109 // sessionCheck is used to create a many-to-one table such that 110 // each check registered by a session can be mapped back to the 111 // session table. This is only used internally in the state 112 // store and thus it is not exported. 113 type sessionCheck struct { 114 Node string 115 CheckID types.CheckID 116 Session string 117 } 118 119 // NewStateStore creates a new in-memory state storage layer. 120 func NewStateStore(gc *TombstoneGC, watchLimit int, logger *log.Logger) (*Store, error) { 121 // Create the in-memory DB. 122 schema := stateStoreSchema() 123 db, err := memdb.NewMemDB(schema) 124 if err != nil { 125 return nil, fmt.Errorf("Failed setting up state store: %s", err) 126 } 127 128 if logger == nil { 129 logger = log.New(os.Stderr, "", log.LstdFlags) 130 } 131 132 // Create and return the state store. 133 s := &Store{ 134 schema: schema, 135 db: db, 136 abandonCh: make(chan struct{}), 137 kvsGraveyard: NewGraveyard(gc), 138 lockDelay: NewDelay(), 139 watchLimit: watchLimit, 140 logger: logger, 141 } 142 return s, nil 143 } 144 145 // Snapshot is used to create a point-in-time snapshot of the entire db. 146 func (s *Store) Snapshot() *Snapshot { 147 tx := s.db.Txn(false) 148 149 var tables []string 150 for table := range s.schema.Tables { 151 tables = append(tables, table) 152 } 153 idx := maxIndexTxn(tx, tables...) 154 155 return &Snapshot{s, tx, idx} 156 } 157 158 // LastIndex returns that last index that affects the snapshotted data. 159 func (s *Snapshot) LastIndex() uint64 { 160 return s.lastIndex 161 } 162 163 func (s *Snapshot) Indexes() (memdb.ResultIterator, error) { 164 iter, err := s.tx.Get("index", "id") 165 if err != nil { 166 return nil, err 167 } 168 return iter, nil 169 } 170 171 // IndexRestore is used to restore an index 172 func (s *Restore) IndexRestore(idx *IndexEntry) error { 173 if err := s.tx.Insert("index", idx); err != nil { 174 return fmt.Errorf("index insert failed: %v", err) 175 } 176 return nil 177 } 178 179 // Close performs cleanup of a state snapshot. 180 func (s *Snapshot) Close() { 181 s.tx.Abort() 182 } 183 184 // Restore is used to efficiently manage restoring a large amount of data into 185 // the state store. It works by doing all the restores inside of a single 186 // transaction. 187 func (s *Store) Restore() *Restore { 188 tx := s.db.Txn(true) 189 return &Restore{s, tx} 190 } 191 192 // Abort abandons the changes made by a restore. This or Commit should always be 193 // called. 194 func (s *Restore) Abort() { 195 s.tx.Abort() 196 } 197 198 // Commit commits the changes made by a restore. This or Abort should always be 199 // called. 200 func (s *Restore) Commit() { 201 s.tx.Commit() 202 } 203 204 // AbandonCh returns a channel you can wait on to know if the state store was 205 // abandoned. 206 func (s *Store) AbandonCh() <-chan struct{} { 207 return s.abandonCh 208 } 209 210 // Abandon is used to signal that the given state store has been abandoned. 211 // Calling this more than one time will panic. 212 func (s *Store) Abandon() { 213 close(s.abandonCh) 214 } 215 216 // maxIndex is a helper used to retrieve the highest known index 217 // amongst a set of tables in the db. 218 func (s *Store) maxIndex(tables ...string) uint64 { 219 tx := s.db.Txn(false) 220 defer tx.Abort() 221 return maxIndexTxn(tx, tables...) 222 } 223 224 // maxIndexTxn is a helper used to retrieve the highest known index 225 // amongst a set of tables in the db. 226 func maxIndexTxn(tx *memdb.Txn, tables ...string) uint64 { 227 var lindex uint64 228 for _, table := range tables { 229 ti, err := tx.First("index", "id", table) 230 if err != nil { 231 panic(fmt.Sprintf("unknown index: %s err: %s", table, err)) 232 } 233 if idx, ok := ti.(*IndexEntry); ok && idx.Value > lindex { 234 lindex = idx.Value 235 } 236 } 237 return lindex 238 } 239 240 // indexUpdateMaxTxn is used when restoring entries and sets the table's index to 241 // the given idx only if it's greater than the current index. 242 func indexUpdateMaxTxn(tx *memdb.Txn, idx uint64, table string) error { 243 ti, err := tx.First("index", "id", table) 244 if err != nil { 245 return fmt.Errorf("failed to retrieve existing index: %s", err) 246 } 247 248 // Always take the first update, otherwise do the > check. 249 if ti == nil { 250 if err := tx.Insert("index", &IndexEntry{table, idx}); err != nil { 251 return fmt.Errorf("failed updating index %s", err) 252 } 253 } else if cur, ok := ti.(*IndexEntry); ok && idx > cur.Value { 254 if err := tx.Insert("index", &IndexEntry{table, idx}); err != nil { 255 return fmt.Errorf("failed updating index %s", err) 256 } 257 } 258 259 return nil 260 }