github.com/bigcommerce/nomad@v0.9.3-bc/command/agent/node_endpoint_test.go (about) 1 package agent 2 3 import ( 4 "net/http" 5 "net/http/httptest" 6 "testing" 7 "time" 8 9 "github.com/hashicorp/nomad/api" 10 "github.com/hashicorp/nomad/nomad/mock" 11 "github.com/hashicorp/nomad/nomad/structs" 12 "github.com/stretchr/testify/assert" 13 "github.com/stretchr/testify/require" 14 ) 15 16 func TestHTTP_NodesList(t *testing.T) { 17 t.Parallel() 18 httpTest(t, nil, func(s *TestAgent) { 19 for i := 0; i < 3; i++ { 20 // Create the node 21 node := mock.Node() 22 args := structs.NodeRegisterRequest{ 23 Node: node, 24 WriteRequest: structs.WriteRequest{Region: "global"}, 25 } 26 var resp structs.NodeUpdateResponse 27 if err := s.Agent.RPC("Node.Register", &args, &resp); err != nil { 28 t.Fatalf("err: %v", err) 29 } 30 } 31 32 // Make the HTTP request 33 req, err := http.NewRequest("GET", "/v1/nodes", nil) 34 if err != nil { 35 t.Fatalf("err: %v", err) 36 } 37 respW := httptest.NewRecorder() 38 39 // Make the request 40 obj, err := s.Server.NodesRequest(respW, req) 41 if err != nil { 42 t.Fatalf("err: %v", err) 43 } 44 45 // Check for the index 46 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 47 t.Fatalf("missing index") 48 } 49 if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" { 50 t.Fatalf("missing known leader") 51 } 52 if respW.HeaderMap.Get("X-Nomad-LastContact") == "" { 53 t.Fatalf("missing last contact") 54 } 55 56 // Check the nodes 57 n := obj.([]*structs.NodeListStub) 58 if len(n) < 3 { // Maybe 4 including client 59 t.Fatalf("bad: %#v", n) 60 } 61 }) 62 } 63 64 func TestHTTP_NodesPrefixList(t *testing.T) { 65 t.Parallel() 66 httpTest(t, nil, func(s *TestAgent) { 67 ids := []string{ 68 "12345678-abcd-efab-cdef-123456789abc", 69 "12345678-aaaa-efab-cdef-123456789abc", 70 "1234aaaa-abcd-efab-cdef-123456789abc", 71 "1234bbbb-abcd-efab-cdef-123456789abc", 72 "1234cccc-abcd-efab-cdef-123456789abc", 73 "1234dddd-abcd-efab-cdef-123456789abc", 74 } 75 for i := 0; i < 5; i++ { 76 // Create the node 77 node := mock.Node() 78 node.ID = ids[i] 79 args := structs.NodeRegisterRequest{ 80 Node: node, 81 WriteRequest: structs.WriteRequest{Region: "global"}, 82 } 83 var resp structs.NodeUpdateResponse 84 if err := s.Agent.RPC("Node.Register", &args, &resp); err != nil { 85 t.Fatalf("err: %v", err) 86 } 87 } 88 89 // Make the HTTP request 90 req, err := http.NewRequest("GET", "/v1/nodes?prefix=12345678", nil) 91 if err != nil { 92 t.Fatalf("err: %v", err) 93 } 94 respW := httptest.NewRecorder() 95 96 // Make the request 97 obj, err := s.Server.NodesRequest(respW, req) 98 if err != nil { 99 t.Fatalf("err: %v", err) 100 } 101 102 // Check for the index 103 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 104 t.Fatalf("missing index") 105 } 106 if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" { 107 t.Fatalf("missing known leader") 108 } 109 if respW.HeaderMap.Get("X-Nomad-LastContact") == "" { 110 t.Fatalf("missing last contact") 111 } 112 113 // Check the nodes 114 n := obj.([]*structs.NodeListStub) 115 if len(n) != 2 { 116 t.Fatalf("bad: %#v", n) 117 } 118 }) 119 } 120 121 func TestHTTP_NodeForceEval(t *testing.T) { 122 t.Parallel() 123 httpTest(t, nil, func(s *TestAgent) { 124 // Create the node 125 node := mock.Node() 126 args := structs.NodeRegisterRequest{ 127 Node: node, 128 WriteRequest: structs.WriteRequest{Region: "global"}, 129 } 130 var resp structs.NodeUpdateResponse 131 if err := s.Agent.RPC("Node.Register", &args, &resp); err != nil { 132 t.Fatalf("err: %v", err) 133 } 134 135 // Directly manipulate the state 136 state := s.Agent.server.State() 137 alloc1 := mock.Alloc() 138 alloc1.NodeID = node.ID 139 if err := state.UpsertJobSummary(999, mock.JobSummary(alloc1.JobID)); err != nil { 140 t.Fatal(err) 141 } 142 err := state.UpsertAllocs(1000, []*structs.Allocation{alloc1}) 143 if err != nil { 144 t.Fatalf("err: %v", err) 145 } 146 147 // Make the HTTP request 148 req, err := http.NewRequest("POST", "/v1/node/"+node.ID+"/evaluate", nil) 149 if err != nil { 150 t.Fatalf("err: %v", err) 151 } 152 respW := httptest.NewRecorder() 153 154 // Make the request 155 obj, err := s.Server.NodeSpecificRequest(respW, req) 156 if err != nil { 157 t.Fatalf("err: %v", err) 158 } 159 160 // Check for the index 161 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 162 t.Fatalf("missing index") 163 } 164 165 // Check the response 166 upd := obj.(structs.NodeUpdateResponse) 167 if len(upd.EvalIDs) == 0 { 168 t.Fatalf("bad: %v", upd) 169 } 170 }) 171 } 172 173 func TestHTTP_NodeAllocations(t *testing.T) { 174 t.Parallel() 175 httpTest(t, nil, func(s *TestAgent) { 176 // Create the job 177 node := mock.Node() 178 args := structs.NodeRegisterRequest{ 179 Node: node, 180 WriteRequest: structs.WriteRequest{Region: "global"}, 181 } 182 var resp structs.NodeUpdateResponse 183 if err := s.Agent.RPC("Node.Register", &args, &resp); err != nil { 184 t.Fatalf("err: %v", err) 185 } 186 187 // Directly manipulate the state 188 state := s.Agent.server.State() 189 alloc1 := mock.Alloc() 190 alloc1.NodeID = node.ID 191 if err := state.UpsertJobSummary(999, mock.JobSummary(alloc1.JobID)); err != nil { 192 t.Fatal(err) 193 } 194 // Create a test event for the allocation 195 testEvent := structs.NewTaskEvent(structs.TaskStarted) 196 var events []*structs.TaskEvent 197 events = append(events, testEvent) 198 taskState := &structs.TaskState{Events: events} 199 alloc1.TaskStates = make(map[string]*structs.TaskState) 200 alloc1.TaskStates["test"] = taskState 201 202 err := state.UpsertAllocs(1000, []*structs.Allocation{alloc1}) 203 if err != nil { 204 t.Fatalf("err: %v", err) 205 } 206 207 // Make the HTTP request 208 req, err := http.NewRequest("GET", "/v1/node/"+node.ID+"/allocations", nil) 209 if err != nil { 210 t.Fatalf("err: %v", err) 211 } 212 respW := httptest.NewRecorder() 213 214 // Make the request 215 obj, err := s.Server.NodeSpecificRequest(respW, req) 216 if err != nil { 217 t.Fatalf("err: %v", err) 218 } 219 220 // Check for the index 221 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 222 t.Fatalf("missing index") 223 } 224 if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" { 225 t.Fatalf("missing known leader") 226 } 227 if respW.HeaderMap.Get("X-Nomad-LastContact") == "" { 228 t.Fatalf("missing last contact") 229 } 230 231 // Check the node 232 allocs := obj.([]*structs.Allocation) 233 if len(allocs) != 1 || allocs[0].ID != alloc1.ID { 234 t.Fatalf("bad: %#v", allocs) 235 } 236 expectedDisplayMsg := "Task started by client" 237 displayMsg := allocs[0].TaskStates["test"].Events[0].DisplayMessage 238 assert.Equal(t, expectedDisplayMsg, displayMsg) 239 }) 240 } 241 242 func TestHTTP_NodeDrain(t *testing.T) { 243 t.Parallel() 244 require := require.New(t) 245 httpTest(t, nil, func(s *TestAgent) { 246 // Create the node 247 node := mock.Node() 248 args := structs.NodeRegisterRequest{ 249 Node: node, 250 WriteRequest: structs.WriteRequest{Region: "global"}, 251 } 252 var resp structs.NodeUpdateResponse 253 require.Nil(s.Agent.RPC("Node.Register", &args, &resp)) 254 255 drainReq := api.NodeUpdateDrainRequest{ 256 NodeID: node.ID, 257 DrainSpec: &api.DrainSpec{ 258 Deadline: 10 * time.Second, 259 }, 260 } 261 262 // Make the HTTP request 263 buf := encodeReq(drainReq) 264 req, err := http.NewRequest("POST", "/v1/node/"+node.ID+"/drain", buf) 265 require.Nil(err) 266 respW := httptest.NewRecorder() 267 268 // Make the request 269 obj, err := s.Server.NodeSpecificRequest(respW, req) 270 require.Nil(err) 271 272 // Check for the index 273 require.NotZero(respW.HeaderMap.Get("X-Nomad-Index")) 274 275 // Check the response 276 _, ok := obj.(structs.NodeDrainUpdateResponse) 277 require.True(ok) 278 279 // Check that the node has been updated 280 state := s.Agent.server.State() 281 out, err := state.NodeByID(nil, node.ID) 282 require.Nil(err) 283 require.True(out.Drain) 284 require.NotNil(out.DrainStrategy) 285 require.Equal(10*time.Second, out.DrainStrategy.Deadline) 286 287 // Make the HTTP request to unset drain 288 drainReq.DrainSpec = nil 289 buf = encodeReq(drainReq) 290 req, err = http.NewRequest("POST", "/v1/node/"+node.ID+"/drain", buf) 291 require.Nil(err) 292 respW = httptest.NewRecorder() 293 294 // Make the request 295 _, err = s.Server.NodeSpecificRequest(respW, req) 296 require.Nil(err) 297 298 out, err = state.NodeByID(nil, node.ID) 299 require.Nil(err) 300 require.False(out.Drain) 301 require.Nil(out.DrainStrategy) 302 }) 303 } 304 305 // Tests backwards compatibility code to support pre 0.8 clients 306 func TestHTTP_NodeDrain_Compat(t *testing.T) { 307 t.Parallel() 308 require := require.New(t) 309 httpTest(t, nil, func(s *TestAgent) { 310 // Create the node 311 node := mock.Node() 312 args := structs.NodeRegisterRequest{ 313 Node: node, 314 WriteRequest: structs.WriteRequest{Region: "global"}, 315 } 316 var resp structs.NodeUpdateResponse 317 require.Nil(s.Agent.RPC("Node.Register", &args, &resp)) 318 319 // Make the HTTP request 320 req, err := http.NewRequest("POST", "/v1/node/"+node.ID+"/drain?enable=true", nil) 321 require.Nil(err) 322 respW := httptest.NewRecorder() 323 324 // Make the request 325 obj, err := s.Server.NodeSpecificRequest(respW, req) 326 require.Nil(err) 327 328 // Check for the index 329 require.NotZero(respW.HeaderMap.Get("X-Nomad-Index")) 330 331 // Check the response 332 _, ok := obj.(structs.NodeDrainUpdateResponse) 333 require.True(ok) 334 335 // Check that the node has been updated 336 state := s.Agent.server.State() 337 out, err := state.NodeByID(nil, node.ID) 338 require.Nil(err) 339 require.True(out.Drain) 340 require.NotNil(out.DrainStrategy) 341 require.Equal(-1*time.Second, out.DrainStrategy.Deadline) 342 343 // Make the HTTP request to unset drain 344 req, err = http.NewRequest("POST", "/v1/node/"+node.ID+"/drain?enable=false", nil) 345 require.Nil(err) 346 respW = httptest.NewRecorder() 347 348 // Make the request 349 _, err = s.Server.NodeSpecificRequest(respW, req) 350 require.Nil(err) 351 352 out, err = state.NodeByID(nil, node.ID) 353 require.Nil(err) 354 require.False(out.Drain) 355 require.Nil(out.DrainStrategy) 356 require.Equal(structs.NodeSchedulingEligible, out.SchedulingEligibility) 357 }) 358 } 359 360 func TestHTTP_NodeEligible(t *testing.T) { 361 t.Parallel() 362 require := require.New(t) 363 httpTest(t, nil, func(s *TestAgent) { 364 // Create the node 365 node := mock.Node() 366 args := structs.NodeRegisterRequest{ 367 Node: node, 368 WriteRequest: structs.WriteRequest{Region: "global"}, 369 } 370 var resp structs.NodeUpdateResponse 371 require.Nil(s.Agent.RPC("Node.Register", &args, &resp)) 372 373 drainReq := api.NodeUpdateEligibilityRequest{ 374 NodeID: node.ID, 375 Eligibility: structs.NodeSchedulingIneligible, 376 } 377 378 // Make the HTTP request 379 buf := encodeReq(drainReq) 380 req, err := http.NewRequest("POST", "/v1/node/"+node.ID+"/eligibility", buf) 381 require.Nil(err) 382 respW := httptest.NewRecorder() 383 384 // Make the request 385 obj, err := s.Server.NodeSpecificRequest(respW, req) 386 require.Nil(err) 387 388 // Check for the index 389 require.NotZero(respW.HeaderMap.Get("X-Nomad-Index")) 390 391 // Check the response 392 _, ok := obj.(structs.NodeEligibilityUpdateResponse) 393 require.True(ok) 394 395 // Check that the node has been updated 396 state := s.Agent.server.State() 397 out, err := state.NodeByID(nil, node.ID) 398 require.Nil(err) 399 require.Equal(structs.NodeSchedulingIneligible, out.SchedulingEligibility) 400 401 // Make the HTTP request to set something invalid 402 drainReq.Eligibility = "foo" 403 buf = encodeReq(drainReq) 404 req, err = http.NewRequest("POST", "/v1/node/"+node.ID+"/eligibility", buf) 405 require.Nil(err) 406 respW = httptest.NewRecorder() 407 408 // Make the request 409 _, err = s.Server.NodeSpecificRequest(respW, req) 410 require.NotNil(err) 411 require.Contains(err.Error(), "invalid") 412 }) 413 } 414 415 func TestHTTP_NodePurge(t *testing.T) { 416 t.Parallel() 417 httpTest(t, nil, func(s *TestAgent) { 418 // Create the node 419 node := mock.Node() 420 args := structs.NodeRegisterRequest{ 421 Node: node, 422 WriteRequest: structs.WriteRequest{Region: "global"}, 423 } 424 var resp structs.NodeUpdateResponse 425 if err := s.Agent.RPC("Node.Register", &args, &resp); err != nil { 426 t.Fatalf("err: %v", err) 427 } 428 429 // Add some allocations to the node 430 state := s.Agent.server.State() 431 alloc1 := mock.Alloc() 432 alloc1.NodeID = node.ID 433 if err := state.UpsertJobSummary(999, mock.JobSummary(alloc1.JobID)); err != nil { 434 t.Fatal(err) 435 } 436 err := state.UpsertAllocs(1000, []*structs.Allocation{alloc1}) 437 if err != nil { 438 t.Fatalf("err: %v", err) 439 } 440 441 // Make the HTTP request to purge it 442 req, err := http.NewRequest("POST", "/v1/node/"+node.ID+"/purge", nil) 443 if err != nil { 444 t.Fatalf("err: %v", err) 445 } 446 respW := httptest.NewRecorder() 447 448 // Make the request 449 obj, err := s.Server.NodeSpecificRequest(respW, req) 450 if err != nil { 451 t.Fatalf("err: %v", err) 452 } 453 454 // Check for the index 455 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 456 t.Fatalf("missing index") 457 } 458 459 // Check the response 460 upd := obj.(structs.NodeUpdateResponse) 461 if len(upd.EvalIDs) == 0 { 462 t.Fatalf("bad: %v", upd) 463 } 464 465 // Ensure that the node is not present anymore 466 args1 := structs.NodeSpecificRequest{ 467 NodeID: node.ID, 468 QueryOptions: structs.QueryOptions{Region: "global"}, 469 } 470 var resp1 structs.SingleNodeResponse 471 if err := s.Agent.RPC("Node.GetNode", &args1, &resp1); err != nil { 472 t.Fatalf("err: %v", err) 473 } 474 if resp1.Node != nil { 475 t.Fatalf("node still exists after purging: %#v", resp1.Node) 476 } 477 }) 478 } 479 480 func TestHTTP_NodeQuery(t *testing.T) { 481 t.Parallel() 482 httpTest(t, nil, func(s *TestAgent) { 483 // Create the job 484 node := mock.Node() 485 args := structs.NodeRegisterRequest{ 486 Node: node, 487 WriteRequest: structs.WriteRequest{Region: "global"}, 488 } 489 var resp structs.NodeUpdateResponse 490 if err := s.Agent.RPC("Node.Register", &args, &resp); err != nil { 491 t.Fatalf("err: %v", err) 492 } 493 494 // Make the HTTP request 495 req, err := http.NewRequest("GET", "/v1/node/"+node.ID, nil) 496 if err != nil { 497 t.Fatalf("err: %v", err) 498 } 499 respW := httptest.NewRecorder() 500 501 // Make the request 502 obj, err := s.Server.NodeSpecificRequest(respW, req) 503 if err != nil { 504 t.Fatalf("err: %v", err) 505 } 506 507 // Check for the index 508 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 509 t.Fatalf("missing index") 510 } 511 if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" { 512 t.Fatalf("missing known leader") 513 } 514 if respW.HeaderMap.Get("X-Nomad-LastContact") == "" { 515 t.Fatalf("missing last contact") 516 } 517 518 // Check the node 519 n := obj.(*structs.Node) 520 if n.ID != node.ID { 521 t.Fatalf("bad: %#v", n) 522 } 523 if len(n.Events) < 1 { 524 t.Fatalf("Expected node registration event to be populated: %#v", n) 525 } 526 if n.Events[0].Message != "Node registered" { 527 t.Fatalf("Expected node registration event to be first node event: %#v", n) 528 } 529 }) 530 }