2013-12-23 21:52:10 +00:00
|
|
|
package agent
|
|
|
|
|
|
|
|
import (
|
2013-12-24 00:20:51 +00:00
|
|
|
"bytes"
|
2017-02-11 02:29:42 +00:00
|
|
|
"context"
|
2013-12-24 00:20:51 +00:00
|
|
|
"encoding/json"
|
2014-05-21 19:31:22 +00:00
|
|
|
"fmt"
|
2013-12-24 00:20:51 +00:00
|
|
|
"io"
|
|
|
|
"io/ioutil"
|
2015-01-16 17:58:37 +00:00
|
|
|
"net"
|
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"
|
2015-01-16 17:58:37 +00:00
|
|
|
"runtime"
|
2014-04-21 20:11:05 +00:00
|
|
|
"strconv"
|
2015-04-12 18:17:31 +00:00
|
|
|
"strings"
|
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
|
|
|
|
pkg refactor
command/agent/* -> agent/*
command/consul/* -> agent/consul/*
command/agent/command{,_test}.go -> command/agent{,_test}.go
command/base/command.go -> command/base.go
command/base/* -> command/*
commands.go -> command/commands.go
The script which did the refactor is:
(
cd $GOPATH/src/github.com/hashicorp/consul
git mv command/agent/command.go command/agent.go
git mv command/agent/command_test.go command/agent_test.go
git mv command/agent/flag_slice_value{,_test}.go command/
git mv command/agent .
git mv command/base/command.go command/base.go
git mv command/base/config_util{,_test}.go command/
git mv commands.go command/
git mv consul agent
rmdir command/base/
gsed -i -e 's|package agent|package command|' command/agent{,_test}.go
gsed -i -e 's|package agent|package command|' command/flag_slice_value{,_test}.go
gsed -i -e 's|package base|package command|' command/base.go command/config_util{,_test}.go
gsed -i -e 's|package main|package command|' command/commands.go
gsed -i -e 's|base.Command|BaseCommand|' command/commands.go
gsed -i -e 's|agent.Command|AgentCommand|' command/commands.go
gsed -i -e 's|\tCommand:|\tBaseCommand:|' command/commands.go
gsed -i -e 's|base\.||' command/commands.go
gsed -i -e 's|command\.||' command/commands.go
gsed -i -e 's|command|c|' main.go
gsed -i -e 's|range Commands|range command.Commands|' main.go
gsed -i -e 's|Commands: Commands|Commands: command.Commands|' main.go
gsed -i -e 's|base\.BoolValue|BoolValue|' command/operator_autopilot_set.go
gsed -i -e 's|base\.DurationValue|DurationValue|' command/operator_autopilot_set.go
gsed -i -e 's|base\.StringValue|StringValue|' command/operator_autopilot_set.go
gsed -i -e 's|base\.UintValue|UintValue|' command/operator_autopilot_set.go
gsed -i -e 's|\bCommand\b|BaseCommand|' command/base.go
gsed -i -e 's|BaseCommand Options|Command Options|' command/base.go
gsed -i -e 's|base.Command|BaseCommand|' command/*.go
gsed -i -e 's|c\.Command|c.BaseCommand|g' command/*.go
gsed -i -e 's|\tCommand:|\tBaseCommand:|' command/*_test.go
gsed -i -e 's|base\.||' command/*_test.go
gsed -i -e 's|\bCommand\b|AgentCommand|' command/agent{,_test}.go
gsed -i -e 's|cmd.AgentCommand|cmd.BaseCommand|' command/agent.go
gsed -i -e 's|cli.AgentCommand = new(Command)|cli.Command = new(AgentCommand)|' command/agent_test.go
gsed -i -e 's|exec.AgentCommand|exec.Command|' command/agent_test.go
gsed -i -e 's|exec.BaseCommand|exec.Command|' command/agent_test.go
gsed -i -e 's|NewTestAgent|agent.NewTestAgent|' command/agent_test.go
gsed -i -e 's|= TestConfig|= agent.TestConfig|' command/agent_test.go
gsed -i -e 's|: RetryJoin|: agent.RetryJoin|' command/agent_test.go
gsed -i -e 's|\.\./\.\./|../|' command/config_util_test.go
gsed -i -e 's|\bverifyUniqueListeners|VerifyUniqueListeners|' agent/config{,_test}.go command/agent.go
gsed -i -e 's|\bserfLANKeyring\b|SerfLANKeyring|g' agent/{agent,keyring,testagent}.go command/agent.go
gsed -i -e 's|\bserfWANKeyring\b|SerfWANKeyring|g' agent/{agent,keyring,testagent}.go command/agent.go
gsed -i -e 's|\bNewAgent\b|agent.New|g' command/agent{,_test}.go
gsed -i -e 's|\bNewAgent|New|' agent/{acl_test,agent,testagent}.go
gsed -i -e 's|\bAgent\b|agent.&|g' command/agent{,_test}.go
gsed -i -e 's|\bBool\b|agent.&|g' command/agent{,_test}.go
gsed -i -e 's|\bConfig\b|agent.&|g' command/agent{,_test}.go
gsed -i -e 's|\bDefaultConfig\b|agent.&|g' command/agent{,_test}.go
gsed -i -e 's|\bDevConfig\b|agent.&|g' command/agent{,_test}.go
gsed -i -e 's|\bMergeConfig\b|agent.&|g' command/agent{,_test}.go
gsed -i -e 's|\bReadConfigPaths\b|agent.&|g' command/agent{,_test}.go
gsed -i -e 's|\bParseMetaPair\b|agent.&|g' command/agent{,_test}.go
gsed -i -e 's|\bSerfLANKeyring\b|agent.&|g' command/agent{,_test}.go
gsed -i -e 's|\bSerfWANKeyring\b|agent.&|g' command/agent{,_test}.go
gsed -i -e 's|circonus\.agent|circonus|g' command/agent{,_test}.go
gsed -i -e 's|logger\.agent|logger|g' command/agent{,_test}.go
gsed -i -e 's|metrics\.agent|metrics|g' command/agent{,_test}.go
gsed -i -e 's|// agent.Agent|// agent|' command/agent{,_test}.go
gsed -i -e 's|a\.agent\.Config|a.Config|' command/agent{,_test}.go
gsed -i -e 's|agent\.AppendSliceValue|AppendSliceValue|' command/{configtest,validate}.go
gsed -i -e 's|consul/consul|agent/consul|' GNUmakefile
gsed -i -e 's|\.\./test|../../test|' agent/consul/server_test.go
# fix imports
f=$(grep -rl 'github.com/hashicorp/consul/command/agent' * | grep '\.go')
gsed -i -e 's|github.com/hashicorp/consul/command/agent|github.com/hashicorp/consul/agent|' $f
goimports -w $f
f=$(grep -rl 'github.com/hashicorp/consul/consul' * | grep '\.go')
gsed -i -e 's|github.com/hashicorp/consul/consul|github.com/hashicorp/consul/agent/consul|' $f
goimports -w $f
goimports -w command/*.go main.go
)
2017-06-09 22:28:28 +00:00
|
|
|
"github.com/hashicorp/consul/agent/consul/structs"
|
2017-05-12 13:41:13 +00:00
|
|
|
"github.com/hashicorp/consul/testutil"
|
2015-10-24 00:14:35 +00:00
|
|
|
"github.com/hashicorp/go-cleanhttp"
|
2013-12-23 21:52:10 +00:00
|
|
|
)
|
|
|
|
|
2015-01-16 17:58:37 +00:00
|
|
|
func TestHTTPServer_UnixSocket(t *testing.T) {
|
2017-05-21 07:54:40 +00:00
|
|
|
t.Parallel()
|
2015-01-16 17:58:37 +00:00
|
|
|
if runtime.GOOS == "windows" {
|
|
|
|
t.SkipNow()
|
|
|
|
}
|
|
|
|
|
2017-05-12 13:41:13 +00:00
|
|
|
tempDir := testutil.TempDir(t, "consul")
|
2015-01-16 17:58:37 +00:00
|
|
|
defer os.RemoveAll(tempDir)
|
2017-05-12 13:41:13 +00:00
|
|
|
socket := filepath.Join(tempDir, "test.sock")
|
2015-01-16 17:58:37 +00:00
|
|
|
|
2017-05-22 11:03:59 +00:00
|
|
|
cfg := TestConfig()
|
|
|
|
cfg.Addresses.HTTP = "unix://" + socket
|
2015-01-20 22:32:15 +00:00
|
|
|
|
2017-05-21 07:11:09 +00:00
|
|
|
// Only testing mode, since uid/gid might not be settable
|
|
|
|
// from test environment.
|
2017-05-22 11:03:59 +00:00
|
|
|
cfg.UnixSockets = UnixSocketConfig{}
|
|
|
|
cfg.UnixSockets.Perms = "0777"
|
|
|
|
a := NewTestAgent(t.Name(), cfg)
|
2017-05-21 07:11:09 +00:00
|
|
|
defer a.Shutdown()
|
2015-01-16 17:58:37 +00:00
|
|
|
|
|
|
|
// Ensure the socket was created
|
|
|
|
if _, err := os.Stat(socket); err != nil {
|
|
|
|
t.Fatalf("err: %s", err)
|
|
|
|
}
|
|
|
|
|
2015-01-20 22:32:15 +00:00
|
|
|
// Ensure the mode was set properly
|
|
|
|
fi, err := os.Stat(socket)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %s", err)
|
|
|
|
}
|
|
|
|
if fi.Mode().String() != "Srwxrwxrwx" {
|
|
|
|
t.Fatalf("bad permissions: %s", fi.Mode())
|
|
|
|
}
|
|
|
|
|
2015-01-16 17:58:37 +00:00
|
|
|
// Ensure we can get a response from the socket.
|
2017-05-21 07:11:09 +00:00
|
|
|
path := socketPath(a.Config.Addresses.HTTP)
|
2015-10-22 14:47:50 +00:00
|
|
|
trans := cleanhttp.DefaultTransport()
|
2017-02-11 02:29:42 +00:00
|
|
|
trans.DialContext = func(_ context.Context, _, _ string) (net.Conn, error) {
|
2015-10-22 14:47:50 +00:00
|
|
|
return net.Dial("unix", path)
|
|
|
|
}
|
2015-01-16 17:58:37 +00:00
|
|
|
client := &http.Client{
|
2015-10-22 14:47:50 +00:00
|
|
|
Transport: trans,
|
2015-01-16 17:58:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// This URL doesn't look like it makes sense, but the scheme (http://) and
|
|
|
|
// the host (127.0.0.1) are required by the HTTP client library. In reality
|
|
|
|
// this will just use the custom dialer and talk to the socket.
|
|
|
|
resp, err := client.Get("http://127.0.0.1/v1/agent/self")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %s", err)
|
|
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
|
|
|
|
|
|
if body, err := ioutil.ReadAll(resp.Body); err != nil || len(body) == 0 {
|
|
|
|
t.Fatalf("bad: %s %v", body, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-01-16 20:39:15 +00:00
|
|
|
func TestHTTPServer_UnixSocket_FileExists(t *testing.T) {
|
2017-05-21 07:54:40 +00:00
|
|
|
t.Parallel()
|
2015-01-16 18:37:13 +00:00
|
|
|
if runtime.GOOS == "windows" {
|
|
|
|
t.SkipNow()
|
|
|
|
}
|
|
|
|
|
2017-05-12 13:41:13 +00:00
|
|
|
tempDir := testutil.TempDir(t, "consul")
|
2015-01-16 18:37:13 +00:00
|
|
|
defer os.RemoveAll(tempDir)
|
2017-05-12 13:41:13 +00:00
|
|
|
socket := filepath.Join(tempDir, "test.sock")
|
2015-01-16 18:37:13 +00:00
|
|
|
|
|
|
|
// Create a regular file at the socket path
|
|
|
|
if err := ioutil.WriteFile(socket, []byte("hello world"), 0644); err != nil {
|
|
|
|
t.Fatalf("err: %s", err)
|
|
|
|
}
|
|
|
|
fi, err := os.Stat(socket)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %s", err)
|
|
|
|
}
|
|
|
|
if !fi.Mode().IsRegular() {
|
|
|
|
t.Fatalf("not a regular file: %s", socket)
|
|
|
|
}
|
|
|
|
|
2017-05-22 11:03:59 +00:00
|
|
|
cfg := TestConfig()
|
|
|
|
cfg.Addresses.HTTP = "unix://" + socket
|
|
|
|
a := NewTestAgent(t.Name(), cfg)
|
2017-05-21 07:11:09 +00:00
|
|
|
defer a.Shutdown()
|
2015-01-16 20:39:15 +00:00
|
|
|
|
2015-01-20 22:13:36 +00:00
|
|
|
// Ensure the file was replaced by the socket
|
|
|
|
fi, err = os.Stat(socket)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %s", err)
|
|
|
|
}
|
|
|
|
if fi.Mode()&os.ModeSocket == 0 {
|
|
|
|
t.Fatalf("expected socket to replace file")
|
2015-01-16 18:37:13 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-02-05 22:47:42 +00:00
|
|
|
func TestSetIndex(t *testing.T) {
|
2017-05-21 07:54:40 +00:00
|
|
|
t.Parallel()
|
2014-02-05 22:47:42 +00:00
|
|
|
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) {
|
2017-05-21 07:54:40 +00:00
|
|
|
t.Parallel()
|
2014-04-21 20:19:18 +00:00
|
|
|
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) {
|
2017-05-21 07:54:40 +00:00
|
|
|
t.Parallel()
|
2017-06-01 11:53:27 +00:00
|
|
|
tests := []struct {
|
|
|
|
desc string
|
|
|
|
d time.Duration
|
|
|
|
h string
|
|
|
|
}{
|
|
|
|
{"neg", -1, "0"},
|
|
|
|
{"zero", 0, "0"},
|
|
|
|
{"pos", 123 * time.Millisecond, "123"},
|
|
|
|
{"pos ms only", 123456 * time.Microsecond, "123"},
|
|
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
|
|
t.Run(tt.desc, func(t *testing.T) {
|
|
|
|
resp := httptest.NewRecorder()
|
|
|
|
setLastContact(resp, tt.d)
|
|
|
|
header := resp.Header().Get("X-Consul-LastContact")
|
|
|
|
if got, want := header, tt.h; got != want {
|
|
|
|
t.Fatalf("got X-Consul-LastContact header %q want %q", got, want)
|
|
|
|
}
|
|
|
|
})
|
2014-04-21 20:19:18 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestSetMeta(t *testing.T) {
|
2017-05-21 07:54:40 +00:00
|
|
|
t.Parallel()
|
2014-04-21 20:19:18 +00:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-08-16 18:31:41 +00:00
|
|
|
func TestHTTPAPI_TranslateAddrHeader(t *testing.T) {
|
2017-05-21 07:54:40 +00:00
|
|
|
t.Parallel()
|
2016-08-16 18:31:41 +00:00
|
|
|
// Header should not be present if address translation is off.
|
|
|
|
{
|
2017-05-21 07:11:09 +00:00
|
|
|
a := NewTestAgent(t.Name(), nil)
|
|
|
|
defer a.Shutdown()
|
2016-08-16 18:31:41 +00:00
|
|
|
|
|
|
|
resp := httptest.NewRecorder()
|
|
|
|
handler := func(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
req, _ := http.NewRequest("GET", "/v1/agent/self", nil)
|
2017-05-21 07:11:09 +00:00
|
|
|
a.srv.wrap(handler)(resp, req)
|
2016-08-16 18:31:41 +00:00
|
|
|
|
|
|
|
translate := resp.Header().Get("X-Consul-Translate-Addresses")
|
|
|
|
if translate != "" {
|
|
|
|
t.Fatalf("bad: expected %q, got %q", "", translate)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Header should be set to true if it's turned on.
|
|
|
|
{
|
2017-05-22 11:03:59 +00:00
|
|
|
cfg := TestConfig()
|
|
|
|
cfg.TranslateWanAddrs = true
|
|
|
|
a := NewTestAgent(t.Name(), cfg)
|
2017-05-21 07:11:09 +00:00
|
|
|
defer a.Shutdown()
|
2016-08-16 18:31:41 +00:00
|
|
|
|
|
|
|
resp := httptest.NewRecorder()
|
|
|
|
handler := func(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
req, _ := http.NewRequest("GET", "/v1/agent/self", nil)
|
2017-05-21 07:11:09 +00:00
|
|
|
a.srv.wrap(handler)(resp, req)
|
2016-08-16 18:31:41 +00:00
|
|
|
|
|
|
|
translate := resp.Header().Get("X-Consul-Translate-Addresses")
|
|
|
|
if translate != "true" {
|
|
|
|
t.Fatalf("bad: expected %q, got %q", "true", translate)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-12-28 04:53:19 +00:00
|
|
|
func TestHTTPAPIResponseHeaders(t *testing.T) {
|
2017-05-21 07:54:40 +00:00
|
|
|
t.Parallel()
|
2017-05-22 11:03:59 +00:00
|
|
|
cfg := TestConfig()
|
2017-06-16 11:58:19 +00:00
|
|
|
cfg.HTTPConfig.ResponseHeaders = map[string]string{
|
2014-12-28 04:53:19 +00:00
|
|
|
"Access-Control-Allow-Origin": "*",
|
|
|
|
"X-XSS-Protection": "1; mode=block",
|
|
|
|
}
|
2017-05-22 11:03:59 +00:00
|
|
|
a := NewTestAgent(t.Name(), cfg)
|
2017-05-21 07:11:09 +00:00
|
|
|
defer a.Shutdown()
|
2014-12-28 04:53:19 +00:00
|
|
|
|
|
|
|
resp := httptest.NewRecorder()
|
|
|
|
handler := func(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
req, _ := http.NewRequest("GET", "/v1/agent/self", nil)
|
2017-05-21 07:11:09 +00:00
|
|
|
a.srv.wrap(handler)(resp, req)
|
2014-12-28 04:53:19 +00:00
|
|
|
|
|
|
|
origin := resp.Header().Get("Access-Control-Allow-Origin")
|
|
|
|
if origin != "*" {
|
|
|
|
t.Fatalf("bad Access-Control-Allow-Origin: expected %q, got %q", "*", origin)
|
|
|
|
}
|
|
|
|
|
|
|
|
xss := resp.Header().Get("X-XSS-Protection")
|
|
|
|
if xss != "1; mode=block" {
|
|
|
|
t.Fatalf("bad X-XSS-Protection header: expected %q, got %q", "1; mode=block", xss)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-04-17 18:38:14 +00:00
|
|
|
func TestContentTypeIsJSON(t *testing.T) {
|
2017-05-21 07:54:40 +00:00
|
|
|
t.Parallel()
|
2017-05-21 07:11:09 +00:00
|
|
|
a := NewTestAgent(t.Name(), nil)
|
|
|
|
defer a.Shutdown()
|
2014-04-17 18:38:14 +00:00
|
|
|
|
|
|
|
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)
|
2017-05-21 07:11:09 +00:00
|
|
|
a.srv.wrap(handler)(resp, req)
|
2014-04-17 18:38:14 +00:00
|
|
|
|
|
|
|
contentType := resp.Header().Get("Content-Type")
|
|
|
|
|
|
|
|
if contentType != "application/json" {
|
|
|
|
t.Fatalf("Content-Type header was not 'application/json'")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-04-12 18:17:31 +00:00
|
|
|
func TestHTTP_wrap_obfuscateLog(t *testing.T) {
|
2017-05-21 07:54:40 +00:00
|
|
|
t.Parallel()
|
2017-05-21 07:11:09 +00:00
|
|
|
buf := new(bytes.Buffer)
|
|
|
|
a := &TestAgent{Name: t.Name(), LogOutput: buf}
|
|
|
|
a.Start()
|
|
|
|
defer a.Shutdown()
|
2015-04-12 18:17:31 +00:00
|
|
|
|
|
|
|
resp := httptest.NewRecorder()
|
|
|
|
req, _ := http.NewRequest("GET", "/some/url?token=secret1&token=secret2", nil)
|
|
|
|
handler := func(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
|
|
|
return nil, nil
|
|
|
|
}
|
2017-05-21 07:11:09 +00:00
|
|
|
a.srv.wrap(handler)(resp, req)
|
2015-04-12 18:17:31 +00:00
|
|
|
|
|
|
|
// Make sure no tokens from the URL show up in the log
|
|
|
|
if strings.Contains(buf.String(), "secret") {
|
|
|
|
t.Fatalf("bad: %s", buf.String())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-01-01 22:27:10 +00:00
|
|
|
func TestPrettyPrint(t *testing.T) {
|
2017-05-21 07:54:40 +00:00
|
|
|
t.Parallel()
|
2015-01-02 08:14:44 +00:00
|
|
|
testPrettyPrint("pretty=1", t)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestPrettyPrintBare(t *testing.T) {
|
2017-05-21 07:54:40 +00:00
|
|
|
t.Parallel()
|
2015-01-02 08:14:44 +00:00
|
|
|
testPrettyPrint("pretty", t)
|
|
|
|
}
|
|
|
|
|
|
|
|
func testPrettyPrint(pretty string, t *testing.T) {
|
2017-05-21 07:11:09 +00:00
|
|
|
a := NewTestAgent(t.Name(), nil)
|
|
|
|
defer a.Shutdown()
|
2015-01-01 22:27:10 +00:00
|
|
|
|
|
|
|
r := &structs.DirEntry{Key: "key"}
|
|
|
|
|
|
|
|
resp := httptest.NewRecorder()
|
|
|
|
handler := func(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
|
|
|
|
return r, nil
|
|
|
|
}
|
|
|
|
|
2015-01-02 08:14:44 +00:00
|
|
|
urlStr := "/v1/kv/key?" + pretty
|
|
|
|
req, _ := http.NewRequest("GET", urlStr, nil)
|
2017-05-21 07:11:09 +00:00
|
|
|
a.srv.wrap(handler)(resp, req)
|
2015-01-01 22:27:10 +00:00
|
|
|
|
|
|
|
expected, _ := json.MarshalIndent(r, "", " ")
|
2016-05-10 21:37:05 +00:00
|
|
|
expected = append(expected, "\n"...)
|
2015-01-01 22:27:10 +00:00
|
|
|
actual, err := ioutil.ReadAll(resp.Body)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("err: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if !bytes.Equal(expected, actual) {
|
|
|
|
t.Fatalf("bad: %q", string(actual))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-06-30 21:25:40 +00:00
|
|
|
func TestParseSource(t *testing.T) {
|
2017-05-21 07:54:40 +00:00
|
|
|
t.Parallel()
|
2017-05-21 07:11:09 +00:00
|
|
|
a := NewTestAgent(t.Name(), nil)
|
|
|
|
defer a.Shutdown()
|
2015-06-30 21:25:40 +00:00
|
|
|
|
|
|
|
// Default is agent's DC and no node (since the user didn't care, then
|
|
|
|
// just give them the cheapest possible query).
|
2017-05-09 11:38:05 +00:00
|
|
|
req, _ := http.NewRequest("GET", "/v1/catalog/nodes", nil)
|
2015-06-30 21:25:40 +00:00
|
|
|
source := structs.QuerySource{}
|
2017-05-21 07:11:09 +00:00
|
|
|
a.srv.parseSource(req, &source)
|
2015-06-30 21:25:40 +00:00
|
|
|
if source.Datacenter != "dc1" || source.Node != "" {
|
|
|
|
t.Fatalf("bad: %v", source)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Adding the source parameter should set that node.
|
2017-05-09 11:38:05 +00:00
|
|
|
req, _ = http.NewRequest("GET", "/v1/catalog/nodes?near=bob", nil)
|
2015-06-30 21:25:40 +00:00
|
|
|
source = structs.QuerySource{}
|
2017-05-21 07:11:09 +00:00
|
|
|
a.srv.parseSource(req, &source)
|
2015-06-30 21:25:40 +00:00
|
|
|
if source.Datacenter != "dc1" || source.Node != "bob" {
|
|
|
|
t.Fatalf("bad: %v", source)
|
|
|
|
}
|
|
|
|
|
|
|
|
// We should follow whatever dc parameter was given so that the node is
|
|
|
|
// looked up correctly on the receiving end.
|
2017-05-09 11:38:05 +00:00
|
|
|
req, _ = http.NewRequest("GET", "/v1/catalog/nodes?near=bob&dc=foo", nil)
|
2015-06-30 21:25:40 +00:00
|
|
|
source = structs.QuerySource{}
|
2017-05-21 07:11:09 +00:00
|
|
|
a.srv.parseSource(req, &source)
|
2015-06-30 21:25:40 +00:00
|
|
|
if source.Datacenter != "foo" || source.Node != "bob" {
|
|
|
|
t.Fatalf("bad: %v", source)
|
|
|
|
}
|
2015-07-24 21:30:53 +00:00
|
|
|
|
2015-07-28 17:39:37 +00:00
|
|
|
// The magic "_agent" node name will use the agent's local node name.
|
2017-05-09 11:38:05 +00:00
|
|
|
req, _ = http.NewRequest("GET", "/v1/catalog/nodes?near=_agent", nil)
|
2015-07-24 21:30:53 +00:00
|
|
|
source = structs.QuerySource{}
|
2017-05-21 07:11:09 +00:00
|
|
|
a.srv.parseSource(req, &source)
|
|
|
|
if source.Datacenter != "dc1" || source.Node != a.Config.NodeName {
|
2015-07-24 21:30:53 +00:00
|
|
|
t.Fatalf("bad: %v", source)
|
|
|
|
}
|
2015-06-30 21:25:40 +00:00
|
|
|
}
|
|
|
|
|
2014-02-05 22:47:42 +00:00
|
|
|
func TestParseWait(t *testing.T) {
|
2017-05-21 07:54:40 +00:00
|
|
|
t.Parallel()
|
2014-02-05 22:47:42 +00:00
|
|
|
resp := httptest.NewRecorder()
|
2014-04-21 20:11:05 +00:00
|
|
|
var b structs.QueryOptions
|
2014-02-05 22:47:42 +00:00
|
|
|
|
2017-05-09 11:38:05 +00:00
|
|
|
req, _ := http.NewRequest("GET", "/v1/catalog/nodes?wait=60s&index=1000", nil)
|
2014-02-05 22:47:42 +00:00
|
|
|
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) {
|
2017-05-21 07:54:40 +00:00
|
|
|
t.Parallel()
|
2014-02-05 22:47:42 +00:00
|
|
|
resp := httptest.NewRecorder()
|
2014-04-21 20:11:05 +00:00
|
|
|
var b structs.QueryOptions
|
2014-02-05 22:47:42 +00:00
|
|
|
|
2017-05-09 11:38:05 +00:00
|
|
|
req, _ := http.NewRequest("GET", "/v1/catalog/nodes?wait=60foo&index=1000", nil)
|
2014-02-05 22:47:42 +00:00
|
|
|
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) {
|
2017-05-21 07:54:40 +00:00
|
|
|
t.Parallel()
|
2014-02-05 22:47:42 +00:00
|
|
|
resp := httptest.NewRecorder()
|
2014-04-21 20:11:05 +00:00
|
|
|
var b structs.QueryOptions
|
2014-02-05 22:47:42 +00:00
|
|
|
|
2017-05-09 11:38:05 +00:00
|
|
|
req, _ := http.NewRequest("GET", "/v1/catalog/nodes?wait=60s&index=foo", nil)
|
2014-02-05 22:47:42 +00:00
|
|
|
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) {
|
2017-05-21 07:54:40 +00:00
|
|
|
t.Parallel()
|
2014-04-21 20:19:18 +00:00
|
|
|
resp := httptest.NewRecorder()
|
|
|
|
|
2017-06-16 08:55:53 +00:00
|
|
|
tests := []struct {
|
|
|
|
url string
|
|
|
|
allowStale bool
|
|
|
|
wantAllowStale bool
|
|
|
|
wantRequireConsistent bool
|
|
|
|
}{
|
|
|
|
{"/v1/catalog/nodes?stale", false, true, false},
|
|
|
|
{"/v1/catalog/nodes?stale", true, true, false},
|
|
|
|
{"/v1/catalog/nodes?consistent", false, false, true},
|
|
|
|
{"/v1/catalog/nodes?consistent", true, false, true},
|
|
|
|
{"/v1/catalog/nodes", false, false, false},
|
|
|
|
{"/v1/catalog/nodes", true, true, false},
|
2014-04-21 20:19:18 +00:00
|
|
|
}
|
|
|
|
|
2017-06-16 08:55:53 +00:00
|
|
|
for _, tt := range tests {
|
|
|
|
name := fmt.Sprintf("url=%v, HTTP.AllowStale=%v", tt.url, tt.allowStale)
|
|
|
|
t.Run(name, func(t *testing.T) {
|
|
|
|
var q structs.QueryOptions
|
|
|
|
req, _ := http.NewRequest("GET", tt.url, nil)
|
|
|
|
if d := parseConsistency(resp, req, tt.allowStale, &q); d {
|
|
|
|
t.Fatalf("Failed to parse consistency.")
|
|
|
|
}
|
|
|
|
if got, want := q.AllowStale, tt.wantAllowStale; got != want {
|
|
|
|
t.Fatalf("got allowStale %v want %v", got, want)
|
|
|
|
}
|
|
|
|
if got, want := q.RequireConsistent, tt.wantRequireConsistent; got != want {
|
|
|
|
t.Fatalf("got requireConsistent %v want %v", got, want)
|
|
|
|
}
|
|
|
|
})
|
2014-04-21 20:19:18 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestParseConsistency_Invalid(t *testing.T) {
|
2017-05-21 07:54:40 +00:00
|
|
|
t.Parallel()
|
2014-04-21 20:19:18 +00:00
|
|
|
resp := httptest.NewRecorder()
|
|
|
|
var b structs.QueryOptions
|
|
|
|
|
2017-05-09 11:38:05 +00:00
|
|
|
req, _ := http.NewRequest("GET", "/v1/catalog/nodes?stale&consistent", nil)
|
2017-06-16 08:55:53 +00:00
|
|
|
if d := parseConsistency(resp, req, false, &b); !d {
|
2014-04-21 20:19:18 +00:00
|
|
|
t.Fatalf("expected done")
|
|
|
|
}
|
|
|
|
|
|
|
|
if resp.Code != 400 {
|
|
|
|
t.Fatalf("bad code: %v", resp.Code)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-02-06 22:38:01 +00:00
|
|
|
// Test ACL token is resolved in correct order
|
|
|
|
func TestACLResolution(t *testing.T) {
|
2017-05-21 07:54:40 +00:00
|
|
|
t.Parallel()
|
2015-02-06 22:38:01 +00:00
|
|
|
var token string
|
|
|
|
// Request without token
|
2017-05-09 11:38:05 +00:00
|
|
|
req, _ := http.NewRequest("GET", "/v1/catalog/nodes", nil)
|
2015-02-06 22:38:01 +00:00
|
|
|
// Request with explicit token
|
2017-05-09 11:38:05 +00:00
|
|
|
reqToken, _ := http.NewRequest("GET", "/v1/catalog/nodes?token=foo", nil)
|
2015-10-19 13:59:24 +00:00
|
|
|
// Request with header token only
|
2017-05-09 11:38:05 +00:00
|
|
|
reqHeaderToken, _ := http.NewRequest("GET", "/v1/catalog/nodes", nil)
|
2015-10-19 13:59:24 +00:00
|
|
|
reqHeaderToken.Header.Add("X-Consul-Token", "bar")
|
|
|
|
|
|
|
|
// Request with header and querystring tokens
|
2017-05-09 11:38:05 +00:00
|
|
|
reqBothTokens, _ := http.NewRequest("GET", "/v1/catalog/nodes?token=baz", nil)
|
2015-10-19 13:59:24 +00:00
|
|
|
reqBothTokens.Header.Add("X-Consul-Token", "zap")
|
|
|
|
|
2017-05-21 18:31:20 +00:00
|
|
|
a := NewTestAgent(t.Name(), nil)
|
|
|
|
defer a.Shutdown()
|
2015-02-06 22:38:01 +00:00
|
|
|
|
2017-05-21 18:31:20 +00:00
|
|
|
// Check when no token is set
|
2017-05-21 18:32:48 +00:00
|
|
|
a.Config.ACLToken = ""
|
2017-05-21 18:31:20 +00:00
|
|
|
a.srv.parseToken(req, &token)
|
|
|
|
if token != "" {
|
|
|
|
t.Fatalf("bad: %s", token)
|
|
|
|
}
|
2015-02-06 22:38:01 +00:00
|
|
|
|
2017-05-21 18:31:20 +00:00
|
|
|
// Check when ACLToken set
|
2017-05-21 18:32:48 +00:00
|
|
|
a.Config.ACLToken = "agent"
|
2017-05-21 18:31:20 +00:00
|
|
|
a.srv.parseToken(req, &token)
|
|
|
|
if token != "agent" {
|
|
|
|
t.Fatalf("bad: %s", token)
|
|
|
|
}
|
2015-10-19 13:59:24 +00:00
|
|
|
|
2017-05-21 18:31:20 +00:00
|
|
|
// Explicit token has highest precedence
|
|
|
|
a.srv.parseToken(reqToken, &token)
|
|
|
|
if token != "foo" {
|
|
|
|
t.Fatalf("bad: %s", token)
|
|
|
|
}
|
2015-10-19 13:59:24 +00:00
|
|
|
|
2017-05-21 18:31:20 +00:00
|
|
|
// Header token has precedence over agent token
|
|
|
|
a.srv.parseToken(reqHeaderToken, &token)
|
|
|
|
if token != "bar" {
|
|
|
|
t.Fatalf("bad: %s", token)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Querystring token has precedence over header and agent tokens
|
|
|
|
a.srv.parseToken(reqBothTokens, &token)
|
|
|
|
if token != "baz" {
|
|
|
|
t.Fatalf("bad: %s", token)
|
|
|
|
}
|
2015-02-06 22:38:01 +00:00
|
|
|
}
|
|
|
|
|
2015-12-22 17:30:19 +00:00
|
|
|
func TestEnableWebUI(t *testing.T) {
|
2017-05-21 07:54:40 +00:00
|
|
|
t.Parallel()
|
2017-05-22 11:03:59 +00:00
|
|
|
cfg := TestConfig()
|
|
|
|
cfg.EnableUI = true
|
|
|
|
a := NewTestAgent(t.Name(), cfg)
|
2017-05-21 18:31:20 +00:00
|
|
|
defer a.Shutdown()
|
|
|
|
|
|
|
|
req, _ := http.NewRequest("GET", "/ui/", nil)
|
|
|
|
resp := httptest.NewRecorder()
|
|
|
|
a.srv.Handler.ServeHTTP(resp, req)
|
|
|
|
if resp.Code != 200 {
|
|
|
|
t.Fatalf("should handle ui")
|
|
|
|
}
|
2015-12-22 17:30:19 +00:00
|
|
|
}
|
|
|
|
|
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
|
|
|
|
2017-05-09 12:17:21 +00:00
|
|
|
func isPermissionDenied(err error) bool {
|
|
|
|
return err != nil && strings.Contains(err.Error(), errPermissionDenied.Error())
|
|
|
|
}
|
2017-05-09 16:58:12 +00:00
|
|
|
|
|
|
|
func jsonReader(v interface{}) io.Reader {
|
|
|
|
if v == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
b := new(bytes.Buffer)
|
|
|
|
if err := json.NewEncoder(b).Encode(v); err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
return b
|
|
|
|
}
|