github.com/karlem/nomad@v0.10.2-rc1/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 eligibilityReq := api.NodeUpdateEligibilityRequest{ 374 Eligibility: structs.NodeSchedulingIneligible, 375 } 376 377 // Make the HTTP request 378 buf := encodeReq(eligibilityReq) 379 req, err := http.NewRequest("POST", "/v1/node/"+node.ID+"/eligibility", buf) 380 require.Nil(err) 381 respW := httptest.NewRecorder() 382 383 // Make the request 384 obj, err := s.Server.NodeSpecificRequest(respW, req) 385 require.Nil(err) 386 387 // Check for the index 388 require.NotZero(respW.HeaderMap.Get("X-Nomad-Index")) 389 390 // Check the response 391 _, ok := obj.(structs.NodeEligibilityUpdateResponse) 392 require.True(ok) 393 394 // Check that the node has been updated 395 state := s.Agent.server.State() 396 out, err := state.NodeByID(nil, node.ID) 397 require.Nil(err) 398 require.Equal(structs.NodeSchedulingIneligible, out.SchedulingEligibility) 399 400 // Make the HTTP request to set something invalid 401 eligibilityReq.Eligibility = "foo" 402 buf = encodeReq(eligibilityReq) 403 req, err = http.NewRequest("POST", "/v1/node/"+node.ID+"/eligibility", buf) 404 require.Nil(err) 405 respW = httptest.NewRecorder() 406 407 // Make the request 408 _, err = s.Server.NodeSpecificRequest(respW, req) 409 require.NotNil(err) 410 require.Contains(err.Error(), "invalid") 411 }) 412 } 413 414 func TestHTTP_NodePurge(t *testing.T) { 415 t.Parallel() 416 httpTest(t, nil, func(s *TestAgent) { 417 // Create the node 418 node := mock.Node() 419 args := structs.NodeRegisterRequest{ 420 Node: node, 421 WriteRequest: structs.WriteRequest{Region: "global"}, 422 } 423 var resp structs.NodeUpdateResponse 424 if err := s.Agent.RPC("Node.Register", &args, &resp); err != nil { 425 t.Fatalf("err: %v", err) 426 } 427 428 // Add some allocations to the node 429 state := s.Agent.server.State() 430 alloc1 := mock.Alloc() 431 alloc1.NodeID = node.ID 432 if err := state.UpsertJobSummary(999, mock.JobSummary(alloc1.JobID)); err != nil { 433 t.Fatal(err) 434 } 435 err := state.UpsertAllocs(1000, []*structs.Allocation{alloc1}) 436 if err != nil { 437 t.Fatalf("err: %v", err) 438 } 439 440 // Make the HTTP request to purge it 441 req, err := http.NewRequest("POST", "/v1/node/"+node.ID+"/purge", nil) 442 if err != nil { 443 t.Fatalf("err: %v", err) 444 } 445 respW := httptest.NewRecorder() 446 447 // Make the request 448 obj, err := s.Server.NodeSpecificRequest(respW, req) 449 if err != nil { 450 t.Fatalf("err: %v", err) 451 } 452 453 // Check for the index 454 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 455 t.Fatalf("missing index") 456 } 457 458 // Check the response 459 upd := obj.(structs.NodeUpdateResponse) 460 if len(upd.EvalIDs) == 0 { 461 t.Fatalf("bad: %v", upd) 462 } 463 464 // Ensure that the node is not present anymore 465 args1 := structs.NodeSpecificRequest{ 466 NodeID: node.ID, 467 QueryOptions: structs.QueryOptions{Region: "global"}, 468 } 469 var resp1 structs.SingleNodeResponse 470 if err := s.Agent.RPC("Node.GetNode", &args1, &resp1); err != nil { 471 t.Fatalf("err: %v", err) 472 } 473 if resp1.Node != nil { 474 t.Fatalf("node still exists after purging: %#v", resp1.Node) 475 } 476 }) 477 } 478 479 func TestHTTP_NodeQuery(t *testing.T) { 480 t.Parallel() 481 httpTest(t, nil, func(s *TestAgent) { 482 // Create the job 483 node := mock.Node() 484 args := structs.NodeRegisterRequest{ 485 Node: node, 486 WriteRequest: structs.WriteRequest{Region: "global"}, 487 } 488 var resp structs.NodeUpdateResponse 489 if err := s.Agent.RPC("Node.Register", &args, &resp); err != nil { 490 t.Fatalf("err: %v", err) 491 } 492 493 // Make the HTTP request 494 req, err := http.NewRequest("GET", "/v1/node/"+node.ID, nil) 495 if err != nil { 496 t.Fatalf("err: %v", err) 497 } 498 respW := httptest.NewRecorder() 499 500 // Make the request 501 obj, err := s.Server.NodeSpecificRequest(respW, req) 502 if err != nil { 503 t.Fatalf("err: %v", err) 504 } 505 506 // Check for the index 507 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 508 t.Fatalf("missing index") 509 } 510 if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" { 511 t.Fatalf("missing known leader") 512 } 513 if respW.HeaderMap.Get("X-Nomad-LastContact") == "" { 514 t.Fatalf("missing last contact") 515 } 516 517 // Check the node 518 n := obj.(*structs.Node) 519 if n.ID != node.ID { 520 t.Fatalf("bad: %#v", n) 521 } 522 if len(n.Events) < 1 { 523 t.Fatalf("Expected node registration event to be populated: %#v", n) 524 } 525 if n.Events[0].Message != "Node registered" { 526 t.Fatalf("Expected node registration event to be first node event: %#v", n) 527 } 528 }) 529 }