open-consul/agent/session_endpoint_test.go
Matt Keeler a34f8c751e
Pass a testing.T into NewTestAgent and TestAgent.Start (#5342)
This way we can avoid unnecessary panics which cause other tests not to run.

This doesn't remove all the possibilities for panics causing other tests not to run, it just fixes the TestAgent
2019-02-14 10:59:14 -05:00

634 lines
16 KiB
Go

package agent
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/hashicorp/consul/agent/structs"
"github.com/hashicorp/consul/api"
"github.com/hashicorp/consul/testrpc"
"github.com/hashicorp/consul/testutil/retry"
"github.com/hashicorp/consul/types"
"github.com/pascaldekloe/goe/verify"
)
func verifySession(r *retry.R, a *TestAgent, want structs.Session) {
args := &structs.SessionSpecificRequest{
Datacenter: "dc1",
Session: want.ID,
}
var out structs.IndexedSessions
if err := a.RPC("Session.Get", args, &out); err != nil {
r.Fatalf("err: %v", err)
}
if len(out.Sessions) != 1 {
r.Fatalf("bad: %#v", out.Sessions)
}
// Make a copy so we don't modify the state store copy for an in-mem
// RPC and zero out the Raft info for the compare.
got := *(out.Sessions[0])
got.CreateIndex = 0
got.ModifyIndex = 0
verify.Values(r, "", got, want)
}
func TestSessionCreate(t *testing.T) {
t.Parallel()
a := NewTestAgent(t, t.Name(), "")
defer a.Shutdown()
testrpc.WaitForTestAgent(t, a.RPC, "dc1")
// Create a health check
args := &structs.RegisterRequest{
Datacenter: "dc1",
Node: a.Config.NodeName,
Address: "127.0.0.1",
Check: &structs.HealthCheck{
CheckID: "consul",
Node: a.Config.NodeName,
Name: "consul",
ServiceID: "consul",
Status: api.HealthPassing,
},
}
retry.Run(t, func(r *retry.R) {
var out struct{}
if err := a.RPC("Catalog.Register", args, &out); err != nil {
r.Fatalf("err: %v", err)
}
// Associate session with node and 2 health checks
body := bytes.NewBuffer(nil)
enc := json.NewEncoder(body)
raw := map[string]interface{}{
"Name": "my-cool-session",
"Node": a.Config.NodeName,
"Checks": []types.CheckID{structs.SerfCheckID, "consul"},
"LockDelay": "20s",
}
enc.Encode(raw)
req, _ := http.NewRequest("PUT", "/v1/session/create", body)
resp := httptest.NewRecorder()
obj, err := a.srv.SessionCreate(resp, req)
if err != nil {
r.Fatalf("err: %v", err)
}
want := structs.Session{
ID: obj.(sessionCreateResponse).ID,
Name: "my-cool-session",
Node: a.Config.NodeName,
Checks: []types.CheckID{structs.SerfCheckID, "consul"},
LockDelay: 20 * time.Second,
Behavior: structs.SessionKeysRelease,
}
verifySession(r, a, want)
})
}
func TestSessionCreate_Delete(t *testing.T) {
t.Parallel()
a := NewTestAgent(t, t.Name(), "")
defer a.Shutdown()
testrpc.WaitForTestAgent(t, a.RPC, "dc1")
// Create a health check
args := &structs.RegisterRequest{
Datacenter: "dc1",
Node: a.Config.NodeName,
Address: "127.0.0.1",
Check: &structs.HealthCheck{
CheckID: "consul",
Node: a.Config.NodeName,
Name: "consul",
ServiceID: "consul",
Status: api.HealthPassing,
},
}
retry.Run(t, func(r *retry.R) {
var out struct{}
if err := a.RPC("Catalog.Register", args, &out); err != nil {
r.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": a.Config.NodeName,
"Checks": []types.CheckID{structs.SerfCheckID, "consul"},
"LockDelay": "20s",
"Behavior": structs.SessionKeysDelete,
}
enc.Encode(raw)
req, _ := http.NewRequest("PUT", "/v1/session/create", body)
resp := httptest.NewRecorder()
obj, err := a.srv.SessionCreate(resp, req)
if err != nil {
r.Fatalf("err: %v", err)
}
want := structs.Session{
ID: obj.(sessionCreateResponse).ID,
Name: "my-cool-session",
Node: a.Config.NodeName,
Checks: []types.CheckID{structs.SerfCheckID, "consul"},
LockDelay: 20 * time.Second,
Behavior: structs.SessionKeysDelete,
}
verifySession(r, a, want)
})
}
func TestSessionCreate_DefaultCheck(t *testing.T) {
t.Parallel()
a := NewTestAgent(t, t.Name(), "")
defer a.Shutdown()
testrpc.WaitForTestAgent(t, a.RPC, "dc1")
// Associate session with node and 2 health checks
body := bytes.NewBuffer(nil)
enc := json.NewEncoder(body)
raw := map[string]interface{}{
"Name": "my-cool-session",
"Node": a.Config.NodeName,
"LockDelay": "20s",
}
enc.Encode(raw)
req, _ := http.NewRequest("PUT", "/v1/session/create", body)
resp := httptest.NewRecorder()
retry.Run(t, func(r *retry.R) {
obj, err := a.srv.SessionCreate(resp, req)
if err != nil {
r.Fatalf("err: %v", err)
}
want := structs.Session{
ID: obj.(sessionCreateResponse).ID,
Name: "my-cool-session",
Node: a.Config.NodeName,
Checks: []types.CheckID{structs.SerfCheckID},
LockDelay: 20 * time.Second,
Behavior: structs.SessionKeysRelease,
}
verifySession(r, a, want)
})
}
func TestSessionCreate_NoCheck(t *testing.T) {
t.Parallel()
a := NewTestAgent(t, t.Name(), "")
defer a.Shutdown()
testrpc.WaitForLeader(t, a.RPC, "dc1")
// Associate session with node and 2 health checks
body := bytes.NewBuffer(nil)
enc := json.NewEncoder(body)
raw := map[string]interface{}{
"Name": "my-cool-session",
"Node": a.Config.NodeName,
"Checks": []types.CheckID{},
"LockDelay": "20s",
}
enc.Encode(raw)
req, _ := http.NewRequest("PUT", "/v1/session/create", body)
resp := httptest.NewRecorder()
retry.Run(t, func(r *retry.R) {
obj, err := a.srv.SessionCreate(resp, req)
if err != nil {
r.Fatalf("err: %v", err)
}
want := structs.Session{
ID: obj.(sessionCreateResponse).ID,
Name: "my-cool-session",
Node: a.Config.NodeName,
Checks: []types.CheckID{},
LockDelay: 20 * time.Second,
Behavior: structs.SessionKeysRelease,
}
verifySession(r, a, want)
})
}
func TestFixupLockDelay(t *testing.T) {
t.Parallel()
inp := map[string]interface{}{
"lockdelay": float64(15),
}
if err := FixupLockDelay(inp); err != nil {
t.Fatalf("err: %v", err)
}
if inp["lockdelay"] != 15*time.Second {
t.Fatalf("bad: %v", inp)
}
inp = map[string]interface{}{
"lockDelay": float64(15 * time.Second),
}
if err := FixupLockDelay(inp); err != nil {
t.Fatalf("err: %v", err)
}
if inp["lockDelay"] != 15*time.Second {
t.Fatalf("bad: %v", inp)
}
inp = map[string]interface{}{
"LockDelay": "15s",
}
if err := FixupLockDelay(inp); err != nil {
t.Fatalf("err: %v", err)
}
if inp["LockDelay"] != 15*time.Second {
t.Fatalf("bad: %v", inp)
}
}
func makeTestSession(t *testing.T, srv *HTTPServer) string {
req, _ := http.NewRequest("PUT", "/v1/session/create", nil)
resp := httptest.NewRecorder()
obj, err := srv.SessionCreate(resp, req)
if err != nil {
t.Fatalf("err: %v", err)
}
sessResp := obj.(sessionCreateResponse)
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, _ := http.NewRequest("PUT", "/v1/session/create", body)
resp := httptest.NewRecorder()
obj, err := srv.SessionCreate(resp, req)
if err != nil {
t.Fatalf("err: %v", err)
}
sessResp := obj.(sessionCreateResponse)
return sessResp.ID
}
func makeTestSessionTTL(t *testing.T, srv *HTTPServer, ttl string) string {
// Create Session with TTL
body := bytes.NewBuffer(nil)
enc := json.NewEncoder(body)
raw := map[string]interface{}{
"TTL": ttl,
}
enc.Encode(raw)
req, _ := http.NewRequest("PUT", "/v1/session/create", body)
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) {
t.Parallel()
a := NewTestAgent(t, t.Name(), "")
defer a.Shutdown()
testrpc.WaitForTestAgent(t, a.RPC, "dc1")
id := makeTestSession(t, a.srv)
req, _ := http.NewRequest("PUT", "/v1/session/destroy/"+id, nil)
resp := httptest.NewRecorder()
obj, err := a.srv.SessionDestroy(resp, req)
if err != nil {
t.Fatalf("err: %v", err)
}
if resp := obj.(bool); !resp {
t.Fatalf("should work")
}
}
func TestSessionCustomTTL(t *testing.T) {
t.Parallel()
ttl := 250 * time.Millisecond
a := NewTestAgent(t, t.Name(), `
session_ttl_min = "250ms"
`)
defer a.Shutdown()
testrpc.WaitForTestAgent(t, a.RPC, "dc1")
retry.Run(t, func(r *retry.R) {
id := makeTestSessionTTL(t, a.srv, ttl.String())
req, _ := http.NewRequest("GET", "/v1/session/info/"+id, nil)
resp := httptest.NewRecorder()
obj, err := a.srv.SessionGet(resp, req)
if err != nil {
r.Fatalf("err: %v", err)
}
respObj, ok := obj.(structs.Sessions)
if !ok {
r.Fatalf("should work")
}
if len(respObj) != 1 {
r.Fatalf("bad: %v", respObj)
}
if respObj[0].TTL != ttl.String() {
r.Fatalf("Incorrect TTL: %s", respObj[0].TTL)
}
time.Sleep(ttl*structs.SessionTTLMultiplier + ttl)
req, _ = http.NewRequest("GET", "/v1/session/info/"+id, nil)
resp = httptest.NewRecorder()
obj, err = a.srv.SessionGet(resp, req)
if err != nil {
r.Fatalf("err: %v", err)
}
respObj, ok = obj.(structs.Sessions)
if len(respObj) != 0 {
r.Fatalf("session '%s' should have been destroyed", id)
}
})
}
func TestSessionTTLRenew(t *testing.T) {
// t.Parallel() // timing test. no parallel
ttl := 250 * time.Millisecond
a := NewTestAgent(t, t.Name(), `
session_ttl_min = "250ms"
`)
defer a.Shutdown()
testrpc.WaitForTestAgent(t, a.RPC, "dc1")
id := makeTestSessionTTL(t, a.srv, ttl.String())
req, _ := http.NewRequest("GET", "/v1/session/info/"+id, nil)
resp := httptest.NewRecorder()
obj, err := a.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)
}
if respObj[0].TTL != ttl.String() {
t.Fatalf("Incorrect TTL: %s", respObj[0].TTL)
}
// Sleep to consume some time before renew
time.Sleep(ttl * (structs.SessionTTLMultiplier / 3))
req, _ = http.NewRequest("PUT", "/v1/session/renew/"+id, nil)
resp = httptest.NewRecorder()
obj, err = a.srv.SessionRenew(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)
}
// Sleep for ttl * TTL Multiplier
time.Sleep(ttl * structs.SessionTTLMultiplier)
req, _ = http.NewRequest("GET", "/v1/session/info/"+id, nil)
resp = httptest.NewRecorder()
obj, err = a.srv.SessionGet(resp, req)
if err != nil {
t.Fatalf("err: %v", err)
}
respObj, ok = obj.(structs.Sessions)
if !ok {
t.Fatalf("session '%s' should have renewed", id)
}
if len(respObj) != 1 {
t.Fatalf("session '%s' should have renewed", id)
}
// now wait for timeout and expect session to get destroyed
time.Sleep(ttl * structs.SessionTTLMultiplier)
req, _ = http.NewRequest("GET", "/v1/session/info/"+id, nil)
resp = httptest.NewRecorder()
obj, err = a.srv.SessionGet(resp, req)
if err != nil {
t.Fatalf("err: %v", err)
}
respObj, ok = obj.(structs.Sessions)
if !ok {
t.Fatalf("session '%s' should have destroyed", id)
}
if len(respObj) != 0 {
t.Fatalf("session '%s' should have destroyed", id)
}
}
func TestSessionGet(t *testing.T) {
t.Parallel()
t.Run("", func(t *testing.T) {
a := NewTestAgent(t, t.Name(), "")
defer a.Shutdown()
testrpc.WaitForTestAgent(t, a.RPC, "dc1")
req, _ := http.NewRequest("GET", "/v1/session/info/adf4238a-882b-9ddc-4a9d-5b6758e4159e", nil)
resp := httptest.NewRecorder()
retry.Run(t, func(r *retry.R) {
obj, err := a.srv.SessionGet(resp, req)
if err != nil {
r.Fatalf("err: %v", err)
}
respObj, ok := obj.(structs.Sessions)
if !ok {
r.Fatalf("should work")
}
if respObj == nil || len(respObj) != 0 {
r.Fatalf("bad: %v", respObj)
}
})
})
t.Run("", func(t *testing.T) {
a := NewTestAgent(t, t.Name(), "")
defer a.Shutdown()
testrpc.WaitForTestAgent(t, a.RPC, "dc1")
id := makeTestSession(t, a.srv)
req, _ := http.NewRequest("GET", "/v1/session/info/"+id, nil)
resp := httptest.NewRecorder()
obj, err := a.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 TestSessionList(t *testing.T) {
t.Run("", func(t *testing.T) {
a := NewTestAgent(t, t.Name(), "")
defer a.Shutdown()
testrpc.WaitForTestAgent(t, a.RPC, "dc1")
req, _ := http.NewRequest("GET", "/v1/session/list", nil)
resp := httptest.NewRecorder()
obj, err := a.srv.SessionList(resp, req)
if err != nil {
t.Fatalf("err: %v", err)
}
respObj, ok := obj.(structs.Sessions)
if !ok {
t.Fatalf("should work")
}
if respObj == nil || len(respObj) != 0 {
t.Fatalf("bad: %v", respObj)
}
})
t.Run("", func(t *testing.T) {
a := NewTestAgent(t, t.Name(), "")
defer a.Shutdown()
testrpc.WaitForTestAgent(t, a.RPC, "dc1")
var ids []string
for i := 0; i < 10; i++ {
ids = append(ids, makeTestSession(t, a.srv))
}
req, _ := http.NewRequest("GET", "/v1/session/list", nil)
resp := httptest.NewRecorder()
obj, err := a.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 TestSessionsForNode(t *testing.T) {
t.Parallel()
t.Run("", func(t *testing.T) {
a := NewTestAgent(t, t.Name(), "")
defer a.Shutdown()
testrpc.WaitForTestAgent(t, a.RPC, "dc1")
req, _ := http.NewRequest("GET", "/v1/session/node/"+a.Config.NodeName, nil)
resp := httptest.NewRecorder()
obj, err := a.srv.SessionsForNode(resp, req)
if err != nil {
t.Fatalf("err: %v", err)
}
respObj, ok := obj.(structs.Sessions)
if !ok {
t.Fatalf("should work")
}
if respObj == nil || len(respObj) != 0 {
t.Fatalf("bad: %v", respObj)
}
})
t.Run("", func(t *testing.T) {
a := NewTestAgent(t, t.Name(), "")
defer a.Shutdown()
testrpc.WaitForTestAgent(t, a.RPC, "dc1")
var ids []string
for i := 0; i < 10; i++ {
ids = append(ids, makeTestSession(t, a.srv))
}
req, _ := http.NewRequest("GET", "/v1/session/node/"+a.Config.NodeName, nil)
resp := httptest.NewRecorder()
obj, err := a.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)
}
})
}
func TestSessionDeleteDestroy(t *testing.T) {
t.Parallel()
a := NewTestAgent(t, t.Name(), "")
defer a.Shutdown()
testrpc.WaitForTestAgent(t, a.RPC, "dc1")
id := makeTestSessionDelete(t, a.srv)
// now create a new key for the session and acquire it
buf := bytes.NewBuffer([]byte("test"))
req, _ := http.NewRequest("PUT", "/v1/kv/ephemeral?acquire="+id, buf)
resp := httptest.NewRecorder()
obj, err := a.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 created above
req, _ = http.NewRequest("PUT", "/v1/session/destroy/"+id, nil)
resp = httptest.NewRecorder()
obj, err = a.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, _ = a.srv.KVSEndpoint(resp, req)
res, found := obj.(structs.DirEntries)
if found || len(res) != 0 {
t.Fatalf("bad: %v found, should be nothing", res)
}
}