From 4ee43e90b726f310b79793598305ed487d07d4fc Mon Sep 17 00:00:00 2001 From: James Phillips Date: Mon, 12 Oct 2015 21:48:15 -0700 Subject: [PATCH] Deletes the old state store and all its accoutrements. --- command/agent/agent.go | 3 +- consul/mdb_table.go | 830 --------- consul/mdb_table_test.go | 1048 ----------- consul/notify.go | 55 - consul/notify_test.go | 72 - consul/state/state_store_test.go | 2 +- consul/state_store.go | 2006 --------------------- consul/state_store_test.go | 2842 ------------------------------ 8 files changed, 3 insertions(+), 6855 deletions(-) delete mode 100644 consul/mdb_table.go delete mode 100644 consul/mdb_table_test.go delete mode 100644 consul/notify.go delete mode 100644 consul/notify_test.go delete mode 100644 consul/state_store.go delete mode 100644 consul/state_store_test.go diff --git a/command/agent/agent.go b/command/agent/agent.go index 4509dce894..e4756e4294 100644 --- a/command/agent/agent.go +++ b/command/agent/agent.go @@ -15,6 +15,7 @@ import ( "time" "github.com/hashicorp/consul/consul" + "github.com/hashicorp/consul/consul/state" "github.com/hashicorp/consul/consul/structs" "github.com/hashicorp/serf/serf" ) @@ -94,7 +95,7 @@ type Agent struct { eventBuf []*UserEvent eventIndex int eventLock sync.RWMutex - eventNotify consul.NotifyGroup + eventNotify state.NotifyGroup shutdown bool shutdownCh chan struct{} diff --git a/consul/mdb_table.go b/consul/mdb_table.go deleted file mode 100644 index 37eb525038..0000000000 --- a/consul/mdb_table.go +++ /dev/null @@ -1,830 +0,0 @@ -package consul - -import ( - "bytes" - "fmt" - "reflect" - "strings" - "sync/atomic" - "time" - - "github.com/armon/gomdb" -) - -var ( - noIndex = fmt.Errorf("undefined index") - tooManyFields = fmt.Errorf("number of fields exceeds index arity") -) - -const ( - // lastIndexRowID is a special RowID used to represent the - // last Raft index that affected the table. The index value - // is not used by MDBTable, but is stored so that the client can map - // back to the Raft index number - lastIndexRowID = 0 - - // deadlockTimeout is a heuristic to detect a potential MDB deadlock. - // If we have a transaction that is left open indefinitely, it can - // prevent new transactions from making progress and deadlocking - // the system. If we fail to start a transaction after this long, - // assume a potential deadlock and panic. - deadlockTimeout = 30 * time.Second -) - -/* - An MDB table is a logical representation of a table, which is a - generic row store. It provides a simple mechanism to store rows - using a row id, while maintaining any number of secondary indexes. -*/ -type MDBTable struct { - // Last used rowID. Must be first to avoid 64bit alignment issues. - lastRowID uint64 - - Env *mdb.Env - Name string // This is the name of the table, must be unique - Indexes map[string]*MDBIndex - Encoder func(interface{}) []byte - Decoder func([]byte) interface{} -} - -// MDBTables is used for when we have a collection of tables -type MDBTables []*MDBTable - -// An Index is named, and uses a series of column values to -// map to the row-id containing the table -type MDBIndex struct { - AllowBlank bool // Can fields be blank - Unique bool // Controls if values are unique - Fields []string // Fields are used to build the index - IdxFunc IndexFunc // Can be used to provide custom indexing - Virtual bool // Virtual index does not exist, but can be used for queries - RealIndex string // Virtual indexes use a RealIndex for iteration - CaseInsensitive bool // Controls if values are case-insensitive - - table *MDBTable - name string - dbiName string - realIndex *MDBIndex -} - -// MDBTxn is used to wrap an underlying transaction -type MDBTxn struct { - readonly bool - tx *mdb.Txn - dbis map[string]mdb.DBI - after []func() -} - -// Abort is used to close the transaction -func (t *MDBTxn) Abort() { - if t != nil && t.tx != nil { - t.tx.Abort() - } -} - -// Commit is used to commit a transaction -func (t *MDBTxn) Commit() error { - if err := t.tx.Commit(); err != nil { - return err - } - for _, f := range t.after { - f() - } - t.after = nil - return nil -} - -// Defer is used to defer a function call until a successful commit -func (t *MDBTxn) Defer(f func()) { - t.after = append(t.after, f) -} - -type IndexFunc func(*MDBIndex, []string) string - -// DefaultIndexFunc is used if no IdxFunc is provided. It joins -// the columns using '||' which is reasonably unlikely to occur. -// We also prefix with a byte to ensure we never have a zero length -// key -func DefaultIndexFunc(idx *MDBIndex, parts []string) string { - if len(parts) == 0 { - return "_" - } - prefix := "_" + strings.Join(parts, "||") + "||" - return prefix -} - -// DefaultIndexPrefixFunc can be used with DefaultIndexFunc to scan -// for index prefix values. This should only be used as part of a -// virtual index. -func DefaultIndexPrefixFunc(idx *MDBIndex, parts []string) string { - if len(parts) == 0 { - return "_" - } - prefix := "_" + strings.Join(parts, "||") - return prefix -} - -// Init is used to initialize the MDBTable and ensure it's ready -func (t *MDBTable) Init() error { - if t.Env == nil { - return fmt.Errorf("Missing mdb env") - } - if t.Name == "" { - return fmt.Errorf("Missing table name") - } - if t.Indexes == nil { - return fmt.Errorf("Missing table indexes") - } - - // Ensure we have a unique id index - id, ok := t.Indexes["id"] - if !ok { - return fmt.Errorf("Missing id index") - } - if !id.Unique { - return fmt.Errorf("id index must be unique") - } - if id.AllowBlank { - return fmt.Errorf("id index must not allow blanks") - } - if id.Virtual { - return fmt.Errorf("id index cannot be virtual") - } - - // Create the table - if err := t.createTable(); err != nil { - return fmt.Errorf("table create failed: %v", err) - } - - // Initialize the indexes - for name, index := range t.Indexes { - if err := index.init(t, name); err != nil { - return fmt.Errorf("index %s error: %s", name, err) - } - } - - // Get the maximum row id - if err := t.restoreLastRowID(); err != nil { - return fmt.Errorf("error scanning table: %s", err) - } - - return nil -} - -// createTable is used to ensure the table exists -func (t *MDBTable) createTable() error { - tx, err := t.Env.BeginTxn(nil, 0) - if err != nil { - return err - } - if _, err := tx.DBIOpen(t.Name, mdb.CREATE); err != nil { - tx.Abort() - return err - } - return tx.Commit() -} - -// restoreLastRowID is used to set the last rowID that we've used -func (t *MDBTable) restoreLastRowID() error { - tx, err := t.StartTxn(true, nil) - if err != nil { - return err - } - defer tx.Abort() - - cursor, err := tx.tx.CursorOpen(tx.dbis[t.Name]) - if err != nil { - return err - } - defer cursor.Close() - - key, _, err := cursor.Get(nil, mdb.LAST) - if err == mdb.NotFound { - t.lastRowID = 0 - return nil - } else if err != nil { - return err - } - - // Set the last row id - t.lastRowID = bytesToUint64(key) - return nil -} - -// nextRowID returns the next usable row id -func (t *MDBTable) nextRowID() uint64 { - return atomic.AddUint64(&t.lastRowID, 1) -} - -// startTxn is used to start a transaction -func (t *MDBTable) StartTxn(readonly bool, mdbTxn *MDBTxn) (*MDBTxn, error) { - var txFlags uint = 0 - var tx *mdb.Txn - var err error - - // Panic if we deadlock acquiring a transaction - timeout := time.AfterFunc(deadlockTimeout, func() { - panic("Timeout starting MDB transaction, potential deadlock") - }) - defer timeout.Stop() - - // Ensure the modes agree - if mdbTxn != nil { - if mdbTxn.readonly != readonly { - return nil, fmt.Errorf("Cannot mix read/write transactions") - } - tx = mdbTxn.tx - goto EXTEND - } - - if readonly { - txFlags |= mdb.RDONLY - } - - tx, err = t.Env.BeginTxn(nil, txFlags) - if err != nil { - return nil, err - } - - mdbTxn = &MDBTxn{ - readonly: readonly, - tx: tx, - dbis: make(map[string]mdb.DBI), - } -EXTEND: - dbi, err := tx.DBIOpen(t.Name, 0) - if err != nil { - tx.Abort() - return nil, err - } - mdbTxn.dbis[t.Name] = dbi - - for _, index := range t.Indexes { - if index.Virtual { - continue - } - dbi, err := index.openDBI(tx) - if err != nil { - tx.Abort() - return nil, err - } - mdbTxn.dbis[index.dbiName] = dbi - } - - return mdbTxn, nil -} - -// objIndexKeys builds the indexes for a given object -func (t *MDBTable) objIndexKeys(obj interface{}) (map[string][]byte, error) { - // Construct the indexes keys - indexes := make(map[string][]byte) - for name, index := range t.Indexes { - if index.Virtual { - continue - } - key, err := index.keyFromObject(obj) - if err != nil { - return nil, err - } - indexes[name] = key - } - return indexes, nil -} - -// Insert is used to insert or update an object -func (t *MDBTable) Insert(obj interface{}) error { - // Start a new txn - tx, err := t.StartTxn(false, nil) - if err != nil { - return err - } - defer tx.Abort() - - if err := t.InsertTxn(tx, obj); err != nil { - return err - } - return tx.Commit() -} - -// Insert is used to insert or update an object within -// a given transaction -func (t *MDBTable) InsertTxn(tx *MDBTxn, obj interface{}) error { - var n int - // Construct the indexes keys - indexes, err := t.objIndexKeys(obj) - if err != nil { - return err - } - - // Encode the obj - raw := t.Encoder(obj) - - // Scan and check if this primary key already exists - primaryDbi := tx.dbis[t.Indexes["id"].dbiName] - _, err = tx.tx.Get(primaryDbi, indexes["id"]) - if err == mdb.NotFound { - goto AFTER_DELETE - } - - // Delete the existing row - n, err = t.deleteWithIndex(tx, t.Indexes["id"], indexes["id"]) - if err != nil { - return err - } - if n != 1 { - return fmt.Errorf("unexpected number of updates: %d", n) - } - -AFTER_DELETE: - // Insert with a new row ID - rowId := t.nextRowID() - encRowId := uint64ToBytes(rowId) - table := tx.dbis[t.Name] - if err := tx.tx.Put(table, encRowId, raw, 0); err != nil { - return err - } - - // Insert the new indexes - for name, index := range t.Indexes { - if index.Virtual { - continue - } - dbi := tx.dbis[index.dbiName] - if err := tx.tx.Put(dbi, indexes[name], encRowId, 0); err != nil { - return err - } - } - return nil -} - -// Get is used to lookup one or more rows. An index an appropriate -// fields are specified. The fields can be a prefix of the index. -func (t *MDBTable) Get(index string, parts ...string) (uint64, []interface{}, error) { - // Start a readonly txn - tx, err := t.StartTxn(true, nil) - if err != nil { - return 0, nil, err - } - defer tx.Abort() - - // Get the last associated index - idx, err := t.LastIndexTxn(tx) - if err != nil { - return 0, nil, err - } - - // Get the actual results - res, err := t.GetTxn(tx, index, parts...) - return idx, res, err -} - -// GetTxn is like Get but it operates within a specific transaction. -// This can be used for read that span multiple tables -func (t *MDBTable) GetTxn(tx *MDBTxn, index string, parts ...string) ([]interface{}, error) { - // Get the associated index - idx, key, err := t.getIndex(index, parts) - if err != nil { - return nil, err - } - - // Accumulate the results - var results []interface{} - err = idx.iterate(tx, key, func(encRowId, res []byte) (bool, bool) { - obj := t.Decoder(res) - results = append(results, obj) - return false, false - }) - - return results, err -} - -// GetTxnLimit is like GetTxn limits the maximum number of -// rows it will return -func (t *MDBTable) GetTxnLimit(tx *MDBTxn, limit int, index string, parts ...string) ([]interface{}, error) { - // Get the associated index - idx, key, err := t.getIndex(index, parts) - if err != nil { - return nil, err - } - - // Accumulate the results - var results []interface{} - num := 0 - err = idx.iterate(tx, key, func(encRowId, res []byte) (bool, bool) { - num++ - obj := t.Decoder(res) - results = append(results, obj) - return false, num == limit - }) - - return results, err -} - -// StreamTxn is like GetTxn but it streams the results over a channel. -// This can be used if the expected data set is very large. The stream -// is always closed on return. -func (t *MDBTable) StreamTxn(stream chan<- interface{}, tx *MDBTxn, index string, parts ...string) error { - // Always close the stream on return - defer close(stream) - - // Get the associated index - idx, key, err := t.getIndex(index, parts) - if err != nil { - return err - } - - // Stream the results - err = idx.iterate(tx, key, func(encRowId, res []byte) (bool, bool) { - obj := t.Decoder(res) - stream <- obj - return false, false - }) - - return err -} - -// getIndex is used to get the proper index, and also check the arity -func (t *MDBTable) getIndex(index string, parts []string) (*MDBIndex, []byte, error) { - // Get the index - idx, ok := t.Indexes[index] - if !ok { - return nil, nil, noIndex - } - - // Check the arity - arity := idx.arity() - if len(parts) > arity { - return nil, nil, tooManyFields - } - - if idx.CaseInsensitive { - parts = ToLowerList(parts) - } - - // Construct the key - key := idx.keyFromParts(parts...) - return idx, key, nil -} - -// Delete is used to delete one or more rows. An index an appropriate -// fields are specified. The fields can be a prefix of the index. -// Returns the rows deleted or an error. -func (t *MDBTable) Delete(index string, parts ...string) (num int, err error) { - // Start a write txn - tx, err := t.StartTxn(false, nil) - if err != nil { - return 0, err - } - defer tx.Abort() - - num, err = t.DeleteTxn(tx, index, parts...) - if err != nil { - return 0, err - } - return num, tx.Commit() -} - -// DeleteTxn is like Delete, but occurs in a specific transaction -// that can span multiple tables. -func (t *MDBTable) DeleteTxn(tx *MDBTxn, index string, parts ...string) (int, error) { - // Get the associated index - idx, key, err := t.getIndex(index, parts) - if err != nil { - return 0, err - } - - // Delete with the index - return t.deleteWithIndex(tx, idx, key) -} - -// deleteWithIndex deletes all associated rows while scanning -// a given index for a key prefix. May perform multiple index traversals. -// This is a hack around a bug in LMDB which can cause a partial delete to -// take place. To fix this, we invoke the innerDelete until all rows are -// removed. This hack can be removed once the LMDB bug is resolved. -func (t *MDBTable) deleteWithIndex(tx *MDBTxn, idx *MDBIndex, key []byte) (int, error) { - var total int - var num int - var err error -DELETE: - num, err = t.innerDeleteWithIndex(tx, idx, key) - total += num - if err != nil { - return total, err - } - if num > 0 { - goto DELETE - } - return total, nil -} - -// innerDeleteWithIndex deletes all associated rows while scanning -// a given index for a key prefix. It only traverses the index a single time. -func (t *MDBTable) innerDeleteWithIndex(tx *MDBTxn, idx *MDBIndex, key []byte) (num int, err error) { - // Handle an error while deleting - defer func() { - if r := recover(); r != nil { - num = 0 - err = fmt.Errorf("Panic while deleting: %v", r) - } - }() - - // Delete everything as we iterate - err = idx.iterate(tx, key, func(encRowId, res []byte) (bool, bool) { - // Get the object - obj := t.Decoder(res) - - // Build index values - indexes, err := t.objIndexKeys(obj) - if err != nil { - panic(err) - } - - // Delete the indexes we are not iterating - for name, otherIdx := range t.Indexes { - if name == idx.name { - continue - } - if idx.Virtual && name == idx.RealIndex { - continue - } - if otherIdx.Virtual { - continue - } - dbi := tx.dbis[otherIdx.dbiName] - if err := tx.tx.Del(dbi, indexes[name], encRowId); err != nil { - panic(err) - } - } - - // Delete the data row - if err := tx.tx.Del(tx.dbis[t.Name], encRowId, nil); err != nil { - panic(err) - } - - // Delete the object - num++ - return true, false - }) - if err != nil { - return 0, err - } - - // Return the deleted count - return num, nil -} - -// Initializes an index and returns a potential error -func (i *MDBIndex) init(table *MDBTable, name string) error { - i.table = table - i.name = name - i.dbiName = fmt.Sprintf("%s_%s_idx", i.table.Name, i.name) - if i.IdxFunc == nil { - i.IdxFunc = DefaultIndexFunc - } - if len(i.Fields) == 0 { - return fmt.Errorf("index missing fields") - } - if err := i.createIndex(); err != nil { - return err - } - // Verify real index exists - if i.Virtual { - if realIndex, ok := table.Indexes[i.RealIndex]; !ok { - return fmt.Errorf("real index '%s' missing", i.RealIndex) - } else { - i.realIndex = realIndex - } - } - return nil -} - -// createIndex is used to ensure the index exists -func (i *MDBIndex) createIndex() error { - // Do not create if this is a virtual index - if i.Virtual { - return nil - } - tx, err := i.table.Env.BeginTxn(nil, 0) - if err != nil { - return err - } - var dbFlags uint = mdb.CREATE - if !i.Unique { - dbFlags |= mdb.DUPSORT - } - if _, err := tx.DBIOpen(i.dbiName, dbFlags); err != nil { - tx.Abort() - return err - } - return tx.Commit() -} - -// openDBI is used to open a handle to the index for a transaction -func (i *MDBIndex) openDBI(tx *mdb.Txn) (mdb.DBI, error) { - var dbFlags uint - if !i.Unique { - dbFlags |= mdb.DUPSORT - } - return tx.DBIOpen(i.dbiName, dbFlags) -} - -// Returns the arity of the index -func (i *MDBIndex) arity() int { - return len(i.Fields) -} - -// keyFromObject constructs the index key from the object -func (i *MDBIndex) keyFromObject(obj interface{}) ([]byte, error) { - v := reflect.ValueOf(obj) - v = reflect.Indirect(v) // Derefence the pointer if any - parts := make([]string, 0, i.arity()) - for _, field := range i.Fields { - fv := v.FieldByName(field) - if !fv.IsValid() { - return nil, fmt.Errorf("Field '%s' for %#v is invalid", field, obj) - } - val := fv.String() - if !i.AllowBlank && val == "" { - return nil, fmt.Errorf("Field '%s' must be set: %#v", field, obj) - } - if i.CaseInsensitive { - val = strings.ToLower(val) - } - parts = append(parts, val) - } - key := i.keyFromParts(parts...) - return key, nil -} - -// keyFromParts returns the key from component parts -func (i *MDBIndex) keyFromParts(parts ...string) []byte { - return []byte(i.IdxFunc(i, parts)) -} - -// iterate is used to iterate over keys matching the prefix, -// and invoking the cb with each row. We dereference the rowid, -// and only return the object row -func (i *MDBIndex) iterate(tx *MDBTxn, prefix []byte, - cb func(encRowId, res []byte) (bool, bool)) error { - table := tx.dbis[i.table.Name] - - // If virtual, use the correct DBI - var dbi mdb.DBI - if i.Virtual { - dbi = tx.dbis[i.realIndex.dbiName] - } else { - dbi = tx.dbis[i.dbiName] - } - - cursor, err := tx.tx.CursorOpen(dbi) - if err != nil { - return err - } - // Read-only cursors are NOT closed by MDB when a transaction - // either commits or aborts, so must be closed explicitly - if tx.readonly { - defer cursor.Close() - } - - var key, encRowId, objBytes []byte - first := true - shouldStop := false - shouldDelete := false - for !shouldStop { - if first && len(prefix) > 0 { - first = false - key, encRowId, err = cursor.Get(prefix, mdb.SET_RANGE) - } else if shouldDelete { - key, encRowId, err = cursor.Get(nil, mdb.GET_CURRENT) - shouldDelete = false - - // LMDB will return EINVAL(22) for the GET_CURRENT op if - // there is no further keys. We treat this as no more - // keys being found. - if num, ok := err.(mdb.Errno); ok && num == 22 { - err = mdb.NotFound - } - } else if i.Unique { - key, encRowId, err = cursor.Get(nil, mdb.NEXT) - } else { - key, encRowId, err = cursor.Get(nil, mdb.NEXT_DUP) - if err == mdb.NotFound { - key, encRowId, err = cursor.Get(nil, mdb.NEXT) - } - } - if err == mdb.NotFound { - break - } else if err != nil { - return fmt.Errorf("iterate failed: %v", err) - } - - // Bail if this does not match our filter - if len(prefix) > 0 && !bytes.HasPrefix(key, prefix) { - break - } - - // Lookup the actual object - objBytes, err = tx.tx.Get(table, encRowId) - if err != nil { - return fmt.Errorf("rowid lookup failed: %v (%v)", err, encRowId) - } - - // Invoke the cb - shouldDelete, shouldStop = cb(encRowId, objBytes) - if shouldDelete { - if err := cursor.Del(0); err != nil { - return fmt.Errorf("delete failed: %v", err) - } - } - } - return nil -} - -// LastIndex is get the last index that updated the table -func (t *MDBTable) LastIndex() (uint64, error) { - // Start a readonly txn - tx, err := t.StartTxn(true, nil) - if err != nil { - return 0, err - } - defer tx.Abort() - return t.LastIndexTxn(tx) -} - -// LastIndexTxn is like LastIndex but it operates within a specific transaction. -func (t *MDBTable) LastIndexTxn(tx *MDBTxn) (uint64, error) { - encRowId := uint64ToBytes(lastIndexRowID) - val, err := tx.tx.Get(tx.dbis[t.Name], encRowId) - if err == mdb.NotFound { - return 0, nil - } else if err != nil { - return 0, err - } - - // Return the last index - return bytesToUint64(val), nil -} - -// SetLastIndex is used to set the last index that updated the table -func (t *MDBTable) SetLastIndex(index uint64) error { - tx, err := t.StartTxn(false, nil) - if err != nil { - return err - } - defer tx.Abort() - - if err := t.SetLastIndexTxn(tx, index); err != nil { - return err - } - return tx.Commit() -} - -// SetLastIndexTxn is used to set the last index within a transaction -func (t *MDBTable) SetLastIndexTxn(tx *MDBTxn, index uint64) error { - encRowId := uint64ToBytes(lastIndexRowID) - encIndex := uint64ToBytes(index) - return tx.tx.Put(tx.dbis[t.Name], encRowId, encIndex, 0) -} - -// SetMaxLastIndexTxn is used to set the last index within a transaction -// if it exceeds the current maximum -func (t *MDBTable) SetMaxLastIndexTxn(tx *MDBTxn, index uint64) error { - current, err := t.LastIndexTxn(tx) - if err != nil { - return err - } - if index > current { - return t.SetLastIndexTxn(tx, index) - } - return nil -} - -// StartTxn is used to create a transaction that spans a list of tables -func (t MDBTables) StartTxn(readonly bool) (*MDBTxn, error) { - var tx *MDBTxn - for _, table := range t { - newTx, err := table.StartTxn(readonly, tx) - if err != nil { - tx.Abort() - return nil, err - } - tx = newTx - } - return tx, nil -} - -// LastIndexTxn is used to get the last transaction from all of the tables -func (t MDBTables) LastIndexTxn(tx *MDBTxn) (uint64, error) { - var index uint64 - for _, table := range t { - idx, err := table.LastIndexTxn(tx) - if err != nil { - return index, err - } - if idx > index { - index = idx - } - } - return index, nil -} diff --git a/consul/mdb_table_test.go b/consul/mdb_table_test.go deleted file mode 100644 index 73e4001d12..0000000000 --- a/consul/mdb_table_test.go +++ /dev/null @@ -1,1048 +0,0 @@ -package consul - -import ( - "bytes" - "io/ioutil" - "os" - "reflect" - "testing" - - "github.com/armon/gomdb" - "github.com/hashicorp/go-msgpack/codec" -) - -type MockData struct { - Key string - First string - Last string - Country string -} - -func MockEncoder(obj interface{}) []byte { - buf := bytes.NewBuffer(nil) - encoder := codec.NewEncoder(buf, msgpackHandle) - err := encoder.Encode(obj) - if err != nil { - panic(err) - } - return buf.Bytes() -} - -func MockDecoder(buf []byte) interface{} { - out := new(MockData) - err := codec.NewDecoder(bytes.NewReader(buf), msgpackHandle).Decode(out) - if err != nil { - panic(err) - } - return out -} - -func testMDBEnv(t *testing.T) (string, *mdb.Env) { - // Create a new temp dir - path, err := ioutil.TempDir("", "consul") - if err != nil { - t.Fatalf("err: %v", err) - } - - // Open the env - env, err := mdb.NewEnv() - if err != nil { - t.Fatalf("err: %v", err) - } - - // Setup the Env first - if err := env.SetMaxDBs(mdb.DBI(32)); err != nil { - t.Fatalf("err: %v", err) - } - - // Increase the maximum map size - if err := env.SetMapSize(dbMaxMapSize32bit); err != nil { - t.Fatalf("err: %v", err) - } - - // Open the DB - var flags uint = mdb.NOMETASYNC | mdb.NOSYNC | mdb.NOTLS - if err := env.Open(path, flags, 0755); err != nil { - t.Fatalf("err: %v", err) - } - - return path, env -} - -func TestMDBTableInsert(t *testing.T) { - dir, env := testMDBEnv(t) - defer os.RemoveAll(dir) - defer env.Close() - - table := &MDBTable{ - Env: env, - Name: "test", - Indexes: map[string]*MDBIndex{ - "id": &MDBIndex{ - Unique: true, - Fields: []string{"Key"}, - }, - "name": &MDBIndex{ - Fields: []string{"First", "Last"}, - }, - "country": &MDBIndex{ - Fields: []string{"Country"}, - }, - }, - Encoder: MockEncoder, - Decoder: MockDecoder, - } - if err := table.Init(); err != nil { - t.Fatalf("err: %v", err) - } - - objs := []*MockData{ - &MockData{ - Key: "1", - First: "Kevin", - Last: "Smith", - Country: "USA", - }, - &MockData{ - Key: "2", - First: "Kevin", - Last: "Wang", - Country: "USA", - }, - &MockData{ - Key: "3", - First: "Bernardo", - Last: "Torres", - Country: "Mexico", - }, - } - - // Insert some mock objects - for idx, obj := range objs { - if err := table.Insert(obj); err != nil { - t.Fatalf("err: %v", err) - } - if err := table.SetLastIndex(uint64(idx + 1)); err != nil { - t.Fatalf("err: %v", err) - } - } - - // Verify with some gets - idx, res, err := table.Get("id", "1") - if err != nil { - t.Fatalf("err: %v", err) - } - if len(res) != 1 { - t.Fatalf("expect 1 result: %#v", res) - } - if !reflect.DeepEqual(res[0], objs[0]) { - t.Fatalf("bad: %#v", res[0]) - } - if idx != 3 { - t.Fatalf("bad index: %d", idx) - } - - idx, res, err = table.Get("name", "Kevin") - if err != nil { - t.Fatalf("err: %v", err) - } - if len(res) != 2 { - t.Fatalf("expect 2 result: %#v", res) - } - if !reflect.DeepEqual(res[0], objs[0]) { - t.Fatalf("bad: %#v", res[0]) - } - if !reflect.DeepEqual(res[1], objs[1]) { - t.Fatalf("bad: %#v", res[1]) - } - if idx != 3 { - t.Fatalf("bad index: %d", idx) - } - - idx, res, err = table.Get("country", "Mexico") - if err != nil { - t.Fatalf("err: %v", err) - } - if len(res) != 1 { - t.Fatalf("expect 1 result: %#v", res) - } - if !reflect.DeepEqual(res[0], objs[2]) { - t.Fatalf("bad: %#v", res[2]) - } - if idx != 3 { - t.Fatalf("bad index: %d", idx) - } - - idx, res, err = table.Get("id") - if err != nil { - t.Fatalf("err: %v", err) - } - if len(res) != 3 { - t.Fatalf("expect 2 result: %#v", res) - } - if !reflect.DeepEqual(res[0], objs[0]) { - t.Fatalf("bad: %#v", res[0]) - } - if !reflect.DeepEqual(res[1], objs[1]) { - t.Fatalf("bad: %#v", res[1]) - } - if !reflect.DeepEqual(res[2], objs[2]) { - t.Fatalf("bad: %#v", res[2]) - } - if idx != 3 { - t.Fatalf("bad index: %d", idx) - } -} - -func TestMDBTableInsert_MissingFields(t *testing.T) { - dir, env := testMDBEnv(t) - defer os.RemoveAll(dir) - defer env.Close() - - table := &MDBTable{ - Env: env, - Name: "test", - Indexes: map[string]*MDBIndex{ - "id": &MDBIndex{ - Unique: true, - Fields: []string{"Key"}, - }, - "name": &MDBIndex{ - Fields: []string{"First", "Last"}, - }, - "country": &MDBIndex{ - Fields: []string{"Country"}, - }, - }, - Encoder: MockEncoder, - Decoder: MockDecoder, - } - if err := table.Init(); err != nil { - t.Fatalf("err: %v", err) - } - - objs := []*MockData{ - &MockData{ - First: "Kevin", - Last: "Smith", - Country: "USA", - }, - &MockData{ - Key: "2", - Last: "Wang", - Country: "USA", - }, - &MockData{ - Key: "2", - First: "Kevin", - Country: "USA", - }, - &MockData{ - Key: "3", - First: "Bernardo", - Last: "Torres", - }, - } - - // Insert some mock objects - for _, obj := range objs { - if err := table.Insert(obj); err == nil { - t.Fatalf("expected err") - } - } -} - -func TestMDBTableInsert_AllowBlank(t *testing.T) { - dir, env := testMDBEnv(t) - defer os.RemoveAll(dir) - defer env.Close() - - table := &MDBTable{ - Env: env, - Name: "test", - Indexes: map[string]*MDBIndex{ - "id": &MDBIndex{ - Unique: true, - Fields: []string{"Key"}, - }, - "name": &MDBIndex{ - Fields: []string{"First", "Last"}, - }, - "country": &MDBIndex{ - Fields: []string{"Country"}, - AllowBlank: true, - }, - }, - Encoder: MockEncoder, - Decoder: MockDecoder, - } - if err := table.Init(); err != nil { - t.Fatalf("err: %v", err) - } - - objs := []*MockData{ - &MockData{ - Key: "1", - First: "Kevin", - Last: "Smith", - Country: "", - }, - } - - // Insert some mock objects - for _, obj := range objs { - if err := table.Insert(obj); err != nil { - t.Fatalf("err: %v", err) - } - } -} - -func TestMDBTableDelete(t *testing.T) { - dir, env := testMDBEnv(t) - defer os.RemoveAll(dir) - defer env.Close() - - table := &MDBTable{ - Env: env, - Name: "test", - Indexes: map[string]*MDBIndex{ - "id": &MDBIndex{ - Unique: true, - Fields: []string{"Key"}, - }, - "name": &MDBIndex{ - Fields: []string{"First", "Last"}, - }, - "country": &MDBIndex{ - Fields: []string{"Country"}, - }, - }, - Encoder: MockEncoder, - Decoder: MockDecoder, - } - if err := table.Init(); err != nil { - t.Fatalf("err: %v", err) - } - - objs := []*MockData{ - &MockData{ - Key: "1", - First: "Kevin", - Last: "Smith", - Country: "USA", - }, - &MockData{ - Key: "2", - First: "Kevin", - Last: "Wang", - Country: "USA", - }, - &MockData{ - Key: "3", - First: "Bernardo", - Last: "Torres", - Country: "Mexico", - }, - } - - // Insert some mock objects - for _, obj := range objs { - if err := table.Insert(obj); err != nil { - t.Fatalf("err: %v", err) - } - } - - _, _, err := table.Get("id", "3") - if err != nil { - t.Fatalf("err: %v", err) - } - - // Verify with some gets - num, err := table.Delete("id", "3") - if err != nil { - t.Fatalf("err: %v", err) - } - if num != 1 { - t.Fatalf("expect 1 delete: %#v", num) - } - _, res, err := table.Get("id", "3") - if err != nil { - t.Fatalf("err: %v", err) - } - if len(res) != 0 { - t.Fatalf("expect 0 result: %#v", res) - } - - num, err = table.Delete("name", "Kevin") - if err != nil { - t.Fatalf("err: %v", err) - } - if num != 2 { - t.Fatalf("expect 2 deletes: %#v", num) - } - _, res, err = table.Get("name", "Kevin") - if err != nil { - t.Fatalf("err: %v", err) - } - if len(res) != 0 { - t.Fatalf("expect 0 results: %#v", res) - } -} - -func TestMDBTableUpdate(t *testing.T) { - dir, env := testMDBEnv(t) - defer os.RemoveAll(dir) - defer env.Close() - - table := &MDBTable{ - Env: env, - Name: "test", - Indexes: map[string]*MDBIndex{ - "id": &MDBIndex{ - Unique: true, - Fields: []string{"Key"}, - }, - "name": &MDBIndex{ - Fields: []string{"First", "Last"}, - }, - "country": &MDBIndex{ - Fields: []string{"Country"}, - }, - }, - Encoder: MockEncoder, - Decoder: MockDecoder, - } - if err := table.Init(); err != nil { - t.Fatalf("err: %v", err) - } - - objs := []*MockData{ - &MockData{ - Key: "1", - First: "Kevin", - Last: "Smith", - Country: "USA", - }, - &MockData{ - Key: "2", - First: "Kevin", - Last: "Wang", - Country: "USA", - }, - &MockData{ - Key: "3", - First: "Bernardo", - Last: "Torres", - Country: "Mexico", - }, - &MockData{ - Key: "1", - First: "Roger", - Last: "Rodrigez", - Country: "Mexico", - }, - &MockData{ - Key: "2", - First: "Anna", - Last: "Smith", - Country: "UK", - }, - &MockData{ - Key: "3", - First: "Ahmad", - Last: "Badari", - Country: "Iran", - }, - } - - // Insert and update some mock objects - for _, obj := range objs { - if err := table.Insert(obj); err != nil { - t.Fatalf("err: %v", err) - } - } - - // Verify with some gets - _, res, err := table.Get("id", "1") - if err != nil { - t.Fatalf("err: %v", err) - } - if len(res) != 1 { - t.Fatalf("expect 1 result: %#v", res) - } - if !reflect.DeepEqual(res[0], objs[3]) { - t.Fatalf("bad: %#v", res[0]) - } - - _, res, err = table.Get("name", "Kevin") - if err != nil { - t.Fatalf("err: %v", err) - } - if len(res) != 0 { - t.Fatalf("expect 0 result: %#v", res) - } - - _, res, err = table.Get("name", "Ahmad") - if err != nil { - t.Fatalf("err: %v", err) - } - if len(res) != 1 { - t.Fatalf("expect 1 result: %#v", res) - } - if !reflect.DeepEqual(res[0], objs[5]) { - t.Fatalf("bad: %#v", res[0]) - } - - _, res, err = table.Get("country", "Mexico") - if err != nil { - t.Fatalf("err: %v", err) - } - if len(res) != 1 { - t.Fatalf("expect 1 result: %#v", res) - } - if !reflect.DeepEqual(res[0], objs[3]) { - t.Fatalf("bad: %#v", res[0]) - } - - _, res, err = table.Get("id") - if err != nil { - t.Fatalf("err: %v", err) - } - if len(res) != 3 { - t.Fatalf("expect 3 result: %#v", res) - } - if !reflect.DeepEqual(res[0], objs[3]) { - t.Fatalf("bad: %#v", res[0]) - } - if !reflect.DeepEqual(res[1], objs[4]) { - t.Fatalf("bad: %#v", res[1]) - } - if !reflect.DeepEqual(res[2], objs[5]) { - t.Fatalf("bad: %#v", res[2]) - } -} - -func TestMDBTableLastRowID(t *testing.T) { - dir, env := testMDBEnv(t) - defer os.RemoveAll(dir) - defer env.Close() - - table := &MDBTable{ - Env: env, - Name: "test", - Indexes: map[string]*MDBIndex{ - "id": &MDBIndex{ - Unique: true, - Fields: []string{"Key"}, - }, - "name": &MDBIndex{ - Fields: []string{"First", "Last"}, - }, - "country": &MDBIndex{ - Fields: []string{"Country"}, - }, - }, - Encoder: MockEncoder, - Decoder: MockDecoder, - } - if err := table.Init(); err != nil { - t.Fatalf("err: %v", err) - } - - if table.lastRowID != 0 { - t.Fatalf("bad last row id: %d", table.lastRowID) - } - - objs := []*MockData{ - &MockData{ - Key: "1", - First: "Kevin", - Last: "Smith", - Country: "USA", - }, - &MockData{ - Key: "2", - First: "Kevin", - Last: "Wang", - Country: "USA", - }, - &MockData{ - Key: "3", - First: "Bernardo", - Last: "Torres", - Country: "Mexico", - }, - } - - // Insert some mock objects - for _, obj := range objs { - if err := table.Insert(obj); err != nil { - t.Fatalf("err: %v", err) - } - } - - if table.lastRowID != 3 { - t.Fatalf("bad last row id: %d", table.lastRowID) - } - - // Remount the table - table2 := &MDBTable{ - Env: env, - Name: "test", - Indexes: map[string]*MDBIndex{ - "id": &MDBIndex{ - Unique: true, - Fields: []string{"Key"}, - }, - "name": &MDBIndex{ - Fields: []string{"First", "Last"}, - }, - "country": &MDBIndex{ - Fields: []string{"Country"}, - }, - }, - Encoder: MockEncoder, - Decoder: MockDecoder, - } - if err := table2.Init(); err != nil { - t.Fatalf("err: %v", err) - } - - if table2.lastRowID != 3 { - t.Fatalf("bad last row id: %d", table2.lastRowID) - } -} - -func TestMDBTableIndex(t *testing.T) { - dir, env := testMDBEnv(t) - defer os.RemoveAll(dir) - defer env.Close() - - table := &MDBTable{ - Env: env, - Name: "test", - Indexes: map[string]*MDBIndex{ - "id": &MDBIndex{ - Unique: true, - Fields: []string{"Key"}, - }, - }, - Encoder: MockEncoder, - Decoder: MockDecoder, - } - if err := table.Init(); err != nil { - t.Fatalf("err: %v", err) - } - - if table.lastRowID != 0 { - t.Fatalf("bad last row id: %d", table.lastRowID) - } - - objs := []*MockData{ - &MockData{ - Key: "1", - First: "Kevin", - Last: "Smith", - Country: "USA", - }, - &MockData{ - Key: "2", - First: "Kevin", - Last: "Wang", - Country: "USA", - }, - &MockData{ - Key: "3", - First: "Bernardo", - Last: "Torres", - Country: "Mexico", - }, - } - - // Insert some mock objects - for idx, obj := range objs { - if err := table.Insert(obj); err != nil { - t.Fatalf("err: %v", err) - } - if err := table.SetLastIndex(uint64(4 * idx)); err != nil { - t.Fatalf("err: %v", err) - } - } - - if table.lastRowID != 3 { - t.Fatalf("bad last row id: %d", table.lastRowID) - } - - if idx, _ := table.LastIndex(); idx != 8 { - t.Fatalf("bad last idx: %d", idx) - } - - // Remount the table - table2 := &MDBTable{ - Env: env, - Name: "test", - Indexes: map[string]*MDBIndex{ - "id": &MDBIndex{ - Unique: true, - Fields: []string{"Key"}, - }, - }, - Encoder: MockEncoder, - Decoder: MockDecoder, - } - if err := table2.Init(); err != nil { - t.Fatalf("err: %v", err) - } - - if table2.lastRowID != 3 { - t.Fatalf("bad last row id: %d", table2.lastRowID) - } - - if idx, _ := table2.LastIndex(); idx != 8 { - t.Fatalf("bad last idx: %d", idx) - } -} - -func TestMDBTableDelete_Prefix(t *testing.T) { - dir, env := testMDBEnv(t) - defer os.RemoveAll(dir) - defer env.Close() - - table := &MDBTable{ - Env: env, - Name: "test", - Indexes: map[string]*MDBIndex{ - "id": &MDBIndex{ - Unique: true, - Fields: []string{"First", "Last"}, - }, - }, - Encoder: MockEncoder, - Decoder: MockDecoder, - } - if err := table.Init(); err != nil { - t.Fatalf("err: %v", err) - } - - objs := []*MockData{ - &MockData{ - Key: "1", - First: "James", - Last: "Smith", - Country: "USA", - }, - &MockData{ - Key: "1", - First: "Kevin", - Last: "Smith", - Country: "USA", - }, - &MockData{ - Key: "2", - First: "Kevin", - Last: "Wang", - Country: "USA", - }, - &MockData{ - Key: "3", - First: "Kevin", - Last: "Torres", - Country: "Mexico", - }, - &MockData{ - Key: "1", - First: "Lana", - Last: "Smith", - Country: "USA", - }, - } - - // Insert some mock objects - for _, obj := range objs { - if err := table.Insert(obj); err != nil { - t.Fatalf("err: %v", err) - } - } - - // This should nuke all kevins - num, err := table.Delete("id", "Kevin") - if err != nil { - t.Fatalf("err: %v", err) - } - if num != 3 { - t.Fatalf("expect 3 delete: %#v", num) - } - _, res, err := table.Get("id") - if err != nil { - t.Fatalf("err: %v", err) - } - if len(res) != 2 { - t.Fatalf("expect 2 result: %#v", res) - } -} - -func TestMDBTableVirtualIndex(t *testing.T) { - dir, env := testMDBEnv(t) - defer os.RemoveAll(dir) - defer env.Close() - - table := &MDBTable{ - Env: env, - Name: "test", - Indexes: map[string]*MDBIndex{ - "id": &MDBIndex{ - Unique: true, - Fields: []string{"First"}, - }, - "id_prefix": &MDBIndex{ - Virtual: true, - RealIndex: "id", - Fields: []string{"First"}, - IdxFunc: DefaultIndexPrefixFunc, - }, - }, - Encoder: MockEncoder, - Decoder: MockDecoder, - } - if err := table.Init(); err != nil { - t.Fatalf("err: %v", err) - } - - if table.lastRowID != 0 { - t.Fatalf("bad last row id: %d", table.lastRowID) - } - - objs := []*MockData{ - &MockData{ - Key: "1", - First: "Jack", - Last: "Smith", - Country: "USA", - }, - &MockData{ - Key: "2", - First: "John", - Last: "Wang", - Country: "USA", - }, - &MockData{ - Key: "3", - First: "James", - Last: "Torres", - Country: "Mexico", - }, - } - - // Insert some mock objects - for idx, obj := range objs { - if err := table.Insert(obj); err != nil { - t.Fatalf("err: %v", err) - } - if err := table.SetLastIndex(uint64(4 * idx)); err != nil { - t.Fatalf("err: %v", err) - } - } - - if table.lastRowID != 3 { - t.Fatalf("bad last row id: %d", table.lastRowID) - } - - if idx, _ := table.LastIndex(); idx != 8 { - t.Fatalf("bad last idx: %d", idx) - } - - _, res, err := table.Get("id_prefix", "J") - if err != nil { - t.Fatalf("err: %v", err) - } - if len(res) != 3 { - t.Fatalf("expect 3 result: %#v", res) - } - - _, res, err = table.Get("id_prefix", "Ja") - if err != nil { - t.Fatalf("err: %v", err) - } - if len(res) != 2 { - t.Fatalf("expect 2 result: %#v", res) - } - - num, err := table.Delete("id_prefix", "Ja") - if err != nil { - t.Fatalf("err: %v", err) - } - if num != 2 { - t.Fatalf("expect 2 result: %#v", num) - } - - _, res, err = table.Get("id_prefix", "J") - if err != nil { - t.Fatalf("err: %v", err) - } - if len(res) != 1 { - t.Fatalf("expect 1 result: %#v", res) - } -} - -func TestMDBTableStream(t *testing.T) { - dir, env := testMDBEnv(t) - defer os.RemoveAll(dir) - defer env.Close() - - table := &MDBTable{ - Env: env, - Name: "test", - Indexes: map[string]*MDBIndex{ - "id": &MDBIndex{ - Unique: true, - Fields: []string{"Key"}, - }, - "name": &MDBIndex{ - Fields: []string{"First", "Last"}, - }, - "country": &MDBIndex{ - Fields: []string{"Country"}, - }, - }, - Encoder: MockEncoder, - Decoder: MockDecoder, - } - if err := table.Init(); err != nil { - t.Fatalf("err: %v", err) - } - - objs := []*MockData{ - &MockData{ - Key: "1", - First: "Kevin", - Last: "Smith", - Country: "USA", - }, - &MockData{ - Key: "2", - First: "Kevin", - Last: "Wang", - Country: "USA", - }, - &MockData{ - Key: "3", - First: "Bernardo", - Last: "Torres", - Country: "Mexico", - }, - } - - // Insert some mock objects - for idx, obj := range objs { - if err := table.Insert(obj); err != nil { - t.Fatalf("err: %v", err) - } - if err := table.SetLastIndex(uint64(idx + 1)); err != nil { - t.Fatalf("err: %v", err) - } - } - - // Start a readonly txn - tx, err := table.StartTxn(true, nil) - if err != nil { - panic(err) - } - defer tx.Abort() - - // Stream the records - streamCh := make(chan interface{}) - go func() { - if err := table.StreamTxn(streamCh, tx, "id"); err != nil { - t.Fatalf("err: %v", err) - } - }() - - // Verify we get them all - idx := 0 - for obj := range streamCh { - p := obj.(*MockData) - if !reflect.DeepEqual(p, objs[idx]) { - t.Fatalf("bad: %#v %#v", p, objs[idx]) - } - idx++ - } - - if idx != 3 { - t.Fatalf("bad index: %d", idx) - } -} - -func TestMDBTableGetTxnLimit(t *testing.T) { - dir, env := testMDBEnv(t) - defer os.RemoveAll(dir) - defer env.Close() - - table := &MDBTable{ - Env: env, - Name: "test", - Indexes: map[string]*MDBIndex{ - "id": &MDBIndex{ - Unique: true, - Fields: []string{"Key"}, - }, - "name": &MDBIndex{ - Fields: []string{"First", "Last"}, - }, - "country": &MDBIndex{ - Fields: []string{"Country"}, - }, - }, - Encoder: MockEncoder, - Decoder: MockDecoder, - } - if err := table.Init(); err != nil { - t.Fatalf("err: %v", err) - } - - objs := []*MockData{ - &MockData{ - Key: "1", - First: "Kevin", - Last: "Smith", - Country: "USA", - }, - &MockData{ - Key: "2", - First: "Kevin", - Last: "Wang", - Country: "USA", - }, - &MockData{ - Key: "3", - First: "Bernardo", - Last: "Torres", - Country: "Mexico", - }, - } - - // Insert some mock objects - for idx, obj := range objs { - if err := table.Insert(obj); err != nil { - t.Fatalf("err: %v", err) - } - if err := table.SetLastIndex(uint64(idx + 1)); err != nil { - t.Fatalf("err: %v", err) - } - } - - // Start a readonly txn - tx, err := table.StartTxn(true, nil) - if err != nil { - panic(err) - } - defer tx.Abort() - - // Verify with some gets - res, err := table.GetTxnLimit(tx, 2, "id") - if err != nil { - t.Fatalf("err: %v", err) - } - if len(res) != 2 { - t.Fatalf("expect 2 result: %#v", res) - } -} diff --git a/consul/notify.go b/consul/notify.go deleted file mode 100644 index 2fe5acbe2b..0000000000 --- a/consul/notify.go +++ /dev/null @@ -1,55 +0,0 @@ -package consul - -import ( - "sync" -) - -// NotifyGroup is used to allow a simple notification mechanism. -// Channels can be marked as waiting, and when notify is invoked, -// all the waiting channels get a message and are cleared from the -// notify list. -type NotifyGroup struct { - l sync.Mutex - notify map[chan struct{}]struct{} -} - -// Notify will do a non-blocking send to all waiting channels, and -// clear the notify list -func (n *NotifyGroup) Notify() { - n.l.Lock() - defer n.l.Unlock() - for ch, _ := range n.notify { - select { - case ch <- struct{}{}: - default: - } - } - n.notify = nil -} - -// Wait adds a channel to the notify group -func (n *NotifyGroup) Wait(ch chan struct{}) { - n.l.Lock() - defer n.l.Unlock() - if n.notify == nil { - n.notify = make(map[chan struct{}]struct{}) - } - n.notify[ch] = struct{}{} -} - -// Clear removes a channel from the notify group -func (n *NotifyGroup) Clear(ch chan struct{}) { - n.l.Lock() - defer n.l.Unlock() - if n.notify == nil { - return - } - delete(n.notify, ch) -} - -// WaitCh allocates a channel that is subscribed to notifications -func (n *NotifyGroup) WaitCh() chan struct{} { - ch := make(chan struct{}, 1) - n.Wait(ch) - return ch -} diff --git a/consul/notify_test.go b/consul/notify_test.go deleted file mode 100644 index 2133e9b312..0000000000 --- a/consul/notify_test.go +++ /dev/null @@ -1,72 +0,0 @@ -package consul - -import ( - "testing" -) - -func TestNotifyGroup(t *testing.T) { - grp := &NotifyGroup{} - - ch1 := grp.WaitCh() - ch2 := grp.WaitCh() - - select { - case <-ch1: - t.Fatalf("should block") - default: - } - select { - case <-ch2: - t.Fatalf("should block") - default: - } - - grp.Notify() - - select { - case <-ch1: - default: - t.Fatalf("should not block") - } - select { - case <-ch2: - default: - t.Fatalf("should not block") - } - - // Should be unregistered - ch3 := grp.WaitCh() - grp.Notify() - - select { - case <-ch1: - t.Fatalf("should block") - default: - } - select { - case <-ch2: - t.Fatalf("should block") - default: - } - select { - case <-ch3: - default: - t.Fatalf("should not block") - } -} - -func TestNotifyGroup_Clear(t *testing.T) { - grp := &NotifyGroup{} - - ch1 := grp.WaitCh() - grp.Clear(ch1) - - grp.Notify() - - // Should not get anything - select { - case <-ch1: - t.Fatalf("should not get message") - default: - } -} diff --git a/consul/state/state_store_test.go b/consul/state/state_store_test.go index 951e65f903..083d480bfd 100644 --- a/consul/state/state_store_test.go +++ b/consul/state/state_store_test.go @@ -264,7 +264,7 @@ func TestStateStore_GetWatches(t *testing.T) { // Make sure requesting a bogus method causes a panic. func() { defer func() { - if r:= recover(); r == nil { + if r := recover(); r == nil { t.Fatalf("didn't get expected panic") } }() diff --git a/consul/state_store.go b/consul/state_store.go deleted file mode 100644 index 6073ffe18f..0000000000 --- a/consul/state_store.go +++ /dev/null @@ -1,2006 +0,0 @@ -package consul - -import ( - "fmt" - "io" - "io/ioutil" - "log" - "os" - "runtime" - "strings" - "sync" - "time" - - "github.com/armon/go-radix" - "github.com/armon/gomdb" - "github.com/hashicorp/consul/consul/state" - "github.com/hashicorp/consul/consul/structs" -) - -const ( - dbNodes = "nodes" - dbServices = "services" - dbChecks = "checks" - dbKVS = "kvs" - dbTombstone = "tombstones" - dbSessions = "sessions" - dbSessionChecks = "sessionChecks" - dbMaxMapSize32bit uint64 = 128 * 1024 * 1024 // 128MB maximum size - dbMaxMapSize64bit uint64 = 32 * 1024 * 1024 * 1024 // 32GB maximum size - dbMaxReaders uint = 4096 // 4K, default is 126 -) - -// kvMode is used internally to control which type of set -// operation we are performing -type kvMode int - -const ( - kvSet kvMode = iota - kvCAS - kvLock - kvUnlock -) - -// The StateStore is responsible for maintaining all the Consul -// state. It is manipulated by the FSM which maintains consistency -// through the use of Raft. The goals of the StateStore are to provide -// high concurrency for read operations without blocking writes, and -// to provide write availability in the face of reads. The current -// implementation uses the Lightning Memory-Mapped Database (MDB). -// This gives us Multi-Version Concurrency Control for "free" -type StateStore struct { - logger *log.Logger - path string - env *mdb.Env - nodeTable *MDBTable - serviceTable *MDBTable - checkTable *MDBTable - kvsTable *MDBTable - tombstoneTable *MDBTable - sessionTable *MDBTable - sessionCheckTable *MDBTable - tables MDBTables - watch map[*MDBTable]*NotifyGroup - queryTables map[string]MDBTables - - // kvWatch is a more optimized way of watching for KV changes. - // Instead of just using a NotifyGroup for the entire table, - // a watcher is instantiated on a given prefix. When a change happens, - // only the relevant watchers are woken up. This reduces the cost of - // watching for KV changes. - kvWatch *radix.Tree - kvWatchLock sync.Mutex - - // lockDelay is used to mark certain locks as unacquirable. - // When a lock is forcefully released (failing health - // check, destroyed session, etc), it is subject to the LockDelay - // imposed by the session. This prevents another session from - // acquiring the lock for some period of time as a protection against - // split-brains. This is inspired by the lock-delay in Chubby. - // Because this relies on wall-time, we cannot assume all peers - // perceive time as flowing uniformly. This means KVSLock MUST ignore - // lockDelay, since the lockDelay may have expired on the leader, - // but not on the follower. Rejecting the lock could result in - // inconsistencies in the FSMs due to the rate time progresses. Instead, - // only the opinion of the leader is respected, and the Raft log - // is never questioned. - lockDelay map[string]time.Time - lockDelayLock sync.RWMutex - - // GC is when we create tombstones to track their time-to-live. - // The GC is consumed upstream to manage clearing of tombstones. - gc *state.TombstoneGC -} - -// StateSnapshot is used to provide a point-in-time snapshot -// It works by starting a readonly transaction against all tables. -type StateSnapshot struct { - store *StateStore - tx *MDBTxn - lastIndex uint64 -} - -// sessionCheck is used to create a many-to-one table such -// that each check registered by a session can be mapped back -// to the session row. -type sessionCheck struct { - Node string - CheckID string - Session string -} - -// Close is used to abort the transaction and allow for cleanup -func (s *StateSnapshot) Close() error { - s.tx.Abort() - return nil -} - -// NewStateStore is used to create a new state store -func NewStateStore(gc *state.TombstoneGC, logOutput io.Writer) (*StateStore, error) { - // Create a new temp dir - path, err := ioutil.TempDir("", "consul") - if err != nil { - return nil, err - } - return NewStateStorePath(gc, path, logOutput) -} - -// NewStateStorePath is used to create a new state store at a given path -// The path is cleared on closing. -func NewStateStorePath(gc *state.TombstoneGC, path string, logOutput io.Writer) (*StateStore, error) { - // Open the env - env, err := mdb.NewEnv() - if err != nil { - return nil, err - } - - s := &StateStore{ - logger: log.New(logOutput, "", log.LstdFlags), - path: path, - env: env, - watch: make(map[*MDBTable]*NotifyGroup), - kvWatch: radix.New(), - lockDelay: make(map[string]time.Time), - gc: gc, - } - - // Ensure we can initialize - if err := s.initialize(); err != nil { - env.Close() - os.RemoveAll(path) - return nil, err - } - return s, nil -} - -// Close is used to safely shutdown the state store -func (s *StateStore) Close() error { - s.env.Close() - os.RemoveAll(s.path) - return nil -} - -// initialize is used to setup the store for use -func (s *StateStore) initialize() error { - // Setup the Env first - if err := s.env.SetMaxDBs(mdb.DBI(32)); err != nil { - return err - } - - // Set the maximum db size based on 32/64bit. Since we are - // doing an mmap underneath, we need to limit our use of virtual - // address space on 32bit, but don't have to care on 64bit. - dbSize := dbMaxMapSize32bit - if runtime.GOARCH == "amd64" { - dbSize = dbMaxMapSize64bit - } - - // Increase the maximum map size - if err := s.env.SetMapSize(dbSize); err != nil { - return err - } - - // Increase the maximum number of concurrent readers - // TODO: Block transactions if we could exceed dbMaxReaders - if err := s.env.SetMaxReaders(dbMaxReaders); err != nil { - return err - } - - // Optimize our flags for speed over safety, since the Raft log + snapshots - // are durable. We treat this as an ephemeral in-memory DB, since we nuke - // the data anyways. - var flags uint = mdb.NOMETASYNC | mdb.NOSYNC | mdb.NOTLS - if err := s.env.Open(s.path, flags, 0755); err != nil { - return err - } - - // Tables use a generic struct encoder - encoder := func(obj interface{}) []byte { - buf, err := structs.Encode(255, obj) - if err != nil { - panic(err) - } - return buf[1:] - } - - // Setup our tables - s.nodeTable = &MDBTable{ - Name: dbNodes, - Indexes: map[string]*MDBIndex{ - "id": &MDBIndex{ - Unique: true, - Fields: []string{"Node"}, - CaseInsensitive: true, - }, - }, - Decoder: func(buf []byte) interface{} { - out := new(structs.Node) - if err := structs.Decode(buf, out); err != nil { - panic(err) - } - return out - }, - } - - s.serviceTable = &MDBTable{ - Name: dbServices, - Indexes: map[string]*MDBIndex{ - "id": &MDBIndex{ - Unique: true, - Fields: []string{"Node", "ServiceID"}, - }, - "service": &MDBIndex{ - AllowBlank: true, - Fields: []string{"ServiceName"}, - CaseInsensitive: true, - }, - }, - Decoder: func(buf []byte) interface{} { - out := new(structs.ServiceNode) - if err := structs.Decode(buf, out); err != nil { - panic(err) - } - return out - }, - } - - s.checkTable = &MDBTable{ - Name: dbChecks, - Indexes: map[string]*MDBIndex{ - "id": &MDBIndex{ - Unique: true, - Fields: []string{"Node", "CheckID"}, - }, - "status": &MDBIndex{ - Fields: []string{"Status"}, - }, - "service": &MDBIndex{ - AllowBlank: true, - Fields: []string{"ServiceName"}, - }, - "node": &MDBIndex{ - AllowBlank: true, - Fields: []string{"Node", "ServiceID"}, - }, - }, - Decoder: func(buf []byte) interface{} { - out := new(structs.HealthCheck) - if err := structs.Decode(buf, out); err != nil { - panic(err) - } - return out - }, - } - - s.kvsTable = &MDBTable{ - Name: dbKVS, - Indexes: map[string]*MDBIndex{ - "id": &MDBIndex{ - Unique: true, - Fields: []string{"Key"}, - }, - "id_prefix": &MDBIndex{ - Virtual: true, - RealIndex: "id", - Fields: []string{"Key"}, - IdxFunc: DefaultIndexPrefixFunc, - }, - "session": &MDBIndex{ - AllowBlank: true, - Fields: []string{"Session"}, - }, - }, - Decoder: func(buf []byte) interface{} { - out := new(structs.DirEntry) - if err := structs.Decode(buf, out); err != nil { - panic(err) - } - return out - }, - } - - s.tombstoneTable = &MDBTable{ - Name: dbTombstone, - Indexes: map[string]*MDBIndex{ - "id": &MDBIndex{ - Unique: true, - Fields: []string{"Key"}, - }, - "id_prefix": &MDBIndex{ - Virtual: true, - RealIndex: "id", - Fields: []string{"Key"}, - IdxFunc: DefaultIndexPrefixFunc, - }, - }, - Decoder: func(buf []byte) interface{} { - out := new(structs.DirEntry) - if err := structs.Decode(buf, out); err != nil { - panic(err) - } - return out - }, - } - - s.sessionTable = &MDBTable{ - Name: dbSessions, - Indexes: map[string]*MDBIndex{ - "id": &MDBIndex{ - Unique: true, - Fields: []string{"ID"}, - }, - "node": &MDBIndex{ - AllowBlank: true, - Fields: []string{"Node"}, - }, - }, - Decoder: func(buf []byte) interface{} { - out := new(structs.Session) - if err := structs.Decode(buf, out); err != nil { - panic(err) - } - return out - }, - } - - s.sessionCheckTable = &MDBTable{ - Name: dbSessionChecks, - Indexes: map[string]*MDBIndex{ - "id": &MDBIndex{ - Unique: true, - Fields: []string{"Node", "CheckID", "Session"}, - }, - }, - Decoder: func(buf []byte) interface{} { - out := new(sessionCheck) - if err := structs.Decode(buf, out); err != nil { - panic(err) - } - return out - }, - } - - // Store the set of tables - s.tables = []*MDBTable{s.nodeTable, s.serviceTable, s.checkTable, - s.kvsTable, s.tombstoneTable, s.sessionTable, s.sessionCheckTable} - for _, table := range s.tables { - table.Env = s.env - table.Encoder = encoder - if err := table.Init(); err != nil { - return err - } - - // Setup a notification group per table - s.watch[table] = &NotifyGroup{} - } - - // Setup the query tables - s.queryTables = map[string]MDBTables{ - "Nodes": MDBTables{s.nodeTable}, - "Services": MDBTables{s.serviceTable}, - "ServiceNodes": MDBTables{s.nodeTable, s.serviceTable}, - "NodeServices": MDBTables{s.nodeTable, s.serviceTable}, - "ChecksInState": MDBTables{s.checkTable}, - "NodeChecks": MDBTables{s.checkTable}, - "ServiceChecks": MDBTables{s.checkTable}, - "CheckServiceNodes": MDBTables{s.nodeTable, s.serviceTable, s.checkTable}, - "NodeInfo": MDBTables{s.nodeTable, s.serviceTable, s.checkTable}, - "NodeDump": MDBTables{s.nodeTable, s.serviceTable, s.checkTable}, - "SessionGet": MDBTables{s.sessionTable}, - "SessionList": MDBTables{s.sessionTable}, - "NodeSessions": MDBTables{s.sessionTable}, - } - return nil -} - -// Watch is used to subscribe a channel to a set of MDBTables -func (s *StateStore) Watch(tables MDBTables, notify chan struct{}) { - for _, t := range tables { - s.watch[t].Wait(notify) - } -} - -// StopWatch is used to unsubscribe a channel to a set of MDBTables -func (s *StateStore) StopWatch(tables MDBTables, notify chan struct{}) { - for _, t := range tables { - s.watch[t].Clear(notify) - } -} - -// WatchKV is used to subscribe a channel to changes in KV data -func (s *StateStore) WatchKV(prefix string, notify chan struct{}) { - s.kvWatchLock.Lock() - defer s.kvWatchLock.Unlock() - - // Check for an existing notify group - if raw, ok := s.kvWatch.Get(prefix); ok { - grp := raw.(*NotifyGroup) - grp.Wait(notify) - return - } - - // Create new notify group - grp := &NotifyGroup{} - grp.Wait(notify) - s.kvWatch.Insert(prefix, grp) -} - -// StopWatchKV is used to unsubscribe a channel from changes in KV data -func (s *StateStore) StopWatchKV(prefix string, notify chan struct{}) { - s.kvWatchLock.Lock() - defer s.kvWatchLock.Unlock() - - // Check for an existing notify group - if raw, ok := s.kvWatch.Get(prefix); ok { - grp := raw.(*NotifyGroup) - grp.Clear(notify) - } -} - -// notifyKV is used to notify any KV listeners of a change -// on a prefix -func (s *StateStore) notifyKV(path string, prefix bool) { - s.kvWatchLock.Lock() - defer s.kvWatchLock.Unlock() - - var toDelete []string - fn := func(s string, v interface{}) bool { - group := v.(*NotifyGroup) - group.Notify() - if s != "" { - toDelete = append(toDelete, s) - } - return false - } - - // Invoke any watcher on the path downward to the key. - s.kvWatch.WalkPath(path, fn) - - // If the entire prefix may be affected (e.g. delete tree), - // invoke the entire prefix - if prefix { - s.kvWatch.WalkPrefix(path, fn) - } - - // Delete the old watch groups - for i := len(toDelete) - 1; i >= 0; i-- { - s.kvWatch.Delete(toDelete[i]) - } -} - -// QueryTables returns the Tables that are queried for a given query -func (s *StateStore) QueryTables(q string) MDBTables { - return s.queryTables[q] -} - -// EnsureRegistration is used to make sure a node, service, and check registration -// is performed within a single transaction to avoid race conditions on state updates. -func (s *StateStore) EnsureRegistration(index uint64, req *structs.RegisterRequest) error { - tx, err := s.tables.StartTxn(false) - if err != nil { - panic(fmt.Errorf("Failed to start txn: %v", err)) - } - defer tx.Abort() - - // Ensure the node - node := structs.Node{Node: req.Node, Address: req.Address} - if err := s.ensureNodeTxn(index, node, tx); err != nil { - return err - } - - // Ensure the service if provided - if req.Service != nil { - if err := s.ensureServiceTxn(index, req.Node, req.Service, tx); err != nil { - return err - } - } - - // Ensure the check(s), if provided - if req.Check != nil { - if err := s.ensureCheckTxn(index, req.Check, tx); err != nil { - return err - } - } - for _, check := range req.Checks { - if err := s.ensureCheckTxn(index, check, tx); err != nil { - return err - } - } - - // Commit as one unit - return tx.Commit() -} - -// EnsureNode is used to ensure a given node exists, with the provided address -func (s *StateStore) EnsureNode(index uint64, node structs.Node) error { - tx, err := s.nodeTable.StartTxn(false, nil) - if err != nil { - return err - } - defer tx.Abort() - if err := s.ensureNodeTxn(index, node, tx); err != nil { - return err - } - return tx.Commit() -} - -// ensureNodeTxn is used to ensure a given node exists, with the provided address -// within a given txn -func (s *StateStore) ensureNodeTxn(index uint64, node structs.Node, tx *MDBTxn) error { - if err := s.nodeTable.InsertTxn(tx, node); err != nil { - return err - } - if err := s.nodeTable.SetLastIndexTxn(tx, index); err != nil { - return err - } - tx.Defer(func() { s.watch[s.nodeTable].Notify() }) - return nil -} - -// GetNode returns all the address of the known and if it was found -func (s *StateStore) GetNode(name string) (uint64, bool, string) { - idx, res, err := s.nodeTable.Get("id", name) - if err != nil { - s.logger.Printf("[ERR] consul.state: Error during node lookup: %v", err) - return 0, false, "" - } - if len(res) == 0 { - return idx, false, "" - } - return idx, true, res[0].(*structs.Node).Address -} - -// GetNodes returns all the known nodes, the slice alternates between -// the node name and address -func (s *StateStore) Nodes() (uint64, structs.Nodes) { - idx, res, err := s.nodeTable.Get("id") - if err != nil { - s.logger.Printf("[ERR] consul.state: Error getting nodes: %v", err) - } - results := make(structs.Nodes, len(res)) - for i, r := range res { - results[i] = r.(*structs.Node) - } - return idx, results -} - -// EnsureService is used to ensure a given node exposes a service -func (s *StateStore) EnsureService(index uint64, node string, ns *structs.NodeService) error { - tx, err := s.tables.StartTxn(false) - if err != nil { - panic(fmt.Errorf("Failed to start txn: %v", err)) - } - defer tx.Abort() - if err := s.ensureServiceTxn(index, node, ns, tx); err != nil { - return nil - } - return tx.Commit() -} - -// ensureServiceTxn is used to ensure a given node exposes a service in a transaction -func (s *StateStore) ensureServiceTxn(index uint64, node string, ns *structs.NodeService, tx *MDBTxn) error { - // Ensure the node exists - res, err := s.nodeTable.GetTxn(tx, "id", node) - if err != nil { - return err - } - if len(res) == 0 { - return fmt.Errorf("Missing node registration") - } - - // Create the entry - entry := structs.ServiceNode{ - Node: node, - ServiceID: ns.ID, - ServiceName: ns.Service, - ServiceTags: ns.Tags, - ServiceAddress: ns.Address, - ServicePort: ns.Port, - } - - // Ensure the service entry is set - if err := s.serviceTable.InsertTxn(tx, &entry); err != nil { - return err - } - if err := s.serviceTable.SetLastIndexTxn(tx, index); err != nil { - return err - } - tx.Defer(func() { s.watch[s.serviceTable].Notify() }) - return nil -} - -// NodeServices is used to return all the services of a given node -func (s *StateStore) NodeServices(name string) (uint64, *structs.NodeServices) { - tables := s.queryTables["NodeServices"] - tx, err := tables.StartTxn(true) - if err != nil { - panic(fmt.Errorf("Failed to start txn: %v", err)) - } - defer tx.Abort() - return s.parseNodeServices(tables, tx, name) -} - -// parseNodeServices is used to get the services belonging to a -// node, using a given txn -func (s *StateStore) parseNodeServices(tables MDBTables, tx *MDBTxn, name string) (uint64, *structs.NodeServices) { - ns := &structs.NodeServices{ - Services: make(map[string]*structs.NodeService), - } - - // Get the maximum index - index, err := tables.LastIndexTxn(tx) - if err != nil { - panic(fmt.Errorf("Failed to get last index: %v", err)) - } - - // Get the node first - res, err := s.nodeTable.GetTxn(tx, "id", name) - if err != nil { - s.logger.Printf("[ERR] consul.state: Failed to get node: %v", err) - } - if len(res) == 0 { - return index, nil - } - - // Set the address - node := res[0].(*structs.Node) - ns.Node = node - - // Get the services - res, err = s.serviceTable.GetTxn(tx, "id", name) - if err != nil { - s.logger.Printf("[ERR] consul.state: Failed to get node '%s' services: %v", name, err) - } - - // Add each service - for _, r := range res { - service := r.(*structs.ServiceNode) - srv := &structs.NodeService{ - ID: service.ServiceID, - Service: service.ServiceName, - Tags: service.ServiceTags, - Address: service.ServiceAddress, - Port: service.ServicePort, - } - ns.Services[srv.ID] = srv - } - return index, ns -} - -// DeleteNodeService is used to delete a node service -func (s *StateStore) DeleteNodeService(index uint64, node, id string) error { - tx, err := s.tables.StartTxn(false) - if err != nil { - panic(fmt.Errorf("Failed to start txn: %v", err)) - } - defer tx.Abort() - - if n, err := s.serviceTable.DeleteTxn(tx, "id", node, id); err != nil { - return err - } else if n > 0 { - if err := s.serviceTable.SetLastIndexTxn(tx, index); err != nil { - return err - } - tx.Defer(func() { s.watch[s.serviceTable].Notify() }) - } - - // Invalidate any sessions using these checks - checks, err := s.checkTable.GetTxn(tx, "node", node, id) - if err != nil { - return err - } - for _, c := range checks { - check := c.(*structs.HealthCheck) - if err := s.invalidateCheck(index, tx, node, check.CheckID); err != nil { - return err - } - } - - if n, err := s.checkTable.DeleteTxn(tx, "node", node, id); err != nil { - return err - } else if n > 0 { - if err := s.checkTable.SetLastIndexTxn(tx, index); err != nil { - return err - } - tx.Defer(func() { s.watch[s.checkTable].Notify() }) - } - return tx.Commit() -} - -// DeleteNode is used to delete a node and all it's services -func (s *StateStore) DeleteNode(index uint64, node string) error { - tx, err := s.tables.StartTxn(false) - if err != nil { - panic(fmt.Errorf("Failed to start txn: %v", err)) - } - defer tx.Abort() - - // Invalidate any sessions held by the node - if err := s.invalidateNode(index, tx, node); err != nil { - return err - } - - if n, err := s.serviceTable.DeleteTxn(tx, "id", node); err != nil { - return err - } else if n > 0 { - if err := s.serviceTable.SetLastIndexTxn(tx, index); err != nil { - return err - } - tx.Defer(func() { s.watch[s.serviceTable].Notify() }) - } - if n, err := s.checkTable.DeleteTxn(tx, "id", node); err != nil { - return err - } else if n > 0 { - if err := s.checkTable.SetLastIndexTxn(tx, index); err != nil { - return err - } - tx.Defer(func() { s.watch[s.checkTable].Notify() }) - } - if n, err := s.nodeTable.DeleteTxn(tx, "id", node); err != nil { - return err - } else if n > 0 { - if err := s.nodeTable.SetLastIndexTxn(tx, index); err != nil { - return err - } - tx.Defer(func() { s.watch[s.nodeTable].Notify() }) - } - return tx.Commit() -} - -// Services is used to return all the services with a list of associated tags -func (s *StateStore) Services() (uint64, map[string][]string) { - services := make(map[string][]string) - idx, res, err := s.serviceTable.Get("id") - if err != nil { - s.logger.Printf("[ERR] consul.state: Failed to get services: %v", err) - return idx, services - } - for _, r := range res { - srv := r.(*structs.ServiceNode) - tags, ok := services[srv.ServiceName] - if !ok { - services[srv.ServiceName] = make([]string, 0) - } - - for _, tag := range srv.ServiceTags { - if !strContains(tags, tag) { - tags = append(tags, tag) - services[srv.ServiceName] = tags - } - } - } - return idx, services -} - -// ServiceNodes returns the nodes associated with a given service -func (s *StateStore) ServiceNodes(service string) (uint64, structs.ServiceNodes) { - tables := s.queryTables["ServiceNodes"] - tx, err := tables.StartTxn(true) - if err != nil { - panic(fmt.Errorf("Failed to start txn: %v", err)) - } - defer tx.Abort() - - idx, err := tables.LastIndexTxn(tx) - if err != nil { - panic(fmt.Errorf("Failed to get last index: %v", err)) - } - - res, err := s.serviceTable.GetTxn(tx, "service", service) - return idx, s.parseServiceNodes(tx, s.nodeTable, res, err) -} - -// ServiceTagNodes returns the nodes associated with a given service matching a tag -func (s *StateStore) ServiceTagNodes(service, tag string) (uint64, structs.ServiceNodes) { - tables := s.queryTables["ServiceNodes"] - tx, err := tables.StartTxn(true) - if err != nil { - panic(fmt.Errorf("Failed to start txn: %v", err)) - } - defer tx.Abort() - - idx, err := tables.LastIndexTxn(tx) - if err != nil { - panic(fmt.Errorf("Failed to get last index: %v", err)) - } - - res, err := s.serviceTable.GetTxn(tx, "service", service) - res = serviceTagFilter(res, tag) - return idx, s.parseServiceNodes(tx, s.nodeTable, res, err) -} - -// serviceTagFilter is used to filter a list of *structs.ServiceNode which do -// not have the specified tag -func serviceTagFilter(l []interface{}, tag string) []interface{} { - n := len(l) - for i := 0; i < n; i++ { - srv := l[i].(*structs.ServiceNode) - if !strContains(ToLowerList(srv.ServiceTags), strings.ToLower(tag)) { - l[i], l[n-1] = l[n-1], nil - i-- - n-- - } - } - return l[:n] -} - -// parseServiceNodes parses results ServiceNodes and ServiceTagNodes -func (s *StateStore) parseServiceNodes(tx *MDBTxn, table *MDBTable, res []interface{}, err error) structs.ServiceNodes { - nodes := make(structs.ServiceNodes, len(res)) - if err != nil { - s.logger.Printf("[ERR] consul.state: Failed to get service nodes: %v", err) - return nodes - } - - for i, r := range res { - srv := r.(*structs.ServiceNode) - - // Get the address of the node - nodeRes, err := table.GetTxn(tx, "id", srv.Node) - if err != nil || len(nodeRes) != 1 { - s.logger.Printf("[ERR] consul.state: Failed to join service node %#v with node: %v", *srv, err) - continue - } - srv.Address = nodeRes[0].(*structs.Node).Address - - nodes[i] = srv - } - - return nodes -} - -// EnsureCheck is used to create a check or updates it's state -func (s *StateStore) EnsureCheck(index uint64, check *structs.HealthCheck) error { - tx, err := s.tables.StartTxn(false) - if err != nil { - panic(fmt.Errorf("Failed to start txn: %v", err)) - } - defer tx.Abort() - if err := s.ensureCheckTxn(index, check, tx); err != nil { - return err - } - return tx.Commit() -} - -// ensureCheckTxn is used to create a check or updates it's state in a transaction -func (s *StateStore) ensureCheckTxn(index uint64, check *structs.HealthCheck, tx *MDBTxn) error { - // Ensure we have a status - if check.Status == "" { - check.Status = structs.HealthCritical - } - - // Ensure the node exists - res, err := s.nodeTable.GetTxn(tx, "id", check.Node) - if err != nil { - return err - } - if len(res) == 0 { - return fmt.Errorf("Missing node registration") - } - - // Ensure the service exists if specified - if check.ServiceID != "" { - res, err = s.serviceTable.GetTxn(tx, "id", check.Node, check.ServiceID) - if err != nil { - return err - } - if len(res) == 0 { - return fmt.Errorf("Missing service registration") - } - // Ensure we set the correct service - srv := res[0].(*structs.ServiceNode) - check.ServiceName = srv.ServiceName - } - - // Invalidate any sessions if status is critical - if check.Status == structs.HealthCritical { - err := s.invalidateCheck(index, tx, check.Node, check.CheckID) - if err != nil { - return err - } - } - - // Ensure the check is set - if err := s.checkTable.InsertTxn(tx, check); err != nil { - return err - } - if err := s.checkTable.SetLastIndexTxn(tx, index); err != nil { - return err - } - tx.Defer(func() { s.watch[s.checkTable].Notify() }) - return nil -} - -// DeleteNodeCheck is used to delete a node health check -func (s *StateStore) DeleteNodeCheck(index uint64, node, id string) error { - tx, err := s.tables.StartTxn(false) - if err != nil { - return err - } - defer tx.Abort() - - // Invalidate any sessions held by this check - if err := s.invalidateCheck(index, tx, node, id); err != nil { - return err - } - - if n, err := s.checkTable.DeleteTxn(tx, "id", node, id); err != nil { - return err - } else if n > 0 { - if err := s.checkTable.SetLastIndexTxn(tx, index); err != nil { - return err - } - tx.Defer(func() { s.watch[s.checkTable].Notify() }) - } - return tx.Commit() -} - -// NodeChecks is used to get all the checks for a node -func (s *StateStore) NodeChecks(node string) (uint64, structs.HealthChecks) { - return s.parseHealthChecks(s.checkTable.Get("id", node)) -} - -// ServiceChecks is used to get all the checks for a service -func (s *StateStore) ServiceChecks(service string) (uint64, structs.HealthChecks) { - return s.parseHealthChecks(s.checkTable.Get("service", service)) -} - -// CheckInState is used to get all the checks for a service in a given state -func (s *StateStore) ChecksInState(state string) (uint64, structs.HealthChecks) { - var idx uint64 - var res []interface{} - var err error - if state == structs.HealthAny { - idx, res, err = s.checkTable.Get("id") - } else { - idx, res, err = s.checkTable.Get("status", state) - } - return s.parseHealthChecks(idx, res, err) -} - -// parseHealthChecks is used to handle the results of a Get against -// the checkTable -func (s *StateStore) parseHealthChecks(idx uint64, res []interface{}, err error) (uint64, structs.HealthChecks) { - results := make([]*structs.HealthCheck, len(res)) - if err != nil { - s.logger.Printf("[ERR] consul.state: Failed to get health checks: %v", err) - return idx, results - } - for i, r := range res { - results[i] = r.(*structs.HealthCheck) - } - return idx, results -} - -// CheckServiceNodes returns the nodes associated with a given service, along -// with any associated check -func (s *StateStore) CheckServiceNodes(service string) (uint64, structs.CheckServiceNodes) { - tables := s.queryTables["CheckServiceNodes"] - tx, err := tables.StartTxn(true) - if err != nil { - panic(fmt.Errorf("Failed to start txn: %v", err)) - } - defer tx.Abort() - - idx, err := tables.LastIndexTxn(tx) - if err != nil { - panic(fmt.Errorf("Failed to get last index: %v", err)) - } - - res, err := s.serviceTable.GetTxn(tx, "service", service) - return idx, s.parseCheckServiceNodes(tx, res, err) -} - -// CheckServiceNodes returns the nodes associated with a given service, along -// with any associated checks -func (s *StateStore) CheckServiceTagNodes(service, tag string) (uint64, structs.CheckServiceNodes) { - tables := s.queryTables["CheckServiceNodes"] - tx, err := tables.StartTxn(true) - if err != nil { - panic(fmt.Errorf("Failed to start txn: %v", err)) - } - defer tx.Abort() - - idx, err := tables.LastIndexTxn(tx) - if err != nil { - panic(fmt.Errorf("Failed to get last index: %v", err)) - } - - res, err := s.serviceTable.GetTxn(tx, "service", service) - res = serviceTagFilter(res, tag) - return idx, s.parseCheckServiceNodes(tx, res, err) -} - -// parseCheckServiceNodes parses results CheckServiceNodes and CheckServiceTagNodes -func (s *StateStore) parseCheckServiceNodes(tx *MDBTxn, res []interface{}, err error) structs.CheckServiceNodes { - nodes := make(structs.CheckServiceNodes, len(res)) - if err != nil { - s.logger.Printf("[ERR] consul.state: Failed to get service nodes: %v", err) - return nodes - } - - for i, r := range res { - srv := r.(*structs.ServiceNode) - - // Get the node - nodeRes, err := s.nodeTable.GetTxn(tx, "id", srv.Node) - if err != nil || len(nodeRes) != 1 { - s.logger.Printf("[ERR] consul.state: Failed to join service node %#v with node: %v", *srv, err) - continue - } - - // Get any associated checks of the service - res, err := s.checkTable.GetTxn(tx, "node", srv.Node, srv.ServiceID) - _, checks := s.parseHealthChecks(0, res, err) - - // Get any checks of the node, not associated with any service - res, err = s.checkTable.GetTxn(tx, "node", srv.Node, "") - _, nodeChecks := s.parseHealthChecks(0, res, err) - checks = append(checks, nodeChecks...) - - // Setup the node - nodes[i].Node = nodeRes[0].(*structs.Node) - nodes[i].Service = &structs.NodeService{ - ID: srv.ServiceID, - Service: srv.ServiceName, - Tags: srv.ServiceTags, - Address: srv.ServiceAddress, - Port: srv.ServicePort, - } - nodes[i].Checks = checks - } - - return nodes -} - -// NodeInfo is used to generate the full info about a node. -func (s *StateStore) NodeInfo(node string) (uint64, structs.NodeDump) { - tables := s.queryTables["NodeInfo"] - tx, err := tables.StartTxn(true) - if err != nil { - panic(fmt.Errorf("Failed to start txn: %v", err)) - } - defer tx.Abort() - - idx, err := tables.LastIndexTxn(tx) - if err != nil { - panic(fmt.Errorf("Failed to get last index: %v", err)) - } - - res, err := s.nodeTable.GetTxn(tx, "id", node) - return idx, s.parseNodeInfo(tx, res, err) -} - -// NodeDump is used to generate the NodeInfo for all nodes. This is very expensive, -// and should generally be avoided for programmatic access. -func (s *StateStore) NodeDump() (uint64, structs.NodeDump) { - tables := s.queryTables["NodeDump"] - tx, err := tables.StartTxn(true) - if err != nil { - panic(fmt.Errorf("Failed to start txn: %v", err)) - } - defer tx.Abort() - - idx, err := tables.LastIndexTxn(tx) - if err != nil { - panic(fmt.Errorf("Failed to get last index: %v", err)) - } - - res, err := s.nodeTable.GetTxn(tx, "id") - return idx, s.parseNodeInfo(tx, res, err) -} - -// parseNodeInfo is used to scan over the results of a node -// iteration and generate a NodeDump -func (s *StateStore) parseNodeInfo(tx *MDBTxn, res []interface{}, err error) structs.NodeDump { - dump := make(structs.NodeDump, 0, len(res)) - if err != nil { - s.logger.Printf("[ERR] consul.state: Failed to get nodes: %v", err) - return dump - } - - for _, r := range res { - // Copy the address and node - node := r.(*structs.Node) - info := &structs.NodeInfo{ - Node: node.Node, - Address: node.Address, - } - - // Get any services of the node - res, err = s.serviceTable.GetTxn(tx, "id", node.Node) - if err != nil { - s.logger.Printf("[ERR] consul.state: Failed to get node services: %v", err) - } - info.Services = make([]*structs.NodeService, 0, len(res)) - for _, r := range res { - service := r.(*structs.ServiceNode) - srv := &structs.NodeService{ - ID: service.ServiceID, - Service: service.ServiceName, - Tags: service.ServiceTags, - Address: service.ServiceAddress, - Port: service.ServicePort, - } - info.Services = append(info.Services, srv) - } - - // Get any checks of the node - res, err = s.checkTable.GetTxn(tx, "node", node.Node) - if err != nil { - s.logger.Printf("[ERR] consul.state: Failed to get node checks: %v", err) - } - info.Checks = make([]*structs.HealthCheck, 0, len(res)) - for _, r := range res { - chk := r.(*structs.HealthCheck) - info.Checks = append(info.Checks, chk) - } - - // Add the node info - dump = append(dump, info) - } - return dump -} - -// KVSSet is used to create or update a KV entry -func (s *StateStore) KVSSet(index uint64, d *structs.DirEntry) error { - _, err := s.kvsSet(index, d, kvSet) - return err -} - -// KVSRestore is used to restore a DirEntry. It should only be used when -// doing a restore, otherwise KVSSet should be used. -func (s *StateStore) KVSRestore(d *structs.DirEntry) error { - // Start a new txn - tx, err := s.kvsTable.StartTxn(false, nil) - if err != nil { - return err - } - defer tx.Abort() - - if err := s.kvsTable.InsertTxn(tx, d); err != nil { - return err - } - if err := s.kvsTable.SetMaxLastIndexTxn(tx, d.ModifyIndex); err != nil { - return err - } - return tx.Commit() -} - -// KVSGet is used to get a KV entry -func (s *StateStore) KVSGet(key string) (uint64, *structs.DirEntry, error) { - idx, res, err := s.kvsTable.Get("id", key) - var d *structs.DirEntry - if len(res) > 0 { - d = res[0].(*structs.DirEntry) - } - return idx, d, err -} - -// KVSList is used to list all KV entries with a prefix -func (s *StateStore) KVSList(prefix string) (uint64, uint64, structs.DirEntries, error) { - tables := MDBTables{s.kvsTable, s.tombstoneTable} - tx, err := tables.StartTxn(true) - if err != nil { - return 0, 0, nil, err - } - defer tx.Abort() - - idx, err := tables.LastIndexTxn(tx) - if err != nil { - return 0, 0, nil, err - } - - res, err := s.kvsTable.GetTxn(tx, "id_prefix", prefix) - if err != nil { - return 0, 0, nil, err - } - ents := make(structs.DirEntries, len(res)) - for idx, r := range res { - ents[idx] = r.(*structs.DirEntry) - } - - // Check for the highest index in the tombstone table - var maxIndex uint64 - res, err = s.tombstoneTable.GetTxn(tx, "id_prefix", prefix) - for _, r := range res { - ent := r.(*structs.DirEntry) - if ent.ModifyIndex > maxIndex { - maxIndex = ent.ModifyIndex - } - } - - return maxIndex, idx, ents, err -} - -// KVSListKeys is used to list keys with a prefix, and up to a given separator -func (s *StateStore) KVSListKeys(prefix, seperator string) (uint64, []string, error) { - tables := MDBTables{s.kvsTable, s.tombstoneTable} - tx, err := tables.StartTxn(true) - if err != nil { - return 0, nil, err - } - defer tx.Abort() - - idx, err := s.kvsTable.LastIndexTxn(tx) - if err != nil { - return 0, nil, err - } - - // Ensure a non-zero index - if idx == 0 { - // Must provide non-zero index to prevent blocking - // Index 1 is impossible anyways (due to Raft internals) - idx = 1 - } - - // Aggregate the stream - stream := make(chan interface{}, 128) - streamTomb := make(chan interface{}, 128) - done := make(chan struct{}) - var keys []string - var maxIndex uint64 - go func() { - prefixLen := len(prefix) - sepLen := len(seperator) - last := "" - for raw := range stream { - ent := raw.(*structs.DirEntry) - after := ent.Key[prefixLen:] - - // Update the highest index we've seen - if ent.ModifyIndex > maxIndex { - maxIndex = ent.ModifyIndex - } - - // If there is no separator, always accumulate - if sepLen == 0 { - keys = append(keys, ent.Key) - continue - } - - // Check for the separator - if idx := strings.Index(after, seperator); idx >= 0 { - toSep := ent.Key[:prefixLen+idx+sepLen] - if last != toSep { - keys = append(keys, toSep) - last = toSep - } - } else { - keys = append(keys, ent.Key) - } - } - - // Handle the tombstones for any index updates - for raw := range streamTomb { - ent := raw.(*structs.DirEntry) - if ent.ModifyIndex > maxIndex { - maxIndex = ent.ModifyIndex - } - } - close(done) - }() - - // Start the stream, and wait for completion - if err = s.kvsTable.StreamTxn(stream, tx, "id_prefix", prefix); err != nil { - return 0, nil, err - } - if err := s.tombstoneTable.StreamTxn(streamTomb, tx, "id_prefix", prefix); err != nil { - return 0, nil, err - } - <-done - - // Use the maxIndex if we have any keys - if maxIndex != 0 { - idx = maxIndex - } - return idx, keys, nil -} - -// KVSDelete is used to delete a KVS entry -func (s *StateStore) KVSDelete(index uint64, key string) error { - return s.kvsDeleteWithIndex(index, "id", key) -} - -// KVSDeleteCheckAndSet is used to perform an atomic delete check-and-set -func (s *StateStore) KVSDeleteCheckAndSet(index uint64, key string, casIndex uint64) (bool, error) { - tx, err := s.tables.StartTxn(false) - if err != nil { - return false, err - } - defer tx.Abort() - - // Get the existing node - res, err := s.kvsTable.GetTxn(tx, "id", key) - if err != nil { - return false, err - } - - // Get the existing node if any - var exist *structs.DirEntry - if len(res) > 0 { - exist = res[0].(*structs.DirEntry) - } - - // Use the casIndex as the constraint. A modify time of 0 means - // we are doing a delete-if-not-exists (odd...), while any other - // value means we expect that modify time. - if casIndex == 0 { - return exist == nil, nil - } else if casIndex > 0 && (exist == nil || exist.ModifyIndex != casIndex) { - return false, nil - } - - // Do the actual delete - if err := s.kvsDeleteWithIndexTxn(index, tx, "id", key); err != nil { - return false, err - } - return true, tx.Commit() -} - -// KVSDeleteTree is used to delete all keys with a given prefix -func (s *StateStore) KVSDeleteTree(index uint64, prefix string) error { - if prefix == "" { - return s.kvsDeleteWithIndex(index, "id") - } - return s.kvsDeleteWithIndex(index, "id_prefix", prefix) -} - -// kvsDeleteWithIndex does a delete with either the id or id_prefix -func (s *StateStore) kvsDeleteWithIndex(index uint64, tableIndex string, parts ...string) error { - tx, err := s.tables.StartTxn(false) - if err != nil { - return err - } - defer tx.Abort() - if err := s.kvsDeleteWithIndexTxn(index, tx, tableIndex, parts...); err != nil { - return err - } - return tx.Commit() -} - -// kvsDeleteWithIndexTxn does a delete within an existing transaction -func (s *StateStore) kvsDeleteWithIndexTxn(index uint64, tx *MDBTxn, tableIndex string, parts ...string) error { - num := 0 - for { - // Get some number of entries to delete - pairs, err := s.kvsTable.GetTxnLimit(tx, 128, tableIndex, parts...) - if err != nil { - return err - } - - // Create the tombstones and delete - for _, raw := range pairs { - ent := raw.(*structs.DirEntry) - ent.ModifyIndex = index // Update the index - ent.Value = nil // Reduce storage required - ent.Session = "" - if err := s.tombstoneTable.InsertTxn(tx, ent); err != nil { - return err - } - if num, err := s.kvsTable.DeleteTxn(tx, "id", ent.Key); err != nil { - return err - } else if num != 1 { - return fmt.Errorf("Failed to delete key '%s'", ent.Key) - } - } - - // Increment the total number - num += len(pairs) - if len(pairs) == 0 { - break - } - } - - if num > 0 { - if err := s.kvsTable.SetLastIndexTxn(tx, index); err != nil { - return err - } - tx.Defer(func() { - // Trigger the most fine grained notifications if possible - switch { - case len(parts) == 0: - s.notifyKV("", true) - case tableIndex == "id": - s.notifyKV(parts[0], false) - case tableIndex == "id_prefix": - s.notifyKV(parts[0], true) - default: - s.notifyKV("", true) - } - if s.gc != nil { - // If GC is configured, then we hint that this index - // required expiration. - s.gc.Hint(index) - } - }) - } - return nil -} - -// KVSCheckAndSet is used to perform an atomic check-and-set -func (s *StateStore) KVSCheckAndSet(index uint64, d *structs.DirEntry) (bool, error) { - return s.kvsSet(index, d, kvCAS) -} - -// KVSLock works like KVSSet but only writes if the lock can be acquired -func (s *StateStore) KVSLock(index uint64, d *structs.DirEntry) (bool, error) { - return s.kvsSet(index, d, kvLock) -} - -// KVSUnlock works like KVSSet but only writes if the lock can be unlocked -func (s *StateStore) KVSUnlock(index uint64, d *structs.DirEntry) (bool, error) { - return s.kvsSet(index, d, kvUnlock) -} - -// KVSLockDelay returns the expiration time of a key lock delay. A key may -// have a lock delay if it was unlocked due to a session invalidation instead -// of a graceful unlock. This must be checked on the leader node, and not in -// KVSLock due to the variability of clocks. -func (s *StateStore) KVSLockDelay(key string) time.Time { - s.lockDelayLock.RLock() - expires := s.lockDelay[key] - s.lockDelayLock.RUnlock() - return expires -} - -// kvsSet is the internal setter -func (s *StateStore) kvsSet( - index uint64, - d *structs.DirEntry, - mode kvMode) (bool, error) { - // Start a new txn - tx, err := s.tables.StartTxn(false) - if err != nil { - return false, err - } - defer tx.Abort() - - // Get the existing node - res, err := s.kvsTable.GetTxn(tx, "id", d.Key) - if err != nil { - return false, err - } - - // Get the existing node if any - var exist *structs.DirEntry - if len(res) > 0 { - exist = res[0].(*structs.DirEntry) - } - - // Use the ModifyIndex as the constraint. A modify of time of 0 - // means we are doing a set-if-not-exists, while any other value - // means we expect that modify time. - if mode == kvCAS { - if d.ModifyIndex == 0 && exist != nil { - return false, nil - } else if d.ModifyIndex > 0 && (exist == nil || exist.ModifyIndex != d.ModifyIndex) { - return false, nil - } - } - - // If attempting to lock, check this is possible - if mode == kvLock { - // Verify we have a session - if d.Session == "" { - return false, fmt.Errorf("Missing session") - } - - // Bail if it is already locked - if exist != nil && exist.Session != "" { - return false, nil - } - - // Verify the session exists - res, err := s.sessionTable.GetTxn(tx, "id", d.Session) - if err != nil { - return false, err - } - if len(res) == 0 { - return false, fmt.Errorf("Invalid session") - } - - // Update the lock index - if exist != nil { - exist.LockIndex++ - exist.Session = d.Session - } else { - d.LockIndex = 1 - } - } - - // If attempting to unlock, verify the key exists and is held - if mode == kvUnlock { - if exist == nil || exist.Session != d.Session { - return false, nil - } - // Clear the session to unlock - exist.Session = "" - } - - // Set the create and modify times - if exist == nil { - d.CreateIndex = index - } else { - d.CreateIndex = exist.CreateIndex - d.LockIndex = exist.LockIndex - d.Session = exist.Session - - } - d.ModifyIndex = index - - if err := s.kvsTable.InsertTxn(tx, d); err != nil { - return false, err - } - if err := s.kvsTable.SetLastIndexTxn(tx, index); err != nil { - return false, err - } - tx.Defer(func() { s.notifyKV(d.Key, false) }) - return true, tx.Commit() -} - -// ReapTombstones is used to delete all the tombstones with a ModifyTime -// less than or equal to the given index. This is used to prevent unbounded -// storage growth of the tombstones. -func (s *StateStore) ReapTombstones(index uint64) error { - tx, err := s.tombstoneTable.StartTxn(false, nil) - if err != nil { - return fmt.Errorf("failed to start txn: %v", err) - } - defer tx.Abort() - - // Scan the tombstone table for all the entries that are - // eligible for GC. This could be improved by indexing on - // ModifyTime and doing a less-than-equals scan, however - // we don't currently support numeric indexes internally. - // Luckily, this is a low frequency operation. - var toDelete []string - streamCh := make(chan interface{}, 128) - doneCh := make(chan struct{}) - go func() { - defer close(doneCh) - for raw := range streamCh { - ent := raw.(*structs.DirEntry) - if ent.ModifyIndex <= index { - toDelete = append(toDelete, ent.Key) - } - } - }() - if err := s.tombstoneTable.StreamTxn(streamCh, tx, "id"); err != nil { - s.logger.Printf("[ERR] consul.state: failed to scan tombstones: %v", err) - return fmt.Errorf("failed to scan tombstones: %v", err) - } - <-doneCh - - // Delete each tombstone - if len(toDelete) > 0 { - s.logger.Printf("[DEBUG] consul.state: reaping %d tombstones up to %d", len(toDelete), index) - } - for _, key := range toDelete { - num, err := s.tombstoneTable.DeleteTxn(tx, "id", key) - if err != nil { - s.logger.Printf("[ERR] consul.state: failed to delete tombstone: %v", err) - return fmt.Errorf("failed to delete tombstone: %v", err) - } - if num != 1 { - return fmt.Errorf("failed to delete tombstone '%s'", key) - } - } - return tx.Commit() -} - -// TombstoneRestore is used to restore a tombstone. -// It should only be used when doing a restore. -func (s *StateStore) TombstoneRestore(d *structs.DirEntry) error { - // Start a new txn - tx, err := s.tombstoneTable.StartTxn(false, nil) - if err != nil { - return err - } - defer tx.Abort() - - if err := s.tombstoneTable.InsertTxn(tx, d); err != nil { - return err - } - return tx.Commit() -} - -// SessionCreate is used to create a new session. The -// ID will be populated on a successful return -func (s *StateStore) SessionCreate(index uint64, session *structs.Session) error { - // Verify a Session ID is generated - if session.ID == "" { - return fmt.Errorf("Missing Session ID") - } - - switch session.Behavior { - case "": - // Default behavior is Release for backwards compatibility - session.Behavior = structs.SessionKeysRelease - case structs.SessionKeysRelease: - case structs.SessionKeysDelete: - default: - return fmt.Errorf("Invalid Session Behavior setting '%s'", session.Behavior) - } - - // Assign the create index - session.CreateIndex = index - - // Start the transaction - tx, err := s.tables.StartTxn(false) - if err != nil { - panic(fmt.Errorf("Failed to start txn: %v", err)) - } - defer tx.Abort() - - // Verify that the node exists - res, err := s.nodeTable.GetTxn(tx, "id", session.Node) - if err != nil { - return err - } - if len(res) == 0 { - return fmt.Errorf("Missing node registration") - } - - // Verify that the checks exist and are not critical - for _, checkId := range session.Checks { - res, err := s.checkTable.GetTxn(tx, "id", session.Node, checkId) - if err != nil { - return err - } - if len(res) == 0 { - return fmt.Errorf("Missing check '%s' registration", checkId) - } - chk := res[0].(*structs.HealthCheck) - if chk.Status == structs.HealthCritical { - return fmt.Errorf("Check '%s' is in %s state", checkId, chk.Status) - } - } - - // Insert the session - if err := s.sessionTable.InsertTxn(tx, session); err != nil { - return err - } - - // Insert the check mappings - sCheck := sessionCheck{Node: session.Node, Session: session.ID} - for _, checkID := range session.Checks { - sCheck.CheckID = checkID - if err := s.sessionCheckTable.InsertTxn(tx, &sCheck); err != nil { - return err - } - } - - // Trigger the update notifications - if err := s.sessionTable.SetLastIndexTxn(tx, index); err != nil { - return err - } - tx.Defer(func() { s.watch[s.sessionTable].Notify() }) - return tx.Commit() -} - -// SessionRestore is used to restore a session. It should only be used when -// doing a restore, otherwise SessionCreate should be used. -func (s *StateStore) SessionRestore(session *structs.Session) error { - // Start the transaction - tx, err := s.tables.StartTxn(false) - if err != nil { - panic(fmt.Errorf("Failed to start txn: %v", err)) - } - defer tx.Abort() - - // Insert the session - if err := s.sessionTable.InsertTxn(tx, session); err != nil { - return err - } - - // Insert the check mappings - sCheck := sessionCheck{Node: session.Node, Session: session.ID} - for _, checkID := range session.Checks { - sCheck.CheckID = checkID - if err := s.sessionCheckTable.InsertTxn(tx, &sCheck); err != nil { - return err - } - } - - // Trigger the update notifications - index := session.CreateIndex - if err := s.sessionTable.SetMaxLastIndexTxn(tx, index); err != nil { - return err - } - tx.Defer(func() { s.watch[s.sessionTable].Notify() }) - return tx.Commit() -} - -// SessionGet is used to get a session entry -func (s *StateStore) SessionGet(id string) (uint64, *structs.Session, error) { - idx, res, err := s.sessionTable.Get("id", id) - var d *structs.Session - if len(res) > 0 { - d = res[0].(*structs.Session) - } - return idx, d, err -} - -// SessionList is used to list all the open sessions -func (s *StateStore) SessionList() (uint64, []*structs.Session, error) { - idx, res, err := s.sessionTable.Get("id") - out := make([]*structs.Session, len(res)) - for i, raw := range res { - out[i] = raw.(*structs.Session) - } - return idx, out, err -} - -// NodeSessions is used to list all the open sessions for a node -func (s *StateStore) NodeSessions(node string) (uint64, []*structs.Session, error) { - idx, res, err := s.sessionTable.Get("node", node) - out := make([]*structs.Session, len(res)) - for i, raw := range res { - out[i] = raw.(*structs.Session) - } - return idx, out, err -} - -// SessionDestroy is used to destroy a session. -func (s *StateStore) SessionDestroy(index uint64, id string) error { - tx, err := s.tables.StartTxn(false) - if err != nil { - panic(fmt.Errorf("Failed to start txn: %v", err)) - } - defer tx.Abort() - - s.logger.Printf("[DEBUG] consul.state: Invalidating session %s due to session destroy", - id) - if err := s.invalidateSession(index, tx, id); err != nil { - return err - } - return tx.Commit() -} - -// invalidateNode is used to invalidate all sessions belonging to a node -// All tables should be locked in the tx. -func (s *StateStore) invalidateNode(index uint64, tx *MDBTxn, node string) error { - sessions, err := s.sessionTable.GetTxn(tx, "node", node) - if err != nil { - return err - } - for _, sess := range sessions { - session := sess.(*structs.Session).ID - s.logger.Printf("[DEBUG] consul.state: Invalidating session %s due to node '%s' invalidation", - session, node) - if err := s.invalidateSession(index, tx, session); err != nil { - return err - } - } - return nil -} - -// invalidateCheck is used to invalidate all sessions belonging to a check -// All tables should be locked in the tx. -func (s *StateStore) invalidateCheck(index uint64, tx *MDBTxn, node, check string) error { - sessionChecks, err := s.sessionCheckTable.GetTxn(tx, "id", node, check) - if err != nil { - return err - } - for _, sc := range sessionChecks { - session := sc.(*sessionCheck).Session - s.logger.Printf("[DEBUG] consul.state: Invalidating session %s due to check '%s' invalidation", - session, check) - if err := s.invalidateSession(index, tx, session); err != nil { - return err - } - } - return nil -} - -// invalidateSession is used to invalidate a session within a given txn -// All tables should be locked in the tx. -func (s *StateStore) invalidateSession(index uint64, tx *MDBTxn, id string) error { - // Get the session - res, err := s.sessionTable.GetTxn(tx, "id", id) - if err != nil { - return err - } - - // Quit if this session does not exist - if len(res) == 0 { - return nil - } - session := res[0].(*structs.Session) - - // Enforce the MaxLockDelay - delay := session.LockDelay - if delay > structs.MaxLockDelay { - delay = structs.MaxLockDelay - } - - // Invalidate any held locks - if session.Behavior == structs.SessionKeysDelete { - if err := s.deleteLocks(index, tx, delay, id); err != nil { - return err - } - } else if err := s.invalidateLocks(index, tx, delay, id); err != nil { - return err - } - - // Nuke the session - if _, err := s.sessionTable.DeleteTxn(tx, "id", id); err != nil { - return err - } - - // Delete the check mappings - for _, checkID := range session.Checks { - if _, err := s.sessionCheckTable.DeleteTxn(tx, "id", - session.Node, checkID, id); err != nil { - return err - } - } - - // Trigger the update notifications - if err := s.sessionTable.SetLastIndexTxn(tx, index); err != nil { - return err - } - tx.Defer(func() { s.watch[s.sessionTable].Notify() }) - return nil -} - -// invalidateLocks is used to invalidate all the locks held by a session -// within a given txn. All tables should be locked in the tx. -func (s *StateStore) invalidateLocks(index uint64, tx *MDBTxn, - lockDelay time.Duration, id string) error { - pairs, err := s.kvsTable.GetTxn(tx, "session", id) - if err != nil { - return err - } - - var expires time.Time - if lockDelay > 0 { - s.lockDelayLock.Lock() - defer s.lockDelayLock.Unlock() - expires = time.Now().Add(lockDelay) - } - - for _, pair := range pairs { - kv := pair.(*structs.DirEntry) - kv.Session = "" // Clear the lock - kv.ModifyIndex = index // Update the modified time - if err := s.kvsTable.InsertTxn(tx, kv); err != nil { - return err - } - // If there is a lock delay, prevent acquisition - // for at least lockDelay period - if lockDelay > 0 { - s.lockDelay[kv.Key] = expires - time.AfterFunc(lockDelay, func() { - s.lockDelayLock.Lock() - delete(s.lockDelay, kv.Key) - s.lockDelayLock.Unlock() - }) - } - tx.Defer(func() { s.notifyKV(kv.Key, false) }) - } - if len(pairs) > 0 { - if err := s.kvsTable.SetLastIndexTxn(tx, index); err != nil { - return err - } - } - return nil -} - -// deleteLocks is used to delete all the locks held by a session -// within a given txn. All tables should be locked in the tx. -func (s *StateStore) deleteLocks(index uint64, tx *MDBTxn, - lockDelay time.Duration, id string) error { - pairs, err := s.kvsTable.GetTxn(tx, "session", id) - if err != nil { - return err - } - - var expires time.Time - if lockDelay > 0 { - s.lockDelayLock.Lock() - defer s.lockDelayLock.Unlock() - expires = time.Now().Add(lockDelay) - } - - for _, pair := range pairs { - kv := pair.(*structs.DirEntry) - if err := s.kvsDeleteWithIndexTxn(index, tx, "id", kv.Key); err != nil { - return err - } - - // If there is a lock delay, prevent acquisition - // for at least lockDelay period - if lockDelay > 0 { - s.lockDelay[kv.Key] = expires - time.AfterFunc(lockDelay, func() { - s.lockDelayLock.Lock() - delete(s.lockDelay, kv.Key) - s.lockDelayLock.Unlock() - }) - } - } - return nil -} - -// Snapshot is used to create a point in time snapshot -func (s *StateStore) Snapshot() (*StateSnapshot, error) { - // Begin a new txn on all tables - tx, err := s.tables.StartTxn(true) - if err != nil { - return nil, err - } - - // Determine the max index - index, err := s.tables.LastIndexTxn(tx) - if err != nil { - tx.Abort() - return nil, err - } - - // Return the snapshot - snap := &StateSnapshot{ - store: s, - tx: tx, - lastIndex: index, - } - return snap, nil -} - -// LastIndex returns the last index that affects the snapshotted data -func (s *StateSnapshot) LastIndex() uint64 { - return s.lastIndex -} - -// Nodes returns all the known nodes, the slice alternates between -// the node name and address -func (s *StateSnapshot) Nodes() structs.Nodes { - res, err := s.store.nodeTable.GetTxn(s.tx, "id") - if err != nil { - s.store.logger.Printf("[ERR] consul.state: Failed to get nodes: %v", err) - return nil - } - results := make(structs.Nodes, len(res)) - for i, r := range res { - results[i] = r.(*structs.Node) - } - return results -} - -// NodeServices is used to return all the services of a given node -func (s *StateSnapshot) NodeServices(name string) *structs.NodeServices { - _, res := s.store.parseNodeServices(s.store.tables, s.tx, name) - return res -} - -// NodeChecks is used to return all the checks of a given node -func (s *StateSnapshot) NodeChecks(node string) structs.HealthChecks { - res, err := s.store.checkTable.GetTxn(s.tx, "id", node) - _, checks := s.store.parseHealthChecks(s.lastIndex, res, err) - return checks -} - -// KVSDump is used to list all KV entries. It takes a channel and streams -// back *struct.DirEntry objects. This will block and should be invoked -// in a goroutine. -func (s *StateSnapshot) KVSDump(stream chan<- interface{}) error { - return s.store.kvsTable.StreamTxn(stream, s.tx, "id") -} - -// TombstoneDump is used to dump all tombstone entries. It takes a channel and streams -// back *struct.DirEntry objects. This will block and should be invoked -// in a goroutine. -func (s *StateSnapshot) TombstoneDump(stream chan<- interface{}) error { - return s.store.tombstoneTable.StreamTxn(stream, s.tx, "id") -} - -// SessionList is used to list all the open sessions -func (s *StateSnapshot) SessionList() ([]*structs.Session, error) { - res, err := s.store.sessionTable.GetTxn(s.tx, "id") - out := make([]*structs.Session, len(res)) - for i, raw := range res { - out[i] = raw.(*structs.Session) - } - return out, err -} diff --git a/consul/state_store_test.go b/consul/state_store_test.go deleted file mode 100644 index 296779c985..0000000000 --- a/consul/state_store_test.go +++ /dev/null @@ -1,2842 +0,0 @@ -package consul - -import ( - "os" - "reflect" - "sort" - "testing" - "time" - - state_store "github.com/hashicorp/consul/consul/state" - "github.com/hashicorp/consul/consul/structs" -) - -func testStateStore() (*StateStore, error) { - return NewStateStore(nil, os.Stderr) -} - -func TestEnsureRegistration(t *testing.T) { - store, err := testStateStore() - if err != nil { - t.Fatalf("err: %v", err) - } - defer store.Close() - - reg := &structs.RegisterRequest{ - Node: "foo", - Address: "127.0.0.1", - Service: &structs.NodeService{ - ID: "api", - Service: "api", - Tags: nil, - Address: "", - Port: 5000}, - Check: &structs.HealthCheck{ - Node: "foo", - CheckID: "api", - Name: "Can connect", - Status: structs.HealthPassing, - ServiceID: "api", - }, - Checks: structs.HealthChecks{ - &structs.HealthCheck{ - Node: "foo", - CheckID: "api-cache", - Name: "Can cache stuff", - Status: structs.HealthPassing, - ServiceID: "api", - }, - }, - } - - if err := store.EnsureRegistration(13, reg); err != nil { - t.Fatalf("err: %v", err) - } - - idx, found, addr := store.GetNode("foo") - if idx != 13 || !found || addr != "127.0.0.1" { - t.Fatalf("Bad: %v %v %v", idx, found, addr) - } - - idx, services := store.NodeServices("foo") - if idx != 13 { - t.Fatalf("bad: %v", idx) - } - - entry, ok := services.Services["api"] - if !ok { - t.Fatalf("missing api: %#v", services) - } - if len(entry.Tags) != 0 || entry.Port != 5000 { - t.Fatalf("Bad entry: %#v", entry) - } - - idx, checks := store.NodeChecks("foo") - if idx != 13 { - t.Fatalf("bad: %v", idx) - } - if len(checks) != 2 { - t.Fatalf("check: %#v", checks) - } -} - -func TestEnsureNode(t *testing.T) { - store, err := testStateStore() - if err != nil { - t.Fatalf("err: %v", err) - } - defer store.Close() - - if err := store.EnsureNode(3, structs.Node{Node: "foo", Address: "127.0.0.1"}); err != nil { - t.Fatalf("err: %v", err) - } - - idx, found, addr := store.GetNode("foo") - if idx != 3 || !found || addr != "127.0.0.1" { - t.Fatalf("Bad: %v %v %v", idx, found, addr) - } - - if err := store.EnsureNode(4, structs.Node{Node: "foo", Address: "127.0.0.2"}); err != nil { - t.Fatalf("err: %v", err) - } - - idx, found, addr = store.GetNode("foo") - if idx != 4 || !found || addr != "127.0.0.2" { - t.Fatalf("Bad: %v %v %v", idx, found, addr) - } -} - -func TestGetNodes(t *testing.T) { - store, err := testStateStore() - if err != nil { - t.Fatalf("err: %v", err) - } - defer store.Close() - - if err := store.EnsureNode(40, structs.Node{Node: "foo", Address: "127.0.0.1"}); err != nil { - t.Fatalf("err: %v", err) - } - - if err := store.EnsureNode(41, structs.Node{Node: "bar", Address: "127.0.0.2"}); err != nil { - t.Fatalf("err: %v", err) - } - - idx, nodes := store.Nodes() - if idx != 41 { - t.Fatalf("idx: %v", idx) - } - if len(nodes) != 2 { - t.Fatalf("Bad: %v", nodes) - } - if nodes[1].Node != "foo" && nodes[0].Node != "bar" { - t.Fatalf("Bad: %v", nodes) - } -} - -func TestGetNodes_Watch_StopWatch(t *testing.T) { - store, err := testStateStore() - if err != nil { - t.Fatalf("err: %v", err) - } - defer store.Close() - - notify1 := make(chan struct{}, 1) - notify2 := make(chan struct{}, 1) - - store.Watch(store.QueryTables("Nodes"), notify1) - store.Watch(store.QueryTables("Nodes"), notify2) - store.StopWatch(store.QueryTables("Nodes"), notify2) - - if err := store.EnsureNode(40, structs.Node{Node: "foo", Address: "127.0.0.1"}); err != nil { - t.Fatalf("err: %v", err) - } - - select { - case <-notify1: - default: - t.Fatalf("should be notified") - } - - select { - case <-notify2: - t.Fatalf("should not be notified") - default: - } -} - -func BenchmarkGetNodes(b *testing.B) { - store, err := testStateStore() - if err != nil { - b.Fatalf("err: %v", err) - } - defer store.Close() - - if err := store.EnsureNode(100, structs.Node{Node: "foo", Address: "127.0.0.1"}); err != nil { - b.Fatalf("err: %v", err) - } - - if err := store.EnsureNode(101, structs.Node{Node: "bar", Address: "127.0.0.2"}); err != nil { - b.Fatalf("err: %v", err) - } - - for i := 0; i < b.N; i++ { - store.Nodes() - } -} - -func TestEnsureService(t *testing.T) { - store, err := testStateStore() - if err != nil { - t.Fatalf("err: %v", err) - } - defer store.Close() - - if err := store.EnsureNode(10, structs.Node{Node: "foo", Address: "127.0.0.1"}); err != nil { - t.Fatalf("err: %v", err) - } - - if err := store.EnsureService(11, "foo", &structs.NodeService{ID: "api", Service: "api", Tags: nil, Address: "", Port: 5000}); err != nil { - t.Fatalf("err: %v", err) - } - - if err := store.EnsureService(12, "foo", &structs.NodeService{ID: "api", Service: "api", Tags: nil, Address: "", Port: 5001}); err != nil { - t.Fatalf("err: %v", err) - } - - if err := store.EnsureService(13, "foo", &structs.NodeService{ID: "db", Service: "db", Tags: []string{"master"}, Address: "", Port: 8000}); err != nil { - t.Fatalf("err: %v", err) - } - - idx, services := store.NodeServices("foo") - if idx != 13 { - t.Fatalf("bad: %v", idx) - } - - entry, ok := services.Services["api"] - if !ok { - t.Fatalf("missing api: %#v", services) - } - if len(entry.Tags) != 0 || entry.Port != 5001 { - t.Fatalf("Bad entry: %#v", entry) - } - - entry, ok = services.Services["db"] - if !ok { - t.Fatalf("missing db: %#v", services) - } - if !strContains(entry.Tags, "master") || entry.Port != 8000 { - t.Fatalf("Bad entry: %#v", entry) - } -} - -func TestEnsureService_DuplicateNode(t *testing.T) { - store, err := testStateStore() - if err != nil { - t.Fatalf("err: %v", err) - } - defer store.Close() - - if err := store.EnsureNode(10, structs.Node{Node: "foo", Address: "127.0.0.1"}); err != nil { - t.Fatalf("err: %v", err) - } - - if err := store.EnsureService(11, "foo", &structs.NodeService{ID: "api1", Service: "api", Tags: nil, Address: "", Port: 5000}); err != nil { - t.Fatalf("err: %v", err) - } - - if err := store.EnsureService(12, "foo", &structs.NodeService{ID: "api2", Service: "api", Tags: nil, Address: "", Port: 5001}); err != nil { - t.Fatalf("err: %v", err) - } - - if err := store.EnsureService(13, "foo", &structs.NodeService{ID: "api3", Service: "api", Tags: nil, Address: "", Port: 5002}); err != nil { - t.Fatalf("err: %v", err) - } - - idx, services := store.NodeServices("foo") - if idx != 13 { - t.Fatalf("bad: %v", idx) - } - - entry, ok := services.Services["api1"] - if !ok { - t.Fatalf("missing api: %#v", services) - } - if len(entry.Tags) != 0 || entry.Port != 5000 { - t.Fatalf("Bad entry: %#v", entry) - } - - entry, ok = services.Services["api2"] - if !ok { - t.Fatalf("missing api: %#v", services) - } - if len(entry.Tags) != 0 || entry.Port != 5001 { - t.Fatalf("Bad entry: %#v", entry) - } - - entry, ok = services.Services["api3"] - if !ok { - t.Fatalf("missing api: %#v", services) - } - if len(entry.Tags) != 0 || entry.Port != 5002 { - t.Fatalf("Bad entry: %#v", entry) - } -} - -func TestDeleteNodeService(t *testing.T) { - store, err := testStateStore() - if err != nil { - t.Fatalf("err: %v", err) - } - defer store.Close() - - if err := store.EnsureNode(11, structs.Node{Node: "foo", Address: "127.0.0.1"}); err != nil { - t.Fatalf("err: %v", err) - } - - if err := store.EnsureService(12, "foo", &structs.NodeService{ID: "api", Service: "api", Tags: nil, Address: "", Port: 5000}); err != nil { - t.Fatalf("err: %v", err) - } - - check := &structs.HealthCheck{ - Node: "foo", - CheckID: "api", - Name: "Can connect", - Status: structs.HealthPassing, - ServiceID: "api", - } - if err := store.EnsureCheck(13, check); err != nil { - t.Fatalf("err: %v", err) - } - - if err := store.DeleteNodeService(14, "foo", "api"); err != nil { - t.Fatalf("err: %v", err) - } - - idx, services := store.NodeServices("foo") - if idx != 14 { - t.Fatalf("bad: %v", idx) - } - _, ok := services.Services["api"] - if ok { - t.Fatalf("has api: %#v", services) - } - - idx, checks := store.NodeChecks("foo") - if idx != 14 { - t.Fatalf("bad: %v", idx) - } - if len(checks) != 0 { - t.Fatalf("has check: %#v", checks) - } -} - -func TestDeleteNodeService_One(t *testing.T) { - store, err := testStateStore() - if err != nil { - t.Fatalf("err: %v", err) - } - defer store.Close() - - if err := store.EnsureNode(11, structs.Node{Node: "foo", Address: "127.0.0.1"}); err != nil { - t.Fatalf("err: %v", err) - } - - if err := store.EnsureService(12, "foo", &structs.NodeService{ID: "api", Service: "api", Tags: nil, Address: "", Port: 5000}); err != nil { - t.Fatalf("err: %v", err) - } - - if err := store.EnsureService(13, "foo", &structs.NodeService{ID: "api2", Service: "api", Tags: nil, Address: "", Port: 5001}); err != nil { - t.Fatalf("err: %v", err) - } - - if err := store.DeleteNodeService(14, "foo", "api"); err != nil { - t.Fatalf("err: %v", err) - } - - idx, services := store.NodeServices("foo") - if idx != 14 { - t.Fatalf("bad: %v", idx) - } - _, ok := services.Services["api"] - if ok { - t.Fatalf("has api: %#v", services) - } - _, ok = services.Services["api2"] - if !ok { - t.Fatalf("does not have api2: %#v", services) - } -} - -func TestDeleteNode(t *testing.T) { - store, err := testStateStore() - if err != nil { - t.Fatalf("err: %v", err) - } - defer store.Close() - - if err := store.EnsureNode(20, structs.Node{Node: "foo", Address: "127.0.0.1"}); err != nil { - t.Fatalf("err: %v", err) - } - - if err := store.EnsureService(21, "foo", &structs.NodeService{ID: "api", Service: "api", Tags: nil, Address: "", Port: 5000}); err != nil { - t.Fatalf("err: %v", err) - } - - check := &structs.HealthCheck{ - Node: "foo", - CheckID: "db", - Name: "Can connect", - Status: structs.HealthPassing, - ServiceID: "api", - } - if err := store.EnsureCheck(22, check); err != nil { - t.Fatalf("err: %v", err) - } - - if err := store.DeleteNode(23, "foo"); err != nil { - t.Fatalf("err: %v", err) - } - - idx, services := store.NodeServices("foo") - if idx != 23 { - t.Fatalf("bad: %v", idx) - } - if services != nil { - t.Fatalf("has services: %#v", services) - } - - idx, checks := store.NodeChecks("foo") - if idx != 23 { - t.Fatalf("bad: %v", idx) - } - if len(checks) > 0 { - t.Fatalf("has checks: %v", checks) - } - - idx, found, _ := store.GetNode("foo") - if idx != 23 { - t.Fatalf("bad: %v", idx) - } - if found { - t.Fatalf("found node") - } -} - -func TestGetServices(t *testing.T) { - store, err := testStateStore() - if err != nil { - t.Fatalf("err: %v", err) - } - defer store.Close() - - if err := store.EnsureNode(30, structs.Node{Node: "foo", Address: "127.0.0.1"}); err != nil { - t.Fatalf("err: %v", err) - } - - if err := store.EnsureNode(31, structs.Node{Node: "bar", Address: "127.0.0.2"}); err != nil { - t.Fatalf("err: %v", err) - } - - if err := store.EnsureService(32, "foo", &structs.NodeService{ID: "api", Service: "api", Tags: nil, Address: "", Port: 5000}); err != nil { - t.Fatalf("err: %v", err) - } - - if err := store.EnsureService(33, "foo", &structs.NodeService{ID: "db", Service: "db", Tags: []string{"master"}, Address: "", Port: 8000}); err != nil { - t.Fatalf("err: %v", err) - } - - if err := store.EnsureService(34, "bar", &structs.NodeService{ID: "db", Service: "db", Tags: []string{"slave"}, Address: "", Port: 8000}); err != nil { - t.Fatalf("err: %v", err) - } - - idx, services := store.Services() - if idx != 34 { - t.Fatalf("bad: %v", idx) - } - - tags, ok := services["api"] - if !ok { - t.Fatalf("missing api: %#v", services) - } - if len(tags) != 0 { - t.Fatalf("Bad entry: %#v", tags) - } - - tags, ok = services["db"] - sort.Strings(tags) - if !ok { - t.Fatalf("missing db: %#v", services) - } - if len(tags) != 2 || tags[0] != "master" || tags[1] != "slave" { - t.Fatalf("Bad entry: %#v", tags) - } -} - -func TestServiceNodes(t *testing.T) { - store, err := testStateStore() - if err != nil { - t.Fatalf("err: %v", err) - } - defer store.Close() - - if err := store.EnsureNode(10, structs.Node{Node: "foo", Address: "127.0.0.1"}); err != nil { - t.Fatalf("err: %v", err) - } - - if err := store.EnsureNode(11, structs.Node{Node: "bar", Address: "127.0.0.2"}); err != nil { - t.Fatalf("err: %v", err) - } - - if err := store.EnsureService(12, "foo", &structs.NodeService{ID: "api", Service: "api", Tags: nil, Address: "", Port: 5000}); err != nil { - t.Fatalf("err: %v", err) - } - - if err := store.EnsureService(13, "bar", &structs.NodeService{ID: "api", Service: "api", Tags: nil, Address: "", Port: 5000}); err != nil { - t.Fatalf("err: %v", err) - } - - if err := store.EnsureService(14, "foo", &structs.NodeService{ID: "db", Service: "db", Tags: []string{"master"}, Address: "", Port: 8000}); err != nil { - t.Fatalf("err: %v", err) - } - - if err := store.EnsureService(15, "bar", &structs.NodeService{ID: "db", Service: "db", Tags: []string{"slave"}, Address: "", Port: 8000}); err != nil { - t.Fatalf("err: %v", err) - } - - if err := store.EnsureService(16, "bar", &structs.NodeService{ID: "db2", Service: "db", Tags: []string{"slave"}, Address: "", Port: 8001}); err != nil { - t.Fatalf("err: %v", err) - } - - idx, nodes := store.ServiceNodes("db") - if idx != 16 { - t.Fatalf("bad: %v", 16) - } - if len(nodes) != 3 { - t.Fatalf("bad: %v", nodes) - } - if nodes[0].Node != "foo" { - t.Fatalf("bad: %v", nodes) - } - if nodes[0].Address != "127.0.0.1" { - t.Fatalf("bad: %v", nodes) - } - if nodes[0].ServiceID != "db" { - t.Fatalf("bad: %v", nodes) - } - if !strContains(nodes[0].ServiceTags, "master") { - t.Fatalf("bad: %v", nodes) - } - if nodes[0].ServicePort != 8000 { - t.Fatalf("bad: %v", nodes) - } - - if nodes[1].Node != "bar" { - t.Fatalf("bad: %v", nodes) - } - if nodes[1].Address != "127.0.0.2" { - t.Fatalf("bad: %v", nodes) - } - if nodes[1].ServiceID != "db" { - t.Fatalf("bad: %v", nodes) - } - if !strContains(nodes[1].ServiceTags, "slave") { - t.Fatalf("bad: %v", nodes) - } - if nodes[1].ServicePort != 8000 { - t.Fatalf("bad: %v", nodes) - } - - if nodes[2].Node != "bar" { - t.Fatalf("bad: %v", nodes) - } - if nodes[2].Address != "127.0.0.2" { - t.Fatalf("bad: %v", nodes) - } - if nodes[2].ServiceID != "db2" { - t.Fatalf("bad: %v", nodes) - } - if !strContains(nodes[2].ServiceTags, "slave") { - t.Fatalf("bad: %v", nodes) - } - if nodes[2].ServicePort != 8001 { - t.Fatalf("bad: %v", nodes) - } -} - -func TestServiceTagNodes(t *testing.T) { - store, err := testStateStore() - if err != nil { - t.Fatalf("err: %v", err) - } - defer store.Close() - - if err := store.EnsureNode(15, structs.Node{Node: "foo", Address: "127.0.0.1"}); err != nil { - t.Fatalf("err: %v", err) - } - - if err := store.EnsureNode(16, structs.Node{Node: "bar", Address: "127.0.0.2"}); err != nil { - t.Fatalf("err: %v", err) - } - - if err := store.EnsureService(17, "foo", &structs.NodeService{ID: "db", Service: "db", Tags: []string{"master"}, Address: "", Port: 8000}); err != nil { - t.Fatalf("err: %v", err) - } - - if err := store.EnsureService(18, "foo", &structs.NodeService{ID: "db2", Service: "db", Tags: []string{"slave"}, Address: "", Port: 8001}); err != nil { - t.Fatalf("err: %v", err) - } - - if err := store.EnsureService(19, "bar", &structs.NodeService{ID: "db", Service: "db", Tags: []string{"slave"}, Address: "", Port: 8000}); err != nil { - t.Fatalf("err: %v", err) - } - - idx, nodes := store.ServiceTagNodes("db", "master") - if idx != 19 { - t.Fatalf("bad: %v", idx) - } - if len(nodes) != 1 { - t.Fatalf("bad: %v", nodes) - } - if nodes[0].Node != "foo" { - t.Fatalf("bad: %v", nodes) - } - if nodes[0].Address != "127.0.0.1" { - t.Fatalf("bad: %v", nodes) - } - if !strContains(nodes[0].ServiceTags, "master") { - t.Fatalf("bad: %v", nodes) - } - if nodes[0].ServicePort != 8000 { - t.Fatalf("bad: %v", nodes) - } -} - -func TestServiceTagNodes_MultipleTags(t *testing.T) { - store, err := testStateStore() - if err != nil { - t.Fatalf("err: %v", err) - } - defer store.Close() - - if err := store.EnsureNode(15, structs.Node{Node: "foo", Address: "127.0.0.1"}); err != nil { - t.Fatalf("err: %v", err) - } - - if err := store.EnsureNode(16, structs.Node{Node: "bar", Address: "127.0.0.2"}); err != nil { - t.Fatalf("err: %v", err) - } - - if err := store.EnsureService(17, "foo", &structs.NodeService{ID: "db", Service: "db", Tags: []string{"master", "v2"}, Address: "", Port: 8000}); err != nil { - t.Fatalf("err: %v", err) - } - - if err := store.EnsureService(18, "foo", &structs.NodeService{ID: "db2", Service: "db", Tags: []string{"slave", "v2", "dev"}, Address: "", Port: 8001}); err != nil { - t.Fatalf("err: %v", err) - } - - if err := store.EnsureService(19, "bar", &structs.NodeService{ID: "db", Service: "db", Tags: []string{"slave", "v2"}, Address: "", Port: 8000}); err != nil { - t.Fatalf("err: %v", err) - } - - idx, nodes := store.ServiceTagNodes("db", "master") - if idx != 19 { - t.Fatalf("bad: %v", idx) - } - if len(nodes) != 1 { - t.Fatalf("bad: %v", nodes) - } - if nodes[0].Node != "foo" { - t.Fatalf("bad: %v", nodes) - } - if nodes[0].Address != "127.0.0.1" { - t.Fatalf("bad: %v", nodes) - } - if !strContains(nodes[0].ServiceTags, "master") { - t.Fatalf("bad: %v", nodes) - } - if nodes[0].ServicePort != 8000 { - t.Fatalf("bad: %v", nodes) - } - - idx, nodes = store.ServiceTagNodes("db", "v2") - if idx != 19 { - t.Fatalf("bad: %v", idx) - } - if len(nodes) != 3 { - t.Fatalf("bad: %v", nodes) - } - - idx, nodes = store.ServiceTagNodes("db", "dev") - if idx != 19 { - t.Fatalf("bad: %v", idx) - } - if len(nodes) != 1 { - t.Fatalf("bad: %v", nodes) - } - if nodes[0].Node != "foo" { - t.Fatalf("bad: %v", nodes) - } - if nodes[0].Address != "127.0.0.1" { - t.Fatalf("bad: %v", nodes) - } - if !strContains(nodes[0].ServiceTags, "dev") { - t.Fatalf("bad: %v", nodes) - } - if nodes[0].ServicePort != 8001 { - t.Fatalf("bad: %v", nodes) - } -} - -func TestStoreSnapshot(t *testing.T) { - store, err := testStateStore() - if err != nil { - t.Fatalf("err: %v", err) - } - defer store.Close() - - if err := store.EnsureNode(8, structs.Node{Node: "foo", Address: "127.0.0.1"}); err != nil { - t.Fatalf("err: %v", err) - } - - if err := store.EnsureNode(9, structs.Node{Node: "bar", Address: "127.0.0.2"}); err != nil { - t.Fatalf("err: %v", err) - } - - if err := store.EnsureService(10, "foo", &structs.NodeService{ID: "db", Service: "db", Tags: []string{"master"}, Address: "", Port: 8000}); err != nil { - t.Fatalf("err: %v", err) - } - - if err := store.EnsureService(11, "foo", &structs.NodeService{ID: "db2", Service: "db", Tags: []string{"slave"}, Address: "", Port: 8001}); err != nil { - t.Fatalf("err: %v", err) - } - - if err := store.EnsureService(12, "bar", &structs.NodeService{ID: "db", Service: "db", Tags: []string{"slave"}, Address: "", Port: 8000}); err != nil { - t.Fatalf("err: %v", err) - } - - check := &structs.HealthCheck{ - Node: "foo", - CheckID: "db", - Name: "Can connect", - Status: structs.HealthPassing, - ServiceID: "db", - } - if err := store.EnsureCheck(13, check); err != nil { - t.Fatalf("err: %v", err) - } - - // Add some KVS entries - d := &structs.DirEntry{Key: "/web/a", Flags: 42, Value: []byte("test")} - if err := store.KVSSet(14, d); err != nil { - t.Fatalf("err: %v", err) - } - d = &structs.DirEntry{Key: "/web/b", Flags: 42, Value: []byte("test")} - if err := store.KVSSet(15, d); err != nil { - t.Fatalf("err: %v", err) - } - d = &structs.DirEntry{Key: "/web/c", Flags: 42, Value: []byte("test")} - if err := store.KVSSet(16, d); err != nil { - t.Fatalf("err: %v", err) - } - // Create a tombstone - // TODO: Change to /web/c causes failure? - if err := store.KVSDelete(17, "/web/a"); err != nil { - t.Fatalf("err: %v", err) - } - - // Add some sessions - session := &structs.Session{ID: generateUUID(), Node: "foo"} - if err := store.SessionCreate(18, session); err != nil { - t.Fatalf("err: %v", err) - } - - session = &structs.Session{ID: generateUUID(), Node: "bar"} - if err := store.SessionCreate(19, session); err != nil { - t.Fatalf("err: %v", err) - } - d.Session = session.ID - if ok, err := store.KVSLock(20, d); err != nil || !ok { - t.Fatalf("err: %v", err) - } - session = &structs.Session{ID: generateUUID(), Node: "bar", TTL: "60s"} - if err := store.SessionCreate(21, session); err != nil { - t.Fatalf("err: %v", err) - } - - // Take a snapshot - snap, err := store.Snapshot() - if err != nil { - t.Fatalf("err: %v", err) - } - defer snap.Close() - - // Check the last nodes - if idx := snap.LastIndex(); idx != 22 { - t.Fatalf("bad: %v", idx) - } - - // Check snapshot has old values - nodes := snap.Nodes() - if len(nodes) != 2 { - t.Fatalf("bad: %v", nodes) - } - - // Ensure we get the service entries - services := snap.NodeServices("foo") - if !strContains(services.Services["db"].Tags, "master") { - t.Fatalf("bad: %v", services) - } - if !strContains(services.Services["db2"].Tags, "slave") { - t.Fatalf("bad: %v", services) - } - - services = snap.NodeServices("bar") - if !strContains(services.Services["db"].Tags, "slave") { - t.Fatalf("bad: %v", services) - } - - // Ensure we get the checks - checks := snap.NodeChecks("foo") - if len(checks) != 1 { - t.Fatalf("bad: %v", checks) - } - if !reflect.DeepEqual(checks[0], check) { - t.Fatalf("bad: %v", checks[0]) - } - - // Check we have the entries - streamCh := make(chan interface{}, 64) - doneCh := make(chan struct{}) - var ents []*structs.DirEntry - go func() { - for { - obj := <-streamCh - if obj == nil { - close(doneCh) - return - } - ents = append(ents, obj.(*structs.DirEntry)) - } - }() - if err := snap.KVSDump(streamCh); err != nil { - t.Fatalf("err: %v", err) - } - <-doneCh - if len(ents) != 2 { - t.Fatalf("missing KVS entries! %#v", ents) - } - - // Check we have the tombstone entries - streamCh = make(chan interface{}, 64) - doneCh = make(chan struct{}) - ents = nil - go func() { - for { - obj := <-streamCh - if obj == nil { - close(doneCh) - return - } - ents = append(ents, obj.(*structs.DirEntry)) - } - }() - if err := snap.TombstoneDump(streamCh); err != nil { - t.Fatalf("err: %v", err) - } - <-doneCh - if len(ents) != 1 { - t.Fatalf("missing tombstone entries!") - } - - // Check there are 3 sessions - sessions, err := snap.SessionList() - if err != nil { - t.Fatalf("err: %v", err) - } - if len(sessions) != 3 { - t.Fatalf("missing sessions") - } - - ttls := 0 - for _, session := range sessions { - if session.TTL != "" { - ttls++ - } - } - if ttls != 1 { - t.Fatalf("Wrong number of sessions with TTL") - } - - // Make some changes! - if err := store.EnsureService(23, "foo", &structs.NodeService{ID: "db", Service: "db", Tags: []string{"slave"}, Address: "", Port: 8000}); err != nil { - t.Fatalf("err: %v", err) - } - if err := store.EnsureService(24, "bar", &structs.NodeService{ID: "db", Service: "db", Tags: []string{"master"}, Address: "", Port: 8000}); err != nil { - t.Fatalf("err: %v", err) - } - if err := store.EnsureNode(25, structs.Node{Node: "baz", Address: "127.0.0.3"}); err != nil { - t.Fatalf("err: %v", err) - } - checkAfter := &structs.HealthCheck{ - Node: "foo", - CheckID: "db", - Name: "Can connect", - Status: structs.HealthCritical, - ServiceID: "db", - } - if err := store.EnsureCheck(27, checkAfter); err != nil { - t.Fatalf("err: %v", err) - } - - if err := store.KVSDelete(28, "/web/b"); err != nil { - t.Fatalf("err: %v", err) - } - - // Check snapshot has old values - nodes = snap.Nodes() - if len(nodes) != 2 { - t.Fatalf("bad: %v", nodes) - } - - // Ensure old service entries - services = snap.NodeServices("foo") - if !strContains(services.Services["db"].Tags, "master") { - t.Fatalf("bad: %v", services) - } - if !strContains(services.Services["db2"].Tags, "slave") { - t.Fatalf("bad: %v", services) - } - - services = snap.NodeServices("bar") - if !strContains(services.Services["db"].Tags, "slave") { - t.Fatalf("bad: %v", services) - } - - checks = snap.NodeChecks("foo") - if len(checks) != 1 { - t.Fatalf("bad: %v", checks) - } - if !reflect.DeepEqual(checks[0], check) { - t.Fatalf("bad: %v", checks[0]) - } - - // Check we have the entries - streamCh = make(chan interface{}, 64) - doneCh = make(chan struct{}) - ents = nil - go func() { - for { - obj := <-streamCh - if obj == nil { - close(doneCh) - return - } - ents = append(ents, obj.(*structs.DirEntry)) - } - }() - if err := snap.KVSDump(streamCh); err != nil { - t.Fatalf("err: %v", err) - } - <-doneCh - if len(ents) != 2 { - t.Fatalf("missing KVS entries!") - } - - // Check we have the tombstone entries - streamCh = make(chan interface{}, 64) - doneCh = make(chan struct{}) - ents = nil - go func() { - for { - obj := <-streamCh - if obj == nil { - close(doneCh) - return - } - ents = append(ents, obj.(*structs.DirEntry)) - } - }() - if err := snap.TombstoneDump(streamCh); err != nil { - t.Fatalf("err: %v", err) - } - <-doneCh - if len(ents) != 1 { - t.Fatalf("missing tombstone entries!") - } - - // Check there are 3 sessions - sessions, err = snap.SessionList() - if err != nil { - t.Fatalf("err: %v", err) - } - if len(sessions) != 3 { - t.Fatalf("missing sessions") - } -} - -func TestEnsureCheck(t *testing.T) { - store, err := testStateStore() - if err != nil { - t.Fatalf("err: %v", err) - } - defer store.Close() - - if err := store.EnsureNode(1, structs.Node{Node: "foo", Address: "127.0.0.1"}); err != nil { - t.Fatalf("err: %v", err) - } - if err := store.EnsureService(2, "foo", &structs.NodeService{ID: "db1", Service: "db", Tags: []string{"master"}, Address: "", Port: 8000}); err != nil { - t.Fatalf("err: %v", err) - } - check := &structs.HealthCheck{ - Node: "foo", - CheckID: "db", - Name: "Can connect", - Status: structs.HealthPassing, - ServiceID: "db1", - } - if err := store.EnsureCheck(3, check); err != nil { - t.Fatalf("err: %v", err) - } - - check2 := &structs.HealthCheck{ - Node: "foo", - CheckID: "memory", - Name: "memory utilization", - Status: structs.HealthWarning, - } - if err := store.EnsureCheck(4, check2); err != nil { - t.Fatalf("err: %v", err) - } - - idx, checks := store.NodeChecks("foo") - if idx != 4 { - t.Fatalf("bad: %v", idx) - } - if len(checks) != 2 { - t.Fatalf("bad: %v", checks) - } - if !reflect.DeepEqual(checks[0], check) { - t.Fatalf("bad: %v", checks[0]) - } - if !reflect.DeepEqual(checks[1], check2) { - t.Fatalf("bad: %v", checks[1]) - } - - idx, checks = store.ServiceChecks("db") - if idx != 4 { - t.Fatalf("bad: %v", idx) - } - if len(checks) != 1 { - t.Fatalf("bad: %v", checks) - } - if !reflect.DeepEqual(checks[0], check) { - t.Fatalf("bad: %v", checks[0]) - } - - idx, checks = store.ChecksInState(structs.HealthPassing) - if idx != 4 { - t.Fatalf("bad: %v", idx) - } - if len(checks) != 1 { - t.Fatalf("bad: %v", checks) - } - if !reflect.DeepEqual(checks[0], check) { - t.Fatalf("bad: %v", checks[0]) - } - - idx, checks = store.ChecksInState(structs.HealthWarning) - if idx != 4 { - t.Fatalf("bad: %v", idx) - } - if len(checks) != 1 { - t.Fatalf("bad: %v", checks) - } - if !reflect.DeepEqual(checks[0], check2) { - t.Fatalf("bad: %v", checks[0]) - } - - idx, checks = store.ChecksInState(structs.HealthAny) - if idx != 4 { - t.Fatalf("bad: %v", idx) - } - if len(checks) != 2 { - t.Fatalf("bad: %v", checks) - } - if !reflect.DeepEqual(checks[0], check) { - t.Fatalf("bad: %v", checks[0]) - } - if !reflect.DeepEqual(checks[1], check2) { - t.Fatalf("bad: %v", checks[1]) - } -} - -func TestDeleteNodeCheck(t *testing.T) { - store, err := testStateStore() - if err != nil { - t.Fatalf("err: %v", err) - } - defer store.Close() - - if err := store.EnsureNode(1, structs.Node{Node: "foo", Address: "127.0.0.1"}); err != nil { - t.Fatalf("err: %v", err) - } - if err := store.EnsureService(2, "foo", &structs.NodeService{ID: "db1", Service: "db", Tags: []string{"master"}, Address: "", Port: 8000}); err != nil { - t.Fatalf("err: %v", err) - } - check := &structs.HealthCheck{ - Node: "foo", - CheckID: "db", - Name: "Can connect", - Status: structs.HealthPassing, - ServiceID: "db1", - } - if err := store.EnsureCheck(3, check); err != nil { - t.Fatalf("err: %v", err) - } - - check2 := &structs.HealthCheck{ - Node: "foo", - CheckID: "memory", - Name: "memory utilization", - Status: structs.HealthWarning, - } - if err := store.EnsureCheck(4, check2); err != nil { - t.Fatalf("err: %v", err) - } - - if err := store.DeleteNodeCheck(5, "foo", "db"); err != nil { - t.Fatalf("err: %v", err) - } - - idx, checks := store.NodeChecks("foo") - if idx != 5 { - t.Fatalf("bad: %v", idx) - } - if len(checks) != 1 { - t.Fatalf("bad: %v", checks) - } - if !reflect.DeepEqual(checks[0], check2) { - t.Fatalf("bad: %v", checks[0]) - } -} - -func TestCheckServiceNodes(t *testing.T) { - store, err := testStateStore() - if err != nil { - t.Fatalf("err: %v", err) - } - defer store.Close() - - if err := store.EnsureNode(1, structs.Node{Node: "foo", Address: "127.0.0.1"}); err != nil { - t.Fatalf("err: %v", err) - } - if err := store.EnsureService(2, "foo", &structs.NodeService{ID: "db1", Service: "db", Tags: []string{"master"}, Address: "", Port: 8000}); err != nil { - t.Fatalf("err: %v", err) - } - check := &structs.HealthCheck{ - Node: "foo", - CheckID: "db", - Name: "Can connect", - Status: structs.HealthPassing, - ServiceID: "db1", - } - if err := store.EnsureCheck(3, check); err != nil { - t.Fatalf("err: %v", err) - } - check = &structs.HealthCheck{ - Node: "foo", - CheckID: SerfCheckID, - Name: SerfCheckName, - Status: structs.HealthPassing, - } - if err := store.EnsureCheck(4, check); err != nil { - t.Fatalf("err: %v", err) - } - - idx, nodes := store.CheckServiceNodes("db") - if idx != 4 { - t.Fatalf("bad: %v", idx) - } - if len(nodes) != 1 { - t.Fatalf("Bad: %v", nodes) - } - - if nodes[0].Node.Node != "foo" { - t.Fatalf("Bad: %v", nodes[0]) - } - if nodes[0].Service.ID != "db1" { - t.Fatalf("Bad: %v", nodes[0]) - } - if len(nodes[0].Checks) != 2 { - t.Fatalf("Bad: %v", nodes[0]) - } - if nodes[0].Checks[0].CheckID != "db" { - t.Fatalf("Bad: %v", nodes[0]) - } - if nodes[0].Checks[1].CheckID != SerfCheckID { - t.Fatalf("Bad: %v", nodes[0]) - } - - idx, nodes = store.CheckServiceTagNodes("db", "master") - if idx != 4 { - t.Fatalf("bad: %v", idx) - } - if len(nodes) != 1 { - t.Fatalf("Bad: %v", nodes) - } - - if nodes[0].Node.Node != "foo" { - t.Fatalf("Bad: %v", nodes[0]) - } - if nodes[0].Service.ID != "db1" { - t.Fatalf("Bad: %v", nodes[0]) - } - if len(nodes[0].Checks) != 2 { - t.Fatalf("Bad: %v", nodes[0]) - } - if nodes[0].Checks[0].CheckID != "db" { - t.Fatalf("Bad: %v", nodes[0]) - } - if nodes[0].Checks[1].CheckID != SerfCheckID { - t.Fatalf("Bad: %v", nodes[0]) - } -} -func BenchmarkCheckServiceNodes(t *testing.B) { - store, err := testStateStore() - if err != nil { - t.Fatalf("err: %v", err) - } - defer store.Close() - - if err := store.EnsureNode(1, structs.Node{Node: "foo", Address: "127.0.0.1"}); err != nil { - t.Fatalf("err: %v", err) - } - if err := store.EnsureService(2, "foo", &structs.NodeService{ID: "db1", Service: "db", Tags: []string{"master"}, Address: "", Port: 8000}); err != nil { - t.Fatalf("err: %v", err) - } - check := &structs.HealthCheck{ - Node: "foo", - CheckID: "db", - Name: "Can connect", - Status: structs.HealthPassing, - ServiceID: "db1", - } - if err := store.EnsureCheck(3, check); err != nil { - t.Fatalf("err: %v", err) - } - check = &structs.HealthCheck{ - Node: "foo", - CheckID: SerfCheckID, - Name: SerfCheckName, - Status: structs.HealthPassing, - } - if err := store.EnsureCheck(4, check); err != nil { - t.Fatalf("err: %v", err) - } - - for i := 0; i < t.N; i++ { - store.CheckServiceNodes("db") - } -} - -func TestSS_Register_Deregister_Query(t *testing.T) { - store, err := testStateStore() - if err != nil { - t.Fatalf("err: %v", err) - } - defer store.Close() - - if err := store.EnsureNode(1, structs.Node{Node: "foo", Address: "127.0.0.1"}); err != nil { - t.Fatalf("err: %v", err) - } - - srv := &structs.NodeService{ - ID: "statsite-box-stats", - Service: "statsite-box-stats", - Tags: nil, - Address: "", - Port: 0} - if err := store.EnsureService(2, "foo", srv); err != nil { - t.Fatalf("err: %v", err) - } - - srv = &structs.NodeService{ - ID: "statsite-share-stats", - Service: "statsite-share-stats", - Tags: nil, - Address: "", - Port: 0} - if err := store.EnsureService(3, "foo", srv); err != nil { - t.Fatalf("err: %v", err) - } - - if err := store.DeleteNode(4, "foo"); err != nil { - t.Fatalf("err: %v", err) - } - - idx, nodes := store.CheckServiceNodes("statsite-share-stats") - if idx != 4 { - t.Fatalf("bad: %v", idx) - } - if len(nodes) != 0 { - t.Fatalf("Bad: %v", nodes) - } -} - -func TestNodeInfo(t *testing.T) { - store, err := testStateStore() - if err != nil { - t.Fatalf("err: %v", err) - } - defer store.Close() - - if err := store.EnsureNode(1, structs.Node{Node: "foo", Address: "127.0.0.1"}); err != nil { - t.Fatalf("err: %v", err) - } - if err := store.EnsureService(2, "foo", &structs.NodeService{ID: "db1", Service: "db", Tags: []string{"master"}, Address: "", Port: 8000}); err != nil { - t.Fatalf("err: %v", err) - } - check := &structs.HealthCheck{ - Node: "foo", - CheckID: "db", - Name: "Can connect", - Status: structs.HealthPassing, - ServiceID: "db1", - } - if err := store.EnsureCheck(3, check); err != nil { - t.Fatalf("err: %v", err) - } - check = &structs.HealthCheck{ - Node: "foo", - CheckID: SerfCheckID, - Name: SerfCheckName, - Status: structs.HealthPassing, - } - if err := store.EnsureCheck(4, check); err != nil { - t.Fatalf("err: %v", err) - } - - idx, dump := store.NodeInfo("foo") - if idx != 4 { - t.Fatalf("bad: %v", idx) - } - if len(dump) != 1 { - t.Fatalf("Bad: %v", dump) - } - - info := dump[0] - if info.Node != "foo" { - t.Fatalf("Bad: %v", info) - } - if info.Services[0].ID != "db1" { - t.Fatalf("Bad: %v", info) - } - if len(info.Checks) != 2 { - t.Fatalf("Bad: %v", info) - } - if info.Checks[0].CheckID != "db" { - t.Fatalf("Bad: %v", info) - } - if info.Checks[1].CheckID != SerfCheckID { - t.Fatalf("Bad: %v", info) - } -} - -func TestNodeDump(t *testing.T) { - store, err := testStateStore() - if err != nil { - t.Fatalf("err: %v", err) - } - defer store.Close() - - if err := store.EnsureNode(1, structs.Node{Node: "foo", Address: "127.0.0.1"}); err != nil { - t.Fatalf("err: %v", err) - } - if err := store.EnsureService(2, "foo", &structs.NodeService{ID: "db1", Service: "db", Tags: []string{"master"}, Address: "", Port: 8000}); err != nil { - t.Fatalf("err: %v", err) - } - if err := store.EnsureNode(3, structs.Node{Node: "baz", Address: "127.0.0.2"}); err != nil { - t.Fatalf("err: %v", err) - } - if err := store.EnsureService(4, "baz", &structs.NodeService{ID: "db1", Service: "db", Tags: []string{"master"}, Address: "", Port: 8000}); err != nil { - t.Fatalf("err: %v", err) - } - - idx, dump := store.NodeDump() - if idx != 4 { - t.Fatalf("bad: %v", idx) - } - if len(dump) != 2 { - t.Fatalf("Bad: %v", dump) - } - - info := dump[0] - if info.Node != "baz" { - t.Fatalf("Bad: %v", info) - } - if info.Services[0].ID != "db1" { - t.Fatalf("Bad: %v", info) - } - info = dump[1] - if info.Node != "foo" { - t.Fatalf("Bad: %v", info) - } - if info.Services[0].ID != "db1" { - t.Fatalf("Bad: %v", info) - } -} - -func TestKVSSet_Watch(t *testing.T) { - store, err := testStateStore() - if err != nil { - t.Fatalf("err: %v", err) - } - defer store.Close() - - notify1 := make(chan struct{}, 1) - notify2 := make(chan struct{}, 1) - notify3 := make(chan struct{}, 1) - - store.WatchKV("", notify1) - store.WatchKV("foo/", notify2) - store.WatchKV("foo/bar", notify3) - - // Create the entry - d := &structs.DirEntry{Key: "foo/baz", Flags: 42, Value: []byte("test")} - if err := store.KVSSet(1000, d); err != nil { - t.Fatalf("err: %v", err) - } - - // Check that we've fired notify1 and notify2 - select { - case <-notify1: - default: - t.Fatalf("should notify root") - } - select { - case <-notify2: - default: - t.Fatalf("should notify foo/") - } - select { - case <-notify3: - t.Fatalf("should not notify foo/bar") - default: - } -} - -func TestKVSSet_Watch_Stop(t *testing.T) { - store, err := testStateStore() - if err != nil { - t.Fatalf("err: %v", err) - } - defer store.Close() - - notify1 := make(chan struct{}, 1) - - store.WatchKV("", notify1) - store.StopWatchKV("", notify1) - - // Create the entry - d := &structs.DirEntry{Key: "foo/baz", Flags: 42, Value: []byte("test")} - if err := store.KVSSet(1000, d); err != nil { - t.Fatalf("err: %v", err) - } - - // Check that we've not fired notify1 - select { - case <-notify1: - t.Fatalf("should not notify ") - default: - } -} - -func TestKVSSet_Get(t *testing.T) { - store, err := testStateStore() - if err != nil { - t.Fatalf("err: %v", err) - } - defer store.Close() - - // Should not exist - idx, d, err := store.KVSGet("/foo") - if err != nil { - t.Fatalf("err: %v", err) - } - if idx != 0 { - t.Fatalf("bad: %v", idx) - } - if d != nil { - t.Fatalf("bad: %v", d) - } - - // Create the entry - d = &structs.DirEntry{Key: "/foo", Flags: 42, Value: []byte("test")} - if err := store.KVSSet(1000, d); err != nil { - t.Fatalf("err: %v", err) - } - - // Should exist exist - idx, d, err = store.KVSGet("/foo") - if err != nil { - t.Fatalf("err: %v", err) - } - if idx != 1000 { - t.Fatalf("bad: %v", idx) - } - if d.CreateIndex != 1000 { - t.Fatalf("bad: %v", d) - } - if d.ModifyIndex != 1000 { - t.Fatalf("bad: %v", d) - } - if d.Key != "/foo" { - t.Fatalf("bad: %v", d) - } - if d.Flags != 42 { - t.Fatalf("bad: %v", d) - } - if string(d.Value) != "test" { - t.Fatalf("bad: %v", d) - } - - // Update the entry - d = &structs.DirEntry{Key: "/foo", Flags: 43, Value: []byte("zip")} - if err := store.KVSSet(1010, d); err != nil { - t.Fatalf("err: %v", err) - } - - // Should update - idx, d, err = store.KVSGet("/foo") - if err != nil { - t.Fatalf("err: %v", err) - } - if idx != 1010 { - t.Fatalf("bad: %v", idx) - } - if d.CreateIndex != 1000 { - t.Fatalf("bad: %v", d) - } - if d.ModifyIndex != 1010 { - t.Fatalf("bad: %v", d) - } - if d.Key != "/foo" { - t.Fatalf("bad: %v", d) - } - if d.Flags != 43 { - t.Fatalf("bad: %v", d) - } - if string(d.Value) != "zip" { - t.Fatalf("bad: %v", d) - } -} - -func TestKVSDelete(t *testing.T) { - store, err := testStateStore() - if err != nil { - t.Fatalf("err: %v", err) - } - defer store.Close() - - ttl := 10 * time.Millisecond - gran := 5 * time.Millisecond - gc, err := state_store.NewTombstoneGC(ttl, gran) - if err != nil { - t.Fatalf("err: %v", err) - } - gc.SetEnabled(true) - store.gc = gc - - // Create the entry - d := &structs.DirEntry{Key: "/foo", Flags: 42, Value: []byte("test")} - if err := store.KVSSet(1000, d); err != nil { - t.Fatalf("err: %v", err) - } - - notify1 := make(chan struct{}, 1) - store.WatchKV("/", notify1) - - // Delete the entry - if err := store.KVSDelete(1020, "/foo"); err != nil { - t.Fatalf("err: %v", err) - } - - // Check that we've fired notify1 - select { - case <-notify1: - default: - t.Fatalf("should notify /") - } - - // Should not exist - idx, d, err := store.KVSGet("/foo") - if err != nil { - t.Fatalf("err: %v", err) - } - if idx != 1020 { - t.Fatalf("bad: %v", idx) - } - if d != nil { - t.Fatalf("bad: %v", d) - } - - // Check tombstone exists - _, res, err := store.tombstoneTable.Get("id", "/foo") - if err != nil { - t.Fatalf("err: %v", err) - } - if res == nil || res[0].(*structs.DirEntry).ModifyIndex != 1020 { - t.Fatalf("bad: %#v", d) - } - - // Check that we get a delete - select { - case idx := <-gc.ExpireCh(): - if idx != 1020 { - t.Fatalf("bad %d", idx) - } - case <-time.After(20 * time.Millisecond): - t.Fatalf("should expire") - } -} - -func TestKVSDeleteCheckAndSet(t *testing.T) { - store, err := testStateStore() - if err != nil { - t.Fatalf("err: %v", err) - } - defer store.Close() - - // CAS should fail, no entry - ok, err := store.KVSDeleteCheckAndSet(1000, "/foo", 100) - if err != nil { - t.Fatalf("err: %v", err) - } - if ok { - t.Fatalf("unexpected commit") - } - - // CAS should work, no entry - ok, err = store.KVSDeleteCheckAndSet(1000, "/foo", 0) - if err != nil { - t.Fatalf("err: %v", err) - } - if !ok { - t.Fatalf("unexpected failure") - } - - // Make an entry - d := &structs.DirEntry{Key: "/foo"} - if err := store.KVSSet(1000, d); err != nil { - t.Fatalf("err: %v", err) - } - - // Constrain on a wrong modify time - ok, err = store.KVSDeleteCheckAndSet(1001, "/foo", 42) - if err != nil { - t.Fatalf("err: %v", err) - } - if ok { - t.Fatalf("unexpected commit") - } - - // Constrain on a correct modify time - ok, err = store.KVSDeleteCheckAndSet(1002, "/foo", 1000) - if err != nil { - t.Fatalf("err: %v", err) - } - if !ok { - t.Fatalf("expected commit") - } -} - -func TestKVSCheckAndSet(t *testing.T) { - store, err := testStateStore() - if err != nil { - t.Fatalf("err: %v", err) - } - defer store.Close() - - // CAS should fail, no entry - d := &structs.DirEntry{ - RaftIndex: structs.RaftIndex{ModifyIndex: 100}, - Key: "/foo", - Flags: 42, - Value: []byte("test"), - } - ok, err := store.KVSCheckAndSet(1000, d) - if err != nil { - t.Fatalf("err: %v", err) - } - if ok { - t.Fatalf("unexpected commit") - } - - // Constrain on not-exist, should work - d.ModifyIndex = 0 - ok, err = store.KVSCheckAndSet(1001, d) - if err != nil { - t.Fatalf("err: %v", err) - } - if !ok { - t.Fatalf("expected commit") - } - - // Constrain on not-exist, should fail - d.ModifyIndex = 0 - ok, err = store.KVSCheckAndSet(1002, d) - if err != nil { - t.Fatalf("err: %v", err) - } - if ok { - t.Fatalf("unexpected commit") - } - - // Constrain on a wrong modify time - d.ModifyIndex = 1000 - ok, err = store.KVSCheckAndSet(1003, d) - if err != nil { - t.Fatalf("err: %v", err) - } - if ok { - t.Fatalf("unexpected commit") - } - - // Constrain on a correct modify time - d.ModifyIndex = 1001 - ok, err = store.KVSCheckAndSet(1004, d) - if err != nil { - t.Fatalf("err: %v", err) - } - if !ok { - t.Fatalf("expected commit") - } -} - -func TestKVS_List(t *testing.T) { - store, err := testStateStore() - if err != nil { - t.Fatalf("err: %v", err) - } - defer store.Close() - - // Should not exist - _, idx, ents, err := store.KVSList("/web") - if err != nil { - t.Fatalf("err: %v", err) - } - if idx != 0 { - t.Fatalf("bad: %v", idx) - } - if len(ents) != 0 { - t.Fatalf("bad: %v", ents) - } - - // Create the entries - d := &structs.DirEntry{Key: "/web/a", Flags: 42, Value: []byte("test")} - if err := store.KVSSet(1000, d); err != nil { - t.Fatalf("err: %v", err) - } - d = &structs.DirEntry{Key: "/web/b", Flags: 42, Value: []byte("test")} - if err := store.KVSSet(1001, d); err != nil { - t.Fatalf("err: %v", err) - } - d = &structs.DirEntry{Key: "/web/sub/c", Flags: 42, Value: []byte("test")} - if err := store.KVSSet(1002, d); err != nil { - t.Fatalf("err: %v", err) - } - - // Should list - _, idx, ents, err = store.KVSList("/web") - if err != nil { - t.Fatalf("err: %v", err) - } - if idx != 1002 { - t.Fatalf("bad: %v", idx) - } - if len(ents) != 3 { - t.Fatalf("bad: %v", ents) - } - - if ents[0].Key != "/web/a" { - t.Fatalf("bad: %v", ents[0]) - } - if ents[1].Key != "/web/b" { - t.Fatalf("bad: %v", ents[1]) - } - if ents[2].Key != "/web/sub/c" { - t.Fatalf("bad: %v", ents[2]) - } -} - -func TestKVSList_TombstoneIndex(t *testing.T) { - store, err := testStateStore() - if err != nil { - t.Fatalf("err: %v", err) - } - defer store.Close() - - // Create the entries - d := &structs.DirEntry{Key: "/web/a", Value: []byte("test")} - if err := store.KVSSet(1000, d); err != nil { - t.Fatalf("err: %v", err) - } - d = &structs.DirEntry{Key: "/web/b", Value: []byte("test")} - if err := store.KVSSet(1001, d); err != nil { - t.Fatalf("err: %v", err) - } - d = &structs.DirEntry{Key: "/web/c", Value: []byte("test")} - if err := store.KVSSet(1002, d); err != nil { - t.Fatalf("err: %v", err) - } - - // Nuke the last node - err = store.KVSDeleteTree(1003, "/web/c") - if err != nil { - t.Fatalf("err: %v", err) - } - - // Add another node - d = &structs.DirEntry{Key: "/other", Value: []byte("test")} - if err := store.KVSSet(1004, d); err != nil { - t.Fatalf("err: %v", err) - } - - // List should properly reflect tombstoned value - tombIdx, idx, ents, err := store.KVSList("/web") - if err != nil { - t.Fatalf("err: %v", err) - } - if idx != 1004 { - t.Fatalf("bad: %v", idx) - } - if tombIdx != 1003 { - t.Fatalf("bad: %v", idx) - } - if len(ents) != 2 { - t.Fatalf("bad: %v", ents) - } -} - -func TestKVS_ListKeys(t *testing.T) { - store, err := testStateStore() - if err != nil { - t.Fatalf("err: %v", err) - } - defer store.Close() - - // Should not exist - idx, keys, err := store.KVSListKeys("", "/") - if err != nil { - t.Fatalf("err: %v", err) - } - if idx != 1 { - t.Fatalf("bad: %v", idx) - } - if len(keys) != 0 { - t.Fatalf("bad: %v", keys) - } - - // Create the entries - d := &structs.DirEntry{Key: "/web/a", Flags: 42, Value: []byte("test")} - if err := store.KVSSet(1000, d); err != nil { - t.Fatalf("err: %v", err) - } - d = &structs.DirEntry{Key: "/web/b", Flags: 42, Value: []byte("test")} - if err := store.KVSSet(1001, d); err != nil { - t.Fatalf("err: %v", err) - } - d = &structs.DirEntry{Key: "/web/sub/c", Flags: 42, Value: []byte("test")} - if err := store.KVSSet(1002, d); err != nil { - t.Fatalf("err: %v", err) - } - - // Should list - idx, keys, err = store.KVSListKeys("", "/") - if err != nil { - t.Fatalf("err: %v", err) - } - if idx != 1002 { - t.Fatalf("bad: %v", idx) - } - if len(keys) != 1 { - t.Fatalf("bad: %v", keys) - } - if keys[0] != "/" { - t.Fatalf("bad: %v", keys) - } - - // Should list just web - idx, keys, err = store.KVSListKeys("/", "/") - if err != nil { - t.Fatalf("err: %v", err) - } - if idx != 1002 { - t.Fatalf("bad: %v", idx) - } - if len(keys) != 1 { - t.Fatalf("bad: %v", keys) - } - if keys[0] != "/web/" { - t.Fatalf("bad: %v", keys) - } - - // Should list a, b, sub/ - idx, keys, err = store.KVSListKeys("/web/", "/") - if err != nil { - t.Fatalf("err: %v", err) - } - if idx != 1002 { - t.Fatalf("bad: %v", idx) - } - if len(keys) != 3 { - t.Fatalf("bad: %v", keys) - } - if keys[0] != "/web/a" { - t.Fatalf("bad: %v", keys) - } - if keys[1] != "/web/b" { - t.Fatalf("bad: %v", keys) - } - if keys[2] != "/web/sub/" { - t.Fatalf("bad: %v", keys) - } - - // Should list c - idx, keys, err = store.KVSListKeys("/web/sub/", "/") - if err != nil { - t.Fatalf("err: %v", err) - } - if idx != 1002 { - t.Fatalf("bad: %v", idx) - } - if len(keys) != 1 { - t.Fatalf("bad: %v", keys) - } - if keys[0] != "/web/sub/c" { - t.Fatalf("bad: %v", keys) - } - - // Should list all - idx, keys, err = store.KVSListKeys("/web/", "") - if err != nil { - t.Fatalf("err: %v", err) - } - if idx != 1002 { - t.Fatalf("bad: %v", idx) - } - if len(keys) != 3 { - t.Fatalf("bad: %v", keys) - } - if keys[2] != "/web/sub/c" { - t.Fatalf("bad: %v", keys) - } -} - -func TestKVS_ListKeys_Index(t *testing.T) { - store, err := testStateStore() - if err != nil { - t.Fatalf("err: %v", err) - } - defer store.Close() - - // Create the entries - d := &structs.DirEntry{Key: "/foo/a", Flags: 42, Value: []byte("test")} - if err := store.KVSSet(1000, d); err != nil { - t.Fatalf("err: %v", err) - } - d = &structs.DirEntry{Key: "/bar/b", Flags: 42, Value: []byte("test")} - if err := store.KVSSet(1001, d); err != nil { - t.Fatalf("err: %v", err) - } - d = &structs.DirEntry{Key: "/baz/c", Flags: 42, Value: []byte("test")} - if err := store.KVSSet(1002, d); err != nil { - t.Fatalf("err: %v", err) - } - d = &structs.DirEntry{Key: "/other/d", Flags: 42, Value: []byte("test")} - if err := store.KVSSet(1003, d); err != nil { - t.Fatalf("err: %v", err) - } - - idx, keys, err := store.KVSListKeys("/foo", "") - if err != nil { - t.Fatalf("err: %v", err) - } - if idx != 1000 { - t.Fatalf("bad: %v", idx) - } - if len(keys) != 1 { - t.Fatalf("bad: %v", keys) - } - - idx, keys, err = store.KVSListKeys("/ba", "") - if err != nil { - t.Fatalf("err: %v", err) - } - if idx != 1002 { - t.Fatalf("bad: %v", idx) - } - if len(keys) != 2 { - t.Fatalf("bad: %v", keys) - } - - idx, keys, err = store.KVSListKeys("/nope", "") - if err != nil { - t.Fatalf("err: %v", err) - } - if idx != 1003 { - t.Fatalf("bad: %v", idx) - } - if len(keys) != 0 { - t.Fatalf("bad: %v", keys) - } -} - -func TestKVS_ListKeys_TombstoneIndex(t *testing.T) { - store, err := testStateStore() - if err != nil { - t.Fatalf("err: %v", err) - } - defer store.Close() - - // Create the entries - d := &structs.DirEntry{Key: "/foo/a", Flags: 42, Value: []byte("test")} - if err := store.KVSSet(1000, d); err != nil { - t.Fatalf("err: %v", err) - } - d = &structs.DirEntry{Key: "/bar/b", Flags: 42, Value: []byte("test")} - if err := store.KVSSet(1001, d); err != nil { - t.Fatalf("err: %v", err) - } - d = &structs.DirEntry{Key: "/baz/c", Flags: 42, Value: []byte("test")} - if err := store.KVSSet(1002, d); err != nil { - t.Fatalf("err: %v", err) - } - d = &structs.DirEntry{Key: "/other/d", Flags: 42, Value: []byte("test")} - if err := store.KVSSet(1003, d); err != nil { - t.Fatalf("err: %v", err) - } - if err := store.KVSDelete(1004, "/baz/c"); err != nil { - t.Fatalf("err: %v", err) - } - - idx, keys, err := store.KVSListKeys("/foo", "") - if err != nil { - t.Fatalf("err: %v", err) - } - if idx != 1000 { - t.Fatalf("bad: %v", idx) - } - if len(keys) != 1 { - t.Fatalf("bad: %v", keys) - } - - idx, keys, err = store.KVSListKeys("/ba", "") - if err != nil { - t.Fatalf("err: %v", err) - } - if idx != 1004 { - t.Fatalf("bad: %v", idx) - } - if len(keys) != 1 { - t.Fatalf("bad: %v", keys) - } - - idx, keys, err = store.KVSListKeys("/nope", "") - if err != nil { - t.Fatalf("err: %v", err) - } - if idx != 1004 { - t.Fatalf("bad: %v", idx) - } - if len(keys) != 0 { - t.Fatalf("bad: %v", keys) - } -} - -func TestKVSDeleteTree(t *testing.T) { - store, err := testStateStore() - if err != nil { - t.Fatalf("err: %v", err) - } - defer store.Close() - - ttl := 10 * time.Millisecond - gran := 5 * time.Millisecond - gc, err := state_store.NewTombstoneGC(ttl, gran) - if err != nil { - t.Fatalf("err: %v", err) - } - gc.SetEnabled(true) - store.gc = gc - - notify1 := make(chan struct{}, 1) - notify2 := make(chan struct{}, 1) - notify3 := make(chan struct{}, 1) - - store.WatchKV("", notify1) - store.WatchKV("/web/sub", notify2) - store.WatchKV("/other", notify3) - - // Should not exist - err = store.KVSDeleteTree(1000, "/web") - if err != nil { - t.Fatalf("err: %v", err) - } - - // Create the entries - d := &structs.DirEntry{Key: "/web/a", Flags: 42, Value: []byte("test")} - if err := store.KVSSet(1000, d); err != nil { - t.Fatalf("err: %v", err) - } - d = &structs.DirEntry{Key: "/web/b", Flags: 42, Value: []byte("test")} - if err := store.KVSSet(1001, d); err != nil { - t.Fatalf("err: %v", err) - } - d = &structs.DirEntry{Key: "/web/sub/c", Flags: 42, Value: []byte("test")} - if err := store.KVSSet(1002, d); err != nil { - t.Fatalf("err: %v", err) - } - - // Nuke the web tree - err = store.KVSDeleteTree(1010, "/web") - if err != nil { - t.Fatalf("err: %v", err) - } - - // Nothing should list - tombIdx, idx, ents, err := store.KVSList("/web") - if err != nil { - t.Fatalf("err: %v", err) - } - if idx != 1010 { - t.Fatalf("bad: %v", idx) - } - if tombIdx != 1010 { - t.Fatalf("bad: %v", idx) - } - if len(ents) != 0 { - t.Fatalf("bad: %v", ents) - } - - // Check tombstones exists - _, res, err := store.tombstoneTable.Get("id_prefix", "/web") - if err != nil { - t.Fatalf("err: %v", err) - } - if len(res) != 3 { - t.Fatalf("bad: %#v", d) - } - for _, r := range res { - if r.(*structs.DirEntry).ModifyIndex != 1010 { - t.Fatalf("bad: %#v", r) - } - } - - // Check that we've fired notify1 and notify2 - select { - case <-notify1: - default: - t.Fatalf("should notify root") - } - select { - case <-notify2: - default: - t.Fatalf("should notify /web/sub") - } - select { - case <-notify3: - t.Fatalf("should not notify /other") - default: - } - - // Check that we get a delete - select { - case idx := <-gc.ExpireCh(): - if idx != 1010 { - t.Fatalf("bad %d", idx) - } - case <-time.After(20 * time.Millisecond): - t.Fatalf("should expire") - } -} - -func TestReapTombstones(t *testing.T) { - store, err := testStateStore() - if err != nil { - t.Fatalf("err: %v", err) - } - defer store.Close() - - ttl := 10 * time.Millisecond - gran := 5 * time.Millisecond - gc, err := state_store.NewTombstoneGC(ttl, gran) - if err != nil { - t.Fatalf("err: %v", err) - } - gc.SetEnabled(true) - store.gc = gc - - // Should not exist - err = store.KVSDeleteTree(1000, "/web") - if err != nil { - t.Fatalf("err: %v", err) - } - - // Create the entries - d := &structs.DirEntry{Key: "/web/a", Flags: 42, Value: []byte("test")} - if err := store.KVSSet(1000, d); err != nil { - t.Fatalf("err: %v", err) - } - d = &structs.DirEntry{Key: "/web/b", Flags: 42, Value: []byte("test")} - if err := store.KVSSet(1001, d); err != nil { - t.Fatalf("err: %v", err) - } - d = &structs.DirEntry{Key: "/web/sub/c", Flags: 42, Value: []byte("test")} - if err := store.KVSSet(1002, d); err != nil { - t.Fatalf("err: %v", err) - } - - // Nuke just a - err = store.KVSDelete(1010, "/web/a") - if err != nil { - t.Fatalf("err: %v", err) - } - - // Nuke the web tree - err = store.KVSDeleteTree(1020, "/web") - if err != nil { - t.Fatalf("err: %v", err) - } - - // Do a reap, should be a noop - if err := store.ReapTombstones(1000); err != nil { - t.Fatalf("err: %v", err) - } - - // Check tombstones exists - _, res, err := store.tombstoneTable.Get("id_prefix", "/web") - if err != nil { - t.Fatalf("err: %v", err) - } - if len(res) != 3 { - t.Fatalf("bad: %#v", d) - } - - // Do a reap, should remove just /web/a - if err := store.ReapTombstones(1010); err != nil { - t.Fatalf("err: %v", err) - } - - // Check tombstones exists - _, res, err = store.tombstoneTable.Get("id_prefix", "/web") - if err != nil { - t.Fatalf("err: %v", err) - } - if len(res) != 2 { - t.Fatalf("bad: %#v", d) - } - - // Do a reap, should remove them all - if err := store.ReapTombstones(1025); err != nil { - t.Fatalf("err: %v", err) - } - - // Check no tombstones exists - _, res, err = store.tombstoneTable.Get("id_prefix", "/web") - if err != nil { - t.Fatalf("err: %v", err) - } - if len(res) != 0 { - t.Fatalf("bad: %#v", d) - } -} - -func TestSessionCreate(t *testing.T) { - store, err := testStateStore() - if err != nil { - t.Fatalf("err: %v", err) - } - defer store.Close() - - if err := store.EnsureNode(3, structs.Node{Node: "foo", Address: "127.0.0.1"}); err != nil { - t.Fatalf("err: %v", err) - } - check := &structs.HealthCheck{ - Node: "foo", - CheckID: "bar", - Status: structs.HealthPassing, - } - if err := store.EnsureCheck(13, check); err != nil { - t.Fatalf("err: %v", err) - } - - session := &structs.Session{ - ID: generateUUID(), - Node: "foo", - Checks: []string{"bar"}, - } - - if err := store.SessionCreate(1000, session); err != nil { - t.Fatalf("err: %v", err) - } - - if session.CreateIndex != 1000 { - t.Fatalf("bad: %v", session) - } -} - -func TestSessionCreate_Invalid(t *testing.T) { - store, err := testStateStore() - if err != nil { - t.Fatalf("err: %v", err) - } - defer store.Close() - - // No node registered - session := &structs.Session{ - ID: generateUUID(), - Node: "foo", - Checks: []string{"bar"}, - } - if err := store.SessionCreate(1000, session); err.Error() != "Missing node registration" { - t.Fatalf("err: %v", err) - } - - // Check not registered - if err := store.EnsureNode(3, structs.Node{Node: "foo", Address: "127.0.0.1"}); err != nil { - t.Fatalf("err: %v", err) - } - if err := store.SessionCreate(1000, session); err.Error() != "Missing check 'bar' registration" { - t.Fatalf("err: %v", err) - } - - // Unhealthy check - check := &structs.HealthCheck{ - Node: "foo", - CheckID: "bar", - Status: structs.HealthCritical, - } - if err := store.EnsureCheck(13, check); err != nil { - t.Fatalf("err: %v", err) - } - if err := store.SessionCreate(1000, session); err.Error() != "Check 'bar' is in critical state" { - t.Fatalf("err: %v", err) - } -} - -func TestSession_Lookups(t *testing.T) { - store, err := testStateStore() - if err != nil { - t.Fatalf("err: %v", err) - } - defer store.Close() - - // Create a session - if err := store.EnsureNode(3, structs.Node{Node: "foo", Address: "127.0.0.1"}); err != nil { - t.Fatalf("err: %v", err) - } - session := &structs.Session{ - ID: generateUUID(), - Node: "foo", - Checks: []string{}, - } - if err := store.SessionCreate(1000, session); err != nil { - t.Fatalf("err: %v", err) - } - - // Lookup by ID - idx, s2, err := store.SessionGet(session.ID) - if err != nil { - t.Fatalf("err: %v", err) - } - if idx != 1000 { - t.Fatalf("bad: %v", idx) - } - if !reflect.DeepEqual(s2, session) { - t.Fatalf("bad: %#v %#v", s2, session) - } - - // Create many sessions - ids := []string{session.ID} - for i := 0; i < 10; i++ { - session := &structs.Session{ - ID: generateUUID(), - Node: "foo", - } - if err := store.SessionCreate(uint64(1000+i), session); err != nil { - t.Fatalf("err: %v", err) - } - ids = append(ids, session.ID) - } - - // List all - idx, all, err := store.SessionList() - if err != nil { - t.Fatalf("err: %v", err) - } - if idx != 1009 { - t.Fatalf("bad: %v", idx) - } - - // Retrieve the ids - var out []string - for _, s := range all { - out = append(out, s.ID) - } - - sort.Strings(ids) - sort.Strings(out) - if !reflect.DeepEqual(ids, out) { - t.Fatalf("bad: %v %v", ids, out) - } - - // List by node - idx, nodes, err := store.NodeSessions("foo") - if err != nil { - t.Fatalf("err: %v", err) - } - if idx != 1009 { - t.Fatalf("bad: %v", idx) - } - - // Check again for the node list - out = nil - for _, s := range nodes { - out = append(out, s.ID) - } - sort.Strings(out) - if !reflect.DeepEqual(ids, out) { - t.Fatalf("bad: %v %v", ids, out) - } -} - -func TestSessionInvalidate_CriticalHealthCheck(t *testing.T) { - store, err := testStateStore() - if err != nil { - t.Fatalf("err: %v", err) - } - defer store.Close() - - if err := store.EnsureNode(3, structs.Node{Node: "foo", Address: "127.0.0.1"}); err != nil { - t.Fatalf("err: %v", err) - } - check := &structs.HealthCheck{ - Node: "foo", - CheckID: "bar", - Status: structs.HealthPassing, - } - if err := store.EnsureCheck(13, check); err != nil { - t.Fatalf("err: %v", err) - } - - session := &structs.Session{ - ID: generateUUID(), - Node: "foo", - Checks: []string{"bar"}, - } - if err := store.SessionCreate(14, session); err != nil { - t.Fatalf("err: %v", err) - } - - // Invalidate the check - check.Status = structs.HealthCritical - if err := store.EnsureCheck(15, check); err != nil { - t.Fatalf("err: %v", err) - } - - // Lookup by ID, should be nil - _, s2, err := store.SessionGet(session.ID) - if err != nil { - t.Fatalf("err: %v", err) - } - if s2 != nil { - t.Fatalf("session should be invalidated") - } -} - -func TestSessionInvalidate_DeleteHealthCheck(t *testing.T) { - store, err := testStateStore() - if err != nil { - t.Fatalf("err: %v", err) - } - defer store.Close() - - if err := store.EnsureNode(3, structs.Node{Node: "foo", Address: "127.0.0.1"}); err != nil { - t.Fatalf("err: %v", err) - } - check := &structs.HealthCheck{ - Node: "foo", - CheckID: "bar", - Status: structs.HealthPassing, - } - if err := store.EnsureCheck(13, check); err != nil { - t.Fatalf("err: %v", err) - } - - session := &structs.Session{ - ID: generateUUID(), - Node: "foo", - Checks: []string{"bar"}, - } - if err := store.SessionCreate(14, session); err != nil { - t.Fatalf("err: %v", err) - } - - // Delete the check - if err := store.DeleteNodeCheck(15, "foo", "bar"); err != nil { - t.Fatalf("err: %v", err) - } - - // Lookup by ID, should be nil - _, s2, err := store.SessionGet(session.ID) - if err != nil { - t.Fatalf("err: %v", err) - } - if s2 != nil { - t.Fatalf("session should be invalidated") - } -} - -func TestSessionInvalidate_DeleteNode(t *testing.T) { - store, err := testStateStore() - if err != nil { - t.Fatalf("err: %v", err) - } - defer store.Close() - - if err := store.EnsureNode(3, structs.Node{Node: "foo", Address: "127.0.0.1"}); err != nil { - t.Fatalf("err: %v", err) - } - - session := &structs.Session{ - ID: generateUUID(), - Node: "foo", - } - if err := store.SessionCreate(14, session); err != nil { - t.Fatalf("err: %v", err) - } - - // Delete the node - if err := store.DeleteNode(15, "foo"); err != nil { - t.Fatalf("err: %v", err) - } - - // Lookup by ID, should be nil - _, s2, err := store.SessionGet(session.ID) - if err != nil { - t.Fatalf("err: %v", err) - } - if s2 != nil { - t.Fatalf("session should be invalidated") - } -} - -func TestSessionInvalidate_DeleteNodeService(t *testing.T) { - store, err := testStateStore() - if err != nil { - t.Fatalf("err: %v", err) - } - defer store.Close() - - if err := store.EnsureNode(11, structs.Node{Node: "foo", Address: "127.0.0.1"}); err != nil { - t.Fatalf("err: %v", err) - } - if err := store.EnsureService(12, "foo", &structs.NodeService{ID: "api", Service: "api", Tags: nil, Address: "", Port: 5000}); err != nil { - t.Fatalf("err: %v", err) - } - check := &structs.HealthCheck{ - Node: "foo", - CheckID: "api", - Name: "Can connect", - Status: structs.HealthPassing, - ServiceID: "api", - } - if err := store.EnsureCheck(13, check); err != nil { - t.Fatalf("err: %v", err) - } - - session := &structs.Session{ - ID: generateUUID(), - Node: "foo", - Checks: []string{"api"}, - } - if err := store.SessionCreate(14, session); err != nil { - t.Fatalf("err: %v", err) - } - - // Should invalidate the session - if err := store.DeleteNodeService(15, "foo", "api"); err != nil { - t.Fatalf("err: %v", err) - } - - // Lookup by ID, should be nil - _, s2, err := store.SessionGet(session.ID) - if err != nil { - t.Fatalf("err: %v", err) - } - if s2 != nil { - t.Fatalf("session should be invalidated") - } -} - -func TestKVSLock(t *testing.T) { - store, err := testStateStore() - if err != nil { - t.Fatalf("err: %v", err) - } - defer store.Close() - - if err := store.EnsureNode(3, structs.Node{Node: "foo", Address: "127.0.0.1"}); err != nil { - t.Fatalf("err: %v", err) - } - session := &structs.Session{ID: generateUUID(), Node: "foo"} - if err := store.SessionCreate(4, session); err != nil { - t.Fatalf("err: %v", err) - } - - // Lock with a non-existing keys should work - d := &structs.DirEntry{ - Key: "/foo", - Flags: 42, - Value: []byte("test"), - Session: session.ID, - } - ok, err := store.KVSLock(5, d) - if err != nil { - t.Fatalf("err: %v", err) - } - if !ok { - t.Fatalf("unexpected fail") - } - if d.LockIndex != 1 { - t.Fatalf("bad: %v", d) - } - - // Re-locking should fail - ok, err = store.KVSLock(6, d) - if err != nil { - t.Fatalf("err: %v", err) - } - if ok { - t.Fatalf("expected fail") - } - - // Set a normal key - k1 := &structs.DirEntry{ - Key: "/bar", - Flags: 0, - Value: []byte("asdf"), - } - if err := store.KVSSet(7, k1); err != nil { - t.Fatalf("err: %v", err) - } - - // Should acquire the lock - k1.Session = session.ID - ok, err = store.KVSLock(8, k1) - if err != nil { - t.Fatalf("err: %v", err) - } - if !ok { - t.Fatalf("unexpected fail") - } - - // Re-acquire should fail - ok, err = store.KVSLock(9, k1) - if err != nil { - t.Fatalf("err: %v", err) - } - if ok { - t.Fatalf("expected fail") - } - -} - -func TestKVSUnlock(t *testing.T) { - store, err := testStateStore() - if err != nil { - t.Fatalf("err: %v", err) - } - defer store.Close() - - if err := store.EnsureNode(3, structs.Node{Node: "foo", Address: "127.0.0.1"}); err != nil { - t.Fatalf("err: %v", err) - } - session := &structs.Session{ID: generateUUID(), Node: "foo"} - if err := store.SessionCreate(4, session); err != nil { - t.Fatalf("err: %v", err) - } - - // Unlock with a non-existing keys should fail - d := &structs.DirEntry{ - Key: "/foo", - Flags: 42, - Value: []byte("test"), - Session: session.ID, - } - ok, err := store.KVSUnlock(5, d) - if err != nil { - t.Fatalf("err: %v", err) - } - if ok { - t.Fatalf("expected fail") - } - - // Lock should work - d.Session = session.ID - if ok, _ := store.KVSLock(6, d); !ok { - t.Fatalf("expected lock") - } - - // Unlock should work - d.Session = session.ID - ok, err = store.KVSUnlock(7, d) - if err != nil { - t.Fatalf("err: %v", err) - } - if !ok { - t.Fatalf("unexpected fail") - } - - // Re-lock should work - d.Session = session.ID - if ok, err := store.KVSLock(8, d); err != nil { - t.Fatalf("err: %v", err) - } else if !ok { - t.Fatalf("expected lock") - } - if d.LockIndex != 2 { - t.Fatalf("bad: %v", d) - } -} - -func TestSessionInvalidate_KeyUnlock(t *testing.T) { - store, err := testStateStore() - if err != nil { - t.Fatalf("err: %v", err) - } - defer store.Close() - if err := store.EnsureNode(3, structs.Node{Node: "foo", Address: "127.0.0.1"}); err != nil { - t.Fatalf("err: %v", err) - } - session := &structs.Session{ - ID: generateUUID(), - Node: "foo", - LockDelay: 50 * time.Millisecond, - } - if err := store.SessionCreate(4, session); err != nil { - t.Fatalf("err: %v", err) - } - - // Lock a key with the session - d := &structs.DirEntry{ - Key: "/foo", - Flags: 42, - Value: []byte("test"), - Session: session.ID, - } - ok, err := store.KVSLock(5, d) - if err != nil { - t.Fatalf("err: %v", err) - } - if !ok { - t.Fatalf("unexpected fail") - } - - notify1 := make(chan struct{}, 1) - store.WatchKV("/f", notify1) - - // Delete the node - if err := store.DeleteNode(6, "foo"); err != nil { - t.Fatalf("err: %v", err) - } - - // Key should be unlocked - idx, d2, err := store.KVSGet("/foo") - if idx != 6 { - t.Fatalf("bad: %v", idx) - } - if d2.LockIndex != 1 { - t.Fatalf("bad: %v", *d2) - } - if d2.Session != "" { - t.Fatalf("bad: %v", *d2) - } - - // Should notify of update - select { - case <-notify1: - default: - t.Fatalf("should notify /f") - } - - // Key should have a lock delay - expires := store.KVSLockDelay("/foo") - if expires.Before(time.Now().Add(30 * time.Millisecond)) { - t.Fatalf("Bad: %v", expires) - } -} - -func TestSessionInvalidate_KeyDelete(t *testing.T) { - store, err := testStateStore() - if err != nil { - t.Fatalf("err: %v", err) - } - defer store.Close() - - if err := store.EnsureNode(3, structs.Node{Node: "foo", Address: "127.0.0.1"}); err != nil { - t.Fatalf("err: %v", err) - } - session := &structs.Session{ - ID: generateUUID(), - Node: "foo", - LockDelay: 50 * time.Millisecond, - Behavior: structs.SessionKeysDelete, - } - if err := store.SessionCreate(4, session); err != nil { - t.Fatalf("err: %v", err) - } - - // Lock a key with the session - d := &structs.DirEntry{ - Key: "/bar", - Flags: 42, - Value: []byte("test"), - Session: session.ID, - } - ok, err := store.KVSLock(5, d) - if err != nil { - t.Fatalf("err: %v", err) - } - if !ok { - t.Fatalf("unexpected fail") - } - - notify1 := make(chan struct{}, 1) - store.WatchKV("/b", notify1) - - // Delete the node - if err := store.DeleteNode(6, "foo"); err != nil { - t.Fatalf("err: %v", err) - } - - // Key should be deleted - _, d2, err := store.KVSGet("/bar") - if d2 != nil { - t.Fatalf("unexpected undeleted key") - } - - // Should notify of update - select { - case <-notify1: - default: - t.Fatalf("should notify /b") - } - - // Key should have a lock delay - expires := store.KVSLockDelay("/bar") - if expires.Before(time.Now().Add(30 * time.Millisecond)) { - t.Fatalf("Bad: %v", expires) - } -}