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  }