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