2013-12-23 21:52:10 +00:00
|
|
|
package agent
|
|
|
|
|
|
|
|
import (
|
2013-12-24 00:20:51 +00:00
|
|
|
"bytes"
|
|
|
|
"encoding/json"
|
2014-05-21 19:31:22 +00:00
|
|
|
"fmt"
|
2013-12-24 00:20:51 +00:00
|
|
|
"io"
|
|
|
|
"io/ioutil"
|
2014-02-05 22:47:42 +00:00
|
|
|
"net/http"
|
|
|
|
"net/http/httptest"
|
2014-04-17 18:38:14 +00:00
|
|
|
"os"
|
2014-04-23 19:57:06 +00:00
|
|
|
"path/filepath"
|
2014-04-21 20:11:05 +00:00
|
|
|
"strconv"
|
2013-12-23 21:52:10 +00:00
|
|
|
"testing"
|
2014-02-05 22:47:42 +00:00
|
|
|
"time"
|
2014-09-02 19:47:40 +00:00
|
|
|
|
|
|
|
"github.com/hashicorp/consul/consul/structs"
|
|
|
|
"github.com/hashicorp/consul/testutil"
|
2013-12-23 21:52:10 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
func makeHTTPServer(t *testing.T) (string, *HTTPServer) {
|
|
|
|
conf := nextConfig()
|
|
|
|
dir, agent := makeAgent(t, conf)
|
2014-04-23 19:57:06 +00:00
|
|
|
uiDir := filepath.Join(dir, "ui")
|
2014-04-23 20:10:18 +00:00
|
|
|
if err := os.Mkdir(uiDir, 755); err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
2014-09-02 19:47:40 +00:00
|
|
|
addr, _ := agent.config.ClientListener("", agent.config.Ports.HTTP)
|
2014-04-23 19:57:06 +00:00
|
|
|
server, err := NewHTTPServer(agent, uiDir, true, agent.logOutput, addr.String())
|
2013-12-23 21:52:10 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
return dir, server
|
|
|
|
}
|
2013-12-24 00:20:51 +00:00
|
|
|
|
|
|
|
func encodeReq(obj interface{}) io.ReadCloser {
|
|
|
|
buf := bytes.NewBuffer(nil)
|
|
|
|
enc := json.NewEncoder(buf)
|
|
|
|
enc.Encode(obj)
|
|
|
|
return ioutil.NopCloser(buf)
|
|
|
|
}
|
2014-02-05 22:47:42 +00:00
|
|
|
|
|
|
|
func TestSetIndex(t *testing.T) {
|
|
|
|
resp := httptest.NewRecorder()
|
|
|
|
setIndex(resp, 1000)
|
|
|
|
header := resp.Header().Get("X-Consul-Index")
|
|
|
|
if header != "1000" {
|
|
|
|
t.Fatalf("Bad: %v", header)
|
|
|
|
}
|
2014-10-14 00:53:54 +00:00
|
|
|
setIndex(resp, 2000)
|
|
|
|
if v := resp.Header()["X-Consul-Index"]; len(v) != 1 {
|
|
|
|
t.Fatalf("bad: %#v", v)
|
|
|
|
}
|
2014-02-05 22:47:42 +00:00
|
|
|
}
|
|
|
|
|
2014-04-21 20:19:18 +00:00
|
|
|
func TestSetKnownLeader(t *testing.T) {
|
|
|
|
resp := httptest.NewRecorder()
|
|
|
|
setKnownLeader(resp, true)
|
|
|
|
header := resp.Header().Get("X-Consul-KnownLeader")
|
|
|
|
if header != "true" {
|
|
|
|
t.Fatalf("Bad: %v", header)
|
|
|
|
}
|
|
|
|
resp = httptest.NewRecorder()
|
|
|
|
setKnownLeader(resp, false)
|
|
|
|
header = resp.Header().Get("X-Consul-KnownLeader")
|
|
|
|
if header != "false" {
|
|
|
|
t.Fatalf("Bad: %v", header)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestSetLastContact(t *testing.T) {
|
|
|
|
resp := httptest.NewRecorder()
|
|
|
|
setLastContact(resp, 123456*time.Microsecond)
|
|
|
|
header := resp.Header().Get("X-Consul-LastContact")
|
|
|
|
if header != "123" {
|
|
|
|
t.Fatalf("Bad: %v", header)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestSetMeta(t *testing.T) {
|
|
|
|
meta := structs.QueryMeta{
|
|
|
|
Index: 1000,
|
|
|
|
KnownLeader: true,
|
|
|
|
LastContact: 123456 * time.Microsecond,
|
|
|
|
}
|
|
|
|
resp := httptest.NewRecorder()
|
|
|
|
setMeta(resp, &meta)
|
|
|
|
header := resp.Header().Get("X-Consul-Index")
|
|
|
|
if header != "1000" {
|
|
|
|
t.Fatalf("Bad: %v", header)
|
|
|
|
}
|
|
|
|
header = resp.Header().Get("X-Consul-KnownLeader")
|
|
|
|
if header != "true" {
|
|
|
|
t.Fatalf("Bad: %v", header)
|
|
|
|
}
|
|
|
|
header = resp.Header().Get("X-Consul-LastContact")
|
|
|
|
if header != "123" {
|
|
|
|
t.Fatalf("Bad: %v", header)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-04-17 18:38:14 +00:00
|
|
|
func TestContentTypeIsJSON(t *testing.T) {
|
|
|
|
dir, srv := makeHTTPServer(t)
|
|
|
|
|
|
|
|
defer os.RemoveAll(dir)
|
|
|
|
defer srv.Shutdown()
|
|
|
|
defer srv.agent.Shutdown()
|
|
|
|
|
|
|
|
resp := httptest.NewRecorder()
|
|
|
|
|
|
|
|
handler := func(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
|
|
|
// stub out a DirEntry so that it will be encoded as JSON
|
|
|
|
return &structs.DirEntry{Key: "key"}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
req, _ := http.NewRequest("GET", "/v1/kv/key", nil)
|
|
|
|
srv.wrap(handler)(resp, req)
|
|
|
|
|
|
|
|
contentType := resp.Header().Get("Content-Type")
|
|
|
|
|
|
|
|
if contentType != "application/json" {
|
|
|
|
t.Fatalf("Content-Type header was not 'application/json'")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-02-05 22:47:42 +00:00
|
|
|
func TestParseWait(t *testing.T) {
|
|
|
|
resp := httptest.NewRecorder()
|
2014-04-21 20:11:05 +00:00
|
|
|
var b structs.QueryOptions
|
2014-02-05 22:47:42 +00:00
|
|
|
|
|
|
|
req, err := http.NewRequest("GET",
|
|
|
|
"/v1/catalog/nodes?wait=60s&index=1000", nil)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if d := parseWait(resp, req, &b); d {
|
|
|
|
t.Fatalf("unexpected done")
|
|
|
|
}
|
|
|
|
|
|
|
|
if b.MinQueryIndex != 1000 {
|
|
|
|
t.Fatalf("Bad: %v", b)
|
|
|
|
}
|
|
|
|
if b.MaxQueryTime != 60*time.Second {
|
|
|
|
t.Fatalf("Bad: %v", b)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestParseWait_InvalidTime(t *testing.T) {
|
|
|
|
resp := httptest.NewRecorder()
|
2014-04-21 20:11:05 +00:00
|
|
|
var b structs.QueryOptions
|
2014-02-05 22:47:42 +00:00
|
|
|
|
|
|
|
req, err := http.NewRequest("GET",
|
|
|
|
"/v1/catalog/nodes?wait=60foo&index=1000", nil)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if d := parseWait(resp, req, &b); !d {
|
|
|
|
t.Fatalf("expected done")
|
|
|
|
}
|
|
|
|
|
|
|
|
if resp.Code != 400 {
|
|
|
|
t.Fatalf("bad code: %v", resp.Code)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestParseWait_InvalidIndex(t *testing.T) {
|
|
|
|
resp := httptest.NewRecorder()
|
2014-04-21 20:11:05 +00:00
|
|
|
var b structs.QueryOptions
|
2014-02-05 22:47:42 +00:00
|
|
|
|
|
|
|
req, err := http.NewRequest("GET",
|
|
|
|
"/v1/catalog/nodes?wait=60s&index=foo", nil)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if d := parseWait(resp, req, &b); !d {
|
|
|
|
t.Fatalf("expected done")
|
|
|
|
}
|
|
|
|
|
|
|
|
if resp.Code != 400 {
|
|
|
|
t.Fatalf("bad code: %v", resp.Code)
|
|
|
|
}
|
|
|
|
}
|
2014-04-21 20:11:05 +00:00
|
|
|
|
2014-04-21 20:19:18 +00:00
|
|
|
func TestParseConsistency(t *testing.T) {
|
|
|
|
resp := httptest.NewRecorder()
|
|
|
|
var b structs.QueryOptions
|
|
|
|
|
|
|
|
req, err := http.NewRequest("GET",
|
|
|
|
"/v1/catalog/nodes?stale", nil)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if d := parseConsistency(resp, req, &b); d {
|
|
|
|
t.Fatalf("unexpected done")
|
|
|
|
}
|
|
|
|
|
|
|
|
if !b.AllowStale {
|
|
|
|
t.Fatalf("Bad: %v", b)
|
|
|
|
}
|
|
|
|
if b.RequireConsistent {
|
|
|
|
t.Fatalf("Bad: %v", b)
|
|
|
|
}
|
|
|
|
|
|
|
|
b = structs.QueryOptions{}
|
|
|
|
req, err = http.NewRequest("GET",
|
|
|
|
"/v1/catalog/nodes?consistent", nil)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if d := parseConsistency(resp, req, &b); d {
|
|
|
|
t.Fatalf("unexpected done")
|
|
|
|
}
|
|
|
|
|
|
|
|
if b.AllowStale {
|
|
|
|
t.Fatalf("Bad: %v", b)
|
|
|
|
}
|
|
|
|
if !b.RequireConsistent {
|
|
|
|
t.Fatalf("Bad: %v", b)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestParseConsistency_Invalid(t *testing.T) {
|
|
|
|
resp := httptest.NewRecorder()
|
|
|
|
var b structs.QueryOptions
|
|
|
|
|
|
|
|
req, err := http.NewRequest("GET",
|
|
|
|
"/v1/catalog/nodes?stale&consistent", nil)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if d := parseConsistency(resp, req, &b); !d {
|
|
|
|
t.Fatalf("expected done")
|
|
|
|
}
|
|
|
|
|
|
|
|
if resp.Code != 400 {
|
|
|
|
t.Fatalf("bad code: %v", resp.Code)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-04-21 20:11:05 +00:00
|
|
|
// assertIndex tests that X-Consul-Index is set and non-zero
|
|
|
|
func assertIndex(t *testing.T, resp *httptest.ResponseRecorder) {
|
|
|
|
header := resp.Header().Get("X-Consul-Index")
|
|
|
|
if header == "" || header == "0" {
|
|
|
|
t.Fatalf("Bad: %v", header)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-05-21 19:31:22 +00:00
|
|
|
// checkIndex is like assertIndex but returns an error
|
|
|
|
func checkIndex(resp *httptest.ResponseRecorder) error {
|
|
|
|
header := resp.Header().Get("X-Consul-Index")
|
|
|
|
if header == "" || header == "0" {
|
|
|
|
return fmt.Errorf("Bad: %v", header)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2014-04-21 20:11:05 +00:00
|
|
|
// getIndex parses X-Consul-Index
|
|
|
|
func getIndex(t *testing.T, resp *httptest.ResponseRecorder) uint64 {
|
|
|
|
header := resp.Header().Get("X-Consul-Index")
|
|
|
|
if header == "" {
|
|
|
|
t.Fatalf("Bad: %v", header)
|
|
|
|
}
|
|
|
|
val, err := strconv.Atoi(header)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Bad: %v", header)
|
|
|
|
}
|
|
|
|
return uint64(val)
|
|
|
|
}
|
2014-05-19 18:29:50 +00:00
|
|
|
|
|
|
|
func httpTest(t *testing.T, f func(srv *HTTPServer)) {
|
|
|
|
dir, srv := makeHTTPServer(t)
|
|
|
|
defer os.RemoveAll(dir)
|
|
|
|
defer srv.Shutdown()
|
|
|
|
defer srv.agent.Shutdown()
|
|
|
|
testutil.WaitForLeader(t, srv.agent.RPC, "dc1")
|
|
|
|
f(srv)
|
|
|
|
}
|