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  }