// KVSSet is used to create or update a KV entry func (s *StateStore) KVSSet(index uint64, d *structs.DirEntry) error { // Start a new txn tx, err := s.kvsTable.StartTxn(false, nil) if err != nil { return err } defer tx.Abort() // Get the existing node res, err := s.kvsTable.GetTxn(tx, "id", d.Key) if err != nil { return err } // Set the create and modify times if len(res) == 0 { d.CreateIndex = index } else { d.CreateIndex = res[0].(*structs.DirEntry).CreateIndex } d.ModifyIndex = index if err := s.kvsTable.InsertTxn(tx, d); err != nil { return err } if err := s.kvsTable.SetLastIndexTxn(tx, index); err != nil { return err } defer s.watch[s.kvsTable].Notify() return tx.Commit() }
// kvsUnlockTxn is the inner method that does an unlock inside an existing // transaction. func (s *StateStore) kvsUnlockTxn(tx *memdb.Txn, idx uint64, entry *structs.DirEntry) (bool, error) { // Verify that a session is present. if entry.Session == "" { return false, fmt.Errorf("missing session") } // Retrieve the existing entry. existing, err := tx.First("kvs", "id", entry.Key) if err != nil { return false, fmt.Errorf("failed kvs lookup: %s", err) } // Bail if there's no existing key. if existing == nil { return false, nil } // Make sure the given session is the lock holder. e := existing.(*structs.DirEntry) if e.Session != entry.Session { return false, nil } // Clear the lock and update the entry. entry.Session = "" entry.LockIndex = e.LockIndex entry.CreateIndex = e.CreateIndex entry.ModifyIndex = idx // If we made it this far, we should perform the set. if err := s.kvsSetTxn(tx, idx, entry, true); err != nil { return false, err } return true, nil }
// kvsLockTxn is the inner method that does a lock inside an existing // transaction. func (s *StateStore) kvsLockTxn(tx *memdb.Txn, idx uint64, entry *structs.DirEntry) (bool, error) { // Verify that a session is present. if entry.Session == "" { return false, fmt.Errorf("missing session") } // Verify that the session exists. sess, err := tx.First("sessions", "id", entry.Session) if err != nil { return false, fmt.Errorf("failed session lookup: %s", err) } if sess == nil { return false, fmt.Errorf("invalid session %#v", entry.Session) } // Retrieve the existing entry. existing, err := tx.First("kvs", "id", entry.Key) if err != nil { return false, fmt.Errorf("failed kvs lookup: %s", err) } // Set up the entry, using the existing entry if present. if existing != nil { e := existing.(*structs.DirEntry) if e.Session == entry.Session { // We already hold this lock, good to go. entry.CreateIndex = e.CreateIndex entry.LockIndex = e.LockIndex } else if e.Session != "" { // Bail out, someone else holds this lock. return false, nil } else { // Set up a new lock with this session. entry.CreateIndex = e.CreateIndex entry.LockIndex = e.LockIndex + 1 } } else { entry.CreateIndex = idx entry.LockIndex = 1 } entry.ModifyIndex = idx // If we made it this far, we should perform the set. if err := s.kvsSetTxn(tx, idx, entry, true); err != nil { return false, err } return true, nil }
// KVSCheckAndSet is used to perform an atomic check-and-set func (s *StateStore) KVSCheckAndSet(index uint64, d *structs.DirEntry) (bool, error) { // Start a new txn tx, err := s.kvsTable.StartTxn(false, nil) 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 d.ModifyIndex == 0 && exist != nil { return false, nil } else if d.ModifyIndex > 0 && (exist == nil || exist.ModifyIndex != d.ModifyIndex) { return false, nil } // Set the create and modify times if exist == nil { d.CreateIndex = index } else { d.CreateIndex = exist.CreateIndex } 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 } defer s.watch[s.kvsTable].Notify() return true, tx.Commit() }
// kvsSetTxn is used to insert or update a key/value pair in the state // store. It is the inner method used and handles only the actual storage. // If updateSession is true, then the incoming entry will set the new // session (should be validated before calling this). Otherwise, we will keep // whatever the existing session is. func (s *StateStore) kvsSetTxn(tx *memdb.Txn, idx uint64, entry *structs.DirEntry, updateSession bool) error { // Retrieve an existing KV pair existing, err := tx.First("kvs", "id", entry.Key) if err != nil { return fmt.Errorf("failed kvs lookup: %s", err) } // Set the indexes. if existing != nil { entry.CreateIndex = existing.(*structs.DirEntry).CreateIndex } else { entry.CreateIndex = idx } entry.ModifyIndex = idx // Preserve the existing session unless told otherwise. The "existing" // session for a new entry is "no session". if !updateSession { if existing != nil { entry.Session = existing.(*structs.DirEntry).Session } else { entry.Session = "" } } // Store the kv pair in the state store and update the index. if err := tx.Insert("kvs", entry); err != nil { return fmt.Errorf("failed inserting kvs entry: %s", err) } if err := tx.Insert("index", &IndexEntry{"kvs", idx}); err != nil { return fmt.Errorf("failed updating index: %s", err) } tx.Defer(func() { s.kvsWatch.Notify(entry.Key, false) }) return nil }
// 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.watch[s.kvsTable].Notify() }) return true, tx.Commit() }