open-consul/agent/consul/state/session_test.go

964 lines
23 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package state
import (
"fmt"
"reflect"
"strings"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/hashicorp/go-memdb"
"github.com/hashicorp/consul/agent/structs"
"github.com/hashicorp/consul/api"
"github.com/hashicorp/consul/types"
)
func TestStateStore_SessionCreate_SessionGet(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}
s := testStateStore(t)
// SessionGet returns nil if the session doesn't exist
ws := memdb.NewWatchSet()
idx, session, err := s.SessionGet(ws, testUUID(), nil)
if session != nil || err != nil {
t.Fatalf("expected (nil, nil), got: (%#v, %#v)", session, err)
}
if idx != 0 {
t.Fatalf("bad index: %d", idx)
}
// Registering without a session ID is disallowed
err = s.SessionCreate(1, &structs.Session{})
if err != ErrMissingSessionID {
t.Fatalf("expected %#v, got: %#v", ErrMissingSessionID, err)
}
// Invalid session behavior throws error
sess := &structs.Session{
ID: testUUID(),
Behavior: "nope",
}
err = s.SessionCreate(1, sess)
if err == nil || !strings.Contains(err.Error(), "session behavior") {
t.Fatalf("expected session behavior error, got: %#v", err)
}
// Registering with an unknown node is disallowed
sess = &structs.Session{ID: testUUID()}
if err := s.SessionCreate(1, sess); err != ErrMissingNode {
t.Fatalf("expected %#v, got: %#v", ErrMissingNode, err)
}
// None of the errored operations modified the index
if idx := s.maxIndex("sessions"); idx != 0 {
t.Fatalf("bad index: %d", idx)
}
if watchFired(ws) {
t.Fatalf("bad")
}
// Valid session is able to register
testRegisterNode(t, s, 1, "node1")
sess = &structs.Session{
ID: testUUID(),
Node: "node1",
}
if err := s.SessionCreate(2, sess); err != nil {
t.Fatalf("err: %s", err)
}
if idx := s.maxIndex(partitionedAndNamespacedIndexEntryName(tableSessions, &sess.EnterpriseMeta)); idx != 2 {
t.Fatalf("bad index: %s", err)
}
if !watchFired(ws) {
t.Fatalf("bad")
}
// Retrieve the session again
ws = memdb.NewWatchSet()
idx, session, err = s.SessionGet(ws, sess.ID, nil)
if err != nil {
t.Fatalf("err: %s", err)
}
if idx != 2 {
t.Fatalf("bad index: %d", idx)
}
// Ensure the session looks correct and was assigned the
// proper default value for session behavior.
expect := &structs.Session{
ID: sess.ID,
Behavior: structs.SessionKeysRelease,
Node: "node1",
}
if session.ID != expect.ID {
t.Fatalf("bad session ID: expected %s, got %s", expect.ID, session.ID)
}
if session.Node != expect.Node {
t.Fatalf("bad session Node: expected %s, got %s", expect.Node, session.Node)
}
if session.Behavior != expect.Behavior {
t.Fatalf("bad session Behavior: expected %s, got %s", expect.Behavior, session.Behavior)
}
// Registering with a non-existent check is disallowed
sess = &structs.Session{
ID: testUUID(),
Node: "node1",
Checks: []types.CheckID{"check1"},
}
err = s.SessionCreate(3, sess)
if err == nil || !strings.Contains(err.Error(), "Missing check") {
t.Fatalf("expected missing check error, got: %#v", err)
}
// Registering with a critical check is disallowed
testRegisterCheck(t, s, 3, "node1", "", "check1", api.HealthCritical)
err = s.SessionCreate(4, sess)
if err == nil || !strings.Contains(err.Error(), api.HealthCritical) {
t.Fatalf("expected critical state error, got: %#v", err)
}
if watchFired(ws) {
t.Fatalf("bad")
}
// Registering with a healthy check succeeds (doesn't hit the watch since
// we are looking at the old session).
testRegisterCheck(t, s, 4, "node1", "", "check1", api.HealthPassing)
if err := s.SessionCreate(5, sess); err != nil {
t.Fatalf("err: %s", err)
}
if watchFired(ws) {
t.Fatalf("bad")
}
tx := s.db.Txn(false)
defer tx.Abort()
// Check mappings were inserted
{
check, err := tx.First(tableSessionChecks, indexSession, Query{Value: sess.ID})
if err != nil {
t.Fatalf("err: %s", err)
}
if check == nil {
t.Fatalf("missing session check")
}
expectCheck := &sessionCheck{
Node: "node1",
CheckID: structs.CheckID{ID: "check1"},
Session: sess.ID,
}
actual := check.(*sessionCheck)
expectCheck.CheckID.EnterpriseMeta = actual.CheckID.EnterpriseMeta
expectCheck.EnterpriseMeta = actual.EnterpriseMeta
assert.Equal(t, expectCheck, actual)
}
// Register a session against two checks.
testRegisterCheck(t, s, 5, "node1", "", "check2", api.HealthPassing)
sess2 := &structs.Session{
ID: testUUID(),
Node: "node1",
Checks: []types.CheckID{"check1", "check2"},
}
if err := s.SessionCreate(6, sess2); err != nil {
t.Fatalf("err: %s", err)
}
checks, err := tx.Get(tableSessionChecks, indexSession, Query{Value: sess2.ID})
if err != nil {
t.Fatalf("err: %s", err)
}
for i, check := 0, checks.Next(); check != nil; i, check = i+1, checks.Next() {
expectCheck := &sessionCheck{
Node: "node1",
CheckID: structs.CheckID{ID: types.CheckID(fmt.Sprintf("check%d", i+1))},
Session: sess2.ID,
}
actual := check.(*sessionCheck)
expectCheck.CheckID.EnterpriseMeta = actual.CheckID.EnterpriseMeta
expectCheck.EnterpriseMeta = actual.EnterpriseMeta
assert.Equal(t, expectCheck, actual)
}
// Pulling a nonexistent session gives the table index.
idx, session, err = s.SessionGet(nil, testUUID(), nil)
if err != nil {
t.Fatalf("err: %s", err)
}
if session != nil {
t.Fatalf("expected not to get a session: %v", session)
}
if idx != 6 {
t.Fatalf("bad index: %d", idx)
}
}
func TestStateStore_SessionList(t *testing.T) {
s := testStateStore(t)
// Listing when no sessions exist returns nil
ws := memdb.NewWatchSet()
idx, res, err := s.SessionList(ws, nil)
if idx != 0 || res != nil || err != nil {
t.Fatalf("expected (0, nil, nil), got: (%d, %#v, %#v)", idx, res, err)
}
// Register some nodes
testRegisterNode(t, s, 1, "node1")
testRegisterNode(t, s, 2, "node2")
testRegisterNode(t, s, 3, "node3")
// Create some sessions in the state store
sessions := structs.Sessions{
&structs.Session{
ID: testUUID(),
Node: "node1",
Behavior: structs.SessionKeysDelete,
},
&structs.Session{
ID: testUUID(),
Node: "node2",
Behavior: structs.SessionKeysRelease,
},
&structs.Session{
ID: testUUID(),
Node: "node3",
Behavior: structs.SessionKeysDelete,
},
}
for i, session := range sessions {
if err := s.SessionCreate(uint64(4+i), session); err != nil {
t.Fatalf("err: %s", err)
}
}
if !watchFired(ws) {
t.Fatalf("bad")
}
// List out all of the sessions
idx, sessionList, err := s.SessionList(nil, nil)
if err != nil {
t.Fatalf("err: %s", err)
}
if idx != 6 {
t.Fatalf("bad index: %d", idx)
}
sessionMap := make(map[string]*structs.Session)
for _, session := range sessionList {
sessionMap[session.ID] = session
}
for _, expect := range sessions {
assert.Equal(t, expect, sessionMap[expect.ID])
}
}
func TestStateStore_NodeSessions(t *testing.T) {
s := testStateStore(t)
// Listing sessions with no results returns nil
ws := memdb.NewWatchSet()
idx, res, err := s.NodeSessions(ws, "node1", nil)
if idx != 0 || res != nil || err != nil {
t.Fatalf("expected (0, nil, nil), got: (%d, %#v, %#v)", idx, res, err)
}
// Create the nodes
testRegisterNode(t, s, 1, "node1")
testRegisterNode(t, s, 2, "node2")
// Register some sessions with the nodes
sessions1 := structs.Sessions{
&structs.Session{
ID: testUUID(),
Node: "node1",
},
&structs.Session{
ID: testUUID(),
Node: "node1",
},
}
sessions2 := []*structs.Session{
{
ID: testUUID(),
Node: "node2",
},
{
ID: testUUID(),
Node: "node2",
},
}
for i, sess := range append(sessions1, sessions2...) {
if err := s.SessionCreate(uint64(3+i), sess); err != nil {
t.Fatalf("err: %s", err)
}
}
if !watchFired(ws) {
t.Fatalf("bad")
}
// Query all of the sessions associated with a specific
// node in the state store.
ws1 := memdb.NewWatchSet()
idx, res, err = s.NodeSessions(ws1, "node1", nil)
if err != nil {
t.Fatalf("err: %s", err)
}
if len(res) != len(sessions1) {
t.Fatalf("bad: %#v", res)
}
if idx != 6 {
t.Fatalf("bad index: %d", idx)
}
ws2 := memdb.NewWatchSet()
idx, res, err = s.NodeSessions(ws2, "node2", nil)
if err != nil {
t.Fatalf("err: %s", err)
}
if len(res) != len(sessions2) {
t.Fatalf("bad: %#v", res)
}
if idx != 6 {
t.Fatalf("bad index: %d", idx)
}
// Destroying a session on node1 should not affect node2's watch.
if err := s.SessionDestroy(100, sessions1[0].ID, nil); err != nil {
t.Fatalf("err: %s", err)
}
if !watchFired(ws1) {
t.Fatalf("bad")
}
if watchFired(ws2) {
t.Fatalf("bad")
}
}
func TestStateStore_SessionDestroy(t *testing.T) {
s := testStateStore(t)
// Session destroy is idempotent and returns no error
// if the session doesn't exist.
if err := s.SessionDestroy(1, testUUID(), nil); err != nil {
t.Fatalf("err: %s", err)
}
// Ensure the index was not updated if nothing was destroyed.
if idx := s.maxIndex("sessions"); idx != 0 {
t.Fatalf("bad index: %d", idx)
}
// Register a node.
testRegisterNode(t, s, 1, "node1")
// Register a new session
sess := &structs.Session{
ID: testUUID(),
Node: "node1",
}
if err := s.SessionCreate(2, sess); err != nil {
t.Fatalf("err: %s", err)
}
// Destroy the session.
if err := s.SessionDestroy(3, sess.ID, nil); err != nil {
t.Fatalf("err: %s", err)
}
// Check that the index was updated
if idx := s.maxIndex(partitionedAndNamespacedIndexEntryName(tableSessions, &sess.EnterpriseMeta)); idx != 3 {
t.Fatalf("bad index: %d", idx)
}
// Make sure the session is really gone.
tx := s.db.Txn(false)
sessions, err := tx.Get(tableSessions, indexID)
if err != nil || sessions.Next() != nil {
t.Fatalf("session should not exist")
}
tx.Abort()
}
func TestStateStore_Session_Snapshot_Restore(t *testing.T) {
s := testStateStore(t)
// Register some nodes and checks.
testRegisterNode(t, s, 1, "node1")
testRegisterNode(t, s, 2, "node2")
testRegisterNode(t, s, 3, "node3")
testRegisterCheck(t, s, 4, "node1", "", "check1", api.HealthPassing)
// Create some sessions in the state store.
session1 := testUUID()
sessions := structs.Sessions{
&structs.Session{
ID: session1,
Node: "node1",
Behavior: structs.SessionKeysDelete,
Checks: []types.CheckID{"check1"},
},
&structs.Session{
ID: testUUID(),
Node: "node2",
Behavior: structs.SessionKeysRelease,
LockDelay: 10 * time.Second,
},
&structs.Session{
ID: testUUID(),
Node: "node3",
Behavior: structs.SessionKeysDelete,
TTL: "1.5s",
},
}
for i, session := range sessions {
if err := s.SessionCreate(uint64(5+i), session); err != nil {
t.Fatalf("err: %s", err)
}
}
// Snapshot the sessions.
snap := s.Snapshot()
defer snap.Close()
// Alter the real state store.
if err := s.SessionDestroy(8, session1, nil); err != nil {
t.Fatalf("err: %s", err)
}
// Verify the snapshot.
if idx := snap.LastIndex(); idx != 7 {
t.Fatalf("bad index: %d", idx)
}
iter, err := snap.Sessions()
if err != nil {
t.Fatalf("err: %s", err)
}
var dump structs.Sessions
for session := iter.Next(); session != nil; session = iter.Next() {
sess := session.(*structs.Session)
dump = append(dump, sess)
found := false
for i := range sessions {
if sess.ID == sessions[i].ID {
if !reflect.DeepEqual(sess, sessions[i]) {
t.Fatalf("bad: %#v", sess)
}
found = true
}
}
if !found {
t.Fatalf("bad: %#v", sess)
}
}
// Restore the sessions into a new state store.
func() {
s := testStateStore(t)
restore := s.Restore()
for _, session := range dump {
if err := restore.Session(session); err != nil {
t.Fatalf("err: %s", err)
}
}
restore.Commit()
// Read the restored sessions back out and verify that they
// match.
idx, res, err := s.SessionList(nil, nil)
if err != nil {
t.Fatalf("err: %s", err)
}
if idx != 7 {
t.Fatalf("bad index: %d", idx)
}
for _, sess := range res {
found := false
for i := range sessions {
if sess.ID == sessions[i].ID {
if !reflect.DeepEqual(sess, sessions[i]) {
t.Fatalf("bad: %#v", sess)
}
found = true
}
}
if !found {
t.Fatalf("bad: %#v", sess)
}
}
// Check that the index was updated.
if idx := s.maxIndex("sessions"); idx != 7 {
t.Fatalf("bad index: %d", idx)
}
// Manually verify that the session check mapping got restored.
tx := s.db.Txn(false)
defer tx.Abort()
check, err := tx.First(tableSessionChecks, indexSession, Query{Value: session1})
if err != nil {
t.Fatalf("err: %s", err)
}
if check == nil {
t.Fatalf("missing session check")
}
expectCheck := &sessionCheck{
Node: "node1",
CheckID: structs.CheckID{ID: "check1"},
Session: session1,
}
actual := check.(*sessionCheck)
expectCheck.CheckID.EnterpriseMeta = actual.CheckID.EnterpriseMeta
expectCheck.EnterpriseMeta = actual.EnterpriseMeta
if !reflect.DeepEqual(actual, expectCheck) {
t.Fatalf("expected %#v, got: %#v", expectCheck, actual)
}
}()
}
func TestStateStore_Session_Invalidate_DeleteNode(t *testing.T) {
s := testStateStore(t)
// Set up our test environment.
if err := s.EnsureNode(3, &structs.Node{Node: "foo", Address: "127.0.0.1"}); err != nil {
t.Fatalf("err: %v", err)
}
session := &structs.Session{
ID: testUUID(),
Node: "foo",
}
if err := s.SessionCreate(14, session); err != nil {
t.Fatalf("err: %v", err)
}
// Delete the node and make sure the watch fires.
ws := memdb.NewWatchSet()
_, _, err := s.SessionGet(ws, session.ID, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
if err := s.DeleteNode(15, "foo", nil, ""); err != nil {
t.Fatalf("err: %v", err)
}
if !watchFired(ws) {
t.Fatalf("bad")
}
// Lookup by ID, should be nil.
idx, s2, err := s.SessionGet(nil, session.ID, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
if s2 != nil {
t.Fatalf("session should be invalidated")
}
if idx != 15 {
t.Fatalf("bad index: %d", idx)
}
}
func TestStateStore_Session_Invalidate_DeleteService(t *testing.T) {
s := testStateStore(t)
// Set up our test environment.
if err := s.EnsureNode(11, &structs.Node{Node: "foo", Address: "127.0.0.1"}); err != nil {
t.Fatalf("err: %v", err)
}
if err := s.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: api.HealthPassing,
ServiceID: "api",
}
if err := s.EnsureCheck(13, check); err != nil {
t.Fatalf("err: %v", err)
}
session := &structs.Session{
ID: testUUID(),
Node: "foo",
Checks: []types.CheckID{"api"},
}
if err := s.SessionCreate(14, session); err != nil {
t.Fatalf("err: %v", err)
}
// Delete the service and make sure the watch fires.
ws := memdb.NewWatchSet()
_, _, err := s.SessionGet(ws, session.ID, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
if err := s.DeleteService(15, "foo", "api", nil, ""); err != nil {
t.Fatalf("err: %v", err)
}
if !watchFired(ws) {
t.Fatalf("bad")
}
// Lookup by ID, should be nil.
idx, s2, err := s.SessionGet(nil, session.ID, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
if s2 != nil {
t.Fatalf("session should be invalidated")
}
if idx != 15 {
t.Fatalf("bad index: %d", idx)
}
}
func TestStateStore_Session_Invalidate_Critical_Check(t *testing.T) {
s := testStateStore(t)
// Set up our test environment.
if err := s.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: api.HealthPassing,
}
if err := s.EnsureCheck(13, check); err != nil {
t.Fatalf("err: %v", err)
}
session := &structs.Session{
ID: testUUID(),
Node: "foo",
Checks: []types.CheckID{"bar"},
}
if err := s.SessionCreate(14, session); err != nil {
t.Fatalf("err: %v", err)
}
// Invalidate the check and make sure the watches fire.
ws := memdb.NewWatchSet()
_, _, err := s.SessionGet(ws, session.ID, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
check.Status = api.HealthCritical
if err := s.EnsureCheck(15, check); err != nil {
t.Fatalf("err: %v", err)
}
if !watchFired(ws) {
t.Fatalf("bad")
}
// Lookup by ID, should be nil.
idx, s2, err := s.SessionGet(nil, session.ID, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
if s2 != nil {
t.Fatalf("session should be invalidated")
}
if idx != 15 {
t.Fatalf("bad index: %d", idx)
}
}
func TestStateStore_Session_Invalidate_DeleteCheck(t *testing.T) {
s := testStateStore(t)
// Set up our test environment.
if err := s.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: api.HealthPassing,
}
if err := s.EnsureCheck(13, check); err != nil {
t.Fatalf("err: %v", err)
}
session := &structs.Session{
ID: testUUID(),
Node: "foo",
Checks: []types.CheckID{"bar"},
}
if err := s.SessionCreate(14, session); err != nil {
t.Fatalf("err: %v", err)
}
// Delete the check and make sure the watches fire.
ws := memdb.NewWatchSet()
_, _, err := s.SessionGet(ws, session.ID, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
if err := s.DeleteCheck(15, "foo", "bar", nil, ""); err != nil {
t.Fatalf("err: %v", err)
}
if !watchFired(ws) {
t.Fatalf("bad")
}
// Lookup by ID, should be nil.
idx, s2, err := s.SessionGet(nil, session.ID, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
if s2 != nil {
t.Fatalf("session should be invalidated")
}
if idx != 15 {
t.Fatalf("bad index: %d", idx)
}
// Manually make sure the session checks mapping is clear.
tx := s.db.Txn(false)
mapping, err := tx.First(tableSessionChecks, indexSession, Query{Value: session.ID})
if err != nil {
t.Fatalf("err: %s", err)
}
if mapping != nil {
t.Fatalf("unexpected session check")
}
tx.Abort()
}
func TestStateStore_Session_Invalidate_Key_Unlock_Behavior(t *testing.T) {
s := testStateStore(t)
// Set up our test environment.
if err := s.EnsureNode(3, &structs.Node{Node: "foo", Address: "127.0.0.1"}); err != nil {
t.Fatalf("err: %v", err)
}
session := &structs.Session{
ID: testUUID(),
Node: "foo",
LockDelay: 50 * time.Millisecond,
}
if err := s.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 := s.KVSLock(5, d)
if err != nil {
t.Fatalf("err: %v", err)
}
if !ok {
t.Fatalf("unexpected fail")
}
// Delete the node and make sure the watches fire.
ws := memdb.NewWatchSet()
_, _, err = s.SessionGet(ws, session.ID, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
if err := s.DeleteNode(6, "foo", nil, ""); err != nil {
t.Fatalf("err: %v", err)
}
if !watchFired(ws) {
t.Fatalf("bad")
}
// Lookup by ID, should be nil.
idx, s2, err := s.SessionGet(nil, session.ID, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
if s2 != nil {
t.Fatalf("session should be invalidated")
}
if idx != 6 {
t.Fatalf("bad index: %d", idx)
}
// Key should be unlocked.
idx, d2, err := s.KVSGet(nil, "/foo", nil)
if err != nil {
t.Fatalf("err: %s", err)
}
if d2.ModifyIndex != 6 {
t.Fatalf("bad index: %v", d2.ModifyIndex)
}
if d2.LockIndex != 1 {
t.Fatalf("bad: %v", *d2)
}
if d2.Session != "" {
t.Fatalf("bad: %v", *d2)
}
if idx != 6 {
t.Fatalf("bad index: %d", idx)
}
// Key should have a lock delay.
expires := s.KVSLockDelay("/foo", nil)
if expires.Before(time.Now().Add(30 * time.Millisecond)) {
t.Fatalf("Bad: %v", expires)
}
}
func TestStateStore_Session_Invalidate_Key_Delete_Behavior(t *testing.T) {
s := testStateStore(t)
// Set up our test environment.
if err := s.EnsureNode(3, &structs.Node{Node: "foo", Address: "127.0.0.1"}); err != nil {
t.Fatalf("err: %v", err)
}
session := &structs.Session{
ID: testUUID(),
Node: "foo",
LockDelay: 50 * time.Millisecond,
Behavior: structs.SessionKeysDelete,
}
if err := s.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 := s.KVSLock(5, d)
if err != nil {
t.Fatalf("err: %v", err)
}
if !ok {
t.Fatalf("unexpected fail")
}
// Delete the node and make sure the watches fire.
ws := memdb.NewWatchSet()
_, _, err = s.SessionGet(ws, session.ID, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
if err := s.DeleteNode(6, "foo", nil, ""); err != nil {
t.Fatalf("err: %v", err)
}
if !watchFired(ws) {
t.Fatalf("bad")
}
// Lookup by ID, should be nil.
idx, s2, err := s.SessionGet(nil, session.ID, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
if s2 != nil {
t.Fatalf("session should be invalidated")
}
if idx != 6 {
t.Fatalf("bad index: %d", idx)
}
// Key should be deleted.
idx, d2, err := s.KVSGet(nil, "/bar", nil)
if err != nil {
t.Fatalf("err: %s", err)
}
if d2 != nil {
t.Fatalf("unexpected deleted key")
}
if idx != 6 {
t.Fatalf("bad index: %d", idx)
}
// Key should have a lock delay.
expires := s.KVSLockDelay("/bar", nil)
if expires.Before(time.Now().Add(30 * time.Millisecond)) {
t.Fatalf("Bad: %v", expires)
}
}
func TestStateStore_Session_Invalidate_PreparedQuery_Delete(t *testing.T) {
s := testStateStore(t)
// Set up our test environment.
testRegisterNode(t, s, 1, "foo")
testRegisterService(t, s, 2, "foo", "redis")
session := &structs.Session{
ID: testUUID(),
Node: "foo",
}
if err := s.SessionCreate(3, session); err != nil {
t.Fatalf("err: %v", err)
}
query := &structs.PreparedQuery{
ID: testUUID(),
Session: session.ID,
Service: structs.ServiceQuery{
Service: "redis",
},
}
if err := s.PreparedQuerySet(4, query); err != nil {
t.Fatalf("err: %s", err)
}
// Invalidate the session and make sure the watches fire.
ws := memdb.NewWatchSet()
_, _, err := s.SessionGet(ws, session.ID, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
if err := s.SessionDestroy(5, session.ID, nil); err != nil {
t.Fatalf("err: %v", err)
}
if !watchFired(ws) {
t.Fatalf("bad")
}
// Make sure the session is gone.
idx, s2, err := s.SessionGet(nil, session.ID, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
if s2 != nil {
t.Fatalf("session should be invalidated")
}
if idx != 5 {
t.Fatalf("bad index: %d", idx)
}
// Make sure the query is gone and the index is updated.
idx, q2, err := s.PreparedQueryGet(nil, query.ID)
if err != nil {
t.Fatalf("err: %s", err)
}
if idx != 5 {
t.Fatalf("bad index: %d", idx)
}
if q2 != nil {
t.Fatalf("bad: %v", q2)
}
}