github.com/janma/nomad@v0.11.3/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 dresp, ok := obj.(structs.NodeDrainUpdateResponse) 277 require.True(ok) 278 279 t.Logf("response index=%v node_update_index=0x%x", respW.HeaderMap.Get("X-Nomad-Index"), 280 dresp.NodeModifyIndex) 281 282 // Check that the node has been updated 283 state := s.Agent.server.State() 284 out, err := state.NodeByID(nil, node.ID) 285 require.Nil(err) 286 287 // the node must either be in drain mode or in elligible 288 // once the node is recognize as not having any running allocs 289 if out.Drain { 290 require.True(out.Drain) 291 require.NotNil(out.DrainStrategy) 292 require.Equal(10*time.Second, out.DrainStrategy.Deadline) 293 } else { 294 require.Equal(structs.NodeSchedulingIneligible, out.SchedulingEligibility) 295 } 296 297 // Make the HTTP request to unset drain 298 drainReq.DrainSpec = nil 299 buf = encodeReq(drainReq) 300 req, err = http.NewRequest("POST", "/v1/node/"+node.ID+"/drain", buf) 301 require.Nil(err) 302 respW = httptest.NewRecorder() 303 304 // Make the request 305 _, err = s.Server.NodeSpecificRequest(respW, req) 306 require.Nil(err) 307 308 out, err = state.NodeByID(nil, node.ID) 309 require.Nil(err) 310 require.False(out.Drain) 311 require.Nil(out.DrainStrategy) 312 }) 313 } 314 315 func TestHTTP_NodeEligible(t *testing.T) { 316 t.Parallel() 317 require := require.New(t) 318 httpTest(t, nil, func(s *TestAgent) { 319 // Create the node 320 node := mock.Node() 321 args := structs.NodeRegisterRequest{ 322 Node: node, 323 WriteRequest: structs.WriteRequest{Region: "global"}, 324 } 325 var resp structs.NodeUpdateResponse 326 require.Nil(s.Agent.RPC("Node.Register", &args, &resp)) 327 328 eligibilityReq := api.NodeUpdateEligibilityRequest{ 329 Eligibility: structs.NodeSchedulingIneligible, 330 } 331 332 // Make the HTTP request 333 buf := encodeReq(eligibilityReq) 334 req, err := http.NewRequest("POST", "/v1/node/"+node.ID+"/eligibility", buf) 335 require.Nil(err) 336 respW := httptest.NewRecorder() 337 338 // Make the request 339 obj, err := s.Server.NodeSpecificRequest(respW, req) 340 require.Nil(err) 341 342 // Check for the index 343 require.NotZero(respW.HeaderMap.Get("X-Nomad-Index")) 344 345 // Check the response 346 _, ok := obj.(structs.NodeEligibilityUpdateResponse) 347 require.True(ok) 348 349 // Check that the node has been updated 350 state := s.Agent.server.State() 351 out, err := state.NodeByID(nil, node.ID) 352 require.Nil(err) 353 require.Equal(structs.NodeSchedulingIneligible, out.SchedulingEligibility) 354 355 // Make the HTTP request to set something invalid 356 eligibilityReq.Eligibility = "foo" 357 buf = encodeReq(eligibilityReq) 358 req, err = http.NewRequest("POST", "/v1/node/"+node.ID+"/eligibility", buf) 359 require.Nil(err) 360 respW = httptest.NewRecorder() 361 362 // Make the request 363 _, err = s.Server.NodeSpecificRequest(respW, req) 364 require.NotNil(err) 365 require.Contains(err.Error(), "invalid") 366 }) 367 } 368 369 func TestHTTP_NodePurge(t *testing.T) { 370 t.Parallel() 371 httpTest(t, nil, func(s *TestAgent) { 372 // Create the node 373 node := mock.Node() 374 args := structs.NodeRegisterRequest{ 375 Node: node, 376 WriteRequest: structs.WriteRequest{Region: "global"}, 377 } 378 var resp structs.NodeUpdateResponse 379 if err := s.Agent.RPC("Node.Register", &args, &resp); err != nil { 380 t.Fatalf("err: %v", err) 381 } 382 383 // Add some allocations to the node 384 state := s.Agent.server.State() 385 alloc1 := mock.Alloc() 386 alloc1.NodeID = node.ID 387 if err := state.UpsertJobSummary(999, mock.JobSummary(alloc1.JobID)); err != nil { 388 t.Fatal(err) 389 } 390 err := state.UpsertAllocs(1000, []*structs.Allocation{alloc1}) 391 if err != nil { 392 t.Fatalf("err: %v", err) 393 } 394 395 // Make the HTTP request to purge it 396 req, err := http.NewRequest("POST", "/v1/node/"+node.ID+"/purge", nil) 397 if err != nil { 398 t.Fatalf("err: %v", err) 399 } 400 respW := httptest.NewRecorder() 401 402 // Make the request 403 obj, err := s.Server.NodeSpecificRequest(respW, req) 404 if err != nil { 405 t.Fatalf("err: %v", err) 406 } 407 408 // Check for the index 409 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 410 t.Fatalf("missing index") 411 } 412 413 // Check the response 414 upd := obj.(structs.NodeUpdateResponse) 415 if len(upd.EvalIDs) == 0 { 416 t.Fatalf("bad: %v", upd) 417 } 418 419 // Ensure that the node is not present anymore 420 args1 := structs.NodeSpecificRequest{ 421 NodeID: node.ID, 422 QueryOptions: structs.QueryOptions{Region: "global"}, 423 } 424 var resp1 structs.SingleNodeResponse 425 if err := s.Agent.RPC("Node.GetNode", &args1, &resp1); err != nil { 426 t.Fatalf("err: %v", err) 427 } 428 if resp1.Node != nil { 429 t.Fatalf("node still exists after purging: %#v", resp1.Node) 430 } 431 }) 432 } 433 434 func TestHTTP_NodeQuery(t *testing.T) { 435 t.Parallel() 436 httpTest(t, nil, func(s *TestAgent) { 437 // Create the job 438 node := mock.Node() 439 args := structs.NodeRegisterRequest{ 440 Node: node, 441 WriteRequest: structs.WriteRequest{Region: "global"}, 442 } 443 var resp structs.NodeUpdateResponse 444 if err := s.Agent.RPC("Node.Register", &args, &resp); err != nil { 445 t.Fatalf("err: %v", err) 446 } 447 448 // Make the HTTP request 449 req, err := http.NewRequest("GET", "/v1/node/"+node.ID, nil) 450 if err != nil { 451 t.Fatalf("err: %v", err) 452 } 453 respW := httptest.NewRecorder() 454 455 // Make the request 456 obj, err := s.Server.NodeSpecificRequest(respW, req) 457 if err != nil { 458 t.Fatalf("err: %v", err) 459 } 460 461 // Check for the index 462 if respW.HeaderMap.Get("X-Nomad-Index") == "" { 463 t.Fatalf("missing index") 464 } 465 if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" { 466 t.Fatalf("missing known leader") 467 } 468 if respW.HeaderMap.Get("X-Nomad-LastContact") == "" { 469 t.Fatalf("missing last contact") 470 } 471 472 // Check the node 473 n := obj.(*structs.Node) 474 if n.ID != node.ID { 475 t.Fatalf("bad: %#v", n) 476 } 477 if len(n.Events) < 1 { 478 t.Fatalf("Expected node registration event to be populated: %#v", n) 479 } 480 if n.Events[0].Message != "Node registered" { 481 t.Fatalf("Expected node registration event to be first node event: %#v", n) 482 } 483 }) 484 }