github.com/kardianos/nomad@v0.1.3-0.20151022182107-b13df73ee850/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/structs" 17 "github.com/hashicorp/nomad/testutil" 18 ) 19 20 type TestServer struct { 21 T *testing.T 22 Dir string 23 Agent *Agent 24 Server *HTTPServer 25 } 26 27 func (s *TestServer) Cleanup() { 28 s.Server.Shutdown() 29 s.Agent.Shutdown() 30 os.RemoveAll(s.Dir) 31 } 32 33 func makeHTTPServer(t *testing.T, cb func(c *Config)) *TestServer { 34 dir, agent := makeAgent(t, cb) 35 srv, err := NewHTTPServer(agent, agent.config, agent.logOutput) 36 if err != nil { 37 t.Fatalf("err: %v", err) 38 } 39 s := &TestServer{ 40 T: t, 41 Dir: dir, 42 Agent: agent, 43 Server: srv, 44 } 45 return s 46 } 47 48 func TestSetIndex(t *testing.T) { 49 resp := httptest.NewRecorder() 50 setIndex(resp, 1000) 51 header := resp.Header().Get("X-Nomad-Index") 52 if header != "1000" { 53 t.Fatalf("Bad: %v", header) 54 } 55 setIndex(resp, 2000) 56 if v := resp.Header()["X-Nomad-Index"]; len(v) != 1 { 57 t.Fatalf("bad: %#v", v) 58 } 59 } 60 61 func TestSetKnownLeader(t *testing.T) { 62 resp := httptest.NewRecorder() 63 setKnownLeader(resp, true) 64 header := resp.Header().Get("X-Nomad-KnownLeader") 65 if header != "true" { 66 t.Fatalf("Bad: %v", header) 67 } 68 resp = httptest.NewRecorder() 69 setKnownLeader(resp, false) 70 header = resp.Header().Get("X-Nomad-KnownLeader") 71 if header != "false" { 72 t.Fatalf("Bad: %v", header) 73 } 74 } 75 76 func TestSetLastContact(t *testing.T) { 77 resp := httptest.NewRecorder() 78 setLastContact(resp, 123456*time.Microsecond) 79 header := resp.Header().Get("X-Nomad-LastContact") 80 if header != "123" { 81 t.Fatalf("Bad: %v", header) 82 } 83 } 84 85 func TestSetMeta(t *testing.T) { 86 meta := structs.QueryMeta{ 87 Index: 1000, 88 KnownLeader: true, 89 LastContact: 123456 * time.Microsecond, 90 } 91 resp := httptest.NewRecorder() 92 setMeta(resp, &meta) 93 header := resp.Header().Get("X-Nomad-Index") 94 if header != "1000" { 95 t.Fatalf("Bad: %v", header) 96 } 97 header = resp.Header().Get("X-Nomad-KnownLeader") 98 if header != "true" { 99 t.Fatalf("Bad: %v", header) 100 } 101 header = resp.Header().Get("X-Nomad-LastContact") 102 if header != "123" { 103 t.Fatalf("Bad: %v", header) 104 } 105 } 106 107 func TestContentTypeIsJSON(t *testing.T) { 108 s := makeHTTPServer(t, nil) 109 defer s.Cleanup() 110 111 resp := httptest.NewRecorder() 112 113 handler := func(resp http.ResponseWriter, req *http.Request) (interface{}, error) { 114 return &structs.Job{Name: "foo"}, nil 115 } 116 117 req, _ := http.NewRequest("GET", "/v1/kv/key", nil) 118 s.Server.wrap(handler)(resp, req) 119 120 contentType := resp.Header().Get("Content-Type") 121 122 if contentType != "application/json" { 123 t.Fatalf("Content-Type header was not 'application/json'") 124 } 125 } 126 127 func TestPrettyPrint(t *testing.T) { 128 testPrettyPrint("pretty=1", t) 129 } 130 131 func TestPrettyPrintBare(t *testing.T) { 132 testPrettyPrint("pretty", t) 133 } 134 135 func testPrettyPrint(pretty string, t *testing.T) { 136 s := makeHTTPServer(t, nil) 137 defer s.Cleanup() 138 139 r := &structs.Job{Name: "foo"} 140 141 resp := httptest.NewRecorder() 142 handler := func(resp http.ResponseWriter, req *http.Request) (interface{}, error) { 143 return r, nil 144 } 145 146 urlStr := "/v1/job/foo?" + pretty 147 req, _ := http.NewRequest("GET", urlStr, nil) 148 s.Server.wrap(handler)(resp, req) 149 150 expected, _ := json.MarshalIndent(r, "", " ") 151 actual, err := ioutil.ReadAll(resp.Body) 152 if err != nil { 153 t.Fatalf("err: %s", err) 154 } 155 156 if !bytes.Equal(expected, actual) { 157 t.Fatalf("bad: %q", string(actual)) 158 } 159 } 160 161 func TestParseWait(t *testing.T) { 162 resp := httptest.NewRecorder() 163 var b structs.QueryOptions 164 165 req, err := http.NewRequest("GET", 166 "/v1/catalog/nodes?wait=60s&index=1000", nil) 167 if err != nil { 168 t.Fatalf("err: %v", err) 169 } 170 171 if d := parseWait(resp, req, &b); d { 172 t.Fatalf("unexpected done") 173 } 174 175 if b.MinQueryIndex != 1000 { 176 t.Fatalf("Bad: %v", b) 177 } 178 if b.MaxQueryTime != 60*time.Second { 179 t.Fatalf("Bad: %v", b) 180 } 181 } 182 183 func TestParseWait_InvalidTime(t *testing.T) { 184 resp := httptest.NewRecorder() 185 var b structs.QueryOptions 186 187 req, err := http.NewRequest("GET", 188 "/v1/catalog/nodes?wait=60foo&index=1000", nil) 189 if err != nil { 190 t.Fatalf("err: %v", err) 191 } 192 193 if d := parseWait(resp, req, &b); !d { 194 t.Fatalf("expected done") 195 } 196 197 if resp.Code != 400 { 198 t.Fatalf("bad code: %v", resp.Code) 199 } 200 } 201 202 func TestParseWait_InvalidIndex(t *testing.T) { 203 resp := httptest.NewRecorder() 204 var b structs.QueryOptions 205 206 req, err := http.NewRequest("GET", 207 "/v1/catalog/nodes?wait=60s&index=foo", nil) 208 if err != nil { 209 t.Fatalf("err: %v", err) 210 } 211 212 if d := parseWait(resp, req, &b); !d { 213 t.Fatalf("expected done") 214 } 215 216 if resp.Code != 400 { 217 t.Fatalf("bad code: %v", resp.Code) 218 } 219 } 220 221 func TestParseConsistency(t *testing.T) { 222 var b structs.QueryOptions 223 224 req, err := http.NewRequest("GET", 225 "/v1/catalog/nodes?stale", nil) 226 if err != nil { 227 t.Fatalf("err: %v", err) 228 } 229 230 parseConsistency(req, &b) 231 if !b.AllowStale { 232 t.Fatalf("Bad: %v", b) 233 } 234 235 b = structs.QueryOptions{} 236 req, err = http.NewRequest("GET", 237 "/v1/catalog/nodes?consistent", nil) 238 if err != nil { 239 t.Fatalf("err: %v", err) 240 } 241 242 parseConsistency(req, &b) 243 if b.AllowStale { 244 t.Fatalf("Bad: %v", b) 245 } 246 } 247 248 func TestParseRegion(t *testing.T) { 249 s := makeHTTPServer(t, nil) 250 defer s.Cleanup() 251 252 req, err := http.NewRequest("GET", 253 "/v1/jobs?region=foo", nil) 254 if err != nil { 255 t.Fatalf("err: %v", err) 256 } 257 258 var region string 259 s.Server.parseRegion(req, ®ion) 260 if region != "foo" { 261 t.Fatalf("bad %s", region) 262 } 263 264 region = "" 265 req, err = http.NewRequest("GET", "/v1/jobs", nil) 266 if err != nil { 267 t.Fatalf("err: %v", err) 268 } 269 270 s.Server.parseRegion(req, ®ion) 271 if region != "global" { 272 t.Fatalf("bad %s", region) 273 } 274 } 275 276 // assertIndex tests that X-Nomad-Index is set and non-zero 277 func assertIndex(t *testing.T, resp *httptest.ResponseRecorder) { 278 header := resp.Header().Get("X-Nomad-Index") 279 if header == "" || header == "0" { 280 t.Fatalf("Bad: %v", header) 281 } 282 } 283 284 // checkIndex is like assertIndex but returns an error 285 func checkIndex(resp *httptest.ResponseRecorder) error { 286 header := resp.Header().Get("X-Nomad-Index") 287 if header == "" || header == "0" { 288 return fmt.Errorf("Bad: %v", header) 289 } 290 return nil 291 } 292 293 // getIndex parses X-Nomad-Index 294 func getIndex(t *testing.T, resp *httptest.ResponseRecorder) uint64 { 295 header := resp.Header().Get("X-Nomad-Index") 296 if header == "" { 297 t.Fatalf("Bad: %v", header) 298 } 299 val, err := strconv.Atoi(header) 300 if err != nil { 301 t.Fatalf("Bad: %v", header) 302 } 303 return uint64(val) 304 } 305 306 func httpTest(t *testing.T, cb func(c *Config), f func(srv *TestServer)) { 307 s := makeHTTPServer(t, cb) 308 defer s.Cleanup() 309 testutil.WaitForLeader(t, s.Agent.RPC) 310 f(s) 311 } 312 313 func encodeReq(obj interface{}) io.ReadCloser { 314 buf := bytes.NewBuffer(nil) 315 enc := json.NewEncoder(buf) 316 enc.Encode(obj) 317 return ioutil.NopCloser(buf) 318 }