github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/nomad/alloc_endpoint_test.go (about) 1 package nomad 2 3 import ( 4 "reflect" 5 "testing" 6 "time" 7 8 msgpackrpc "github.com/hashicorp/net-rpc-msgpackrpc" 9 "github.com/hashicorp/nomad/acl" 10 "github.com/hashicorp/nomad/ci" 11 "github.com/hashicorp/nomad/helper/pointer" 12 "github.com/hashicorp/nomad/helper/uuid" 13 "github.com/hashicorp/nomad/nomad/mock" 14 "github.com/hashicorp/nomad/nomad/structs" 15 "github.com/hashicorp/nomad/testutil" 16 "github.com/stretchr/testify/assert" 17 "github.com/stretchr/testify/require" 18 ) 19 20 func TestAllocEndpoint_List(t *testing.T) { 21 ci.Parallel(t) 22 23 s1, cleanupS1 := TestServer(t, nil) 24 defer cleanupS1() 25 26 codec := rpcClient(t, s1) 27 testutil.WaitForLeader(t, s1.RPC) 28 29 // Create the register request 30 alloc := mock.Alloc() 31 summary := mock.JobSummary(alloc.JobID) 32 state := s1.fsm.State() 33 34 if err := state.UpsertJobSummary(999, summary); err != nil { 35 t.Fatalf("err: %v", err) 36 } 37 if err := state.UpsertAllocs(structs.MsgTypeTestSetup, 1000, []*structs.Allocation{alloc}); err != nil { 38 t.Fatalf("err: %v", err) 39 } 40 41 // Lookup the allocations 42 get := &structs.AllocListRequest{ 43 QueryOptions: structs.QueryOptions{ 44 Region: "global", 45 Namespace: structs.DefaultNamespace, 46 }, 47 } 48 var resp structs.AllocListResponse 49 if err := msgpackrpc.CallWithCodec(codec, "Alloc.List", get, &resp); err != nil { 50 t.Fatalf("err: %v", err) 51 } 52 if resp.Index != 1000 { 53 t.Fatalf("Bad index: %d %d", resp.Index, 1000) 54 } 55 56 if len(resp.Allocations) != 1 { 57 t.Fatalf("bad: %#v", resp.Allocations) 58 } 59 if resp.Allocations[0].ID != alloc.ID { 60 t.Fatalf("bad: %#v", resp.Allocations[0]) 61 } 62 63 // Lookup the allocations by prefix 64 get = &structs.AllocListRequest{ 65 QueryOptions: structs.QueryOptions{ 66 Region: "global", 67 Namespace: structs.DefaultNamespace, 68 Prefix: alloc.ID[:4], 69 }, 70 } 71 72 var resp2 structs.AllocListResponse 73 require.NoError(t, msgpackrpc.CallWithCodec(codec, "Alloc.List", get, &resp2)) 74 require.Equal(t, uint64(1000), resp2.Index) 75 require.Len(t, resp2.Allocations, 1) 76 require.Equal(t, alloc.ID, resp2.Allocations[0].ID) 77 78 // Lookup allocations with a filter 79 get = &structs.AllocListRequest{ 80 QueryOptions: structs.QueryOptions{ 81 Region: "global", 82 Namespace: structs.DefaultNamespace, 83 Filter: "TaskGroup == web", 84 }, 85 } 86 87 var resp3 structs.AllocListResponse 88 require.NoError(t, msgpackrpc.CallWithCodec(codec, "Alloc.List", get, &resp3)) 89 require.Equal(t, uint64(1000), resp3.Index) 90 require.Len(t, resp3.Allocations, 1) 91 require.Equal(t, alloc.ID, resp3.Allocations[0].ID) 92 } 93 94 func TestAllocEndpoint_List_PaginationFiltering(t *testing.T) { 95 ci.Parallel(t) 96 s1, _, cleanupS1 := TestACLServer(t, nil) 97 defer cleanupS1() 98 codec := rpcClient(t, s1) 99 testutil.WaitForLeader(t, s1.RPC) 100 101 // create a set of allocs and field values to filter on. these are in the order 102 // that the state store will return them from the iterator (sorted by create 103 // index), for ease of writing tests. 104 mocks := []struct { 105 ids []string 106 namespace string 107 group string 108 }{ 109 {ids: []string{"aaaa1111-3350-4b4b-d185-0e1992ed43e9"}}, // 0 110 {ids: []string{"aaaaaa22-3350-4b4b-d185-0e1992ed43e9"}}, // 1 111 {ids: []string{"aaaaaa33-3350-4b4b-d185-0e1992ed43e9"}, namespace: "non-default"}, // 2 112 {ids: []string{"aaaaaaaa-3350-4b4b-d185-0e1992ed43e9"}, group: "bar"}, // 3 113 {ids: []string{"aaaaaabb-3350-4b4b-d185-0e1992ed43e9"}, group: "goo"}, // 4 114 {ids: []string{"aaaaaacc-3350-4b4b-d185-0e1992ed43e9"}}, // 5 115 {ids: []string{"aaaaaadd-3350-4b4b-d185-0e1992ed43e9"}, group: "bar"}, // 6 116 {ids: []string{"aaaaaaee-3350-4b4b-d185-0e1992ed43e9"}, group: "goo"}, // 7 117 {ids: []string{"aaaaaaff-3350-4b4b-d185-0e1992ed43e9"}, group: "bar"}, // 8 118 {ids: []string{"00000111-3350-4b4b-d185-0e1992ed43e9"}}, // 9 119 {ids: []string{ // 10 120 "00000222-3350-4b4b-d185-0e1992ed43e9", 121 "00000333-3350-4b4b-d185-0e1992ed43e9", 122 }}, 123 {}, // 11, index missing 124 {ids: []string{"bbbb1111-3350-4b4b-d185-0e1992ed43e9"}}, // 12 125 } 126 127 state := s1.fsm.State() 128 129 require.NoError(t, state.UpsertNamespaces(1099, []*structs.Namespace{ 130 {Name: "non-default"}, 131 })) 132 133 var allocs []*structs.Allocation 134 for i, m := range mocks { 135 allocsInTx := []*structs.Allocation{} 136 for _, id := range m.ids { 137 alloc := mock.Alloc() 138 alloc.ID = id 139 if m.namespace != "" { 140 alloc.Namespace = m.namespace 141 } 142 if m.group != "" { 143 alloc.TaskGroup = m.group 144 } 145 allocs = append(allocs, alloc) 146 allocsInTx = append(allocsInTx, alloc) 147 } 148 // other fields 149 index := 1000 + uint64(i) 150 require.NoError(t, state.UpsertAllocs(structs.MsgTypeTestSetup, index, allocsInTx)) 151 } 152 153 aclToken := mock.CreatePolicyAndToken(t, 154 state, 1100, "test-valid-read", 155 mock.NamespacePolicy("*", "read", nil), 156 ).SecretID 157 158 cases := []struct { 159 name string 160 namespace string 161 prefix string 162 nextToken string 163 pageSize int32 164 filter string 165 expIDs []string 166 expNextToken string 167 expErr string 168 }{ 169 { 170 name: "test01 size-2 page-1 ns-default", 171 pageSize: 2, 172 expIDs: []string{ // first two items 173 "aaaa1111-3350-4b4b-d185-0e1992ed43e9", 174 "aaaaaa22-3350-4b4b-d185-0e1992ed43e9", 175 }, 176 expNextToken: "1003.aaaaaaaa-3350-4b4b-d185-0e1992ed43e9", // next one in default ns 177 }, 178 { 179 name: "test02 size-2 page-1 ns-default with-prefix", 180 prefix: "aaaa", 181 pageSize: 2, 182 expIDs: []string{ 183 "aaaa1111-3350-4b4b-d185-0e1992ed43e9", 184 "aaaaaa22-3350-4b4b-d185-0e1992ed43e9", 185 }, 186 expNextToken: "aaaaaaaa-3350-4b4b-d185-0e1992ed43e9", 187 }, 188 { 189 name: "test03 size-2 page-2 ns-default", 190 pageSize: 2, 191 nextToken: "1003.aaaaaaaa-3350-4b4b-d185-0e1992ed43e9", 192 expNextToken: "1005.aaaaaacc-3350-4b4b-d185-0e1992ed43e9", 193 expIDs: []string{ 194 "aaaaaaaa-3350-4b4b-d185-0e1992ed43e9", 195 "aaaaaabb-3350-4b4b-d185-0e1992ed43e9", 196 }, 197 }, 198 { 199 name: "test04 size-2 page-2 ns-default with prefix", 200 prefix: "aaaa", 201 pageSize: 2, 202 nextToken: "aaaaaabb-3350-4b4b-d185-0e1992ed43e9", 203 expNextToken: "aaaaaadd-3350-4b4b-d185-0e1992ed43e9", 204 expIDs: []string{ 205 "aaaaaabb-3350-4b4b-d185-0e1992ed43e9", 206 "aaaaaacc-3350-4b4b-d185-0e1992ed43e9", 207 }, 208 }, 209 { 210 name: "test05 go-bexpr filter", 211 filter: `TaskGroup == "goo"`, 212 nextToken: "", 213 expIDs: []string{ 214 "aaaaaabb-3350-4b4b-d185-0e1992ed43e9", 215 "aaaaaaee-3350-4b4b-d185-0e1992ed43e9", 216 }, 217 }, 218 { 219 name: "test06 go-bexpr filter with pagination", 220 filter: `TaskGroup == "bar"`, 221 pageSize: 2, 222 expNextToken: "1008.aaaaaaff-3350-4b4b-d185-0e1992ed43e9", 223 expIDs: []string{ 224 "aaaaaaaa-3350-4b4b-d185-0e1992ed43e9", 225 "aaaaaadd-3350-4b4b-d185-0e1992ed43e9", 226 }, 227 }, 228 { 229 name: "test07 go-bexpr filter namespace", 230 namespace: "non-default", 231 filter: `ID contains "aaa"`, 232 expIDs: []string{ 233 "aaaaaa33-3350-4b4b-d185-0e1992ed43e9", 234 }, 235 }, 236 { 237 name: "test08 go-bexpr wrong namespace", 238 namespace: "default", 239 filter: `Namespace == "non-default"`, 240 expIDs: []string(nil), 241 }, 242 { 243 name: "test09 go-bexpr invalid expression", 244 filter: `NotValid`, 245 expErr: "failed to read filter expression", 246 }, 247 { 248 name: "test10 go-bexpr invalid field", 249 filter: `InvalidField == "value"`, 250 expErr: "error finding value in datum", 251 }, 252 { 253 name: "test11 non-lexicographic order", 254 pageSize: 1, 255 nextToken: "1009.00000111-3350-4b4b-d185-0e1992ed43e9", 256 expNextToken: "1010.00000222-3350-4b4b-d185-0e1992ed43e9", 257 expIDs: []string{ 258 "00000111-3350-4b4b-d185-0e1992ed43e9", 259 }, 260 }, 261 { 262 name: "test12 same index", 263 pageSize: 1, 264 nextToken: "1010.00000222-3350-4b4b-d185-0e1992ed43e9", 265 expNextToken: "1010.00000333-3350-4b4b-d185-0e1992ed43e9", 266 expIDs: []string{ 267 "00000222-3350-4b4b-d185-0e1992ed43e9", 268 }, 269 }, 270 { 271 name: "test13 missing index", 272 pageSize: 1, 273 nextToken: "1011.e9522802-0cd8-4b1d-9c9e-ab3d97938371", 274 expIDs: []string{ 275 "bbbb1111-3350-4b4b-d185-0e1992ed43e9", 276 }, 277 }, 278 } 279 280 for _, tc := range cases { 281 t.Run(tc.name, func(t *testing.T) { 282 var req = &structs.AllocListRequest{ 283 QueryOptions: structs.QueryOptions{ 284 Region: "global", 285 Namespace: tc.namespace, 286 Prefix: tc.prefix, 287 PerPage: tc.pageSize, 288 NextToken: tc.nextToken, 289 Filter: tc.filter, 290 }, 291 Fields: &structs.AllocStubFields{ 292 Resources: false, 293 TaskStates: false, 294 }, 295 } 296 req.AuthToken = aclToken 297 var resp structs.AllocListResponse 298 err := msgpackrpc.CallWithCodec(codec, "Alloc.List", req, &resp) 299 if tc.expErr == "" { 300 require.NoError(t, err) 301 } else { 302 require.Contains(t, err, tc.expErr) 303 } 304 305 var gotIDs []string 306 for _, alloc := range resp.Allocations { 307 gotIDs = append(gotIDs, alloc.ID) 308 } 309 require.Equal(t, tc.expIDs, gotIDs) 310 require.Equal(t, tc.expNextToken, resp.QueryMeta.NextToken) 311 }) 312 } 313 } 314 315 func TestAllocEndpoint_List_order(t *testing.T) { 316 ci.Parallel(t) 317 318 s1, cleanupS1 := TestServer(t, nil) 319 defer cleanupS1() 320 codec := rpcClient(t, s1) 321 testutil.WaitForLeader(t, s1.RPC) 322 323 // Create register requests 324 uuid1 := uuid.Generate() 325 alloc1 := mock.Alloc() 326 alloc1.ID = uuid1 327 328 uuid2 := uuid.Generate() 329 alloc2 := mock.Alloc() 330 alloc2.ID = uuid2 331 332 uuid3 := uuid.Generate() 333 alloc3 := mock.Alloc() 334 alloc3.ID = uuid3 335 336 err := s1.fsm.State().UpsertAllocs(structs.MsgTypeTestSetup, 1000, []*structs.Allocation{alloc1}) 337 require.NoError(t, err) 338 339 err = s1.fsm.State().UpsertAllocs(structs.MsgTypeTestSetup, 1001, []*structs.Allocation{alloc2}) 340 require.NoError(t, err) 341 342 err = s1.fsm.State().UpsertAllocs(structs.MsgTypeTestSetup, 1002, []*structs.Allocation{alloc3}) 343 require.NoError(t, err) 344 345 // update alloc2 again so we can later assert create index order did not change 346 err = s1.fsm.State().UpsertAllocs(structs.MsgTypeTestSetup, 1003, []*structs.Allocation{alloc2}) 347 require.NoError(t, err) 348 349 t.Run("default", func(t *testing.T) { 350 // Lookup the allocations in the default order (oldest first) 351 get := &structs.AllocListRequest{ 352 QueryOptions: structs.QueryOptions{ 353 Region: "global", 354 Namespace: "*", 355 }, 356 } 357 358 var resp structs.AllocListResponse 359 err = msgpackrpc.CallWithCodec(codec, "Alloc.List", get, &resp) 360 require.NoError(t, err) 361 require.Equal(t, uint64(1003), resp.Index) 362 require.Len(t, resp.Allocations, 3) 363 364 // Assert returned order is by CreateIndex (ascending) 365 require.Equal(t, uint64(1000), resp.Allocations[0].CreateIndex) 366 require.Equal(t, uuid1, resp.Allocations[0].ID) 367 368 require.Equal(t, uint64(1001), resp.Allocations[1].CreateIndex) 369 require.Equal(t, uuid2, resp.Allocations[1].ID) 370 371 require.Equal(t, uint64(1002), resp.Allocations[2].CreateIndex) 372 require.Equal(t, uuid3, resp.Allocations[2].ID) 373 }) 374 375 t.Run("reverse", func(t *testing.T) { 376 // Lookup the allocations in reverse order (newest first) 377 get := &structs.AllocListRequest{ 378 QueryOptions: structs.QueryOptions{ 379 Region: "global", 380 Namespace: "*", 381 Reverse: true, 382 }, 383 } 384 385 var resp structs.AllocListResponse 386 err = msgpackrpc.CallWithCodec(codec, "Alloc.List", get, &resp) 387 require.NoError(t, err) 388 require.Equal(t, uint64(1003), resp.Index) 389 require.Len(t, resp.Allocations, 3) 390 391 // Assert returned order is by CreateIndex (descending) 392 require.Equal(t, uint64(1002), resp.Allocations[0].CreateIndex) 393 require.Equal(t, uuid3, resp.Allocations[0].ID) 394 395 require.Equal(t, uint64(1001), resp.Allocations[1].CreateIndex) 396 require.Equal(t, uuid2, resp.Allocations[1].ID) 397 398 require.Equal(t, uint64(1000), resp.Allocations[2].CreateIndex) 399 require.Equal(t, uuid1, resp.Allocations[2].ID) 400 }) 401 } 402 403 func TestAllocEndpoint_List_Fields(t *testing.T) { 404 ci.Parallel(t) 405 406 s1, cleanupS1 := TestServer(t, nil) 407 defer cleanupS1() 408 409 codec := rpcClient(t, s1) 410 testutil.WaitForLeader(t, s1.RPC) 411 412 // Create a running alloc 413 alloc := mock.Alloc() 414 alloc.ClientStatus = structs.AllocClientStatusRunning 415 alloc.TaskStates = map[string]*structs.TaskState{ 416 "web": { 417 State: structs.TaskStateRunning, 418 StartedAt: time.Now(), 419 }, 420 } 421 summary := mock.JobSummary(alloc.JobID) 422 state := s1.fsm.State() 423 424 require.NoError(t, state.UpsertJobSummary(999, summary)) 425 require.NoError(t, state.UpsertAllocs(structs.MsgTypeTestSetup, 1000, []*structs.Allocation{alloc})) 426 427 cases := []struct { 428 Name string 429 Fields *structs.AllocStubFields 430 Assert func(t *testing.T, allocs []*structs.AllocListStub) 431 }{ 432 { 433 Name: "None", 434 Fields: nil, 435 Assert: func(t *testing.T, allocs []*structs.AllocListStub) { 436 require.Nil(t, allocs[0].AllocatedResources) 437 require.Len(t, allocs[0].TaskStates, 1) 438 }, 439 }, 440 { 441 Name: "Default", 442 Fields: structs.NewAllocStubFields(), 443 Assert: func(t *testing.T, allocs []*structs.AllocListStub) { 444 require.Nil(t, allocs[0].AllocatedResources) 445 require.Len(t, allocs[0].TaskStates, 1) 446 }, 447 }, 448 { 449 Name: "Resources", 450 Fields: &structs.AllocStubFields{ 451 Resources: true, 452 TaskStates: false, 453 }, 454 Assert: func(t *testing.T, allocs []*structs.AllocListStub) { 455 require.NotNil(t, allocs[0].AllocatedResources) 456 require.Len(t, allocs[0].TaskStates, 0) 457 }, 458 }, 459 { 460 Name: "NoTaskStates", 461 Fields: &structs.AllocStubFields{ 462 Resources: false, 463 TaskStates: false, 464 }, 465 Assert: func(t *testing.T, allocs []*structs.AllocListStub) { 466 require.Nil(t, allocs[0].AllocatedResources) 467 require.Len(t, allocs[0].TaskStates, 0) 468 }, 469 }, 470 { 471 Name: "Both", 472 Fields: &structs.AllocStubFields{ 473 Resources: true, 474 TaskStates: true, 475 }, 476 Assert: func(t *testing.T, allocs []*structs.AllocListStub) { 477 require.NotNil(t, allocs[0].AllocatedResources) 478 require.Len(t, allocs[0].TaskStates, 1) 479 }, 480 }, 481 } 482 483 for i := range cases { 484 tc := cases[i] 485 t.Run(tc.Name, func(t *testing.T) { 486 get := &structs.AllocListRequest{ 487 QueryOptions: structs.QueryOptions{ 488 Region: "global", 489 Namespace: structs.DefaultNamespace, 490 }, 491 Fields: tc.Fields, 492 } 493 var resp structs.AllocListResponse 494 require.NoError(t, msgpackrpc.CallWithCodec(codec, "Alloc.List", get, &resp)) 495 require.Equal(t, uint64(1000), resp.Index) 496 require.Len(t, resp.Allocations, 1) 497 require.Equal(t, alloc.ID, resp.Allocations[0].ID) 498 tc.Assert(t, resp.Allocations) 499 }) 500 } 501 502 } 503 504 func TestAllocEndpoint_List_ACL(t *testing.T) { 505 ci.Parallel(t) 506 507 s1, root, cleanupS1 := TestACLServer(t, nil) 508 defer cleanupS1() 509 codec := rpcClient(t, s1) 510 testutil.WaitForLeader(t, s1.RPC) 511 assert := assert.New(t) 512 513 // Create the alloc 514 alloc := mock.Alloc() 515 allocs := []*structs.Allocation{alloc} 516 summary := mock.JobSummary(alloc.JobID) 517 state := s1.fsm.State() 518 519 assert.Nil(state.UpsertJobSummary(999, summary), "UpsertJobSummary") 520 assert.Nil(state.UpsertAllocs(structs.MsgTypeTestSetup, 1000, allocs), "UpsertAllocs") 521 522 stubAllocs := []*structs.AllocListStub{alloc.Stub(nil)} 523 stubAllocs[0].CreateIndex = 1000 524 stubAllocs[0].ModifyIndex = 1000 525 526 // Create the namespace policy and tokens 527 validToken := mock.CreatePolicyAndToken(t, state, 1001, "test-valid", 528 mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityReadJob})) 529 invalidToken := mock.CreatePolicyAndToken(t, state, 1003, "test-invalid", 530 mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityListJobs})) 531 532 // Lookup the allocs without a token and expect failure 533 get := &structs.AllocListRequest{ 534 QueryOptions: structs.QueryOptions{ 535 Region: "global", 536 Namespace: structs.DefaultNamespace, 537 }, 538 } 539 var resp structs.AllocListResponse 540 assert.NotNil(msgpackrpc.CallWithCodec(codec, "Alloc.List", get, &resp), "RPC") 541 542 // Try with a valid token 543 get.AuthToken = validToken.SecretID 544 assert.Nil(msgpackrpc.CallWithCodec(codec, "Alloc.List", get, &resp), "RPC") 545 assert.EqualValues(resp.Index, 1000, "resp.Index") 546 assert.Equal(stubAllocs, resp.Allocations, "Returned alloc list not equal") 547 548 // Try with a invalid token 549 get.AuthToken = invalidToken.SecretID 550 err := msgpackrpc.CallWithCodec(codec, "Alloc.List", get, &resp) 551 assert.NotNil(err, "RPC") 552 assert.Equal(err.Error(), structs.ErrPermissionDenied.Error()) 553 554 // Try with a root token 555 get.AuthToken = root.SecretID 556 assert.Nil(msgpackrpc.CallWithCodec(codec, "Alloc.List", get, &resp), "RPC") 557 assert.EqualValues(resp.Index, 1000, "resp.Index") 558 assert.Equal(stubAllocs, resp.Allocations, "Returned alloc list not equal") 559 } 560 561 func TestAllocEndpoint_List_Blocking(t *testing.T) { 562 ci.Parallel(t) 563 564 s1, cleanupS1 := TestServer(t, nil) 565 defer cleanupS1() 566 state := s1.fsm.State() 567 codec := rpcClient(t, s1) 568 testutil.WaitForLeader(t, s1.RPC) 569 570 // Create the alloc 571 alloc := mock.Alloc() 572 573 summary := mock.JobSummary(alloc.JobID) 574 if err := state.UpsertJobSummary(1, summary); err != nil { 575 t.Fatalf("err: %v", err) 576 } 577 // Upsert alloc triggers watches 578 time.AfterFunc(100*time.Millisecond, func() { 579 if err := state.UpsertAllocs(structs.MsgTypeTestSetup, 2, []*structs.Allocation{alloc}); err != nil { 580 t.Fatalf("err: %v", err) 581 } 582 }) 583 584 req := &structs.AllocListRequest{ 585 QueryOptions: structs.QueryOptions{ 586 Region: "global", 587 Namespace: structs.DefaultNamespace, 588 MinQueryIndex: 1, 589 }, 590 } 591 start := time.Now() 592 var resp structs.AllocListResponse 593 if err := msgpackrpc.CallWithCodec(codec, "Alloc.List", req, &resp); err != nil { 594 t.Fatalf("err: %v", err) 595 } 596 597 if elapsed := time.Since(start); elapsed < 100*time.Millisecond { 598 t.Fatalf("should block (returned in %s) %#v", elapsed, resp) 599 } 600 if resp.Index != 2 { 601 t.Fatalf("Bad index: %d %d", resp.Index, 2) 602 } 603 if len(resp.Allocations) != 1 || resp.Allocations[0].ID != alloc.ID { 604 t.Fatalf("bad: %#v", resp.Allocations) 605 } 606 607 // Client updates trigger watches 608 alloc2 := mock.Alloc() 609 alloc2.ID = alloc.ID 610 alloc2.ClientStatus = structs.AllocClientStatusRunning 611 time.AfterFunc(100*time.Millisecond, func() { 612 state.UpsertJobSummary(3, mock.JobSummary(alloc2.JobID)) 613 if err := state.UpdateAllocsFromClient(structs.MsgTypeTestSetup, 4, []*structs.Allocation{alloc2}); err != nil { 614 t.Fatalf("err: %v", err) 615 } 616 }) 617 618 req.MinQueryIndex = 3 619 start = time.Now() 620 var resp2 structs.AllocListResponse 621 if err := msgpackrpc.CallWithCodec(codec, "Alloc.List", req, &resp2); err != nil { 622 t.Fatalf("err: %v", err) 623 } 624 625 if elapsed := time.Since(start); elapsed < 100*time.Millisecond { 626 t.Fatalf("should block (returned in %s) %#v", elapsed, resp2) 627 } 628 if resp2.Index != 4 { 629 t.Fatalf("Bad index: %d %d", resp2.Index, 4) 630 } 631 if len(resp2.Allocations) != 1 || resp.Allocations[0].ID != alloc.ID || 632 resp2.Allocations[0].ClientStatus != structs.AllocClientStatusRunning { 633 t.Fatalf("bad: %#v", resp2.Allocations) 634 } 635 } 636 637 // TestAllocEndpoint_List_AllNamespaces_OSS asserts that server 638 // returns all allocations across namespaces. 639 func TestAllocEndpoint_List_AllNamespaces_OSS(t *testing.T) { 640 ci.Parallel(t) 641 642 s1, cleanupS1 := TestServer(t, nil) 643 defer cleanupS1() 644 codec := rpcClient(t, s1) 645 testutil.WaitForLeader(t, s1.RPC) 646 state := s1.fsm.State() 647 648 // two namespaces 649 ns1 := mock.Namespace() 650 ns2 := mock.Namespace() 651 require.NoError(t, state.UpsertNamespaces(900, []*structs.Namespace{ns1, ns2})) 652 653 // Create the allocations 654 uuid1 := uuid.Generate() 655 alloc1 := mock.Alloc() 656 alloc1.ID = uuid1 657 alloc1.Namespace = ns1.Name 658 659 uuid2 := uuid.Generate() 660 alloc2 := mock.Alloc() 661 alloc2.ID = uuid2 662 alloc2.Namespace = ns2.Name 663 664 summary1 := mock.JobSummary(alloc1.JobID) 665 summary2 := mock.JobSummary(alloc2.JobID) 666 667 require.NoError(t, state.UpsertJobSummary(1000, summary1)) 668 require.NoError(t, state.UpsertJobSummary(1001, summary2)) 669 require.NoError(t, state.UpsertAllocs(structs.MsgTypeTestSetup, 1002, []*structs.Allocation{alloc1})) 670 require.NoError(t, state.UpsertAllocs(structs.MsgTypeTestSetup, 1003, []*structs.Allocation{alloc2})) 671 672 t.Run("looking up all allocations", func(t *testing.T) { 673 get := &structs.AllocListRequest{ 674 QueryOptions: structs.QueryOptions{ 675 Region: "global", 676 Namespace: "*", 677 }, 678 } 679 var resp structs.AllocListResponse 680 require.NoError(t, msgpackrpc.CallWithCodec(codec, "Alloc.List", get, &resp)) 681 require.Equal(t, uint64(1003), resp.Index) 682 require.Len(t, resp.Allocations, 2) 683 require.ElementsMatch(t, 684 []string{resp.Allocations[0].ID, resp.Allocations[1].ID}, 685 []string{alloc1.ID, alloc2.ID}) 686 }) 687 688 t.Run("looking up allocations with prefix", func(t *testing.T) { 689 get := &structs.AllocListRequest{ 690 QueryOptions: structs.QueryOptions{ 691 Region: "global", 692 Namespace: "*", 693 // allocations were constructed above to have non-matching prefix 694 Prefix: alloc1.ID[:4], 695 }, 696 } 697 var resp structs.AllocListResponse 698 require.NoError(t, msgpackrpc.CallWithCodec(codec, "Alloc.List", get, &resp)) 699 require.Equal(t, uint64(1003), resp.Index) 700 require.Len(t, resp.Allocations, 1) 701 require.Equal(t, alloc1.ID, resp.Allocations[0].ID) 702 require.Equal(t, alloc1.Namespace, resp.Allocations[0].Namespace) 703 }) 704 705 t.Run("looking up allocations with mismatch prefix", func(t *testing.T) { 706 get := &structs.AllocListRequest{ 707 QueryOptions: structs.QueryOptions{ 708 Region: "global", 709 Namespace: "*", 710 Prefix: "000000", // unlikely to match 711 }, 712 } 713 var resp structs.AllocListResponse 714 require.NoError(t, msgpackrpc.CallWithCodec(codec, "Alloc.List", get, &resp)) 715 require.Equal(t, uint64(1003), resp.Index) 716 require.Empty(t, resp.Allocations) 717 }) 718 } 719 720 func TestAllocEndpoint_GetAlloc(t *testing.T) { 721 ci.Parallel(t) 722 723 s1, cleanupS1 := TestServer(t, nil) 724 defer cleanupS1() 725 codec := rpcClient(t, s1) 726 testutil.WaitForLeader(t, s1.RPC) 727 728 // Create the register request 729 prevAllocID := uuid.Generate() 730 alloc := mock.Alloc() 731 alloc.RescheduleTracker = &structs.RescheduleTracker{ 732 Events: []*structs.RescheduleEvent{ 733 {RescheduleTime: time.Now().UTC().UnixNano(), PrevNodeID: "boom", PrevAllocID: prevAllocID}, 734 }, 735 } 736 state := s1.fsm.State() 737 state.UpsertJobSummary(999, mock.JobSummary(alloc.JobID)) 738 err := state.UpsertAllocs(structs.MsgTypeTestSetup, 1000, []*structs.Allocation{alloc}) 739 if err != nil { 740 t.Fatalf("err: %v", err) 741 } 742 743 // Lookup the alloc 744 get := &structs.AllocSpecificRequest{ 745 AllocID: alloc.ID, 746 QueryOptions: structs.QueryOptions{Region: "global"}, 747 } 748 var resp structs.SingleAllocResponse 749 if err := msgpackrpc.CallWithCodec(codec, "Alloc.GetAlloc", get, &resp); err != nil { 750 t.Fatalf("err: %v", err) 751 } 752 if resp.Index != 1000 { 753 t.Fatalf("Bad index: %d %d", resp.Index, 1000) 754 } 755 756 if !reflect.DeepEqual(alloc, resp.Alloc) { 757 t.Fatalf("bad: %#v", resp.Alloc) 758 } 759 } 760 761 func TestAllocEndpoint_GetAlloc_ACL(t *testing.T) { 762 ci.Parallel(t) 763 764 s1, root, cleanupS1 := TestACLServer(t, nil) 765 defer cleanupS1() 766 codec := rpcClient(t, s1) 767 testutil.WaitForLeader(t, s1.RPC) 768 assert := assert.New(t) 769 770 // Create the alloc 771 alloc := mock.Alloc() 772 allocs := []*structs.Allocation{alloc} 773 summary := mock.JobSummary(alloc.JobID) 774 state := s1.fsm.State() 775 776 assert.Nil(state.UpsertJobSummary(999, summary), "UpsertJobSummary") 777 assert.Nil(state.UpsertAllocs(structs.MsgTypeTestSetup, 1000, allocs), "UpsertAllocs") 778 779 // Create the namespace policy and tokens 780 validToken := mock.CreatePolicyAndToken(t, state, 1001, "test-valid", 781 mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityReadJob})) 782 invalidToken := mock.CreatePolicyAndToken(t, state, 1003, "test-invalid", 783 mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityListJobs})) 784 785 getReq := func() *structs.AllocSpecificRequest { 786 return &structs.AllocSpecificRequest{ 787 AllocID: alloc.ID, 788 QueryOptions: structs.QueryOptions{ 789 Region: "global", 790 }, 791 } 792 } 793 794 cases := []struct { 795 Name string 796 F func(t *testing.T) 797 }{ 798 // Lookup the alloc without a token and expect failure 799 { 800 Name: "no-token", 801 F: func(t *testing.T) { 802 var resp structs.SingleAllocResponse 803 err := msgpackrpc.CallWithCodec(codec, "Alloc.GetAlloc", getReq(), &resp) 804 require.True(t, structs.IsErrUnknownAllocation(err), "expected unknown alloc but found: %v", err) 805 }, 806 }, 807 808 // Try with a valid ACL token 809 { 810 Name: "valid-token", 811 F: func(t *testing.T) { 812 get := getReq() 813 get.AuthToken = validToken.SecretID 814 get.AllocID = alloc.ID 815 var resp structs.SingleAllocResponse 816 require.NoError(t, msgpackrpc.CallWithCodec(codec, "Alloc.GetAlloc", get, &resp), "RPC") 817 require.EqualValues(t, resp.Index, 1000, "resp.Index") 818 require.Equal(t, alloc, resp.Alloc, "Returned alloc not equal") 819 }, 820 }, 821 822 // Try with a valid Node.SecretID 823 { 824 Name: "valid-node-secret", 825 F: func(t *testing.T) { 826 node := mock.Node() 827 assert.Nil(state.UpsertNode(structs.MsgTypeTestSetup, 1005, node)) 828 get := getReq() 829 get.AuthToken = node.SecretID 830 get.AllocID = alloc.ID 831 var resp structs.SingleAllocResponse 832 require.NoError(t, msgpackrpc.CallWithCodec(codec, "Alloc.GetAlloc", get, &resp), "RPC") 833 require.EqualValues(t, resp.Index, 1000, "resp.Index") 834 require.Equal(t, alloc, resp.Alloc, "Returned alloc not equal") 835 }, 836 }, 837 838 // Try with a invalid token 839 { 840 Name: "invalid-token", 841 F: func(t *testing.T) { 842 get := getReq() 843 get.AuthToken = invalidToken.SecretID 844 get.AllocID = alloc.ID 845 var resp structs.SingleAllocResponse 846 err := msgpackrpc.CallWithCodec(codec, "Alloc.GetAlloc", get, &resp) 847 require.NotNil(t, err, "RPC") 848 require.True(t, structs.IsErrUnknownAllocation(err), "expected unknown alloc but found: %v", err) 849 }, 850 }, 851 852 // Try with a root token 853 { 854 Name: "root-token", 855 F: func(t *testing.T) { 856 get := getReq() 857 get.AuthToken = root.SecretID 858 get.AllocID = alloc.ID 859 var resp structs.SingleAllocResponse 860 require.NoError(t, msgpackrpc.CallWithCodec(codec, "Alloc.GetAlloc", get, &resp), "RPC") 861 require.EqualValues(t, resp.Index, 1000, "resp.Index") 862 require.Equal(t, alloc, resp.Alloc, "Returned alloc not equal") 863 }, 864 }, 865 } 866 867 for _, tc := range cases { 868 t.Run(tc.Name, tc.F) 869 } 870 } 871 872 func TestAllocEndpoint_GetAlloc_Blocking(t *testing.T) { 873 ci.Parallel(t) 874 875 s1, cleanupS1 := TestServer(t, nil) 876 defer cleanupS1() 877 state := s1.fsm.State() 878 codec := rpcClient(t, s1) 879 testutil.WaitForLeader(t, s1.RPC) 880 881 // Create the allocs 882 alloc1 := mock.Alloc() 883 alloc2 := mock.Alloc() 884 885 // First create an unrelated alloc 886 time.AfterFunc(100*time.Millisecond, func() { 887 state.UpsertJobSummary(99, mock.JobSummary(alloc1.JobID)) 888 err := state.UpsertAllocs(structs.MsgTypeTestSetup, 100, []*structs.Allocation{alloc1}) 889 if err != nil { 890 t.Fatalf("err: %v", err) 891 } 892 }) 893 894 // Create the alloc we are watching later 895 time.AfterFunc(200*time.Millisecond, func() { 896 state.UpsertJobSummary(199, mock.JobSummary(alloc2.JobID)) 897 err := state.UpsertAllocs(structs.MsgTypeTestSetup, 200, []*structs.Allocation{alloc2}) 898 if err != nil { 899 t.Fatalf("err: %v", err) 900 } 901 }) 902 903 // Lookup the allocs 904 get := &structs.AllocSpecificRequest{ 905 AllocID: alloc2.ID, 906 QueryOptions: structs.QueryOptions{ 907 Region: "global", 908 MinQueryIndex: 150, 909 }, 910 } 911 var resp structs.SingleAllocResponse 912 start := time.Now() 913 if err := msgpackrpc.CallWithCodec(codec, "Alloc.GetAlloc", get, &resp); err != nil { 914 t.Fatalf("err: %v", err) 915 } 916 917 if elapsed := time.Since(start); elapsed < 200*time.Millisecond { 918 t.Fatalf("should block (returned in %s) %#v", elapsed, resp) 919 } 920 if resp.Index != 200 { 921 t.Fatalf("Bad index: %d %d", resp.Index, 200) 922 } 923 if resp.Alloc == nil || resp.Alloc.ID != alloc2.ID { 924 t.Fatalf("bad: %#v", resp.Alloc) 925 } 926 } 927 928 func TestAllocEndpoint_GetAllocs(t *testing.T) { 929 ci.Parallel(t) 930 931 s1, cleanupS1 := TestServer(t, nil) 932 defer cleanupS1() 933 codec := rpcClient(t, s1) 934 testutil.WaitForLeader(t, s1.RPC) 935 936 // Create the register request 937 alloc := mock.Alloc() 938 alloc2 := mock.Alloc() 939 state := s1.fsm.State() 940 state.UpsertJobSummary(998, mock.JobSummary(alloc.JobID)) 941 state.UpsertJobSummary(999, mock.JobSummary(alloc2.JobID)) 942 err := state.UpsertAllocs(structs.MsgTypeTestSetup, 1000, []*structs.Allocation{alloc, alloc2}) 943 if err != nil { 944 t.Fatalf("err: %v", err) 945 } 946 947 // Lookup the allocs 948 get := &structs.AllocsGetRequest{ 949 AllocIDs: []string{alloc.ID, alloc2.ID}, 950 QueryOptions: structs.QueryOptions{ 951 Region: "global", 952 }, 953 } 954 var resp structs.AllocsGetResponse 955 if err := msgpackrpc.CallWithCodec(codec, "Alloc.GetAllocs", get, &resp); err != nil { 956 t.Fatalf("err: %v", err) 957 } 958 if resp.Index != 1000 { 959 t.Fatalf("Bad index: %d %d", resp.Index, 1000) 960 } 961 962 if len(resp.Allocs) != 2 { 963 t.Fatalf("bad: %#v", resp.Allocs) 964 } 965 966 // Lookup nonexistent allocs. 967 get = &structs.AllocsGetRequest{ 968 AllocIDs: []string{"foo"}, 969 QueryOptions: structs.QueryOptions{Region: "global"}, 970 } 971 if err := msgpackrpc.CallWithCodec(codec, "Alloc.GetAllocs", get, &resp); err == nil { 972 t.Fatalf("expect error") 973 } 974 } 975 976 func TestAllocEndpoint_GetAllocs_Blocking(t *testing.T) { 977 ci.Parallel(t) 978 979 s1, cleanupS1 := TestServer(t, nil) 980 defer cleanupS1() 981 state := s1.fsm.State() 982 codec := rpcClient(t, s1) 983 testutil.WaitForLeader(t, s1.RPC) 984 985 // Create the allocs 986 alloc1 := mock.Alloc() 987 alloc2 := mock.Alloc() 988 989 // First create an unrelated alloc 990 time.AfterFunc(100*time.Millisecond, func() { 991 state.UpsertJobSummary(99, mock.JobSummary(alloc1.JobID)) 992 err := state.UpsertAllocs(structs.MsgTypeTestSetup, 100, []*structs.Allocation{alloc1}) 993 if err != nil { 994 t.Fatalf("err: %v", err) 995 } 996 }) 997 998 // Create the alloc we are watching later 999 time.AfterFunc(200*time.Millisecond, func() { 1000 state.UpsertJobSummary(199, mock.JobSummary(alloc2.JobID)) 1001 err := state.UpsertAllocs(structs.MsgTypeTestSetup, 200, []*structs.Allocation{alloc2}) 1002 if err != nil { 1003 t.Fatalf("err: %v", err) 1004 } 1005 }) 1006 1007 // Lookup the allocs 1008 get := &structs.AllocsGetRequest{ 1009 AllocIDs: []string{alloc1.ID, alloc2.ID}, 1010 QueryOptions: structs.QueryOptions{ 1011 Region: "global", 1012 MinQueryIndex: 150, 1013 }, 1014 } 1015 var resp structs.AllocsGetResponse 1016 start := time.Now() 1017 if err := msgpackrpc.CallWithCodec(codec, "Alloc.GetAllocs", get, &resp); err != nil { 1018 t.Fatalf("err: %v", err) 1019 } 1020 1021 if elapsed := time.Since(start); elapsed < 200*time.Millisecond { 1022 t.Fatalf("should block (returned in %s) %#v", elapsed, resp) 1023 } 1024 if resp.Index != 200 { 1025 t.Fatalf("Bad index: %d %d", resp.Index, 200) 1026 } 1027 if len(resp.Allocs) != 2 { 1028 t.Fatalf("bad: %#v", resp.Allocs) 1029 } 1030 } 1031 1032 func TestAllocEndpoint_UpdateDesiredTransition(t *testing.T) { 1033 ci.Parallel(t) 1034 require := require.New(t) 1035 1036 s1, _, cleanupS1 := TestACLServer(t, nil) 1037 defer cleanupS1() 1038 codec := rpcClient(t, s1) 1039 testutil.WaitForLeader(t, s1.RPC) 1040 1041 // Create the register request 1042 alloc := mock.Alloc() 1043 alloc2 := mock.Alloc() 1044 state := s1.fsm.State() 1045 require.Nil(state.UpsertJobSummary(998, mock.JobSummary(alloc.JobID))) 1046 require.Nil(state.UpsertJobSummary(999, mock.JobSummary(alloc2.JobID))) 1047 require.Nil(state.UpsertAllocs(structs.MsgTypeTestSetup, 1000, []*structs.Allocation{alloc, alloc2})) 1048 1049 t1 := &structs.DesiredTransition{ 1050 Migrate: pointer.Of(true), 1051 } 1052 1053 // Update the allocs desired status 1054 get := &structs.AllocUpdateDesiredTransitionRequest{ 1055 Allocs: map[string]*structs.DesiredTransition{ 1056 alloc.ID: t1, 1057 alloc2.ID: t1, 1058 }, 1059 Evals: []*structs.Evaluation{ 1060 { 1061 ID: uuid.Generate(), 1062 Namespace: alloc.Namespace, 1063 Priority: alloc.Job.Priority, 1064 Type: alloc.Job.Type, 1065 TriggeredBy: structs.EvalTriggerNodeDrain, 1066 JobID: alloc.Job.ID, 1067 JobModifyIndex: alloc.Job.ModifyIndex, 1068 Status: structs.EvalStatusPending, 1069 }, 1070 { 1071 ID: uuid.Generate(), 1072 Namespace: alloc2.Namespace, 1073 Priority: alloc2.Job.Priority, 1074 Type: alloc2.Job.Type, 1075 TriggeredBy: structs.EvalTriggerNodeDrain, 1076 JobID: alloc2.Job.ID, 1077 JobModifyIndex: alloc2.Job.ModifyIndex, 1078 Status: structs.EvalStatusPending, 1079 }, 1080 }, 1081 WriteRequest: structs.WriteRequest{ 1082 Region: "global", 1083 }, 1084 } 1085 1086 // Try without permissions 1087 var resp structs.GenericResponse 1088 err := msgpackrpc.CallWithCodec(codec, "Alloc.UpdateDesiredTransition", get, &resp) 1089 require.NotNil(err) 1090 require.True(structs.IsErrPermissionDenied(err)) 1091 1092 // Try with permissions 1093 get.WriteRequest.AuthToken = s1.getLeaderAcl() 1094 var resp2 structs.GenericResponse 1095 require.Nil(msgpackrpc.CallWithCodec(codec, "Alloc.UpdateDesiredTransition", get, &resp2)) 1096 require.NotZero(resp2.Index) 1097 1098 // Look up the allocations 1099 out1, err := state.AllocByID(nil, alloc.ID) 1100 require.Nil(err) 1101 out2, err := state.AllocByID(nil, alloc.ID) 1102 require.Nil(err) 1103 e1, err := state.EvalByID(nil, get.Evals[0].ID) 1104 require.Nil(err) 1105 e2, err := state.EvalByID(nil, get.Evals[1].ID) 1106 require.Nil(err) 1107 1108 require.NotNil(out1.DesiredTransition.Migrate) 1109 require.NotNil(out2.DesiredTransition.Migrate) 1110 require.NotNil(e1) 1111 require.NotNil(e2) 1112 require.True(*out1.DesiredTransition.Migrate) 1113 require.True(*out2.DesiredTransition.Migrate) 1114 } 1115 1116 func TestAllocEndpoint_Stop_ACL(t *testing.T) { 1117 ci.Parallel(t) 1118 require := require.New(t) 1119 1120 s1, _, cleanupS1 := TestACLServer(t, nil) 1121 defer cleanupS1() 1122 codec := rpcClient(t, s1) 1123 testutil.WaitForLeader(t, s1.RPC) 1124 1125 // Create the register request 1126 alloc := mock.Alloc() 1127 alloc2 := mock.Alloc() 1128 state := s1.fsm.State() 1129 require.Nil(state.UpsertJobSummary(998, mock.JobSummary(alloc.JobID))) 1130 require.Nil(state.UpsertJobSummary(999, mock.JobSummary(alloc2.JobID))) 1131 require.Nil(state.UpsertAllocs(structs.MsgTypeTestSetup, 1000, []*structs.Allocation{alloc, alloc2})) 1132 1133 req := &structs.AllocStopRequest{ 1134 AllocID: alloc.ID, 1135 } 1136 req.Namespace = structs.DefaultNamespace 1137 req.Region = alloc.Job.Region 1138 1139 // Try without permissions 1140 var resp structs.AllocStopResponse 1141 err := msgpackrpc.CallWithCodec(codec, "Alloc.Stop", req, &resp) 1142 require.True(structs.IsErrPermissionDenied(err), "expected permissions error, got: %v", err) 1143 1144 // Try with management permissions 1145 req.WriteRequest.AuthToken = s1.getLeaderAcl() 1146 var resp2 structs.AllocStopResponse 1147 require.Nil(msgpackrpc.CallWithCodec(codec, "Alloc.Stop", req, &resp2)) 1148 require.NotZero(resp2.Index) 1149 1150 // Try with alloc-lifecycle permissions 1151 validToken := mock.CreatePolicyAndToken(t, state, 1002, "valid", 1152 mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityAllocLifecycle})) 1153 req.WriteRequest.AuthToken = validToken.SecretID 1154 req.AllocID = alloc2.ID 1155 1156 var resp3 structs.AllocStopResponse 1157 require.Nil(msgpackrpc.CallWithCodec(codec, "Alloc.Stop", req, &resp3)) 1158 require.NotZero(resp3.Index) 1159 1160 // Look up the allocations 1161 out1, err := state.AllocByID(nil, alloc.ID) 1162 require.Nil(err) 1163 out2, err := state.AllocByID(nil, alloc2.ID) 1164 require.Nil(err) 1165 e1, err := state.EvalByID(nil, resp2.EvalID) 1166 require.Nil(err) 1167 e2, err := state.EvalByID(nil, resp3.EvalID) 1168 require.Nil(err) 1169 1170 require.NotNil(out1.DesiredTransition.Migrate) 1171 require.NotNil(out2.DesiredTransition.Migrate) 1172 require.NotNil(e1) 1173 require.NotNil(e2) 1174 require.True(*out1.DesiredTransition.Migrate) 1175 require.True(*out2.DesiredTransition.Migrate) 1176 } 1177 1178 func TestAllocEndpoint_List_AllNamespaces_ACL_OSS(t *testing.T) { 1179 ci.Parallel(t) 1180 1181 s1, root, cleanupS1 := TestACLServer(t, nil) 1182 defer cleanupS1() 1183 codec := rpcClient(t, s1) 1184 testutil.WaitForLeader(t, s1.RPC) 1185 state := s1.fsm.State() 1186 1187 // two namespaces 1188 ns1 := mock.Namespace() 1189 ns2 := mock.Namespace() 1190 require.NoError(t, state.UpsertNamespaces(900, []*structs.Namespace{ns1, ns2})) 1191 1192 // Create the allocations 1193 alloc1 := mock.Alloc() 1194 alloc1.ID = "a" + alloc1.ID[1:] 1195 alloc1.Namespace = ns1.Name 1196 alloc2 := mock.Alloc() 1197 alloc2.ID = "b" + alloc2.ID[1:] 1198 alloc2.Namespace = ns2.Name 1199 summary1 := mock.JobSummary(alloc1.JobID) 1200 summary2 := mock.JobSummary(alloc2.JobID) 1201 1202 require.NoError(t, state.UpsertJobSummary(999, summary1)) 1203 require.NoError(t, state.UpsertJobSummary(999, summary2)) 1204 require.NoError(t, state.UpsertAllocs(structs.MsgTypeTestSetup, 1000, []*structs.Allocation{alloc1, alloc2})) 1205 alloc1.CreateIndex = 1000 1206 alloc1.ModifyIndex = 1000 1207 alloc2.CreateIndex = 1000 1208 alloc2.ModifyIndex = 1000 1209 1210 everythingButReadJob := []string{ 1211 acl.NamespaceCapabilityDeny, 1212 acl.NamespaceCapabilityListJobs, 1213 // acl.NamespaceCapabilityReadJob, 1214 acl.NamespaceCapabilitySubmitJob, 1215 acl.NamespaceCapabilityDispatchJob, 1216 acl.NamespaceCapabilityReadLogs, 1217 acl.NamespaceCapabilityReadFS, 1218 acl.NamespaceCapabilityAllocExec, 1219 acl.NamespaceCapabilityAllocNodeExec, 1220 acl.NamespaceCapabilityAllocLifecycle, 1221 acl.NamespaceCapabilitySentinelOverride, 1222 acl.NamespaceCapabilityCSIRegisterPlugin, 1223 acl.NamespaceCapabilityCSIWriteVolume, 1224 acl.NamespaceCapabilityCSIReadVolume, 1225 acl.NamespaceCapabilityCSIListVolume, 1226 acl.NamespaceCapabilityCSIMountVolume, 1227 acl.NamespaceCapabilityListScalingPolicies, 1228 acl.NamespaceCapabilityReadScalingPolicy, 1229 acl.NamespaceCapabilityReadJobScaling, 1230 acl.NamespaceCapabilityScaleJob, 1231 acl.NamespaceCapabilitySubmitRecommendation, 1232 } 1233 1234 ns1token := mock.CreatePolicyAndToken(t, state, 1001, "ns1", 1235 mock.NamespacePolicy(ns1.Name, "", []string{acl.NamespaceCapabilityReadJob})) 1236 ns1tokenInsufficient := mock.CreatePolicyAndToken(t, state, 1001, "ns1-insufficient", 1237 mock.NamespacePolicy(ns1.Name, "", everythingButReadJob)) 1238 ns2token := mock.CreatePolicyAndToken(t, state, 1001, "ns2", 1239 mock.NamespacePolicy(ns2.Name, "", []string{acl.NamespaceCapabilityReadJob})) 1240 bothToken := mock.CreatePolicyAndToken(t, state, 1001, "nsBoth", 1241 mock.NamespacePolicy(ns1.Name, "", []string{acl.NamespaceCapabilityReadJob})+ 1242 mock.NamespacePolicy(ns2.Name, "", []string{acl.NamespaceCapabilityReadJob})) 1243 1244 cases := []struct { 1245 Label string 1246 Namespace string 1247 Token string 1248 Allocs []*structs.Allocation 1249 Error bool 1250 Message string 1251 Prefix string 1252 }{ 1253 { 1254 Label: "all namespaces with sufficient token", 1255 Namespace: "*", 1256 Token: bothToken.SecretID, 1257 Allocs: []*structs.Allocation{alloc1, alloc2}, 1258 }, 1259 { 1260 Label: "all namespaces with root token", 1261 Namespace: "*", 1262 Token: root.SecretID, 1263 Allocs: []*structs.Allocation{alloc1, alloc2}, 1264 }, 1265 { 1266 Label: "all namespaces with ns1 token", 1267 Namespace: "*", 1268 Token: ns1token.SecretID, 1269 Allocs: []*structs.Allocation{alloc1}, 1270 }, 1271 { 1272 Label: "all namespaces with ns2 token", 1273 Namespace: "*", 1274 Token: ns2token.SecretID, 1275 Allocs: []*structs.Allocation{alloc2}, 1276 }, 1277 { 1278 Label: "all namespaces with bad token", 1279 Namespace: "*", 1280 Token: uuid.Generate(), 1281 Error: true, 1282 Message: structs.ErrTokenNotFound.Error(), 1283 }, 1284 { 1285 Label: "all namespaces with insufficient token", 1286 Namespace: "*", 1287 Token: ns1tokenInsufficient.SecretID, 1288 Error: true, 1289 Message: structs.ErrPermissionDenied.Error(), 1290 }, 1291 { 1292 Label: "ns1 with ns1 token", 1293 Namespace: ns1.Name, 1294 Token: ns1token.SecretID, 1295 Allocs: []*structs.Allocation{alloc1}, 1296 }, 1297 { 1298 Label: "ns1 with root token", 1299 Namespace: ns1.Name, 1300 Token: root.SecretID, 1301 Allocs: []*structs.Allocation{alloc1}, 1302 }, 1303 { 1304 Label: "ns1 with ns2 token", 1305 Namespace: ns1.Name, 1306 Token: ns2token.SecretID, 1307 Error: true, 1308 }, 1309 { 1310 Label: "ns1 with invalid token", 1311 Namespace: ns1.Name, 1312 Token: uuid.Generate(), 1313 Error: true, 1314 Message: structs.ErrTokenNotFound.Error(), 1315 }, 1316 { 1317 Label: "bad namespace with root token", 1318 Namespace: uuid.Generate(), 1319 Token: root.SecretID, 1320 Allocs: []*structs.Allocation{}, 1321 }, 1322 { 1323 Label: "all namespaces with prefix", 1324 Namespace: "*", 1325 Prefix: alloc1.ID[:2], 1326 Token: root.SecretID, 1327 Allocs: []*structs.Allocation{alloc1}, 1328 }, 1329 } 1330 1331 for _, tc := range cases { 1332 t.Run(tc.Label, func(t *testing.T) { 1333 1334 get := &structs.AllocListRequest{ 1335 QueryOptions: structs.QueryOptions{ 1336 Region: "global", 1337 Namespace: tc.Namespace, 1338 Prefix: tc.Prefix, 1339 AuthToken: tc.Token, 1340 }, 1341 } 1342 var resp structs.AllocListResponse 1343 err := msgpackrpc.CallWithCodec(codec, "Alloc.List", get, &resp) 1344 if tc.Error { 1345 require.Error(t, err) 1346 if tc.Message != "" { 1347 require.Equal(t, err.Error(), tc.Message) 1348 } else { 1349 require.Equal(t, err.Error(), structs.ErrPermissionDenied.Error()) 1350 } 1351 } else { 1352 require.NoError(t, err) 1353 require.Equal(t, uint64(1000), resp.Index) 1354 exp := make([]*structs.AllocListStub, len(tc.Allocs)) 1355 for i, a := range tc.Allocs { 1356 exp[i] = a.Stub(nil) 1357 } 1358 require.ElementsMatch(t, exp, resp.Allocations) 1359 } 1360 }) 1361 } 1362 1363 } 1364 1365 func TestAlloc_GetServiceRegistrations(t *testing.T) { 1366 ci.Parallel(t) 1367 1368 // This function is a helper function to set up an allocation and service 1369 // which can be queried. 1370 correctSetupFn := func(s *Server) (error, string, *structs.ServiceRegistration) { 1371 // Generate an upsert an allocation. 1372 alloc := mock.Alloc() 1373 err := s.State().UpsertAllocs(structs.MsgTypeTestSetup, 10, []*structs.Allocation{alloc}) 1374 if err != nil { 1375 return nil, "", nil 1376 } 1377 1378 // Generate services. Set the allocation ID to the first, so it 1379 // matches the allocation. The alloc and first service both 1380 // reside in the default namespace. 1381 services := mock.ServiceRegistrations() 1382 services[0].AllocID = alloc.ID 1383 err = s.State().UpsertServiceRegistrations(structs.MsgTypeTestSetup, 20, services) 1384 1385 return err, alloc.ID, services[0] 1386 } 1387 1388 testCases := []struct { 1389 serverFn func(t *testing.T) (*Server, *structs.ACLToken, func()) 1390 testFn func(t *testing.T, s *Server, token *structs.ACLToken) 1391 name string 1392 }{ 1393 { 1394 serverFn: func(t *testing.T) (*Server, *structs.ACLToken, func()) { 1395 server, cleanup := TestServer(t, nil) 1396 return server, nil, cleanup 1397 }, 1398 testFn: func(t *testing.T, s *Server, _ *structs.ACLToken) { 1399 codec := rpcClient(t, s) 1400 testutil.WaitForLeader(t, s.RPC) 1401 1402 err, allocID, service := correctSetupFn(s) 1403 require.NoError(t, err) 1404 1405 // Perform a lookup on the first service. 1406 serviceRegReq := &structs.AllocServiceRegistrationsRequest{ 1407 AllocID: allocID, 1408 QueryOptions: structs.QueryOptions{ 1409 Namespace: service.Namespace, 1410 Region: s.Region(), 1411 }, 1412 } 1413 var serviceRegResp structs.AllocServiceRegistrationsResponse 1414 err = msgpackrpc.CallWithCodec(codec, structs.AllocServiceRegistrationsRPCMethod, serviceRegReq, &serviceRegResp) 1415 require.NoError(t, err) 1416 require.EqualValues(t, uint64(20), serviceRegResp.Index) 1417 require.ElementsMatch(t, serviceRegResp.Services, []*structs.ServiceRegistration{service}) 1418 }, 1419 name: "ACLs disabled alloc found with regs", 1420 }, 1421 { 1422 serverFn: func(t *testing.T) (*Server, *structs.ACLToken, func()) { 1423 server, cleanup := TestServer(t, nil) 1424 return server, nil, cleanup 1425 }, 1426 testFn: func(t *testing.T, s *Server, _ *structs.ACLToken) { 1427 codec := rpcClient(t, s) 1428 testutil.WaitForLeader(t, s.RPC) 1429 1430 // Generate and upsert our services. 1431 services := mock.ServiceRegistrations() 1432 require.NoError(t, s.State().UpsertServiceRegistrations(structs.MsgTypeTestSetup, 20, services)) 1433 1434 // Perform a lookup on the first service using the allocation 1435 // ID. This allocation does not exist within the Nomad state 1436 // meaning the service is orphaned or the caller used an 1437 // incorrect allocation ID. 1438 serviceRegReq := &structs.AllocServiceRegistrationsRequest{ 1439 AllocID: services[0].AllocID, 1440 QueryOptions: structs.QueryOptions{ 1441 Namespace: services[0].Namespace, 1442 Region: s.Region(), 1443 }, 1444 } 1445 var serviceRegResp structs.AllocServiceRegistrationsResponse 1446 err := msgpackrpc.CallWithCodec(codec, structs.AllocServiceRegistrationsRPCMethod, serviceRegReq, &serviceRegResp) 1447 require.NoError(t, err) 1448 require.Nil(t, serviceRegResp.Services) 1449 }, 1450 name: "ACLs disabled alloc not found", 1451 }, 1452 { 1453 serverFn: func(t *testing.T) (*Server, *structs.ACLToken, func()) { 1454 server, cleanup := TestServer(t, nil) 1455 return server, nil, cleanup 1456 }, 1457 testFn: func(t *testing.T, s *Server, _ *structs.ACLToken) { 1458 codec := rpcClient(t, s) 1459 testutil.WaitForLeader(t, s.RPC) 1460 1461 err, allocID, _ := correctSetupFn(s) 1462 require.NoError(t, err) 1463 1464 // Perform a lookup on the first service using the allocation 1465 // ID but a random namespace. The namespace on the allocation 1466 // does therefore not match the request args. 1467 serviceRegReq := &structs.AllocServiceRegistrationsRequest{ 1468 AllocID: allocID, 1469 QueryOptions: structs.QueryOptions{ 1470 Namespace: "platform", 1471 Region: s.Region(), 1472 }, 1473 } 1474 var serviceRegResp structs.AllocServiceRegistrationsResponse 1475 err = msgpackrpc.CallWithCodec(codec, structs.AllocServiceRegistrationsRPCMethod, serviceRegReq, &serviceRegResp) 1476 require.NoError(t, err) 1477 require.ElementsMatch(t, serviceRegResp.Services, []*structs.ServiceRegistration{}) 1478 }, 1479 name: "ACLs disabled alloc found in different namespace than request", 1480 }, 1481 { 1482 serverFn: func(t *testing.T) (*Server, *structs.ACLToken, func()) { 1483 server, cleanup := TestServer(t, nil) 1484 return server, nil, cleanup 1485 }, 1486 testFn: func(t *testing.T, s *Server, _ *structs.ACLToken) { 1487 codec := rpcClient(t, s) 1488 testutil.WaitForLeader(t, s.RPC) 1489 1490 // Generate an upsert an allocation. 1491 alloc := mock.Alloc() 1492 require.NoError(t, s.State().UpsertAllocs( 1493 structs.MsgTypeTestSetup, 10, []*structs.Allocation{alloc})) 1494 1495 // Perform a lookup using the allocation information. 1496 serviceRegReq := &structs.AllocServiceRegistrationsRequest{ 1497 AllocID: alloc.ID, 1498 QueryOptions: structs.QueryOptions{ 1499 Namespace: alloc.Namespace, 1500 Region: s.Region(), 1501 }, 1502 } 1503 var serviceRegResp structs.AllocServiceRegistrationsResponse 1504 err := msgpackrpc.CallWithCodec(codec, structs.AllocServiceRegistrationsRPCMethod, serviceRegReq, &serviceRegResp) 1505 require.NoError(t, err) 1506 require.ElementsMatch(t, serviceRegResp.Services, []*structs.ServiceRegistration{}) 1507 }, 1508 name: "ACLs disabled alloc found without regs", 1509 }, 1510 { 1511 serverFn: func(t *testing.T) (*Server, *structs.ACLToken, func()) { 1512 return TestACLServer(t, nil) 1513 }, 1514 testFn: func(t *testing.T, s *Server, token *structs.ACLToken) { 1515 codec := rpcClient(t, s) 1516 testutil.WaitForLeader(t, s.RPC) 1517 1518 err, allocID, service := correctSetupFn(s) 1519 require.NoError(t, err) 1520 1521 // Perform a lookup using the allocation information. 1522 serviceRegReq := &structs.AllocServiceRegistrationsRequest{ 1523 AllocID: allocID, 1524 QueryOptions: structs.QueryOptions{ 1525 Namespace: service.Namespace, 1526 Region: s.Region(), 1527 AuthToken: token.SecretID, 1528 }, 1529 } 1530 var serviceRegResp structs.AllocServiceRegistrationsResponse 1531 err = msgpackrpc.CallWithCodec(codec, structs.AllocServiceRegistrationsRPCMethod, serviceRegReq, &serviceRegResp) 1532 require.NoError(t, err) 1533 require.ElementsMatch(t, serviceRegResp.Services, []*structs.ServiceRegistration{service}) 1534 }, 1535 name: "ACLs enabled use management token", 1536 }, 1537 { 1538 serverFn: func(t *testing.T) (*Server, *structs.ACLToken, func()) { 1539 return TestACLServer(t, nil) 1540 }, 1541 testFn: func(t *testing.T, s *Server, _ *structs.ACLToken) { 1542 codec := rpcClient(t, s) 1543 testutil.WaitForLeader(t, s.RPC) 1544 1545 err, allocID, service := correctSetupFn(s) 1546 require.NoError(t, err) 1547 1548 // Create and policy and grab the auth token. 1549 authToken := mock.CreatePolicyAndToken(t, s.State(), 30, "test-node-get-service-reg", 1550 mock.NamespacePolicy(service.Namespace, "", []string{acl.NamespaceCapabilityReadJob})).SecretID 1551 1552 // Perform a lookup using the allocation information. 1553 serviceRegReq := &structs.AllocServiceRegistrationsRequest{ 1554 AllocID: allocID, 1555 QueryOptions: structs.QueryOptions{ 1556 Namespace: service.Namespace, 1557 Region: s.Region(), 1558 AuthToken: authToken, 1559 }, 1560 } 1561 var serviceRegResp structs.AllocServiceRegistrationsResponse 1562 err = msgpackrpc.CallWithCodec(codec, structs.AllocServiceRegistrationsRPCMethod, serviceRegReq, &serviceRegResp) 1563 require.NoError(t, err) 1564 require.ElementsMatch(t, serviceRegResp.Services, []*structs.ServiceRegistration{service}) 1565 }, 1566 name: "ACLs enabled use read-job namespace capability token", 1567 }, 1568 { 1569 serverFn: func(t *testing.T) (*Server, *structs.ACLToken, func()) { 1570 return TestACLServer(t, nil) 1571 }, 1572 testFn: func(t *testing.T, s *Server, _ *structs.ACLToken) { 1573 codec := rpcClient(t, s) 1574 testutil.WaitForLeader(t, s.RPC) 1575 1576 err, allocID, service := correctSetupFn(s) 1577 require.NoError(t, err) 1578 1579 // Create and policy and grab the auth token. 1580 authToken := mock.CreatePolicyAndToken(t, s.State(), 30, "test-node-get-service-reg", 1581 mock.NamespacePolicy(service.Namespace, "read", nil)).SecretID 1582 1583 // Perform a lookup using the allocation information. 1584 serviceRegReq := &structs.AllocServiceRegistrationsRequest{ 1585 AllocID: allocID, 1586 QueryOptions: structs.QueryOptions{ 1587 Namespace: service.Namespace, 1588 Region: s.Region(), 1589 AuthToken: authToken, 1590 }, 1591 } 1592 var serviceRegResp structs.AllocServiceRegistrationsResponse 1593 err = msgpackrpc.CallWithCodec(codec, structs.AllocServiceRegistrationsRPCMethod, serviceRegReq, &serviceRegResp) 1594 require.NoError(t, err) 1595 require.ElementsMatch(t, serviceRegResp.Services, []*structs.ServiceRegistration{service}) 1596 }, 1597 name: "ACLs enabled use read namespace policy token", 1598 }, 1599 { 1600 serverFn: func(t *testing.T) (*Server, *structs.ACLToken, func()) { 1601 return TestACLServer(t, nil) 1602 }, 1603 testFn: func(t *testing.T, s *Server, _ *structs.ACLToken) { 1604 codec := rpcClient(t, s) 1605 testutil.WaitForLeader(t, s.RPC) 1606 1607 err, allocID, service := correctSetupFn(s) 1608 require.NoError(t, err) 1609 1610 // Create and policy and grab the auth token. 1611 authToken := mock.CreatePolicyAndToken(t, s.State(), 30, "test-node-get-service-reg", 1612 mock.NamespacePolicy("ohno", "read", nil)).SecretID 1613 1614 // Perform a lookup using the allocation information. 1615 serviceRegReq := &structs.AllocServiceRegistrationsRequest{ 1616 AllocID: allocID, 1617 QueryOptions: structs.QueryOptions{ 1618 Namespace: service.Namespace, 1619 Region: s.Region(), 1620 AuthToken: authToken, 1621 }, 1622 } 1623 var serviceRegResp structs.AllocServiceRegistrationsResponse 1624 err = msgpackrpc.CallWithCodec(codec, structs.AllocServiceRegistrationsRPCMethod, serviceRegReq, &serviceRegResp) 1625 require.Error(t, err) 1626 require.Contains(t, err.Error(), "Permission denied") 1627 require.Empty(t, serviceRegResp.Services) 1628 }, 1629 name: "ACLs enabled use read incorrect namespace policy token", 1630 }, 1631 { 1632 serverFn: func(t *testing.T) (*Server, *structs.ACLToken, func()) { 1633 return TestACLServer(t, nil) 1634 }, 1635 testFn: func(t *testing.T, s *Server, _ *structs.ACLToken) { 1636 codec := rpcClient(t, s) 1637 testutil.WaitForLeader(t, s.RPC) 1638 1639 err, allocID, service := correctSetupFn(s) 1640 require.NoError(t, err) 1641 1642 // Create and policy and grab the auth token. 1643 authToken := mock.CreatePolicyAndToken(t, s.State(), 30, "test-node-get-service-reg", 1644 mock.NamespacePolicy(service.Namespace, "", []string{acl.NamespaceCapabilityReadScalingPolicy})).SecretID 1645 1646 // Perform a lookup using the allocation information. 1647 serviceRegReq := &structs.AllocServiceRegistrationsRequest{ 1648 AllocID: allocID, 1649 QueryOptions: structs.QueryOptions{ 1650 Namespace: service.Namespace, 1651 Region: s.Region(), 1652 AuthToken: authToken, 1653 }, 1654 } 1655 var serviceRegResp structs.AllocServiceRegistrationsResponse 1656 err = msgpackrpc.CallWithCodec(codec, structs.AllocServiceRegistrationsRPCMethod, serviceRegReq, &serviceRegResp) 1657 require.Error(t, err) 1658 require.Contains(t, err.Error(), "Permission denied") 1659 require.Empty(t, serviceRegResp.Services) 1660 }, 1661 name: "ACLs enabled use incorrect capability", 1662 }, 1663 } 1664 1665 for _, tc := range testCases { 1666 t.Run(tc.name, func(t *testing.T) { 1667 server, aclToken, cleanup := tc.serverFn(t) 1668 defer cleanup() 1669 tc.testFn(t, server, aclToken) 1670 }) 1671 } 1672 }