github.com/hooklift/nomad@v0.5.7-0.20170407200202-db11e7dd7b55/command/agent/http_test.go (about)

     1  package agent
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io"
     8  	"io/ioutil"
     9  	"net/http"
    10  	"net/http/httptest"
    11  	"os"
    12  	"strconv"
    13  	"testing"
    14  	"time"
    15  
    16  	"github.com/hashicorp/nomad/nomad/mock"
    17  	"github.com/hashicorp/nomad/nomad/structs"
    18  	"github.com/hashicorp/nomad/testutil"
    19  )
    20  
    21  type TestServer struct {
    22  	T      testing.TB
    23  	Dir    string
    24  	Agent  *Agent
    25  	Server *HTTPServer
    26  }
    27  
    28  func (s *TestServer) Cleanup() {
    29  	s.Server.Shutdown()
    30  	s.Agent.Shutdown()
    31  	os.RemoveAll(s.Dir)
    32  }
    33  
    34  // makeHTTPServerNoLogs returns a test server with full logging.
    35  func makeHTTPServer(t testing.TB, cb func(c *Config)) *TestServer {
    36  	return makeHTTPServerWithWriter(t, nil, cb)
    37  }
    38  
    39  // makeHTTPServerNoLogs returns a test server which only prints agent logs and
    40  // no http server logs
    41  func makeHTTPServerNoLogs(t testing.TB, cb func(c *Config)) *TestServer {
    42  	return makeHTTPServerWithWriter(t, ioutil.Discard, cb)
    43  }
    44  
    45  // makeHTTPServerWithWriter returns a test server whose logs will be written to
    46  // the passed writer. If the writer is nil, the logs are written to stderr.
    47  func makeHTTPServerWithWriter(t testing.TB, w io.Writer, cb func(c *Config)) *TestServer {
    48  	dir, agent := makeAgent(t, cb)
    49  	if w == nil {
    50  		w = agent.logOutput
    51  	}
    52  	srv, err := NewHTTPServer(agent, agent.config, w)
    53  	if err != nil {
    54  		t.Fatalf("err: %v", err)
    55  	}
    56  	s := &TestServer{
    57  		T:      t,
    58  		Dir:    dir,
    59  		Agent:  agent,
    60  		Server: srv,
    61  	}
    62  	return s
    63  }
    64  
    65  func BenchmarkHTTPRequests(b *testing.B) {
    66  	s := makeHTTPServerNoLogs(b, func(c *Config) {
    67  		c.Client.Enabled = false
    68  	})
    69  	defer s.Cleanup()
    70  
    71  	job := mock.Job()
    72  	var allocs []*structs.Allocation
    73  	count := 1000
    74  	for i := 0; i < count; i++ {
    75  		alloc := mock.Alloc()
    76  		alloc.Job = job
    77  		alloc.JobID = job.ID
    78  		alloc.Name = fmt.Sprintf("my-job.web[%d]", i)
    79  		allocs = append(allocs, alloc)
    80  	}
    81  
    82  	handler := func(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
    83  		return allocs[:count], nil
    84  	}
    85  	b.ResetTimer()
    86  
    87  	b.RunParallel(func(pb *testing.PB) {
    88  		for pb.Next() {
    89  			resp := httptest.NewRecorder()
    90  			req, _ := http.NewRequest("GET", "/v1/kv/key", nil)
    91  			s.Server.wrap(handler)(resp, req)
    92  		}
    93  	})
    94  }
    95  
    96  func TestSetIndex(t *testing.T) {
    97  	resp := httptest.NewRecorder()
    98  	setIndex(resp, 1000)
    99  	header := resp.Header().Get("X-Nomad-Index")
   100  	if header != "1000" {
   101  		t.Fatalf("Bad: %v", header)
   102  	}
   103  	setIndex(resp, 2000)
   104  	if v := resp.Header()["X-Nomad-Index"]; len(v) != 1 {
   105  		t.Fatalf("bad: %#v", v)
   106  	}
   107  }
   108  
   109  func TestSetKnownLeader(t *testing.T) {
   110  	resp := httptest.NewRecorder()
   111  	setKnownLeader(resp, true)
   112  	header := resp.Header().Get("X-Nomad-KnownLeader")
   113  	if header != "true" {
   114  		t.Fatalf("Bad: %v", header)
   115  	}
   116  	resp = httptest.NewRecorder()
   117  	setKnownLeader(resp, false)
   118  	header = resp.Header().Get("X-Nomad-KnownLeader")
   119  	if header != "false" {
   120  		t.Fatalf("Bad: %v", header)
   121  	}
   122  }
   123  
   124  func TestSetLastContact(t *testing.T) {
   125  	resp := httptest.NewRecorder()
   126  	setLastContact(resp, 123456*time.Microsecond)
   127  	header := resp.Header().Get("X-Nomad-LastContact")
   128  	if header != "123" {
   129  		t.Fatalf("Bad: %v", header)
   130  	}
   131  }
   132  
   133  func TestSetMeta(t *testing.T) {
   134  	meta := structs.QueryMeta{
   135  		Index:       1000,
   136  		KnownLeader: true,
   137  		LastContact: 123456 * time.Microsecond,
   138  	}
   139  	resp := httptest.NewRecorder()
   140  	setMeta(resp, &meta)
   141  	header := resp.Header().Get("X-Nomad-Index")
   142  	if header != "1000" {
   143  		t.Fatalf("Bad: %v", header)
   144  	}
   145  	header = resp.Header().Get("X-Nomad-KnownLeader")
   146  	if header != "true" {
   147  		t.Fatalf("Bad: %v", header)
   148  	}
   149  	header = resp.Header().Get("X-Nomad-LastContact")
   150  	if header != "123" {
   151  		t.Fatalf("Bad: %v", header)
   152  	}
   153  }
   154  
   155  func TestSetHeaders(t *testing.T) {
   156  	s := makeHTTPServer(t, nil)
   157  	s.Agent.config.HTTPAPIResponseHeaders = map[string]string{"foo": "bar"}
   158  	defer s.Cleanup()
   159  
   160  	resp := httptest.NewRecorder()
   161  	handler := func(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
   162  		return &structs.Job{Name: "foo"}, nil
   163  	}
   164  
   165  	req, _ := http.NewRequest("GET", "/v1/kv/key", nil)
   166  	s.Server.wrap(handler)(resp, req)
   167  	header := resp.Header().Get("foo")
   168  
   169  	if header != "bar" {
   170  		t.Fatalf("expected header: %v, actual: %v", "bar", header)
   171  	}
   172  
   173  }
   174  
   175  func TestContentTypeIsJSON(t *testing.T) {
   176  	s := makeHTTPServer(t, nil)
   177  	defer s.Cleanup()
   178  
   179  	resp := httptest.NewRecorder()
   180  
   181  	handler := func(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
   182  		return &structs.Job{Name: "foo"}, nil
   183  	}
   184  
   185  	req, _ := http.NewRequest("GET", "/v1/kv/key", nil)
   186  	s.Server.wrap(handler)(resp, req)
   187  
   188  	contentType := resp.Header().Get("Content-Type")
   189  
   190  	if contentType != "application/json" {
   191  		t.Fatalf("Content-Type header was not 'application/json'")
   192  	}
   193  }
   194  
   195  func TestPrettyPrint(t *testing.T) {
   196  	testPrettyPrint("pretty=1", true, t)
   197  }
   198  
   199  func TestPrettyPrintOff(t *testing.T) {
   200  	testPrettyPrint("pretty=0", false, t)
   201  }
   202  
   203  func TestPrettyPrintBare(t *testing.T) {
   204  	testPrettyPrint("pretty", true, t)
   205  }
   206  
   207  func testPrettyPrint(pretty string, prettyFmt bool, t *testing.T) {
   208  	s := makeHTTPServer(t, nil)
   209  	defer s.Cleanup()
   210  
   211  	r := &structs.Job{Name: "foo"}
   212  
   213  	resp := httptest.NewRecorder()
   214  	handler := func(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
   215  		return r, nil
   216  	}
   217  
   218  	urlStr := "/v1/job/foo?" + pretty
   219  	req, _ := http.NewRequest("GET", urlStr, nil)
   220  	s.Server.wrap(handler)(resp, req)
   221  
   222  	var expected []byte
   223  	if prettyFmt {
   224  		expected, _ = json.MarshalIndent(r, "", "    ")
   225  		expected = append(expected, "\n"...)
   226  	} else {
   227  		expected, _ = json.Marshal(r)
   228  	}
   229  	actual, err := ioutil.ReadAll(resp.Body)
   230  	if err != nil {
   231  		t.Fatalf("err: %s", err)
   232  	}
   233  
   234  	if !bytes.Equal(expected, actual) {
   235  		t.Fatalf("bad:\nexpected:\t%q\nactual:\t\t%q", string(expected), string(actual))
   236  	}
   237  }
   238  
   239  func TestParseWait(t *testing.T) {
   240  	resp := httptest.NewRecorder()
   241  	var b structs.QueryOptions
   242  
   243  	req, err := http.NewRequest("GET",
   244  		"/v1/catalog/nodes?wait=60s&index=1000", nil)
   245  	if err != nil {
   246  		t.Fatalf("err: %v", err)
   247  	}
   248  
   249  	if d := parseWait(resp, req, &b); d {
   250  		t.Fatalf("unexpected done")
   251  	}
   252  
   253  	if b.MinQueryIndex != 1000 {
   254  		t.Fatalf("Bad: %v", b)
   255  	}
   256  	if b.MaxQueryTime != 60*time.Second {
   257  		t.Fatalf("Bad: %v", b)
   258  	}
   259  }
   260  
   261  func TestParseWait_InvalidTime(t *testing.T) {
   262  	resp := httptest.NewRecorder()
   263  	var b structs.QueryOptions
   264  
   265  	req, err := http.NewRequest("GET",
   266  		"/v1/catalog/nodes?wait=60foo&index=1000", nil)
   267  	if err != nil {
   268  		t.Fatalf("err: %v", err)
   269  	}
   270  
   271  	if d := parseWait(resp, req, &b); !d {
   272  		t.Fatalf("expected done")
   273  	}
   274  
   275  	if resp.Code != 400 {
   276  		t.Fatalf("bad code: %v", resp.Code)
   277  	}
   278  }
   279  
   280  func TestParseWait_InvalidIndex(t *testing.T) {
   281  	resp := httptest.NewRecorder()
   282  	var b structs.QueryOptions
   283  
   284  	req, err := http.NewRequest("GET",
   285  		"/v1/catalog/nodes?wait=60s&index=foo", nil)
   286  	if err != nil {
   287  		t.Fatalf("err: %v", err)
   288  	}
   289  
   290  	if d := parseWait(resp, req, &b); !d {
   291  		t.Fatalf("expected done")
   292  	}
   293  
   294  	if resp.Code != 400 {
   295  		t.Fatalf("bad code: %v", resp.Code)
   296  	}
   297  }
   298  
   299  func TestParseConsistency(t *testing.T) {
   300  	var b structs.QueryOptions
   301  
   302  	req, err := http.NewRequest("GET",
   303  		"/v1/catalog/nodes?stale", nil)
   304  	if err != nil {
   305  		t.Fatalf("err: %v", err)
   306  	}
   307  
   308  	parseConsistency(req, &b)
   309  	if !b.AllowStale {
   310  		t.Fatalf("Bad: %v", b)
   311  	}
   312  
   313  	b = structs.QueryOptions{}
   314  	req, err = http.NewRequest("GET",
   315  		"/v1/catalog/nodes?consistent", nil)
   316  	if err != nil {
   317  		t.Fatalf("err: %v", err)
   318  	}
   319  
   320  	parseConsistency(req, &b)
   321  	if b.AllowStale {
   322  		t.Fatalf("Bad: %v", b)
   323  	}
   324  }
   325  
   326  func TestParseRegion(t *testing.T) {
   327  	s := makeHTTPServer(t, nil)
   328  	defer s.Cleanup()
   329  
   330  	req, err := http.NewRequest("GET",
   331  		"/v1/jobs?region=foo", nil)
   332  	if err != nil {
   333  		t.Fatalf("err: %v", err)
   334  	}
   335  
   336  	var region string
   337  	s.Server.parseRegion(req, &region)
   338  	if region != "foo" {
   339  		t.Fatalf("bad %s", region)
   340  	}
   341  
   342  	region = ""
   343  	req, err = http.NewRequest("GET", "/v1/jobs", nil)
   344  	if err != nil {
   345  		t.Fatalf("err: %v", err)
   346  	}
   347  
   348  	s.Server.parseRegion(req, &region)
   349  	if region != "global" {
   350  		t.Fatalf("bad %s", region)
   351  	}
   352  }
   353  
   354  // assertIndex tests that X-Nomad-Index is set and non-zero
   355  func assertIndex(t *testing.T, resp *httptest.ResponseRecorder) {
   356  	header := resp.Header().Get("X-Nomad-Index")
   357  	if header == "" || header == "0" {
   358  		t.Fatalf("Bad: %v", header)
   359  	}
   360  }
   361  
   362  // checkIndex is like assertIndex but returns an error
   363  func checkIndex(resp *httptest.ResponseRecorder) error {
   364  	header := resp.Header().Get("X-Nomad-Index")
   365  	if header == "" || header == "0" {
   366  		return fmt.Errorf("Bad: %v", header)
   367  	}
   368  	return nil
   369  }
   370  
   371  // getIndex parses X-Nomad-Index
   372  func getIndex(t *testing.T, resp *httptest.ResponseRecorder) uint64 {
   373  	header := resp.Header().Get("X-Nomad-Index")
   374  	if header == "" {
   375  		t.Fatalf("Bad: %v", header)
   376  	}
   377  	val, err := strconv.Atoi(header)
   378  	if err != nil {
   379  		t.Fatalf("Bad: %v", header)
   380  	}
   381  	return uint64(val)
   382  }
   383  
   384  func httpTest(t testing.TB, cb func(c *Config), f func(srv *TestServer)) {
   385  	s := makeHTTPServer(t, cb)
   386  	defer s.Cleanup()
   387  	testutil.WaitForLeader(t, s.Agent.RPC)
   388  	f(s)
   389  }
   390  
   391  func encodeReq(obj interface{}) io.ReadCloser {
   392  	buf := bytes.NewBuffer(nil)
   393  	enc := json.NewEncoder(buf)
   394  	enc.Encode(obj)
   395  	return ioutil.NopCloser(buf)
   396  }