github.com/manicqin/nomad@v0.9.5/nomad/search_endpoint_test.go (about) 1 package nomad 2 3 import ( 4 "strconv" 5 "strings" 6 "testing" 7 8 msgpackrpc "github.com/hashicorp/net-rpc-msgpackrpc" 9 "github.com/hashicorp/nomad/acl" 10 "github.com/hashicorp/nomad/nomad/mock" 11 "github.com/hashicorp/nomad/nomad/structs" 12 "github.com/hashicorp/nomad/testutil" 13 "github.com/stretchr/testify/assert" 14 ) 15 16 const jobIndex = 1000 17 18 func registerAndVerifyJob(s *Server, t *testing.T, prefix string, counter int) *structs.Job { 19 job := mock.Job() 20 job.ID = prefix + strconv.Itoa(counter) 21 state := s.fsm.State() 22 if err := state.UpsertJob(jobIndex, job); err != nil { 23 t.Fatalf("err: %v", err) 24 } 25 26 return job 27 } 28 29 func TestSearch_PrefixSearch_Job(t *testing.T) { 30 t.Parallel() 31 assert := assert.New(t) 32 prefix := "aaaaaaaa-e8f7-fd38-c855-ab94ceb8970" 33 34 s, cleanupS := TestServer(t, func(c *Config) { 35 c.NumSchedulers = 0 36 }) 37 defer cleanupS() 38 codec := rpcClient(t, s) 39 testutil.WaitForLeader(t, s.RPC) 40 41 job := registerAndVerifyJob(s, t, prefix, 0) 42 43 req := &structs.SearchRequest{ 44 Prefix: prefix, 45 Context: structs.Jobs, 46 QueryOptions: structs.QueryOptions{ 47 Region: "global", 48 Namespace: job.Namespace, 49 }, 50 } 51 52 var resp structs.SearchResponse 53 if err := msgpackrpc.CallWithCodec(codec, "Search.PrefixSearch", req, &resp); err != nil { 54 t.Fatalf("err: %v", err) 55 } 56 57 assert.Equal(1, len(resp.Matches[structs.Jobs])) 58 assert.Equal(job.ID, resp.Matches[structs.Jobs][0]) 59 assert.Equal(uint64(jobIndex), resp.Index) 60 } 61 62 func TestSearch_PrefixSearch_ACL(t *testing.T) { 63 t.Parallel() 64 assert := assert.New(t) 65 jobID := "aaaaaaaa-e8f7-fd38-c855-ab94ceb8970" 66 67 s, root, cleanupS := TestACLServer(t, func(c *Config) { 68 c.NumSchedulers = 0 69 }) 70 defer cleanupS() 71 codec := rpcClient(t, s) 72 testutil.WaitForLeader(t, s.RPC) 73 state := s.fsm.State() 74 75 job := registerAndVerifyJob(s, t, jobID, 0) 76 assert.Nil(state.UpsertNode(1001, mock.Node())) 77 78 req := &structs.SearchRequest{ 79 Prefix: "", 80 Context: structs.Jobs, 81 QueryOptions: structs.QueryOptions{ 82 Region: "global", 83 Namespace: job.Namespace, 84 }, 85 } 86 87 // Try without a token and expect failure 88 { 89 var resp structs.SearchResponse 90 err := msgpackrpc.CallWithCodec(codec, "Search.PrefixSearch", req, &resp) 91 assert.NotNil(err) 92 assert.Equal(err.Error(), structs.ErrPermissionDenied.Error()) 93 } 94 95 // Try with an invalid token and expect failure 96 { 97 invalidToken := mock.CreatePolicyAndToken(t, state, 1003, "test-invalid", 98 mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityListJobs})) 99 req.AuthToken = invalidToken.SecretID 100 var resp structs.SearchResponse 101 err := msgpackrpc.CallWithCodec(codec, "Search.PrefixSearch", req, &resp) 102 assert.NotNil(err) 103 assert.Equal(err.Error(), structs.ErrPermissionDenied.Error()) 104 } 105 106 // Try with a node:read token and expect failure due to Jobs being the context 107 { 108 validToken := mock.CreatePolicyAndToken(t, state, 1005, "test-invalid2", mock.NodePolicy(acl.PolicyRead)) 109 req.AuthToken = validToken.SecretID 110 var resp structs.SearchResponse 111 err := msgpackrpc.CallWithCodec(codec, "Search.PrefixSearch", req, &resp) 112 assert.NotNil(err) 113 assert.Equal(err.Error(), structs.ErrPermissionDenied.Error()) 114 } 115 116 // Try with a node:read token and expect success due to All context 117 { 118 validToken := mock.CreatePolicyAndToken(t, state, 1007, "test-valid", mock.NodePolicy(acl.PolicyRead)) 119 req.Context = structs.All 120 req.AuthToken = validToken.SecretID 121 var resp structs.SearchResponse 122 assert.Nil(msgpackrpc.CallWithCodec(codec, "Search.PrefixSearch", req, &resp)) 123 assert.Equal(uint64(1001), resp.Index) 124 assert.Len(resp.Matches[structs.Nodes], 1) 125 126 // Jobs filtered out since token only has access to node:read 127 assert.Len(resp.Matches[structs.Jobs], 0) 128 } 129 130 // Try with a valid token for namespace:read-job 131 { 132 validToken := mock.CreatePolicyAndToken(t, state, 1009, "test-valid2", 133 mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityReadJob})) 134 req.AuthToken = validToken.SecretID 135 var resp structs.SearchResponse 136 assert.Nil(msgpackrpc.CallWithCodec(codec, "Search.PrefixSearch", req, &resp)) 137 assert.Len(resp.Matches[structs.Jobs], 1) 138 assert.Equal(job.ID, resp.Matches[structs.Jobs][0]) 139 140 // Index of job - not node - because node context is filtered out 141 assert.Equal(uint64(1000), resp.Index) 142 143 // Nodes filtered out since token only has access to namespace:read-job 144 assert.Len(resp.Matches[structs.Nodes], 0) 145 } 146 147 // Try with a valid token for node:read and namespace:read-job 148 { 149 validToken := mock.CreatePolicyAndToken(t, state, 1011, "test-valid3", strings.Join([]string{ 150 mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityReadJob}), 151 mock.NodePolicy(acl.PolicyRead), 152 }, "\n")) 153 req.AuthToken = validToken.SecretID 154 var resp structs.SearchResponse 155 assert.Nil(msgpackrpc.CallWithCodec(codec, "Search.PrefixSearch", req, &resp)) 156 assert.Len(resp.Matches[structs.Jobs], 1) 157 assert.Equal(job.ID, resp.Matches[structs.Jobs][0]) 158 assert.Len(resp.Matches[structs.Nodes], 1) 159 assert.Equal(uint64(1001), resp.Index) 160 } 161 162 // Try with a management token 163 { 164 req.AuthToken = root.SecretID 165 var resp structs.SearchResponse 166 assert.Nil(msgpackrpc.CallWithCodec(codec, "Search.PrefixSearch", req, &resp)) 167 assert.Equal(uint64(1001), resp.Index) 168 assert.Len(resp.Matches[structs.Jobs], 1) 169 assert.Equal(job.ID, resp.Matches[structs.Jobs][0]) 170 assert.Len(resp.Matches[structs.Nodes], 1) 171 } 172 } 173 174 func TestSearch_PrefixSearch_All_JobWithHyphen(t *testing.T) { 175 t.Parallel() 176 assert := assert.New(t) 177 prefix := "example-test-------" // Assert that a job with more than 4 hyphens works 178 179 s, cleanupS := TestServer(t, func(c *Config) { 180 c.NumSchedulers = 0 181 }) 182 defer cleanupS() 183 codec := rpcClient(t, s) 184 testutil.WaitForLeader(t, s.RPC) 185 186 // Register a job and an allocation 187 job := registerAndVerifyJob(s, t, prefix, 0) 188 alloc := mock.Alloc() 189 alloc.JobID = job.ID 190 alloc.Namespace = job.Namespace 191 summary := mock.JobSummary(alloc.JobID) 192 state := s.fsm.State() 193 194 if err := state.UpsertJobSummary(999, summary); err != nil { 195 t.Fatalf("err: %v", err) 196 } 197 if err := state.UpsertAllocs(1000, []*structs.Allocation{alloc}); err != nil { 198 t.Fatalf("err: %v", err) 199 } 200 201 req := &structs.SearchRequest{ 202 Context: structs.All, 203 QueryOptions: structs.QueryOptions{ 204 Region: "global", 205 Namespace: job.Namespace, 206 }, 207 } 208 209 // req.Prefix = "example-te": 9 210 for i := 1; i < len(prefix); i++ { 211 req.Prefix = prefix[:i] 212 var resp structs.SearchResponse 213 assert.Nil(msgpackrpc.CallWithCodec(codec, "Search.PrefixSearch", req, &resp)) 214 assert.Equal(1, len(resp.Matches[structs.Jobs])) 215 assert.Equal(job.ID, resp.Matches[structs.Jobs][0]) 216 assert.EqualValues(jobIndex, resp.Index) 217 } 218 } 219 220 func TestSearch_PrefixSearch_All_LongJob(t *testing.T) { 221 t.Parallel() 222 assert := assert.New(t) 223 prefix := strings.Repeat("a", 100) 224 225 s, cleanupS := TestServer(t, func(c *Config) { 226 c.NumSchedulers = 0 227 }) 228 defer cleanupS() 229 codec := rpcClient(t, s) 230 testutil.WaitForLeader(t, s.RPC) 231 232 // Register a job and an allocation 233 job := registerAndVerifyJob(s, t, prefix, 0) 234 alloc := mock.Alloc() 235 alloc.JobID = job.ID 236 summary := mock.JobSummary(alloc.JobID) 237 state := s.fsm.State() 238 239 if err := state.UpsertJobSummary(999, summary); err != nil { 240 t.Fatalf("err: %v", err) 241 } 242 if err := state.UpsertAllocs(1000, []*structs.Allocation{alloc}); err != nil { 243 t.Fatalf("err: %v", err) 244 } 245 246 req := &structs.SearchRequest{ 247 Prefix: prefix, 248 Context: structs.All, 249 QueryOptions: structs.QueryOptions{ 250 Region: "global", 251 Namespace: job.Namespace, 252 }, 253 } 254 255 var resp structs.SearchResponse 256 if err := msgpackrpc.CallWithCodec(codec, "Search.PrefixSearch", req, &resp); err != nil { 257 t.Fatalf("err: %v", err) 258 } 259 260 assert.Equal(1, len(resp.Matches[structs.Jobs])) 261 assert.Equal(job.ID, resp.Matches[structs.Jobs][0]) 262 assert.EqualValues(jobIndex, resp.Index) 263 } 264 265 // truncate should limit results to 20 266 func TestSearch_PrefixSearch_Truncate(t *testing.T) { 267 t.Parallel() 268 assert := assert.New(t) 269 prefix := "aaaaaaaa-e8f7-fd38-c855-ab94ceb8970" 270 271 s, cleanupS := TestServer(t, func(c *Config) { 272 c.NumSchedulers = 0 273 }) 274 defer cleanupS() 275 codec := rpcClient(t, s) 276 testutil.WaitForLeader(t, s.RPC) 277 278 var job *structs.Job 279 for counter := 0; counter < 25; counter++ { 280 job = registerAndVerifyJob(s, t, prefix, counter) 281 } 282 283 req := &structs.SearchRequest{ 284 Prefix: prefix, 285 Context: structs.Jobs, 286 QueryOptions: structs.QueryOptions{ 287 Region: "global", 288 Namespace: job.Namespace, 289 }, 290 } 291 292 var resp structs.SearchResponse 293 if err := msgpackrpc.CallWithCodec(codec, "Search.PrefixSearch", req, &resp); err != nil { 294 t.Fatalf("err: %v", err) 295 } 296 297 assert.Equal(20, len(resp.Matches[structs.Jobs])) 298 assert.Equal(resp.Truncations[structs.Jobs], true) 299 assert.Equal(uint64(jobIndex), resp.Index) 300 } 301 302 func TestSearch_PrefixSearch_AllWithJob(t *testing.T) { 303 t.Parallel() 304 assert := assert.New(t) 305 prefix := "aaaaaaaa-e8f7-fd38-c855-ab94ceb8970" 306 307 s, cleanupS := TestServer(t, func(c *Config) { 308 c.NumSchedulers = 0 309 }) 310 311 defer cleanupS() 312 codec := rpcClient(t, s) 313 testutil.WaitForLeader(t, s.RPC) 314 315 job := registerAndVerifyJob(s, t, prefix, 0) 316 317 eval1 := mock.Eval() 318 eval1.ID = job.ID 319 s.fsm.State().UpsertEvals(2000, []*structs.Evaluation{eval1}) 320 321 req := &structs.SearchRequest{ 322 Prefix: prefix, 323 Context: structs.All, 324 QueryOptions: structs.QueryOptions{ 325 Region: "global", 326 Namespace: job.Namespace, 327 }, 328 } 329 330 var resp structs.SearchResponse 331 if err := msgpackrpc.CallWithCodec(codec, "Search.PrefixSearch", req, &resp); err != nil { 332 t.Fatalf("err: %v", err) 333 } 334 335 assert.Equal(1, len(resp.Matches[structs.Jobs])) 336 assert.Equal(job.ID, resp.Matches[structs.Jobs][0]) 337 338 assert.Equal(1, len(resp.Matches[structs.Evals])) 339 assert.Equal(eval1.ID, resp.Matches[structs.Evals][0]) 340 } 341 342 func TestSearch_PrefixSearch_Evals(t *testing.T) { 343 t.Parallel() 344 assert := assert.New(t) 345 346 s, cleanupS := TestServer(t, func(c *Config) { 347 c.NumSchedulers = 0 348 }) 349 defer cleanupS() 350 codec := rpcClient(t, s) 351 testutil.WaitForLeader(t, s.RPC) 352 353 eval1 := mock.Eval() 354 s.fsm.State().UpsertEvals(2000, []*structs.Evaluation{eval1}) 355 356 prefix := eval1.ID[:len(eval1.ID)-2] 357 358 req := &structs.SearchRequest{ 359 Prefix: prefix, 360 Context: structs.Evals, 361 QueryOptions: structs.QueryOptions{ 362 Region: "global", 363 Namespace: eval1.Namespace, 364 }, 365 } 366 367 var resp structs.SearchResponse 368 if err := msgpackrpc.CallWithCodec(codec, "Search.PrefixSearch", req, &resp); err != nil { 369 t.Fatalf("err: %v", err) 370 } 371 372 assert.Equal(1, len(resp.Matches[structs.Evals])) 373 assert.Equal(eval1.ID, resp.Matches[structs.Evals][0]) 374 assert.Equal(resp.Truncations[structs.Evals], false) 375 376 assert.Equal(uint64(2000), resp.Index) 377 } 378 379 func TestSearch_PrefixSearch_Allocation(t *testing.T) { 380 t.Parallel() 381 assert := assert.New(t) 382 383 s, cleanupS := TestServer(t, func(c *Config) { 384 c.NumSchedulers = 0 385 }) 386 defer cleanupS() 387 codec := rpcClient(t, s) 388 testutil.WaitForLeader(t, s.RPC) 389 390 alloc := mock.Alloc() 391 summary := mock.JobSummary(alloc.JobID) 392 state := s.fsm.State() 393 394 if err := state.UpsertJobSummary(999, summary); err != nil { 395 t.Fatalf("err: %v", err) 396 } 397 if err := state.UpsertAllocs(90, []*structs.Allocation{alloc}); err != nil { 398 t.Fatalf("err: %v", err) 399 } 400 401 prefix := alloc.ID[:len(alloc.ID)-2] 402 403 req := &structs.SearchRequest{ 404 Prefix: prefix, 405 Context: structs.Allocs, 406 QueryOptions: structs.QueryOptions{ 407 Region: "global", 408 Namespace: alloc.Namespace, 409 }, 410 } 411 412 var resp structs.SearchResponse 413 if err := msgpackrpc.CallWithCodec(codec, "Search.PrefixSearch", req, &resp); err != nil { 414 t.Fatalf("err: %v", err) 415 } 416 417 assert.Equal(1, len(resp.Matches[structs.Allocs])) 418 assert.Equal(alloc.ID, resp.Matches[structs.Allocs][0]) 419 assert.Equal(resp.Truncations[structs.Allocs], false) 420 421 assert.Equal(uint64(90), resp.Index) 422 } 423 424 func TestSearch_PrefixSearch_All_UUID(t *testing.T) { 425 t.Parallel() 426 assert := assert.New(t) 427 428 s, cleanupS := TestServer(t, func(c *Config) { 429 c.NumSchedulers = 0 430 }) 431 defer cleanupS() 432 codec := rpcClient(t, s) 433 testutil.WaitForLeader(t, s.RPC) 434 435 alloc := mock.Alloc() 436 summary := mock.JobSummary(alloc.JobID) 437 state := s.fsm.State() 438 439 if err := state.UpsertJobSummary(999, summary); err != nil { 440 t.Fatalf("err: %v", err) 441 } 442 if err := state.UpsertAllocs(1000, []*structs.Allocation{alloc}); err != nil { 443 t.Fatalf("err: %v", err) 444 } 445 446 node := mock.Node() 447 if err := state.UpsertNode(1001, node); err != nil { 448 t.Fatalf("err: %v", err) 449 } 450 451 eval1 := mock.Eval() 452 eval1.ID = node.ID 453 if err := state.UpsertEvals(1002, []*structs.Evaluation{eval1}); err != nil { 454 t.Fatalf("err: %v", err) 455 } 456 457 req := &structs.SearchRequest{ 458 Context: structs.All, 459 QueryOptions: structs.QueryOptions{ 460 Region: "global", 461 Namespace: eval1.Namespace, 462 }, 463 } 464 465 for i := 1; i < len(alloc.ID); i++ { 466 req.Prefix = alloc.ID[:i] 467 var resp structs.SearchResponse 468 assert.Nil(msgpackrpc.CallWithCodec(codec, "Search.PrefixSearch", req, &resp)) 469 assert.Equal(1, len(resp.Matches[structs.Allocs])) 470 assert.Equal(alloc.ID, resp.Matches[structs.Allocs][0]) 471 assert.Equal(resp.Truncations[structs.Allocs], false) 472 assert.EqualValues(1002, resp.Index) 473 } 474 } 475 476 func TestSearch_PrefixSearch_Node(t *testing.T) { 477 t.Parallel() 478 assert := assert.New(t) 479 480 s, cleanupS := TestServer(t, func(c *Config) { 481 c.NumSchedulers = 0 482 }) 483 defer cleanupS() 484 codec := rpcClient(t, s) 485 testutil.WaitForLeader(t, s.RPC) 486 487 state := s.fsm.State() 488 node := mock.Node() 489 490 if err := state.UpsertNode(100, node); err != nil { 491 t.Fatalf("err: %v", err) 492 } 493 494 prefix := node.ID[:len(node.ID)-2] 495 496 req := &structs.SearchRequest{ 497 Prefix: prefix, 498 Context: structs.Nodes, 499 QueryOptions: structs.QueryOptions{ 500 Region: "global", 501 Namespace: structs.DefaultNamespace, 502 }, 503 } 504 505 var resp structs.SearchResponse 506 if err := msgpackrpc.CallWithCodec(codec, "Search.PrefixSearch", req, &resp); err != nil { 507 t.Fatalf("err: %v", err) 508 } 509 510 assert.Equal(1, len(resp.Matches[structs.Nodes])) 511 assert.Equal(node.ID, resp.Matches[structs.Nodes][0]) 512 assert.Equal(false, resp.Truncations[structs.Nodes]) 513 514 assert.Equal(uint64(100), resp.Index) 515 } 516 517 func TestSearch_PrefixSearch_Deployment(t *testing.T) { 518 t.Parallel() 519 assert := assert.New(t) 520 521 s, cleanupS := TestServer(t, func(c *Config) { 522 c.NumSchedulers = 0 523 }) 524 defer cleanupS() 525 codec := rpcClient(t, s) 526 testutil.WaitForLeader(t, s.RPC) 527 528 deployment := mock.Deployment() 529 s.fsm.State().UpsertDeployment(2000, deployment) 530 531 prefix := deployment.ID[:len(deployment.ID)-2] 532 533 req := &structs.SearchRequest{ 534 Prefix: prefix, 535 Context: structs.Deployments, 536 QueryOptions: structs.QueryOptions{ 537 Region: "global", 538 Namespace: deployment.Namespace, 539 }, 540 } 541 542 var resp structs.SearchResponse 543 if err := msgpackrpc.CallWithCodec(codec, "Search.PrefixSearch", req, &resp); err != nil { 544 t.Fatalf("err: %v", err) 545 } 546 547 assert.Equal(1, len(resp.Matches[structs.Deployments])) 548 assert.Equal(deployment.ID, resp.Matches[structs.Deployments][0]) 549 assert.Equal(resp.Truncations[structs.Deployments], false) 550 551 assert.Equal(uint64(2000), resp.Index) 552 } 553 554 func TestSearch_PrefixSearch_AllContext(t *testing.T) { 555 t.Parallel() 556 assert := assert.New(t) 557 558 s, cleanupS := TestServer(t, func(c *Config) { 559 c.NumSchedulers = 0 560 }) 561 562 defer cleanupS() 563 codec := rpcClient(t, s) 564 testutil.WaitForLeader(t, s.RPC) 565 566 state := s.fsm.State() 567 node := mock.Node() 568 569 if err := state.UpsertNode(100, node); err != nil { 570 t.Fatalf("err: %v", err) 571 } 572 573 eval1 := mock.Eval() 574 eval1.ID = node.ID 575 if err := state.UpsertEvals(1000, []*structs.Evaluation{eval1}); err != nil { 576 t.Fatalf("err: %v", err) 577 } 578 579 prefix := node.ID[:len(node.ID)-2] 580 581 req := &structs.SearchRequest{ 582 Prefix: prefix, 583 Context: structs.All, 584 QueryOptions: structs.QueryOptions{ 585 Region: "global", 586 Namespace: eval1.Namespace, 587 }, 588 } 589 590 var resp structs.SearchResponse 591 if err := msgpackrpc.CallWithCodec(codec, "Search.PrefixSearch", req, &resp); err != nil { 592 t.Fatalf("err: %v", err) 593 } 594 595 assert.Equal(1, len(resp.Matches[structs.Nodes])) 596 assert.Equal(1, len(resp.Matches[structs.Evals])) 597 598 assert.Equal(node.ID, resp.Matches[structs.Nodes][0]) 599 assert.Equal(eval1.ID, resp.Matches[structs.Evals][0]) 600 601 assert.Equal(uint64(1000), resp.Index) 602 } 603 604 // Tests that the top 20 matches are returned when no prefix is set 605 func TestSearch_PrefixSearch_NoPrefix(t *testing.T) { 606 t.Parallel() 607 assert := assert.New(t) 608 prefix := "aaaaaaaa-e8f7-fd38-c855-ab94ceb8970" 609 610 s, cleanupS := TestServer(t, func(c *Config) { 611 c.NumSchedulers = 0 612 }) 613 defer cleanupS() 614 codec := rpcClient(t, s) 615 testutil.WaitForLeader(t, s.RPC) 616 617 job := registerAndVerifyJob(s, t, prefix, 0) 618 619 req := &structs.SearchRequest{ 620 Prefix: "", 621 Context: structs.Jobs, 622 QueryOptions: structs.QueryOptions{ 623 Region: "global", 624 Namespace: job.Namespace, 625 }, 626 } 627 628 var resp structs.SearchResponse 629 if err := msgpackrpc.CallWithCodec(codec, "Search.PrefixSearch", req, &resp); err != nil { 630 t.Fatalf("err: %v", err) 631 } 632 633 assert.Equal(1, len(resp.Matches[structs.Jobs])) 634 assert.Equal(job.ID, resp.Matches[structs.Jobs][0]) 635 assert.Equal(uint64(jobIndex), resp.Index) 636 } 637 638 // Tests that the zero matches are returned when a prefix has no matching 639 // results 640 func TestSearch_PrefixSearch_NoMatches(t *testing.T) { 641 t.Parallel() 642 assert := assert.New(t) 643 prefix := "aaaaaaaa-e8f7-fd38-c855-ab94ceb8970" 644 645 s, cleanupS := TestServer(t, func(c *Config) { 646 c.NumSchedulers = 0 647 }) 648 defer cleanupS() 649 codec := rpcClient(t, s) 650 testutil.WaitForLeader(t, s.RPC) 651 652 req := &structs.SearchRequest{ 653 Prefix: prefix, 654 Context: structs.Jobs, 655 QueryOptions: structs.QueryOptions{ 656 Region: "global", 657 Namespace: structs.DefaultNamespace, 658 }, 659 } 660 661 var resp structs.SearchResponse 662 if err := msgpackrpc.CallWithCodec(codec, "Search.PrefixSearch", req, &resp); err != nil { 663 t.Fatalf("err: %v", err) 664 } 665 666 assert.Equal(0, len(resp.Matches[structs.Jobs])) 667 assert.Equal(uint64(0), resp.Index) 668 } 669 670 // Prefixes can only be looked up if their length is a power of two. For 671 // prefixes which are an odd length, use the length-1 characters. 672 func TestSearch_PrefixSearch_RoundDownToEven(t *testing.T) { 673 t.Parallel() 674 assert := assert.New(t) 675 id1 := "aaafaaaa-e8f7-fd38-c855-ab94ceb89" 676 id2 := "aaafeaaa-e8f7-fd38-c855-ab94ceb89" 677 prefix := "aaafa" 678 679 s, cleanupS := TestServer(t, func(c *Config) { 680 c.NumSchedulers = 0 681 }) 682 defer cleanupS() 683 codec := rpcClient(t, s) 684 testutil.WaitForLeader(t, s.RPC) 685 686 job := registerAndVerifyJob(s, t, id1, 0) 687 registerAndVerifyJob(s, t, id2, 50) 688 689 req := &structs.SearchRequest{ 690 Prefix: prefix, 691 Context: structs.Jobs, 692 QueryOptions: structs.QueryOptions{ 693 Region: "global", 694 Namespace: job.Namespace, 695 }, 696 } 697 698 var resp structs.SearchResponse 699 if err := msgpackrpc.CallWithCodec(codec, "Search.PrefixSearch", req, &resp); err != nil { 700 t.Fatalf("err: %v", err) 701 } 702 703 assert.Equal(1, len(resp.Matches[structs.Jobs])) 704 assert.Equal(job.ID, resp.Matches[structs.Jobs][0]) 705 } 706 707 func TestSearch_PrefixSearch_MultiRegion(t *testing.T) { 708 t.Parallel() 709 assert := assert.New(t) 710 jobName := "exampleexample" 711 712 s1, cleanupS1 := TestServer(t, func(c *Config) { 713 c.NumSchedulers = 0 714 c.Region = "foo" 715 }) 716 defer cleanupS1() 717 718 s2, cleanupS2 := TestServer(t, func(c *Config) { 719 c.NumSchedulers = 0 720 c.Region = "bar" 721 }) 722 defer cleanupS2() 723 724 TestJoin(t, s1, s2) 725 testutil.WaitForLeader(t, s1.RPC) 726 727 job := registerAndVerifyJob(s1, t, jobName, 0) 728 729 req := &structs.SearchRequest{ 730 Prefix: "", 731 Context: structs.Jobs, 732 QueryOptions: structs.QueryOptions{ 733 Region: "foo", 734 Namespace: job.Namespace, 735 }, 736 } 737 738 codec := rpcClient(t, s2) 739 740 var resp structs.SearchResponse 741 if err := msgpackrpc.CallWithCodec(codec, "Search.PrefixSearch", req, &resp); err != nil { 742 t.Fatalf("err: %v", err) 743 } 744 745 assert.Equal(1, len(resp.Matches[structs.Jobs])) 746 assert.Equal(job.ID, resp.Matches[structs.Jobs][0]) 747 assert.Equal(uint64(jobIndex), resp.Index) 748 }