github.com/djenriquez/nomad-1@v0.8.1/nomad/alloc_endpoint_test.go (about) 1 package nomad 2 3 import ( 4 "reflect" 5 "testing" 6 "time" 7 8 "github.com/hashicorp/net-rpc-msgpackrpc" 9 "github.com/hashicorp/nomad/acl" 10 "github.com/hashicorp/nomad/helper" 11 "github.com/hashicorp/nomad/helper/uuid" 12 "github.com/hashicorp/nomad/nomad/mock" 13 "github.com/hashicorp/nomad/nomad/structs" 14 "github.com/hashicorp/nomad/testutil" 15 "github.com/stretchr/testify/assert" 16 "github.com/stretchr/testify/require" 17 ) 18 19 func TestAllocEndpoint_List(t *testing.T) { 20 t.Parallel() 21 s1 := TestServer(t, nil) 22 defer s1.Shutdown() 23 codec := rpcClient(t, s1) 24 testutil.WaitForLeader(t, s1.RPC) 25 26 // Create the register request 27 alloc := mock.Alloc() 28 summary := mock.JobSummary(alloc.JobID) 29 state := s1.fsm.State() 30 31 if err := state.UpsertJobSummary(999, summary); err != nil { 32 t.Fatalf("err: %v", err) 33 } 34 if err := state.UpsertAllocs(1000, []*structs.Allocation{alloc}); err != nil { 35 t.Fatalf("err: %v", err) 36 } 37 38 // Lookup the allocations 39 get := &structs.AllocListRequest{ 40 QueryOptions: structs.QueryOptions{ 41 Region: "global", 42 Namespace: structs.DefaultNamespace, 43 }, 44 } 45 var resp structs.AllocListResponse 46 if err := msgpackrpc.CallWithCodec(codec, "Alloc.List", get, &resp); err != nil { 47 t.Fatalf("err: %v", err) 48 } 49 if resp.Index != 1000 { 50 t.Fatalf("Bad index: %d %d", resp.Index, 1000) 51 } 52 53 if len(resp.Allocations) != 1 { 54 t.Fatalf("bad: %#v", resp.Allocations) 55 } 56 if resp.Allocations[0].ID != alloc.ID { 57 t.Fatalf("bad: %#v", resp.Allocations[0]) 58 } 59 60 // Lookup the allocations by prefix 61 get = &structs.AllocListRequest{ 62 QueryOptions: structs.QueryOptions{ 63 Region: "global", 64 Namespace: structs.DefaultNamespace, 65 Prefix: alloc.ID[:4], 66 }, 67 } 68 69 var resp2 structs.AllocListResponse 70 if err := msgpackrpc.CallWithCodec(codec, "Alloc.List", get, &resp2); err != nil { 71 t.Fatalf("err: %v", err) 72 } 73 if resp2.Index != 1000 { 74 t.Fatalf("Bad index: %d %d", resp2.Index, 1000) 75 } 76 77 if len(resp2.Allocations) != 1 { 78 t.Fatalf("bad: %#v", resp2.Allocations) 79 } 80 if resp2.Allocations[0].ID != alloc.ID { 81 t.Fatalf("bad: %#v", resp2.Allocations[0]) 82 } 83 } 84 85 func TestAllocEndpoint_List_ACL(t *testing.T) { 86 t.Parallel() 87 s1, root := TestACLServer(t, nil) 88 defer s1.Shutdown() 89 codec := rpcClient(t, s1) 90 testutil.WaitForLeader(t, s1.RPC) 91 assert := assert.New(t) 92 93 // Create the alloc 94 alloc := mock.Alloc() 95 allocs := []*structs.Allocation{alloc} 96 summary := mock.JobSummary(alloc.JobID) 97 state := s1.fsm.State() 98 99 assert.Nil(state.UpsertJobSummary(999, summary), "UpsertJobSummary") 100 assert.Nil(state.UpsertAllocs(1000, allocs), "UpsertAllocs") 101 102 stubAllocs := []*structs.AllocListStub{alloc.Stub()} 103 stubAllocs[0].CreateIndex = 1000 104 stubAllocs[0].ModifyIndex = 1000 105 106 // Create the namespace policy and tokens 107 validToken := mock.CreatePolicyAndToken(t, state, 1001, "test-valid", 108 mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityReadJob})) 109 invalidToken := mock.CreatePolicyAndToken(t, state, 1003, "test-invalid", 110 mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityListJobs})) 111 112 // Lookup the allocs without a token and expect failure 113 get := &structs.AllocListRequest{ 114 QueryOptions: structs.QueryOptions{ 115 Region: "global", 116 Namespace: structs.DefaultNamespace, 117 }, 118 } 119 var resp structs.AllocListResponse 120 assert.NotNil(msgpackrpc.CallWithCodec(codec, "Alloc.List", get, &resp), "RPC") 121 122 // Try with a valid token 123 get.AuthToken = validToken.SecretID 124 assert.Nil(msgpackrpc.CallWithCodec(codec, "Alloc.List", get, &resp), "RPC") 125 assert.EqualValues(resp.Index, 1000, "resp.Index") 126 assert.Equal(stubAllocs, resp.Allocations, "Returned alloc list not equal") 127 128 // Try with a invalid token 129 get.AuthToken = invalidToken.SecretID 130 err := msgpackrpc.CallWithCodec(codec, "Alloc.List", get, &resp) 131 assert.NotNil(err, "RPC") 132 assert.Equal(err.Error(), structs.ErrPermissionDenied.Error()) 133 134 // Try with a root token 135 get.AuthToken = root.SecretID 136 assert.Nil(msgpackrpc.CallWithCodec(codec, "Alloc.List", get, &resp), "RPC") 137 assert.EqualValues(resp.Index, 1000, "resp.Index") 138 assert.Equal(stubAllocs, resp.Allocations, "Returned alloc list not equal") 139 } 140 141 func TestAllocEndpoint_List_Blocking(t *testing.T) { 142 t.Parallel() 143 s1 := TestServer(t, nil) 144 defer s1.Shutdown() 145 state := s1.fsm.State() 146 codec := rpcClient(t, s1) 147 testutil.WaitForLeader(t, s1.RPC) 148 149 // Create the alloc 150 alloc := mock.Alloc() 151 152 summary := mock.JobSummary(alloc.JobID) 153 if err := state.UpsertJobSummary(1, summary); err != nil { 154 t.Fatalf("err: %v", err) 155 } 156 // Upsert alloc triggers watches 157 time.AfterFunc(100*time.Millisecond, func() { 158 if err := state.UpsertAllocs(2, []*structs.Allocation{alloc}); err != nil { 159 t.Fatalf("err: %v", err) 160 } 161 }) 162 163 req := &structs.AllocListRequest{ 164 QueryOptions: structs.QueryOptions{ 165 Region: "global", 166 Namespace: structs.DefaultNamespace, 167 MinQueryIndex: 1, 168 }, 169 } 170 start := time.Now() 171 var resp structs.AllocListResponse 172 if err := msgpackrpc.CallWithCodec(codec, "Alloc.List", req, &resp); err != nil { 173 t.Fatalf("err: %v", err) 174 } 175 176 if elapsed := time.Since(start); elapsed < 100*time.Millisecond { 177 t.Fatalf("should block (returned in %s) %#v", elapsed, resp) 178 } 179 if resp.Index != 2 { 180 t.Fatalf("Bad index: %d %d", resp.Index, 2) 181 } 182 if len(resp.Allocations) != 1 || resp.Allocations[0].ID != alloc.ID { 183 t.Fatalf("bad: %#v", resp.Allocations) 184 } 185 186 // Client updates trigger watches 187 alloc2 := mock.Alloc() 188 alloc2.ID = alloc.ID 189 alloc2.ClientStatus = structs.AllocClientStatusRunning 190 time.AfterFunc(100*time.Millisecond, func() { 191 state.UpsertJobSummary(3, mock.JobSummary(alloc2.JobID)) 192 if err := state.UpdateAllocsFromClient(4, []*structs.Allocation{alloc2}); err != nil { 193 t.Fatalf("err: %v", err) 194 } 195 }) 196 197 req.MinQueryIndex = 3 198 start = time.Now() 199 var resp2 structs.AllocListResponse 200 if err := msgpackrpc.CallWithCodec(codec, "Alloc.List", req, &resp2); err != nil { 201 t.Fatalf("err: %v", err) 202 } 203 204 if elapsed := time.Since(start); elapsed < 100*time.Millisecond { 205 t.Fatalf("should block (returned in %s) %#v", elapsed, resp2) 206 } 207 if resp2.Index != 4 { 208 t.Fatalf("Bad index: %d %d", resp2.Index, 4) 209 } 210 if len(resp2.Allocations) != 1 || resp.Allocations[0].ID != alloc.ID || 211 resp2.Allocations[0].ClientStatus != structs.AllocClientStatusRunning { 212 t.Fatalf("bad: %#v", resp2.Allocations) 213 } 214 } 215 216 func TestAllocEndpoint_GetAlloc(t *testing.T) { 217 t.Parallel() 218 s1 := TestServer(t, nil) 219 defer s1.Shutdown() 220 codec := rpcClient(t, s1) 221 testutil.WaitForLeader(t, s1.RPC) 222 223 // Create the register request 224 prevAllocID := uuid.Generate() 225 alloc := mock.Alloc() 226 alloc.RescheduleTracker = &structs.RescheduleTracker{ 227 Events: []*structs.RescheduleEvent{ 228 {RescheduleTime: time.Now().UTC().UnixNano(), PrevNodeID: "boom", PrevAllocID: prevAllocID}, 229 }, 230 } 231 state := s1.fsm.State() 232 state.UpsertJobSummary(999, mock.JobSummary(alloc.JobID)) 233 err := state.UpsertAllocs(1000, []*structs.Allocation{alloc}) 234 if err != nil { 235 t.Fatalf("err: %v", err) 236 } 237 238 // Lookup the alloc 239 get := &structs.AllocSpecificRequest{ 240 AllocID: alloc.ID, 241 QueryOptions: structs.QueryOptions{Region: "global"}, 242 } 243 var resp structs.SingleAllocResponse 244 if err := msgpackrpc.CallWithCodec(codec, "Alloc.GetAlloc", get, &resp); err != nil { 245 t.Fatalf("err: %v", err) 246 } 247 if resp.Index != 1000 { 248 t.Fatalf("Bad index: %d %d", resp.Index, 1000) 249 } 250 251 if !reflect.DeepEqual(alloc, resp.Alloc) { 252 t.Fatalf("bad: %#v", resp.Alloc) 253 } 254 } 255 256 func TestAllocEndpoint_GetAlloc_ACL(t *testing.T) { 257 t.Parallel() 258 s1, root := TestACLServer(t, nil) 259 defer s1.Shutdown() 260 codec := rpcClient(t, s1) 261 testutil.WaitForLeader(t, s1.RPC) 262 assert := assert.New(t) 263 264 // Create the alloc 265 alloc := mock.Alloc() 266 allocs := []*structs.Allocation{alloc} 267 summary := mock.JobSummary(alloc.JobID) 268 state := s1.fsm.State() 269 270 assert.Nil(state.UpsertJobSummary(999, summary), "UpsertJobSummary") 271 assert.Nil(state.UpsertAllocs(1000, allocs), "UpsertAllocs") 272 273 // Create the namespace policy and tokens 274 validToken := mock.CreatePolicyAndToken(t, state, 1001, "test-valid", 275 mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityReadJob})) 276 invalidToken := mock.CreatePolicyAndToken(t, state, 1003, "test-invalid", 277 mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityListJobs})) 278 279 get := &structs.AllocSpecificRequest{ 280 AllocID: alloc.ID, 281 QueryOptions: structs.QueryOptions{Region: "global"}, 282 } 283 284 // Lookup the alloc without a token and expect failure 285 { 286 var resp structs.SingleAllocResponse 287 err := msgpackrpc.CallWithCodec(codec, "Alloc.GetAlloc", get, &resp) 288 assert.Equal(structs.ErrPermissionDenied.Error(), err.Error()) 289 } 290 291 // Try with a valid ACL token 292 { 293 get.AuthToken = validToken.SecretID 294 var resp structs.SingleAllocResponse 295 assert.Nil(msgpackrpc.CallWithCodec(codec, "Alloc.GetAlloc", get, &resp), "RPC") 296 assert.EqualValues(resp.Index, 1000, "resp.Index") 297 assert.Equal(alloc, resp.Alloc, "Returned alloc not equal") 298 } 299 300 // Try with a valid Node.SecretID 301 { 302 node := mock.Node() 303 assert.Nil(state.UpsertNode(1005, node)) 304 get.AuthToken = node.SecretID 305 var resp structs.SingleAllocResponse 306 assert.Nil(msgpackrpc.CallWithCodec(codec, "Alloc.GetAlloc", get, &resp), "RPC") 307 assert.EqualValues(resp.Index, 1000, "resp.Index") 308 assert.Equal(alloc, resp.Alloc, "Returned alloc not equal") 309 } 310 311 // Try with a invalid token 312 { 313 get.AuthToken = invalidToken.SecretID 314 var resp structs.SingleAllocResponse 315 err := msgpackrpc.CallWithCodec(codec, "Alloc.GetAlloc", get, &resp) 316 assert.NotNil(err, "RPC") 317 assert.Equal(err.Error(), structs.ErrPermissionDenied.Error()) 318 } 319 320 // Try with a root token 321 { 322 get.AuthToken = root.SecretID 323 var resp structs.SingleAllocResponse 324 assert.Nil(msgpackrpc.CallWithCodec(codec, "Alloc.GetAlloc", get, &resp), "RPC") 325 assert.EqualValues(resp.Index, 1000, "resp.Index") 326 assert.Equal(alloc, resp.Alloc, "Returned alloc not equal") 327 } 328 } 329 330 func TestAllocEndpoint_GetAlloc_Blocking(t *testing.T) { 331 t.Parallel() 332 s1 := TestServer(t, nil) 333 defer s1.Shutdown() 334 state := s1.fsm.State() 335 codec := rpcClient(t, s1) 336 testutil.WaitForLeader(t, s1.RPC) 337 338 // Create the allocs 339 alloc1 := mock.Alloc() 340 alloc2 := mock.Alloc() 341 342 // First create an unrelated alloc 343 time.AfterFunc(100*time.Millisecond, func() { 344 state.UpsertJobSummary(99, mock.JobSummary(alloc1.JobID)) 345 err := state.UpsertAllocs(100, []*structs.Allocation{alloc1}) 346 if err != nil { 347 t.Fatalf("err: %v", err) 348 } 349 }) 350 351 // Create the alloc we are watching later 352 time.AfterFunc(200*time.Millisecond, func() { 353 state.UpsertJobSummary(199, mock.JobSummary(alloc2.JobID)) 354 err := state.UpsertAllocs(200, []*structs.Allocation{alloc2}) 355 if err != nil { 356 t.Fatalf("err: %v", err) 357 } 358 }) 359 360 // Lookup the allocs 361 get := &structs.AllocSpecificRequest{ 362 AllocID: alloc2.ID, 363 QueryOptions: structs.QueryOptions{ 364 Region: "global", 365 MinQueryIndex: 150, 366 }, 367 } 368 var resp structs.SingleAllocResponse 369 start := time.Now() 370 if err := msgpackrpc.CallWithCodec(codec, "Alloc.GetAlloc", get, &resp); err != nil { 371 t.Fatalf("err: %v", err) 372 } 373 374 if elapsed := time.Since(start); elapsed < 200*time.Millisecond { 375 t.Fatalf("should block (returned in %s) %#v", elapsed, resp) 376 } 377 if resp.Index != 200 { 378 t.Fatalf("Bad index: %d %d", resp.Index, 200) 379 } 380 if resp.Alloc == nil || resp.Alloc.ID != alloc2.ID { 381 t.Fatalf("bad: %#v", resp.Alloc) 382 } 383 } 384 385 func TestAllocEndpoint_GetAllocs(t *testing.T) { 386 t.Parallel() 387 s1 := TestServer(t, nil) 388 defer s1.Shutdown() 389 codec := rpcClient(t, s1) 390 testutil.WaitForLeader(t, s1.RPC) 391 392 // Create the register request 393 alloc := mock.Alloc() 394 alloc2 := mock.Alloc() 395 state := s1.fsm.State() 396 state.UpsertJobSummary(998, mock.JobSummary(alloc.JobID)) 397 state.UpsertJobSummary(999, mock.JobSummary(alloc2.JobID)) 398 err := state.UpsertAllocs(1000, []*structs.Allocation{alloc, alloc2}) 399 if err != nil { 400 t.Fatalf("err: %v", err) 401 } 402 403 // Lookup the allocs 404 get := &structs.AllocsGetRequest{ 405 AllocIDs: []string{alloc.ID, alloc2.ID}, 406 QueryOptions: structs.QueryOptions{ 407 Region: "global", 408 }, 409 } 410 var resp structs.AllocsGetResponse 411 if err := msgpackrpc.CallWithCodec(codec, "Alloc.GetAllocs", get, &resp); err != nil { 412 t.Fatalf("err: %v", err) 413 } 414 if resp.Index != 1000 { 415 t.Fatalf("Bad index: %d %d", resp.Index, 1000) 416 } 417 418 if len(resp.Allocs) != 2 { 419 t.Fatalf("bad: %#v", resp.Allocs) 420 } 421 422 // Lookup nonexistent allocs. 423 get = &structs.AllocsGetRequest{ 424 AllocIDs: []string{"foo"}, 425 QueryOptions: structs.QueryOptions{Region: "global"}, 426 } 427 if err := msgpackrpc.CallWithCodec(codec, "Alloc.GetAllocs", get, &resp); err == nil { 428 t.Fatalf("expect error") 429 } 430 } 431 432 func TestAllocEndpoint_GetAllocs_Blocking(t *testing.T) { 433 t.Parallel() 434 s1 := TestServer(t, nil) 435 defer s1.Shutdown() 436 state := s1.fsm.State() 437 codec := rpcClient(t, s1) 438 testutil.WaitForLeader(t, s1.RPC) 439 440 // Create the allocs 441 alloc1 := mock.Alloc() 442 alloc2 := mock.Alloc() 443 444 // First create an unrelated alloc 445 time.AfterFunc(100*time.Millisecond, func() { 446 state.UpsertJobSummary(99, mock.JobSummary(alloc1.JobID)) 447 err := state.UpsertAllocs(100, []*structs.Allocation{alloc1}) 448 if err != nil { 449 t.Fatalf("err: %v", err) 450 } 451 }) 452 453 // Create the alloc we are watching later 454 time.AfterFunc(200*time.Millisecond, func() { 455 state.UpsertJobSummary(199, mock.JobSummary(alloc2.JobID)) 456 err := state.UpsertAllocs(200, []*structs.Allocation{alloc2}) 457 if err != nil { 458 t.Fatalf("err: %v", err) 459 } 460 }) 461 462 // Lookup the allocs 463 get := &structs.AllocsGetRequest{ 464 AllocIDs: []string{alloc1.ID, alloc2.ID}, 465 QueryOptions: structs.QueryOptions{ 466 Region: "global", 467 MinQueryIndex: 150, 468 }, 469 } 470 var resp structs.AllocsGetResponse 471 start := time.Now() 472 if err := msgpackrpc.CallWithCodec(codec, "Alloc.GetAllocs", get, &resp); err != nil { 473 t.Fatalf("err: %v", err) 474 } 475 476 if elapsed := time.Since(start); elapsed < 200*time.Millisecond { 477 t.Fatalf("should block (returned in %s) %#v", elapsed, resp) 478 } 479 if resp.Index != 200 { 480 t.Fatalf("Bad index: %d %d", resp.Index, 200) 481 } 482 if len(resp.Allocs) != 2 { 483 t.Fatalf("bad: %#v", resp.Allocs) 484 } 485 } 486 487 func TestAllocEndpoint_UpdateDesiredTransition(t *testing.T) { 488 t.Parallel() 489 require := require.New(t) 490 491 s1, _ := TestACLServer(t, nil) 492 defer s1.Shutdown() 493 codec := rpcClient(t, s1) 494 testutil.WaitForLeader(t, s1.RPC) 495 496 // Create the register request 497 alloc := mock.Alloc() 498 alloc2 := mock.Alloc() 499 state := s1.fsm.State() 500 require.Nil(state.UpsertJobSummary(998, mock.JobSummary(alloc.JobID))) 501 require.Nil(state.UpsertJobSummary(999, mock.JobSummary(alloc2.JobID))) 502 require.Nil(state.UpsertAllocs(1000, []*structs.Allocation{alloc, alloc2})) 503 504 t1 := &structs.DesiredTransition{ 505 Migrate: helper.BoolToPtr(true), 506 } 507 508 // Update the allocs desired status 509 get := &structs.AllocUpdateDesiredTransitionRequest{ 510 Allocs: map[string]*structs.DesiredTransition{ 511 alloc.ID: t1, 512 alloc2.ID: t1, 513 }, 514 Evals: []*structs.Evaluation{ 515 { 516 ID: uuid.Generate(), 517 Namespace: alloc.Namespace, 518 Priority: alloc.Job.Priority, 519 Type: alloc.Job.Type, 520 TriggeredBy: structs.EvalTriggerNodeDrain, 521 JobID: alloc.Job.ID, 522 JobModifyIndex: alloc.Job.ModifyIndex, 523 Status: structs.EvalStatusPending, 524 }, 525 { 526 ID: uuid.Generate(), 527 Namespace: alloc2.Namespace, 528 Priority: alloc2.Job.Priority, 529 Type: alloc2.Job.Type, 530 TriggeredBy: structs.EvalTriggerNodeDrain, 531 JobID: alloc2.Job.ID, 532 JobModifyIndex: alloc2.Job.ModifyIndex, 533 Status: structs.EvalStatusPending, 534 }, 535 }, 536 WriteRequest: structs.WriteRequest{ 537 Region: "global", 538 }, 539 } 540 541 // Try without permissions 542 var resp structs.GenericResponse 543 err := msgpackrpc.CallWithCodec(codec, "Alloc.UpdateDesiredTransition", get, &resp) 544 require.NotNil(err) 545 require.True(structs.IsErrPermissionDenied(err)) 546 547 // Try with permissions 548 get.WriteRequest.AuthToken = s1.getLeaderAcl() 549 var resp2 structs.GenericResponse 550 require.Nil(msgpackrpc.CallWithCodec(codec, "Alloc.UpdateDesiredTransition", get, &resp2)) 551 require.NotZero(resp2.Index) 552 553 // Look up the allocations 554 out1, err := state.AllocByID(nil, alloc.ID) 555 require.Nil(err) 556 out2, err := state.AllocByID(nil, alloc.ID) 557 require.Nil(err) 558 e1, err := state.EvalByID(nil, get.Evals[0].ID) 559 require.Nil(err) 560 e2, err := state.EvalByID(nil, get.Evals[1].ID) 561 require.Nil(err) 562 563 require.NotNil(out1.DesiredTransition.Migrate) 564 require.NotNil(out2.DesiredTransition.Migrate) 565 require.NotNil(e1) 566 require.NotNil(e2) 567 require.True(*out1.DesiredTransition.Migrate) 568 require.True(*out2.DesiredTransition.Migrate) 569 }