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  }