mirror of https://github.com/hashicorp/consul
Ephemeral Nodes for via Session behavior settings.
Added a "delete" behavior for session invalidation, in addition to the default "release" behavior. On session invalidation, the sessions Behavior field is checked and if it is set to "delete", all nodes owned by the session are deleted. If it is "release", then just the locks are released as default.pull/487/head
parent
a3cd40cf9d
commit
47241fc1c8
|
@ -32,13 +32,14 @@ func (s *HTTPServer) SessionCreate(resp http.ResponseWriter, req *http.Request)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default the session to our node + serf check
|
// Default the session to our node + serf check + release session invalidate behavior
|
||||||
args := structs.SessionRequest{
|
args := structs.SessionRequest{
|
||||||
Op: structs.SessionCreate,
|
Op: structs.SessionCreate,
|
||||||
Session: structs.Session{
|
Session: structs.Session{
|
||||||
Node: s.agent.config.NodeName,
|
Node: s.agent.config.NodeName,
|
||||||
Checks: []string{consul.SerfCheckID},
|
Checks: []string{consul.SerfCheckID},
|
||||||
LockDelay: 15 * time.Second,
|
LockDelay: 15 * time.Second,
|
||||||
|
Behavior: structs.SessionKeysRelease,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
s.parseDC(req, &args.Datacenter)
|
s.parseDC(req, &args.Datacenter)
|
||||||
|
|
|
@ -59,6 +59,55 @@ func TestSessionCreate(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSessionCreateDelete(t *testing.T) {
|
||||||
|
httpTest(t, func(srv *HTTPServer) {
|
||||||
|
// Create a health check
|
||||||
|
args := &structs.RegisterRequest{
|
||||||
|
Datacenter: "dc1",
|
||||||
|
Node: srv.agent.config.NodeName,
|
||||||
|
Address: "127.0.0.1",
|
||||||
|
Check: &structs.HealthCheck{
|
||||||
|
CheckID: "consul",
|
||||||
|
Node: srv.agent.config.NodeName,
|
||||||
|
Name: "consul",
|
||||||
|
ServiceID: "consul",
|
||||||
|
Status: structs.HealthPassing,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
var out struct{}
|
||||||
|
if err := srv.agent.RPC("Catalog.Register", args, &out); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Associate session with node and 2 health checks, and make it delete on session destroy
|
||||||
|
body := bytes.NewBuffer(nil)
|
||||||
|
enc := json.NewEncoder(body)
|
||||||
|
raw := map[string]interface{}{
|
||||||
|
"Name": "my-cool-session",
|
||||||
|
"Node": srv.agent.config.NodeName,
|
||||||
|
"Checks": []string{consul.SerfCheckID, "consul"},
|
||||||
|
"LockDelay": "20s",
|
||||||
|
"Behavior": "delete",
|
||||||
|
}
|
||||||
|
enc.Encode(raw)
|
||||||
|
|
||||||
|
req, err := http.NewRequest("PUT", "/v1/session/create", body)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := httptest.NewRecorder()
|
||||||
|
obj, err := srv.SessionCreate(resp, req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := obj.(sessionCreateResponse); !ok {
|
||||||
|
t.Fatalf("should work")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestFixupLockDelay(t *testing.T) {
|
func TestFixupLockDelay(t *testing.T) {
|
||||||
inp := map[string]interface{}{
|
inp := map[string]interface{}{
|
||||||
"lockdelay": float64(15),
|
"lockdelay": float64(15),
|
||||||
|
@ -105,6 +154,28 @@ func makeTestSession(t *testing.T, srv *HTTPServer) string {
|
||||||
return sessResp.ID
|
return sessResp.ID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func makeTestSessionDelete(t *testing.T, srv *HTTPServer) string {
|
||||||
|
// Create Session with delete behavior
|
||||||
|
body := bytes.NewBuffer(nil)
|
||||||
|
enc := json.NewEncoder(body)
|
||||||
|
raw := map[string]interface{}{
|
||||||
|
"Behavior": "delete",
|
||||||
|
}
|
||||||
|
enc.Encode(raw)
|
||||||
|
|
||||||
|
req, err := http.NewRequest("PUT", "/v1/session/create", body)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
resp := httptest.NewRecorder()
|
||||||
|
obj, err := srv.SessionCreate(resp, req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
sessResp := obj.(sessionCreateResponse)
|
||||||
|
return sessResp.ID
|
||||||
|
}
|
||||||
|
|
||||||
func TestSessionDestroy(t *testing.T) {
|
func TestSessionDestroy(t *testing.T) {
|
||||||
httpTest(t, func(srv *HTTPServer) {
|
httpTest(t, func(srv *HTTPServer) {
|
||||||
id := makeTestSession(t, srv)
|
id := makeTestSession(t, srv)
|
||||||
|
@ -188,3 +259,113 @@ func TestSessionsForNode(t *testing.T) {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSessionDeleteDestroy(t *testing.T) {
|
||||||
|
httpTest(t, func(srv *HTTPServer) {
|
||||||
|
id := makeTestSessionDelete(t, srv)
|
||||||
|
|
||||||
|
// now create a new key for the session and acquire it
|
||||||
|
buf := bytes.NewBuffer([]byte("test"))
|
||||||
|
req, err := http.NewRequest("PUT", "/v1/kv/ephemeral?acquire="+id, buf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
resp := httptest.NewRecorder()
|
||||||
|
obj, err := srv.KVSEndpoint(resp, req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if res := obj.(bool); !res {
|
||||||
|
t.Fatalf("should work")
|
||||||
|
}
|
||||||
|
|
||||||
|
// now destroy the session, this should delete the key create above
|
||||||
|
req, err = http.NewRequest("PUT", "/v1/session/destroy/"+id, nil)
|
||||||
|
resp = httptest.NewRecorder()
|
||||||
|
obj, err = srv.SessionDestroy(resp, req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
if resp := obj.(bool); !resp {
|
||||||
|
t.Fatalf("should work")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify that the key is gone
|
||||||
|
req, _ = http.NewRequest("GET", "/v1/kv/ephemeral", nil)
|
||||||
|
resp = httptest.NewRecorder()
|
||||||
|
obj, _ = srv.KVSEndpoint(resp, req)
|
||||||
|
res, found := obj.(structs.DirEntries)
|
||||||
|
if found || len(res) != 0 {
|
||||||
|
t.Fatalf("bad: %v found, should be nothing", res)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSessionDeleteGet(t *testing.T) {
|
||||||
|
httpTest(t, func(srv *HTTPServer) {
|
||||||
|
id := makeTestSessionDelete(t, srv)
|
||||||
|
|
||||||
|
req, err := http.NewRequest("GET",
|
||||||
|
"/v1/session/info/"+id, nil)
|
||||||
|
resp := httptest.NewRecorder()
|
||||||
|
obj, err := srv.SessionGet(resp, req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
respObj, ok := obj.(structs.Sessions)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("should work")
|
||||||
|
}
|
||||||
|
if len(respObj) != 1 {
|
||||||
|
t.Fatalf("bad: %v", respObj)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSessionDeleteList(t *testing.T) {
|
||||||
|
httpTest(t, func(srv *HTTPServer) {
|
||||||
|
var ids []string
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
ids = append(ids, makeTestSessionDelete(t, srv))
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest("GET", "/v1/session/list", nil)
|
||||||
|
resp := httptest.NewRecorder()
|
||||||
|
obj, err := srv.SessionList(resp, req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
respObj, ok := obj.(structs.Sessions)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("should work")
|
||||||
|
}
|
||||||
|
if len(respObj) != 10 {
|
||||||
|
t.Fatalf("bad: %v", respObj)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSessionsDeleteForNode(t *testing.T) {
|
||||||
|
httpTest(t, func(srv *HTTPServer) {
|
||||||
|
var ids []string
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
ids = append(ids, makeTestSessionDelete(t, srv))
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest("GET",
|
||||||
|
"/v1/session/node/"+srv.agent.config.NodeName, nil)
|
||||||
|
resp := httptest.NewRecorder()
|
||||||
|
obj, err := srv.SessionsForNode(resp, req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
respObj, ok := obj.(structs.Sessions)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("should work")
|
||||||
|
}
|
||||||
|
if len(respObj) != 10 {
|
||||||
|
t.Fatalf("bad: %v", respObj)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -28,6 +28,9 @@ func (s *Session) Apply(args *structs.SessionRequest, reply *string) error {
|
||||||
if args.Session.Node == "" && args.Op == structs.SessionCreate {
|
if args.Session.Node == "" && args.Op == structs.SessionCreate {
|
||||||
return fmt.Errorf("Must provide Node")
|
return fmt.Errorf("Must provide Node")
|
||||||
}
|
}
|
||||||
|
if args.Session.Behavior == "" {
|
||||||
|
args.Session.Behavior = structs.SessionKeysRelease // force default behavior
|
||||||
|
}
|
||||||
|
|
||||||
// If this is a create, we must generate the Session ID. This must
|
// If this is a create, we must generate the Session ID. This must
|
||||||
// be done prior to appending to the raft log, because the ID is not
|
// be done prior to appending to the raft log, because the ID is not
|
||||||
|
|
|
@ -66,6 +66,66 @@ func TestSessionEndpoint_Apply(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSessionEndpoint_DeleteApply(t *testing.T) {
|
||||||
|
dir1, s1 := testServer(t)
|
||||||
|
defer os.RemoveAll(dir1)
|
||||||
|
defer s1.Shutdown()
|
||||||
|
client := rpcClient(t, s1)
|
||||||
|
defer client.Close()
|
||||||
|
|
||||||
|
testutil.WaitForLeader(t, client.Call, "dc1")
|
||||||
|
|
||||||
|
// Just add a node
|
||||||
|
s1.fsm.State().EnsureNode(1, structs.Node{"foo", "127.0.0.1"})
|
||||||
|
|
||||||
|
arg := structs.SessionRequest{
|
||||||
|
Datacenter: "dc1",
|
||||||
|
Op: structs.SessionCreate,
|
||||||
|
Session: structs.Session{
|
||||||
|
Node: "foo",
|
||||||
|
Name: "my-session",
|
||||||
|
Behavior: structs.SessionKeysDelete,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
var out string
|
||||||
|
if err := client.Call("Session.Apply", &arg, &out); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
id := out
|
||||||
|
|
||||||
|
// Verify
|
||||||
|
state := s1.fsm.State()
|
||||||
|
_, s, err := state.SessionGet(out)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
if s == nil {
|
||||||
|
t.Fatalf("should not be nil")
|
||||||
|
}
|
||||||
|
if s.Node != "foo" {
|
||||||
|
t.Fatalf("bad: %v", s)
|
||||||
|
}
|
||||||
|
if s.Name != "my-session" {
|
||||||
|
t.Fatalf("bad: %v", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do a delete
|
||||||
|
arg.Op = structs.SessionDestroy
|
||||||
|
arg.Session.ID = out
|
||||||
|
if err := client.Call("Session.Apply", &arg, &out); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify
|
||||||
|
_, s, err = state.SessionGet(id)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
if s != nil {
|
||||||
|
t.Fatalf("bad: %v", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestSessionEndpoint_Get(t *testing.T) {
|
func TestSessionEndpoint_Get(t *testing.T) {
|
||||||
dir1, s1 := testServer(t)
|
dir1, s1 := testServer(t)
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
|
@ -80,7 +140,51 @@ func TestSessionEndpoint_Get(t *testing.T) {
|
||||||
Datacenter: "dc1",
|
Datacenter: "dc1",
|
||||||
Op: structs.SessionCreate,
|
Op: structs.SessionCreate,
|
||||||
Session: structs.Session{
|
Session: structs.Session{
|
||||||
Node: "foo",
|
Node: "foo",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
var out string
|
||||||
|
if err := client.Call("Session.Apply", &arg, &out); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
getR := structs.SessionSpecificRequest{
|
||||||
|
Datacenter: "dc1",
|
||||||
|
Session: out,
|
||||||
|
}
|
||||||
|
var sessions structs.IndexedSessions
|
||||||
|
if err := client.Call("Session.Get", &getR, &sessions); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if sessions.Index == 0 {
|
||||||
|
t.Fatalf("Bad: %v", sessions)
|
||||||
|
}
|
||||||
|
if len(sessions.Sessions) != 1 {
|
||||||
|
t.Fatalf("Bad: %v", sessions)
|
||||||
|
}
|
||||||
|
s := sessions.Sessions[0]
|
||||||
|
if s.ID != out {
|
||||||
|
t.Fatalf("bad: %v", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSessionEndpoint_DeleteGet(t *testing.T) {
|
||||||
|
dir1, s1 := testServer(t)
|
||||||
|
defer os.RemoveAll(dir1)
|
||||||
|
defer s1.Shutdown()
|
||||||
|
client := rpcClient(t, s1)
|
||||||
|
defer client.Close()
|
||||||
|
|
||||||
|
testutil.WaitForLeader(t, client.Call, "dc1")
|
||||||
|
|
||||||
|
s1.fsm.State().EnsureNode(1, structs.Node{"foo", "127.0.0.1"})
|
||||||
|
arg := structs.SessionRequest{
|
||||||
|
Datacenter: "dc1",
|
||||||
|
Op: structs.SessionCreate,
|
||||||
|
Session: structs.Session{
|
||||||
|
Node: "foo",
|
||||||
|
Behavior: structs.SessionKeysDelete,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
var out string
|
var out string
|
||||||
|
@ -160,6 +264,58 @@ func TestSessionEndpoint_List(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSessionEndpoint_DeleteList(t *testing.T) {
|
||||||
|
dir1, s1 := testServer(t)
|
||||||
|
defer os.RemoveAll(dir1)
|
||||||
|
defer s1.Shutdown()
|
||||||
|
client := rpcClient(t, s1)
|
||||||
|
defer client.Close()
|
||||||
|
|
||||||
|
testutil.WaitForLeader(t, client.Call, "dc1")
|
||||||
|
|
||||||
|
s1.fsm.State().EnsureNode(1, structs.Node{"foo", "127.0.0.1"})
|
||||||
|
ids := []string{}
|
||||||
|
for i := 0; i < 5; i++ {
|
||||||
|
arg := structs.SessionRequest{
|
||||||
|
Datacenter: "dc1",
|
||||||
|
Op: structs.SessionCreate,
|
||||||
|
Session: structs.Session{
|
||||||
|
Node: "foo",
|
||||||
|
Behavior: structs.SessionKeysDelete,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
var out string
|
||||||
|
if err := client.Call("Session.Apply", &arg, &out); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
ids = append(ids, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
getR := structs.DCSpecificRequest{
|
||||||
|
Datacenter: "dc1",
|
||||||
|
}
|
||||||
|
var sessions structs.IndexedSessions
|
||||||
|
if err := client.Call("Session.List", &getR, &sessions); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if sessions.Index == 0 {
|
||||||
|
t.Fatalf("Bad: %v", sessions)
|
||||||
|
}
|
||||||
|
if len(sessions.Sessions) != 5 {
|
||||||
|
t.Fatalf("Bad: %v", sessions.Sessions)
|
||||||
|
}
|
||||||
|
for i := 0; i < len(sessions.Sessions); i++ {
|
||||||
|
s := sessions.Sessions[i]
|
||||||
|
if !strContains(ids, s.ID) {
|
||||||
|
t.Fatalf("bad: %v", s)
|
||||||
|
}
|
||||||
|
if s.Node != "foo" {
|
||||||
|
t.Fatalf("bad: %v", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestSessionEndpoint_NodeSessions(t *testing.T) {
|
func TestSessionEndpoint_NodeSessions(t *testing.T) {
|
||||||
dir1, s1 := testServer(t)
|
dir1, s1 := testServer(t)
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
|
@ -217,3 +373,62 @@ func TestSessionEndpoint_NodeSessions(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSessionEndpoint_DeleteNodeSessions(t *testing.T) {
|
||||||
|
dir1, s1 := testServer(t)
|
||||||
|
defer os.RemoveAll(dir1)
|
||||||
|
defer s1.Shutdown()
|
||||||
|
client := rpcClient(t, s1)
|
||||||
|
defer client.Close()
|
||||||
|
|
||||||
|
testutil.WaitForLeader(t, client.Call, "dc1")
|
||||||
|
|
||||||
|
s1.fsm.State().EnsureNode(1, structs.Node{"foo", "127.0.0.1"})
|
||||||
|
s1.fsm.State().EnsureNode(1, structs.Node{"bar", "127.0.0.1"})
|
||||||
|
ids := []string{}
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
arg := structs.SessionRequest{
|
||||||
|
Datacenter: "dc1",
|
||||||
|
Op: structs.SessionCreate,
|
||||||
|
Session: structs.Session{
|
||||||
|
Node: "bar",
|
||||||
|
Behavior: structs.SessionKeysDelete,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if i < 5 {
|
||||||
|
arg.Session.Node = "foo"
|
||||||
|
}
|
||||||
|
var out string
|
||||||
|
if err := client.Call("Session.Apply", &arg, &out); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
if i < 5 {
|
||||||
|
ids = append(ids, out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getR := structs.NodeSpecificRequest{
|
||||||
|
Datacenter: "dc1",
|
||||||
|
Node: "foo",
|
||||||
|
}
|
||||||
|
var sessions structs.IndexedSessions
|
||||||
|
if err := client.Call("Session.NodeSessions", &getR, &sessions); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if sessions.Index == 0 {
|
||||||
|
t.Fatalf("Bad: %v", sessions)
|
||||||
|
}
|
||||||
|
if len(sessions.Sessions) != 5 {
|
||||||
|
t.Fatalf("Bad: %v", sessions.Sessions)
|
||||||
|
}
|
||||||
|
for i := 0; i < len(sessions.Sessions); i++ {
|
||||||
|
s := sessions.Sessions[i]
|
||||||
|
if !strContains(ids, s.ID) {
|
||||||
|
t.Fatalf("bad: %v", s)
|
||||||
|
}
|
||||||
|
if s.Node != "foo" {
|
||||||
|
t.Fatalf("bad: %v", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1327,6 +1327,11 @@ func (s *StateStore) SessionCreate(index uint64, session *structs.Session) error
|
||||||
return fmt.Errorf("Missing Session ID")
|
return fmt.Errorf("Missing Session ID")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// make sure we have a default set for session.Behavior
|
||||||
|
if session.Behavior == "" {
|
||||||
|
session.Behavior = structs.SessionKeysRelease
|
||||||
|
}
|
||||||
|
|
||||||
// Assign the create index
|
// Assign the create index
|
||||||
session.CreateIndex = index
|
session.CreateIndex = index
|
||||||
|
|
||||||
|
@ -1454,7 +1459,7 @@ func (s *StateStore) SessionDestroy(index uint64, id string) error {
|
||||||
}
|
}
|
||||||
defer tx.Abort()
|
defer tx.Abort()
|
||||||
|
|
||||||
log.Printf("[DEBUG] consul.state: Invalidating session %s due to session destroy",
|
s.logger.Printf("[DEBUG] consul.state: Invalidating session %s due to session destroy",
|
||||||
id)
|
id)
|
||||||
if err := s.invalidateSession(index, tx, id); err != nil {
|
if err := s.invalidateSession(index, tx, id); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -1471,7 +1476,7 @@ func (s *StateStore) invalidateNode(index uint64, tx *MDBTxn, node string) error
|
||||||
}
|
}
|
||||||
for _, sess := range sessions {
|
for _, sess := range sessions {
|
||||||
session := sess.(*structs.Session).ID
|
session := sess.(*structs.Session).ID
|
||||||
log.Printf("[DEBUG] consul.state: Invalidating session %s due to node '%s' invalidation",
|
s.logger.Printf("[DEBUG] consul.state: Invalidating session %s due to node '%s' invalidation",
|
||||||
session, node)
|
session, node)
|
||||||
if err := s.invalidateSession(index, tx, session); err != nil {
|
if err := s.invalidateSession(index, tx, session); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -1489,7 +1494,7 @@ func (s *StateStore) invalidateCheck(index uint64, tx *MDBTxn, node, check strin
|
||||||
}
|
}
|
||||||
for _, sc := range sessionChecks {
|
for _, sc := range sessionChecks {
|
||||||
session := sc.(*sessionCheck).Session
|
session := sc.(*sessionCheck).Session
|
||||||
log.Printf("[DEBUG] consul.state: Invalidating session %s due to check '%s' invalidation",
|
s.logger.Printf("[DEBUG] consul.state: Invalidating session %s due to check '%s' invalidation",
|
||||||
session, check)
|
session, check)
|
||||||
if err := s.invalidateSession(index, tx, session); err != nil {
|
if err := s.invalidateSession(index, tx, session); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -1513,15 +1518,23 @@ func (s *StateStore) invalidateSession(index uint64, tx *MDBTxn, id string) erro
|
||||||
}
|
}
|
||||||
session := res[0].(*structs.Session)
|
session := res[0].(*structs.Session)
|
||||||
|
|
||||||
// Enforce the MaxLockDelay
|
if session.Behavior == structs.SessionKeysDelete {
|
||||||
delay := session.LockDelay
|
// delete the keys held by the session
|
||||||
if delay > structs.MaxLockDelay {
|
if err := s.deleteKeys(index, tx, id); err != nil {
|
||||||
delay = structs.MaxLockDelay
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Invalidate any held locks
|
} else { // default to release
|
||||||
if err := s.invalidateLocks(index, tx, delay, id); err != nil {
|
// Enforce the MaxLockDelay
|
||||||
return err
|
delay := session.LockDelay
|
||||||
|
if delay > structs.MaxLockDelay {
|
||||||
|
delay = structs.MaxLockDelay
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invalidate any held locks
|
||||||
|
if err := s.invalidateLocks(index, tx, delay, id); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Nuke the session
|
// Nuke the session
|
||||||
|
@ -1588,6 +1601,23 @@ func (s *StateStore) invalidateLocks(index uint64, tx *MDBTxn,
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// deleteKeys is used to delete all the keys created by a session
|
||||||
|
// within a given txn. All tables should be locked in the tx.
|
||||||
|
func (s *StateStore) deleteKeys(index uint64, tx *MDBTxn, id string) error {
|
||||||
|
num, err := s.kvsTable.DeleteTxn(tx, "session", id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if num > 0 {
|
||||||
|
if err := s.kvsTable.SetLastIndexTxn(tx, index); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
tx.Defer(func() { s.watch[s.kvsTable].Notify() })
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// ACLSet is used to create or update an ACL entry
|
// ACLSet is used to create or update an ACL entry
|
||||||
func (s *StateStore) ACLSet(index uint64, acl *structs.ACL) error {
|
func (s *StateStore) ACLSet(index uint64, acl *structs.ACL) error {
|
||||||
// Check for an ID
|
// Check for an ID
|
||||||
|
|
|
@ -2279,6 +2279,53 @@ func TestSessionInvalidate_KeyUnlock(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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{"foo", "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")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestACLSet_Get(t *testing.T) {
|
func TestACLSet_Get(t *testing.T) {
|
||||||
store, err := testStateStore()
|
store, err := testStateStore()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -378,6 +378,13 @@ type IndexedKeyList struct {
|
||||||
QueryMeta
|
QueryMeta
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SessionBehavior string
|
||||||
|
|
||||||
|
const (
|
||||||
|
SessionKeysRelease SessionBehavior = "release"
|
||||||
|
SessionKeysDelete = "delete"
|
||||||
|
)
|
||||||
|
|
||||||
// Session is used to represent an open session in the KV store.
|
// Session is used to represent an open session in the KV store.
|
||||||
// This issued to associate node checks with acquired locks.
|
// This issued to associate node checks with acquired locks.
|
||||||
type Session struct {
|
type Session struct {
|
||||||
|
@ -387,6 +394,7 @@ type Session struct {
|
||||||
Node string
|
Node string
|
||||||
Checks []string
|
Checks []string
|
||||||
LockDelay time.Duration
|
LockDelay time.Duration
|
||||||
|
Behavior SessionBehavior // What to do when session is invalidated
|
||||||
}
|
}
|
||||||
type Sessions []*Session
|
type Sessions []*Session
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue