github.com/zoomfoo/nomad@v0.8.5-0.20180907175415-f28fd3a1a056/nomad/job_endpoint_test.go (about)

     1  package nomad
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     6  	"strings"
     7  	"testing"
     8  	"time"
     9  
    10  	memdb "github.com/hashicorp/go-memdb"
    11  	msgpackrpc "github.com/hashicorp/net-rpc-msgpackrpc"
    12  	"github.com/hashicorp/nomad/acl"
    13  	"github.com/hashicorp/nomad/helper"
    14  	"github.com/hashicorp/nomad/helper/uuid"
    15  	"github.com/hashicorp/nomad/nomad/mock"
    16  	"github.com/hashicorp/nomad/nomad/structs"
    17  	"github.com/hashicorp/nomad/testutil"
    18  	"github.com/kr/pretty"
    19  	"github.com/stretchr/testify/require"
    20  )
    21  
    22  func TestJobEndpoint_Register(t *testing.T) {
    23  	t.Parallel()
    24  	s1 := TestServer(t, func(c *Config) {
    25  		c.NumSchedulers = 0 // Prevent automatic dequeue
    26  	})
    27  	defer s1.Shutdown()
    28  	codec := rpcClient(t, s1)
    29  	testutil.WaitForLeader(t, s1.RPC)
    30  
    31  	// Create the register request
    32  	job := mock.Job()
    33  	req := &structs.JobRegisterRequest{
    34  		Job: job,
    35  		WriteRequest: structs.WriteRequest{
    36  			Region:    "global",
    37  			Namespace: job.Namespace,
    38  		},
    39  	}
    40  
    41  	// Fetch the response
    42  	var resp structs.JobRegisterResponse
    43  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil {
    44  		t.Fatalf("err: %v", err)
    45  	}
    46  	if resp.Index == 0 {
    47  		t.Fatalf("bad index: %d", resp.Index)
    48  	}
    49  
    50  	// Check for the node in the FSM
    51  	state := s1.fsm.State()
    52  	ws := memdb.NewWatchSet()
    53  	out, err := state.JobByID(ws, job.Namespace, job.ID)
    54  	if err != nil {
    55  		t.Fatalf("err: %v", err)
    56  	}
    57  	if out == nil {
    58  		t.Fatalf("expected job")
    59  	}
    60  	if out.CreateIndex != resp.JobModifyIndex {
    61  		t.Fatalf("index mis-match")
    62  	}
    63  	serviceName := out.TaskGroups[0].Tasks[0].Services[0].Name
    64  	expectedServiceName := "web-frontend"
    65  	if serviceName != expectedServiceName {
    66  		t.Fatalf("Expected Service Name: %s, Actual: %s", expectedServiceName, serviceName)
    67  	}
    68  
    69  	// Lookup the evaluation
    70  	eval, err := state.EvalByID(ws, resp.EvalID)
    71  	if err != nil {
    72  		t.Fatalf("err: %v", err)
    73  	}
    74  	if eval == nil {
    75  		t.Fatalf("expected eval")
    76  	}
    77  	if eval.CreateIndex != resp.EvalCreateIndex {
    78  		t.Fatalf("index mis-match")
    79  	}
    80  
    81  	if eval.Priority != job.Priority {
    82  		t.Fatalf("bad: %#v", eval)
    83  	}
    84  	if eval.Type != job.Type {
    85  		t.Fatalf("bad: %#v", eval)
    86  	}
    87  	if eval.TriggeredBy != structs.EvalTriggerJobRegister {
    88  		t.Fatalf("bad: %#v", eval)
    89  	}
    90  	if eval.JobID != job.ID {
    91  		t.Fatalf("bad: %#v", eval)
    92  	}
    93  	if eval.JobModifyIndex != resp.JobModifyIndex {
    94  		t.Fatalf("bad: %#v", eval)
    95  	}
    96  	if eval.Status != structs.EvalStatusPending {
    97  		t.Fatalf("bad: %#v", eval)
    98  	}
    99  }
   100  
   101  func TestJobEndpoint_Register_ACL(t *testing.T) {
   102  	t.Parallel()
   103  	s1, root := TestACLServer(t, func(c *Config) {
   104  		c.NumSchedulers = 0 // Prevent automatic dequeue
   105  	})
   106  	defer s1.Shutdown()
   107  	codec := rpcClient(t, s1)
   108  	testutil.WaitForLeader(t, s1.RPC)
   109  
   110  	// Create the register request
   111  	job := mock.Job()
   112  	req := &structs.JobRegisterRequest{
   113  		Job:          job,
   114  		WriteRequest: structs.WriteRequest{Region: "global"},
   115  	}
   116  
   117  	// Try without a token, expect failure
   118  	var resp structs.JobRegisterResponse
   119  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err == nil {
   120  		t.Fatalf("expected error")
   121  	}
   122  
   123  	// Try with a token
   124  	req.AuthToken = root.SecretID
   125  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil {
   126  		t.Fatalf("err: %v", err)
   127  	}
   128  	if resp.Index == 0 {
   129  		t.Fatalf("bad index: %d", resp.Index)
   130  	}
   131  
   132  	// Check for the node in the FSM
   133  	state := s1.fsm.State()
   134  	ws := memdb.NewWatchSet()
   135  	out, err := state.JobByID(ws, job.Namespace, job.ID)
   136  	if err != nil {
   137  		t.Fatalf("err: %v", err)
   138  	}
   139  	if out == nil {
   140  		t.Fatalf("expected job")
   141  	}
   142  }
   143  
   144  func TestJobEndpoint_Register_InvalidNamespace(t *testing.T) {
   145  	t.Parallel()
   146  	s1 := TestServer(t, func(c *Config) {
   147  		c.NumSchedulers = 0 // Prevent automatic dequeue
   148  	})
   149  	defer s1.Shutdown()
   150  	codec := rpcClient(t, s1)
   151  	testutil.WaitForLeader(t, s1.RPC)
   152  
   153  	// Create the register request
   154  	job := mock.Job()
   155  	job.Namespace = "foo"
   156  	req := &structs.JobRegisterRequest{
   157  		Job:          job,
   158  		WriteRequest: structs.WriteRequest{Region: "global"},
   159  	}
   160  
   161  	// Try without a token, expect failure
   162  	var resp structs.JobRegisterResponse
   163  	err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp)
   164  	if err == nil || !strings.Contains(err.Error(), "nonexistent namespace") {
   165  		t.Fatalf("expected namespace error: %v", err)
   166  	}
   167  
   168  	// Check for the job in the FSM
   169  	state := s1.fsm.State()
   170  	ws := memdb.NewWatchSet()
   171  	out, err := state.JobByID(ws, job.Namespace, job.ID)
   172  	if err != nil {
   173  		t.Fatalf("err: %v", err)
   174  	}
   175  	if out != nil {
   176  		t.Fatalf("expected no job")
   177  	}
   178  }
   179  
   180  func TestJobEndpoint_Register_InvalidDriverConfig(t *testing.T) {
   181  	t.Parallel()
   182  	s1 := TestServer(t, func(c *Config) {
   183  		c.NumSchedulers = 0 // Prevent automatic dequeue
   184  	})
   185  	defer s1.Shutdown()
   186  	codec := rpcClient(t, s1)
   187  	testutil.WaitForLeader(t, s1.RPC)
   188  
   189  	// Create the register request with a job containing an invalid driver
   190  	// config
   191  	job := mock.Job()
   192  	job.TaskGroups[0].Tasks[0].Config["foo"] = 1
   193  	req := &structs.JobRegisterRequest{
   194  		Job: job,
   195  		WriteRequest: structs.WriteRequest{
   196  			Region:    "global",
   197  			Namespace: job.Namespace,
   198  		},
   199  	}
   200  
   201  	// Fetch the response
   202  	var resp structs.JobRegisterResponse
   203  	err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp)
   204  	if err == nil {
   205  		t.Fatalf("expected a validation error")
   206  	}
   207  
   208  	if !strings.Contains(err.Error(), "-> config:") {
   209  		t.Fatalf("expected a driver config validation error but got: %v", err)
   210  	}
   211  }
   212  
   213  func TestJobEndpoint_Register_Payload(t *testing.T) {
   214  	t.Parallel()
   215  	s1 := TestServer(t, func(c *Config) {
   216  		c.NumSchedulers = 0 // Prevent automatic dequeue
   217  	})
   218  	defer s1.Shutdown()
   219  	codec := rpcClient(t, s1)
   220  	testutil.WaitForLeader(t, s1.RPC)
   221  
   222  	// Create the register request with a job containing an invalid driver
   223  	// config
   224  	job := mock.Job()
   225  	job.Payload = []byte{0x1}
   226  	req := &structs.JobRegisterRequest{
   227  		Job: job,
   228  		WriteRequest: structs.WriteRequest{
   229  			Region:    "global",
   230  			Namespace: job.Namespace,
   231  		},
   232  	}
   233  
   234  	// Fetch the response
   235  	var resp structs.JobRegisterResponse
   236  	err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp)
   237  	if err == nil {
   238  		t.Fatalf("expected a validation error")
   239  	}
   240  
   241  	if !strings.Contains(err.Error(), "payload") {
   242  		t.Fatalf("expected a payload error but got: %v", err)
   243  	}
   244  }
   245  
   246  func TestJobEndpoint_Register_Existing(t *testing.T) {
   247  	t.Parallel()
   248  	s1 := TestServer(t, func(c *Config) {
   249  		c.NumSchedulers = 0 // Prevent automatic dequeue
   250  	})
   251  	defer s1.Shutdown()
   252  	codec := rpcClient(t, s1)
   253  	testutil.WaitForLeader(t, s1.RPC)
   254  
   255  	// Create the register request
   256  	job := mock.Job()
   257  	req := &structs.JobRegisterRequest{
   258  		Job: job,
   259  		WriteRequest: structs.WriteRequest{
   260  			Region:    "global",
   261  			Namespace: job.Namespace,
   262  		},
   263  	}
   264  
   265  	// Fetch the response
   266  	var resp structs.JobRegisterResponse
   267  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil {
   268  		t.Fatalf("err: %v", err)
   269  	}
   270  	if resp.Index == 0 {
   271  		t.Fatalf("bad index: %d", resp.Index)
   272  	}
   273  
   274  	// Update the job definition
   275  	job2 := mock.Job()
   276  	job2.Priority = 100
   277  	job2.ID = job.ID
   278  	req.Job = job2
   279  
   280  	// Attempt update
   281  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil {
   282  		t.Fatalf("err: %v", err)
   283  	}
   284  	if resp.Index == 0 {
   285  		t.Fatalf("bad index: %d", resp.Index)
   286  	}
   287  
   288  	// Check for the node in the FSM
   289  	state := s1.fsm.State()
   290  	ws := memdb.NewWatchSet()
   291  	out, err := state.JobByID(ws, job.Namespace, job.ID)
   292  	if err != nil {
   293  		t.Fatalf("err: %v", err)
   294  	}
   295  	if out == nil {
   296  		t.Fatalf("expected job")
   297  	}
   298  	if out.ModifyIndex != resp.JobModifyIndex {
   299  		t.Fatalf("index mis-match")
   300  	}
   301  	if out.Priority != 100 {
   302  		t.Fatalf("expected update")
   303  	}
   304  	if out.Version != 1 {
   305  		t.Fatalf("expected update")
   306  	}
   307  
   308  	// Lookup the evaluation
   309  	eval, err := state.EvalByID(ws, resp.EvalID)
   310  	if err != nil {
   311  		t.Fatalf("err: %v", err)
   312  	}
   313  	if eval == nil {
   314  		t.Fatalf("expected eval")
   315  	}
   316  	if eval.CreateIndex != resp.EvalCreateIndex {
   317  		t.Fatalf("index mis-match")
   318  	}
   319  
   320  	if eval.Priority != job2.Priority {
   321  		t.Fatalf("bad: %#v", eval)
   322  	}
   323  	if eval.Type != job2.Type {
   324  		t.Fatalf("bad: %#v", eval)
   325  	}
   326  	if eval.TriggeredBy != structs.EvalTriggerJobRegister {
   327  		t.Fatalf("bad: %#v", eval)
   328  	}
   329  	if eval.JobID != job2.ID {
   330  		t.Fatalf("bad: %#v", eval)
   331  	}
   332  	if eval.JobModifyIndex != resp.JobModifyIndex {
   333  		t.Fatalf("bad: %#v", eval)
   334  	}
   335  	if eval.Status != structs.EvalStatusPending {
   336  		t.Fatalf("bad: %#v", eval)
   337  	}
   338  
   339  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil {
   340  		t.Fatalf("err: %v", err)
   341  	}
   342  	if resp.Index == 0 {
   343  		t.Fatalf("bad index: %d", resp.Index)
   344  	}
   345  
   346  	// Check to ensure the job version didn't get bumped because we submitted
   347  	// the same job
   348  	state = s1.fsm.State()
   349  	ws = memdb.NewWatchSet()
   350  	out, err = state.JobByID(ws, job.Namespace, job.ID)
   351  	if err != nil {
   352  		t.Fatalf("err: %v", err)
   353  	}
   354  	if out == nil {
   355  		t.Fatalf("expected job")
   356  	}
   357  	if out.Version != 1 {
   358  		t.Fatalf("expected no update; got %v; diff %v", out.Version, pretty.Diff(job2, out))
   359  	}
   360  }
   361  
   362  func TestJobEndpoint_Register_Periodic(t *testing.T) {
   363  	t.Parallel()
   364  	s1 := TestServer(t, func(c *Config) {
   365  		c.NumSchedulers = 0 // Prevent automatic dequeue
   366  	})
   367  	defer s1.Shutdown()
   368  	codec := rpcClient(t, s1)
   369  	testutil.WaitForLeader(t, s1.RPC)
   370  
   371  	// Create the register request for a periodic job.
   372  	job := mock.PeriodicJob()
   373  	req := &structs.JobRegisterRequest{
   374  		Job: job,
   375  		WriteRequest: structs.WriteRequest{
   376  			Region:    "global",
   377  			Namespace: job.Namespace,
   378  		},
   379  	}
   380  
   381  	// Fetch the response
   382  	var resp structs.JobRegisterResponse
   383  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil {
   384  		t.Fatalf("err: %v", err)
   385  	}
   386  	if resp.JobModifyIndex == 0 {
   387  		t.Fatalf("bad index: %d", resp.Index)
   388  	}
   389  
   390  	// Check for the node in the FSM
   391  	state := s1.fsm.State()
   392  	ws := memdb.NewWatchSet()
   393  	out, err := state.JobByID(ws, job.Namespace, job.ID)
   394  	if err != nil {
   395  		t.Fatalf("err: %v", err)
   396  	}
   397  	if out == nil {
   398  		t.Fatalf("expected job")
   399  	}
   400  	if out.CreateIndex != resp.JobModifyIndex {
   401  		t.Fatalf("index mis-match")
   402  	}
   403  	serviceName := out.TaskGroups[0].Tasks[0].Services[0].Name
   404  	expectedServiceName := "web-frontend"
   405  	if serviceName != expectedServiceName {
   406  		t.Fatalf("Expected Service Name: %s, Actual: %s", expectedServiceName, serviceName)
   407  	}
   408  
   409  	if resp.EvalID != "" {
   410  		t.Fatalf("Register created an eval for a periodic job")
   411  	}
   412  }
   413  
   414  func TestJobEndpoint_Register_ParameterizedJob(t *testing.T) {
   415  	t.Parallel()
   416  	s1 := TestServer(t, func(c *Config) {
   417  		c.NumSchedulers = 0 // Prevent automatic dequeue
   418  	})
   419  	defer s1.Shutdown()
   420  	codec := rpcClient(t, s1)
   421  	testutil.WaitForLeader(t, s1.RPC)
   422  
   423  	// Create the register request for a parameterized job.
   424  	job := mock.BatchJob()
   425  	job.ParameterizedJob = &structs.ParameterizedJobConfig{}
   426  	req := &structs.JobRegisterRequest{
   427  		Job: job,
   428  		WriteRequest: structs.WriteRequest{
   429  			Region:    "global",
   430  			Namespace: job.Namespace,
   431  		},
   432  	}
   433  
   434  	// Fetch the response
   435  	var resp structs.JobRegisterResponse
   436  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil {
   437  		t.Fatalf("err: %v", err)
   438  	}
   439  	if resp.JobModifyIndex == 0 {
   440  		t.Fatalf("bad index: %d", resp.Index)
   441  	}
   442  
   443  	// Check for the job in the FSM
   444  	state := s1.fsm.State()
   445  	ws := memdb.NewWatchSet()
   446  	out, err := state.JobByID(ws, job.Namespace, job.ID)
   447  	if err != nil {
   448  		t.Fatalf("err: %v", err)
   449  	}
   450  	if out == nil {
   451  		t.Fatalf("expected job")
   452  	}
   453  	if out.CreateIndex != resp.JobModifyIndex {
   454  		t.Fatalf("index mis-match")
   455  	}
   456  	if resp.EvalID != "" {
   457  		t.Fatalf("Register created an eval for a parameterized job")
   458  	}
   459  }
   460  
   461  func TestJobEndpoint_Register_Dispatched(t *testing.T) {
   462  	t.Parallel()
   463  	require := require.New(t)
   464  	s1 := TestServer(t, func(c *Config) {
   465  		c.NumSchedulers = 0 // Prevent automatic dequeue
   466  	})
   467  	defer s1.Shutdown()
   468  	codec := rpcClient(t, s1)
   469  	testutil.WaitForLeader(t, s1.RPC)
   470  
   471  	// Create the register request with a job with 'Dispatch' set to true
   472  	job := mock.Job()
   473  	job.Dispatched = true
   474  	req := &structs.JobRegisterRequest{
   475  		Job: job,
   476  		WriteRequest: structs.WriteRequest{
   477  			Region:    "global",
   478  			Namespace: job.Namespace,
   479  		},
   480  	}
   481  
   482  	// Fetch the response
   483  	var resp structs.JobRegisterResponse
   484  	err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp)
   485  	require.Error(err)
   486  	require.Contains(err.Error(), "job can't be submitted with 'Dispatched'")
   487  }
   488  func TestJobEndpoint_Register_EnforceIndex(t *testing.T) {
   489  	t.Parallel()
   490  	s1 := TestServer(t, func(c *Config) {
   491  		c.NumSchedulers = 0 // Prevent automatic dequeue
   492  	})
   493  	defer s1.Shutdown()
   494  	codec := rpcClient(t, s1)
   495  	testutil.WaitForLeader(t, s1.RPC)
   496  
   497  	// Create the register request and enforcing an incorrect index
   498  	job := mock.Job()
   499  	req := &structs.JobRegisterRequest{
   500  		Job:            job,
   501  		EnforceIndex:   true,
   502  		JobModifyIndex: 100, // Not registered yet so not possible
   503  		WriteRequest: structs.WriteRequest{
   504  			Region:    "global",
   505  			Namespace: job.Namespace,
   506  		},
   507  	}
   508  
   509  	// Fetch the response
   510  	var resp structs.JobRegisterResponse
   511  	err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp)
   512  	if err == nil || !strings.Contains(err.Error(), RegisterEnforceIndexErrPrefix) {
   513  		t.Fatalf("expected enforcement error")
   514  	}
   515  
   516  	// Create the register request and enforcing it is new
   517  	req = &structs.JobRegisterRequest{
   518  		Job:            job,
   519  		EnforceIndex:   true,
   520  		JobModifyIndex: 0,
   521  		WriteRequest: structs.WriteRequest{
   522  			Region:    "global",
   523  			Namespace: job.Namespace,
   524  		},
   525  	}
   526  
   527  	// Fetch the response
   528  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil {
   529  		t.Fatalf("err: %v", err)
   530  	}
   531  	if resp.Index == 0 {
   532  		t.Fatalf("bad index: %d", resp.Index)
   533  	}
   534  
   535  	curIndex := resp.JobModifyIndex
   536  
   537  	// Check for the node in the FSM
   538  	state := s1.fsm.State()
   539  	ws := memdb.NewWatchSet()
   540  	out, err := state.JobByID(ws, job.Namespace, job.ID)
   541  	if err != nil {
   542  		t.Fatalf("err: %v", err)
   543  	}
   544  	if out == nil {
   545  		t.Fatalf("expected job")
   546  	}
   547  	if out.CreateIndex != resp.JobModifyIndex {
   548  		t.Fatalf("index mis-match")
   549  	}
   550  
   551  	// Reregister request and enforcing it be a new job
   552  	req = &structs.JobRegisterRequest{
   553  		Job:            job,
   554  		EnforceIndex:   true,
   555  		JobModifyIndex: 0,
   556  		WriteRequest: structs.WriteRequest{
   557  			Region:    "global",
   558  			Namespace: job.Namespace,
   559  		},
   560  	}
   561  
   562  	// Fetch the response
   563  	err = msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp)
   564  	if err == nil || !strings.Contains(err.Error(), RegisterEnforceIndexErrPrefix) {
   565  		t.Fatalf("expected enforcement error")
   566  	}
   567  
   568  	// Reregister request and enforcing it be at an incorrect index
   569  	req = &structs.JobRegisterRequest{
   570  		Job:            job,
   571  		EnforceIndex:   true,
   572  		JobModifyIndex: curIndex - 1,
   573  		WriteRequest: structs.WriteRequest{
   574  			Region:    "global",
   575  			Namespace: job.Namespace,
   576  		},
   577  	}
   578  
   579  	// Fetch the response
   580  	err = msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp)
   581  	if err == nil || !strings.Contains(err.Error(), RegisterEnforceIndexErrPrefix) {
   582  		t.Fatalf("expected enforcement error")
   583  	}
   584  
   585  	// Reregister request and enforcing it be at the correct index
   586  	job.Priority = job.Priority + 1
   587  	req = &structs.JobRegisterRequest{
   588  		Job:            job,
   589  		EnforceIndex:   true,
   590  		JobModifyIndex: curIndex,
   591  		WriteRequest: structs.WriteRequest{
   592  			Region:    "global",
   593  			Namespace: job.Namespace,
   594  		},
   595  	}
   596  
   597  	// Fetch the response
   598  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil {
   599  		t.Fatalf("err: %v", err)
   600  	}
   601  	if resp.Index == 0 {
   602  		t.Fatalf("bad index: %d", resp.Index)
   603  	}
   604  
   605  	out, err = state.JobByID(ws, job.Namespace, job.ID)
   606  	if err != nil {
   607  		t.Fatalf("err: %v", err)
   608  	}
   609  	if out == nil {
   610  		t.Fatalf("expected job")
   611  	}
   612  	if out.Priority != job.Priority {
   613  		t.Fatalf("priority mis-match")
   614  	}
   615  }
   616  
   617  func TestJobEndpoint_Register_Vault_Disabled(t *testing.T) {
   618  	t.Parallel()
   619  	s1 := TestServer(t, func(c *Config) {
   620  		c.NumSchedulers = 0 // Prevent automatic dequeue
   621  		f := false
   622  		c.VaultConfig.Enabled = &f
   623  	})
   624  	defer s1.Shutdown()
   625  	codec := rpcClient(t, s1)
   626  	testutil.WaitForLeader(t, s1.RPC)
   627  
   628  	// Create the register request with a job asking for a vault policy
   629  	job := mock.Job()
   630  	job.TaskGroups[0].Tasks[0].Vault = &structs.Vault{
   631  		Policies:   []string{"foo"},
   632  		ChangeMode: structs.VaultChangeModeRestart,
   633  	}
   634  	req := &structs.JobRegisterRequest{
   635  		Job: job,
   636  		WriteRequest: structs.WriteRequest{
   637  			Region:    "global",
   638  			Namespace: job.Namespace,
   639  		},
   640  	}
   641  
   642  	// Fetch the response
   643  	var resp structs.JobRegisterResponse
   644  	err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp)
   645  	if err == nil || !strings.Contains(err.Error(), "Vault not enabled") {
   646  		t.Fatalf("expected Vault not enabled error: %v", err)
   647  	}
   648  }
   649  
   650  func TestJobEndpoint_Register_Vault_AllowUnauthenticated(t *testing.T) {
   651  	t.Parallel()
   652  	s1 := TestServer(t, func(c *Config) {
   653  		c.NumSchedulers = 0 // Prevent automatic dequeue
   654  	})
   655  	defer s1.Shutdown()
   656  	codec := rpcClient(t, s1)
   657  	testutil.WaitForLeader(t, s1.RPC)
   658  
   659  	// Enable vault and allow authenticated
   660  	tr := true
   661  	s1.config.VaultConfig.Enabled = &tr
   662  	s1.config.VaultConfig.AllowUnauthenticated = &tr
   663  
   664  	// Replace the Vault Client on the server
   665  	s1.vault = &TestVaultClient{}
   666  
   667  	// Create the register request with a job asking for a vault policy
   668  	job := mock.Job()
   669  	job.TaskGroups[0].Tasks[0].Vault = &structs.Vault{
   670  		Policies:   []string{"foo"},
   671  		ChangeMode: structs.VaultChangeModeRestart,
   672  	}
   673  	req := &structs.JobRegisterRequest{
   674  		Job: job,
   675  		WriteRequest: structs.WriteRequest{
   676  			Region:    "global",
   677  			Namespace: job.Namespace,
   678  		},
   679  	}
   680  
   681  	// Fetch the response
   682  	var resp structs.JobRegisterResponse
   683  	err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp)
   684  	if err != nil {
   685  		t.Fatalf("bad: %v", err)
   686  	}
   687  
   688  	// Check for the job in the FSM
   689  	state := s1.fsm.State()
   690  	ws := memdb.NewWatchSet()
   691  	out, err := state.JobByID(ws, job.Namespace, job.ID)
   692  	if err != nil {
   693  		t.Fatalf("err: %v", err)
   694  	}
   695  	if out == nil {
   696  		t.Fatalf("expected job")
   697  	}
   698  	if out.CreateIndex != resp.JobModifyIndex {
   699  		t.Fatalf("index mis-match")
   700  	}
   701  }
   702  
   703  func TestJobEndpoint_Register_Vault_NoToken(t *testing.T) {
   704  	t.Parallel()
   705  	s1 := TestServer(t, func(c *Config) {
   706  		c.NumSchedulers = 0 // Prevent automatic dequeue
   707  	})
   708  	defer s1.Shutdown()
   709  	codec := rpcClient(t, s1)
   710  	testutil.WaitForLeader(t, s1.RPC)
   711  
   712  	// Enable vault
   713  	tr, f := true, false
   714  	s1.config.VaultConfig.Enabled = &tr
   715  	s1.config.VaultConfig.AllowUnauthenticated = &f
   716  
   717  	// Replace the Vault Client on the server
   718  	s1.vault = &TestVaultClient{}
   719  
   720  	// Create the register request with a job asking for a vault policy but
   721  	// don't send a Vault token
   722  	job := mock.Job()
   723  	job.TaskGroups[0].Tasks[0].Vault = &structs.Vault{
   724  		Policies:   []string{"foo"},
   725  		ChangeMode: structs.VaultChangeModeRestart,
   726  	}
   727  	req := &structs.JobRegisterRequest{
   728  		Job: job,
   729  		WriteRequest: structs.WriteRequest{
   730  			Region:    "global",
   731  			Namespace: job.Namespace,
   732  		},
   733  	}
   734  
   735  	// Fetch the response
   736  	var resp structs.JobRegisterResponse
   737  	err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp)
   738  	if err == nil || !strings.Contains(err.Error(), "missing Vault Token") {
   739  		t.Fatalf("expected Vault not enabled error: %v", err)
   740  	}
   741  }
   742  
   743  func TestJobEndpoint_Register_Vault_Policies(t *testing.T) {
   744  	t.Parallel()
   745  	s1 := TestServer(t, func(c *Config) {
   746  		c.NumSchedulers = 0 // Prevent automatic dequeue
   747  	})
   748  	defer s1.Shutdown()
   749  	codec := rpcClient(t, s1)
   750  	testutil.WaitForLeader(t, s1.RPC)
   751  
   752  	// Enable vault
   753  	tr, f := true, false
   754  	s1.config.VaultConfig.Enabled = &tr
   755  	s1.config.VaultConfig.AllowUnauthenticated = &f
   756  
   757  	// Replace the Vault Client on the server
   758  	tvc := &TestVaultClient{}
   759  	s1.vault = tvc
   760  
   761  	// Add three tokens: one that allows the requesting policy, one that does
   762  	// not and one that returns an error
   763  	policy := "foo"
   764  
   765  	badToken := uuid.Generate()
   766  	badPolicies := []string{"a", "b", "c"}
   767  	tvc.SetLookupTokenAllowedPolicies(badToken, badPolicies)
   768  
   769  	goodToken := uuid.Generate()
   770  	goodPolicies := []string{"foo", "bar", "baz"}
   771  	tvc.SetLookupTokenAllowedPolicies(goodToken, goodPolicies)
   772  
   773  	rootToken := uuid.Generate()
   774  	rootPolicies := []string{"root"}
   775  	tvc.SetLookupTokenAllowedPolicies(rootToken, rootPolicies)
   776  
   777  	errToken := uuid.Generate()
   778  	expectedErr := fmt.Errorf("return errors from vault")
   779  	tvc.SetLookupTokenError(errToken, expectedErr)
   780  
   781  	// Create the register request with a job asking for a vault policy but
   782  	// send the bad Vault token
   783  	job := mock.Job()
   784  	job.VaultToken = badToken
   785  	job.TaskGroups[0].Tasks[0].Vault = &structs.Vault{
   786  		Policies:   []string{policy},
   787  		ChangeMode: structs.VaultChangeModeRestart,
   788  	}
   789  	req := &structs.JobRegisterRequest{
   790  		Job: job,
   791  		WriteRequest: structs.WriteRequest{
   792  			Region:    "global",
   793  			Namespace: job.Namespace,
   794  		},
   795  	}
   796  
   797  	// Fetch the response
   798  	var resp structs.JobRegisterResponse
   799  	err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp)
   800  	if err == nil || !strings.Contains(err.Error(),
   801  		"doesn't allow access to the following policies: "+policy) {
   802  		t.Fatalf("expected permission denied error: %v", err)
   803  	}
   804  
   805  	// Use the err token
   806  	job.VaultToken = errToken
   807  	err = msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp)
   808  	if err == nil || !strings.Contains(err.Error(), expectedErr.Error()) {
   809  		t.Fatalf("expected permission denied error: %v", err)
   810  	}
   811  
   812  	// Use the good token
   813  	job.VaultToken = goodToken
   814  
   815  	// Fetch the response
   816  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil {
   817  		t.Fatalf("bad: %v", err)
   818  	}
   819  
   820  	// Check for the job in the FSM
   821  	state := s1.fsm.State()
   822  	ws := memdb.NewWatchSet()
   823  	out, err := state.JobByID(ws, job.Namespace, job.ID)
   824  	if err != nil {
   825  		t.Fatalf("err: %v", err)
   826  	}
   827  	if out == nil {
   828  		t.Fatalf("expected job")
   829  	}
   830  	if out.CreateIndex != resp.JobModifyIndex {
   831  		t.Fatalf("index mis-match")
   832  	}
   833  	if out.VaultToken != "" {
   834  		t.Fatalf("vault token not cleared")
   835  	}
   836  
   837  	// Check that an implicit constraint was created
   838  	constraints := out.TaskGroups[0].Constraints
   839  	if l := len(constraints); l != 1 {
   840  		t.Fatalf("Unexpected number of tests: %v", l)
   841  	}
   842  
   843  	if !constraints[0].Equal(vaultConstraint) {
   844  		t.Fatalf("bad constraint; got %#v; want %#v", constraints[0], vaultConstraint)
   845  	}
   846  
   847  	// Create the register request with another job asking for a vault policy but
   848  	// send the root Vault token
   849  	job2 := mock.Job()
   850  	job2.VaultToken = rootToken
   851  	job.TaskGroups[0].Tasks[0].Vault = &structs.Vault{
   852  		Policies:   []string{policy},
   853  		ChangeMode: structs.VaultChangeModeRestart,
   854  	}
   855  	req = &structs.JobRegisterRequest{
   856  		Job: job2,
   857  		WriteRequest: structs.WriteRequest{
   858  			Region:    "global",
   859  			Namespace: job.Namespace,
   860  		},
   861  	}
   862  
   863  	// Fetch the response
   864  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil {
   865  		t.Fatalf("bad: %v", err)
   866  	}
   867  
   868  	// Check for the job in the FSM
   869  	out, err = state.JobByID(ws, job2.Namespace, job2.ID)
   870  	if err != nil {
   871  		t.Fatalf("err: %v", err)
   872  	}
   873  	if out == nil {
   874  		t.Fatalf("expected job")
   875  	}
   876  	if out.CreateIndex != resp.JobModifyIndex {
   877  		t.Fatalf("index mis-match")
   878  	}
   879  	if out.VaultToken != "" {
   880  		t.Fatalf("vault token not cleared")
   881  	}
   882  }
   883  
   884  func TestJobEndpoint_Revert(t *testing.T) {
   885  	t.Parallel()
   886  	s1 := TestServer(t, func(c *Config) {
   887  		c.NumSchedulers = 0 // Prevent automatic dequeue
   888  	})
   889  	defer s1.Shutdown()
   890  	codec := rpcClient(t, s1)
   891  	testutil.WaitForLeader(t, s1.RPC)
   892  
   893  	// Create the initial register request
   894  	job := mock.Job()
   895  	job.Priority = 100
   896  	req := &structs.JobRegisterRequest{
   897  		Job: job,
   898  		WriteRequest: structs.WriteRequest{
   899  			Region:    "global",
   900  			Namespace: job.Namespace,
   901  		},
   902  	}
   903  
   904  	// Fetch the response
   905  	var resp structs.JobRegisterResponse
   906  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil {
   907  		t.Fatalf("err: %v", err)
   908  	}
   909  	if resp.Index == 0 {
   910  		t.Fatalf("bad index: %d", resp.Index)
   911  	}
   912  
   913  	// Reregister again to get another version
   914  	job2 := job.Copy()
   915  	job2.Priority = 1
   916  	req = &structs.JobRegisterRequest{
   917  		Job: job2,
   918  		WriteRequest: structs.WriteRequest{
   919  			Region:    "global",
   920  			Namespace: job.Namespace,
   921  		},
   922  	}
   923  
   924  	// Fetch the response
   925  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil {
   926  		t.Fatalf("err: %v", err)
   927  	}
   928  	if resp.Index == 0 {
   929  		t.Fatalf("bad index: %d", resp.Index)
   930  	}
   931  
   932  	// Create revert request and enforcing it be at an incorrect version
   933  	revertReq := &structs.JobRevertRequest{
   934  		JobID:               job.ID,
   935  		JobVersion:          0,
   936  		EnforcePriorVersion: helper.Uint64ToPtr(10),
   937  		WriteRequest: structs.WriteRequest{
   938  			Region:    "global",
   939  			Namespace: job.Namespace,
   940  		},
   941  	}
   942  
   943  	// Fetch the response
   944  	err := msgpackrpc.CallWithCodec(codec, "Job.Revert", revertReq, &resp)
   945  	if err == nil || !strings.Contains(err.Error(), "enforcing version 10") {
   946  		t.Fatalf("expected enforcement error")
   947  	}
   948  
   949  	// Create revert request and enforcing it be at the current version
   950  	revertReq = &structs.JobRevertRequest{
   951  		JobID:      job.ID,
   952  		JobVersion: 1,
   953  		WriteRequest: structs.WriteRequest{
   954  			Region:    "global",
   955  			Namespace: job.Namespace,
   956  		},
   957  	}
   958  
   959  	// Fetch the response
   960  	err = msgpackrpc.CallWithCodec(codec, "Job.Revert", revertReq, &resp)
   961  	if err == nil || !strings.Contains(err.Error(), "current version") {
   962  		t.Fatalf("expected current version err: %v", err)
   963  	}
   964  
   965  	// Create revert request and enforcing it be at version 1
   966  	revertReq = &structs.JobRevertRequest{
   967  		JobID:               job.ID,
   968  		JobVersion:          0,
   969  		EnforcePriorVersion: helper.Uint64ToPtr(1),
   970  		WriteRequest: structs.WriteRequest{
   971  			Region:    "global",
   972  			Namespace: job.Namespace,
   973  		},
   974  	}
   975  
   976  	// Fetch the response
   977  	if err := msgpackrpc.CallWithCodec(codec, "Job.Revert", revertReq, &resp); err != nil {
   978  		t.Fatalf("err: %v", err)
   979  	}
   980  	if resp.Index == 0 {
   981  		t.Fatalf("bad index: %d", resp.Index)
   982  	}
   983  	if resp.EvalID == "" || resp.EvalCreateIndex == 0 {
   984  		t.Fatalf("bad created eval: %+v", resp)
   985  	}
   986  	if resp.JobModifyIndex == 0 {
   987  		t.Fatalf("bad job modify index: %d", resp.JobModifyIndex)
   988  	}
   989  
   990  	// Create revert request and don't enforce. We are at version 2 but it is
   991  	// the same as version 0
   992  	revertReq = &structs.JobRevertRequest{
   993  		JobID:      job.ID,
   994  		JobVersion: 0,
   995  		WriteRequest: structs.WriteRequest{
   996  			Region:    "global",
   997  			Namespace: job.Namespace,
   998  		},
   999  	}
  1000  
  1001  	// Fetch the response
  1002  	if err := msgpackrpc.CallWithCodec(codec, "Job.Revert", revertReq, &resp); err != nil {
  1003  		t.Fatalf("err: %v", err)
  1004  	}
  1005  	if resp.Index == 0 {
  1006  		t.Fatalf("bad index: %d", resp.Index)
  1007  	}
  1008  	if resp.EvalID == "" || resp.EvalCreateIndex == 0 {
  1009  		t.Fatalf("bad created eval: %+v", resp)
  1010  	}
  1011  	if resp.JobModifyIndex == 0 {
  1012  		t.Fatalf("bad job modify index: %d", resp.JobModifyIndex)
  1013  	}
  1014  
  1015  	// Check that the job is at the correct version and that the eval was
  1016  	// created
  1017  	state := s1.fsm.State()
  1018  	ws := memdb.NewWatchSet()
  1019  	out, err := state.JobByID(ws, job.Namespace, job.ID)
  1020  	if err != nil {
  1021  		t.Fatalf("err: %v", err)
  1022  	}
  1023  	if out == nil {
  1024  		t.Fatalf("expected job")
  1025  	}
  1026  	if out.Priority != job.Priority {
  1027  		t.Fatalf("priority mis-match")
  1028  	}
  1029  	if out.Version != 2 {
  1030  		t.Fatalf("got version %d; want %d", out.Version, 2)
  1031  	}
  1032  
  1033  	eout, err := state.EvalByID(ws, resp.EvalID)
  1034  	if err != nil {
  1035  		t.Fatalf("err: %v", err)
  1036  	}
  1037  	if eout == nil {
  1038  		t.Fatalf("expected eval")
  1039  	}
  1040  	if eout.JobID != job.ID {
  1041  		t.Fatalf("job id mis-match")
  1042  	}
  1043  
  1044  	versions, err := state.JobVersionsByID(ws, job.Namespace, job.ID)
  1045  	if err != nil {
  1046  		t.Fatalf("err: %v", err)
  1047  	}
  1048  	if len(versions) != 3 {
  1049  		t.Fatalf("got %d versions; want %d", len(versions), 3)
  1050  	}
  1051  }
  1052  
  1053  func TestJobEndpoint_Revert_ACL(t *testing.T) {
  1054  	t.Parallel()
  1055  	require := require.New(t)
  1056  
  1057  	s1, root := TestACLServer(t, func(c *Config) {
  1058  		c.NumSchedulers = 0 // Prevent automatic dequeue
  1059  	})
  1060  
  1061  	defer s1.Shutdown()
  1062  	codec := rpcClient(t, s1)
  1063  	state := s1.fsm.State()
  1064  	testutil.WaitForLeader(t, s1.RPC)
  1065  
  1066  	// Create the job
  1067  	job := mock.Job()
  1068  	err := state.UpsertJob(300, job)
  1069  	require.Nil(err)
  1070  
  1071  	job2 := job.Copy()
  1072  	job2.Priority = 1
  1073  	err = state.UpsertJob(400, job2)
  1074  	require.Nil(err)
  1075  
  1076  	// Create revert request and enforcing it be at the current version
  1077  	revertReq := &structs.JobRevertRequest{
  1078  		JobID:      job.ID,
  1079  		JobVersion: 0,
  1080  		WriteRequest: structs.WriteRequest{
  1081  			Region:    "global",
  1082  			Namespace: job.Namespace,
  1083  		},
  1084  	}
  1085  
  1086  	// Attempt to fetch the response without a valid token
  1087  	var resp structs.JobRegisterResponse
  1088  	err = msgpackrpc.CallWithCodec(codec, "Job.Revert", revertReq, &resp)
  1089  	require.NotNil(err)
  1090  	require.Contains(err.Error(), "Permission denied")
  1091  
  1092  	// Attempt to fetch the response with an invalid token
  1093  	invalidToken := mock.CreatePolicyAndToken(t, state, 1001, "test-invalid",
  1094  		mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityReadJob}))
  1095  
  1096  	revertReq.AuthToken = invalidToken.SecretID
  1097  	var invalidResp structs.JobRegisterResponse
  1098  	err = msgpackrpc.CallWithCodec(codec, "Job.Revert", revertReq, &invalidResp)
  1099  	require.NotNil(err)
  1100  	require.Contains(err.Error(), "Permission denied")
  1101  
  1102  	// Fetch the response with a valid management token
  1103  	revertReq.AuthToken = root.SecretID
  1104  	var validResp structs.JobRegisterResponse
  1105  	err = msgpackrpc.CallWithCodec(codec, "Job.Revert", revertReq, &validResp)
  1106  	require.Nil(err)
  1107  
  1108  	// Try with a valid non-management token
  1109  	validToken := mock.CreatePolicyAndToken(t, state, 1003, "test-valid",
  1110  		mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilitySubmitJob}))
  1111  
  1112  	revertReq.AuthToken = validToken.SecretID
  1113  	var validResp2 structs.JobRegisterResponse
  1114  	err = msgpackrpc.CallWithCodec(codec, "Job.Revert", revertReq, &validResp2)
  1115  	require.Nil(err)
  1116  }
  1117  
  1118  func TestJobEndpoint_Stable(t *testing.T) {
  1119  	t.Parallel()
  1120  	s1 := TestServer(t, func(c *Config) {
  1121  		c.NumSchedulers = 0 // Prevent automatic dequeue
  1122  	})
  1123  	defer s1.Shutdown()
  1124  	codec := rpcClient(t, s1)
  1125  	testutil.WaitForLeader(t, s1.RPC)
  1126  
  1127  	// Create the initial register request
  1128  	job := mock.Job()
  1129  	req := &structs.JobRegisterRequest{
  1130  		Job: job,
  1131  		WriteRequest: structs.WriteRequest{
  1132  			Region:    "global",
  1133  			Namespace: job.Namespace,
  1134  		},
  1135  	}
  1136  
  1137  	// Fetch the response
  1138  	var resp structs.JobRegisterResponse
  1139  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil {
  1140  		t.Fatalf("err: %v", err)
  1141  	}
  1142  	if resp.Index == 0 {
  1143  		t.Fatalf("bad index: %d", resp.Index)
  1144  	}
  1145  
  1146  	// Create stability request
  1147  	stableReq := &structs.JobStabilityRequest{
  1148  		JobID:      job.ID,
  1149  		JobVersion: 0,
  1150  		Stable:     true,
  1151  		WriteRequest: structs.WriteRequest{
  1152  			Region:    "global",
  1153  			Namespace: job.Namespace,
  1154  		},
  1155  	}
  1156  
  1157  	// Fetch the response
  1158  	var stableResp structs.JobStabilityResponse
  1159  	if err := msgpackrpc.CallWithCodec(codec, "Job.Stable", stableReq, &stableResp); err != nil {
  1160  		t.Fatalf("err: %v", err)
  1161  	}
  1162  	if stableResp.Index == 0 {
  1163  		t.Fatalf("bad index: %d", resp.Index)
  1164  	}
  1165  
  1166  	// Check that the job is marked stable
  1167  	state := s1.fsm.State()
  1168  	ws := memdb.NewWatchSet()
  1169  	out, err := state.JobByID(ws, job.Namespace, job.ID)
  1170  	if err != nil {
  1171  		t.Fatalf("err: %v", err)
  1172  	}
  1173  	if out == nil {
  1174  		t.Fatalf("expected job")
  1175  	}
  1176  	if !out.Stable {
  1177  		t.Fatalf("Job is not marked stable")
  1178  	}
  1179  }
  1180  
  1181  func TestJobEndpoint_Stable_ACL(t *testing.T) {
  1182  	t.Parallel()
  1183  	require := require.New(t)
  1184  
  1185  	s1, root := TestACLServer(t, func(c *Config) {
  1186  		c.NumSchedulers = 0 // Prevent automatic dequeue
  1187  	})
  1188  	defer s1.Shutdown()
  1189  	codec := rpcClient(t, s1)
  1190  	state := s1.fsm.State()
  1191  	testutil.WaitForLeader(t, s1.RPC)
  1192  
  1193  	// Register the job
  1194  	job := mock.Job()
  1195  	err := state.UpsertJob(1000, job)
  1196  	require.Nil(err)
  1197  
  1198  	// Create stability request
  1199  	stableReq := &structs.JobStabilityRequest{
  1200  		JobID:      job.ID,
  1201  		JobVersion: 0,
  1202  		Stable:     true,
  1203  		WriteRequest: structs.WriteRequest{
  1204  			Region:    "global",
  1205  			Namespace: job.Namespace,
  1206  		},
  1207  	}
  1208  
  1209  	// Attempt to fetch the token without a token
  1210  	var stableResp structs.JobStabilityResponse
  1211  	err = msgpackrpc.CallWithCodec(codec, "Job.Stable", stableReq, &stableResp)
  1212  	require.NotNil(err)
  1213  	require.Contains("Permission denied", err.Error())
  1214  
  1215  	// Expect failure for request with an invalid token
  1216  	invalidToken := mock.CreatePolicyAndToken(t, state, 1003, "test-invalid",
  1217  		mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityListJobs}))
  1218  
  1219  	stableReq.AuthToken = invalidToken.SecretID
  1220  	var invalidStableResp structs.JobStabilityResponse
  1221  	err = msgpackrpc.CallWithCodec(codec, "Job.Stable", stableReq, &invalidStableResp)
  1222  	require.NotNil(err)
  1223  	require.Contains("Permission denied", err.Error())
  1224  
  1225  	// Attempt to fetch with a management token
  1226  	stableReq.AuthToken = root.SecretID
  1227  	var validStableResp structs.JobStabilityResponse
  1228  	err = msgpackrpc.CallWithCodec(codec, "Job.Stable", stableReq, &validStableResp)
  1229  	require.Nil(err)
  1230  
  1231  	// Attempt to fetch with a valid token
  1232  	validToken := mock.CreatePolicyAndToken(t, state, 1005, "test-invalid",
  1233  		mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilitySubmitJob}))
  1234  
  1235  	stableReq.AuthToken = validToken.SecretID
  1236  	var validStableResp2 structs.JobStabilityResponse
  1237  	err = msgpackrpc.CallWithCodec(codec, "Job.Stable", stableReq, &validStableResp2)
  1238  	require.Nil(err)
  1239  
  1240  	// Check that the job is marked stable
  1241  	ws := memdb.NewWatchSet()
  1242  	out, err := state.JobByID(ws, job.Namespace, job.ID)
  1243  	require.Nil(err)
  1244  	require.NotNil(job)
  1245  	require.Equal(true, out.Stable)
  1246  }
  1247  
  1248  func TestJobEndpoint_Evaluate(t *testing.T) {
  1249  	t.Parallel()
  1250  	s1 := TestServer(t, func(c *Config) {
  1251  		c.NumSchedulers = 0 // Prevent automatic dequeue
  1252  	})
  1253  	defer s1.Shutdown()
  1254  	codec := rpcClient(t, s1)
  1255  	testutil.WaitForLeader(t, s1.RPC)
  1256  
  1257  	// Create the register request
  1258  	job := mock.Job()
  1259  	req := &structs.JobRegisterRequest{
  1260  		Job: job,
  1261  		WriteRequest: structs.WriteRequest{
  1262  			Region:    "global",
  1263  			Namespace: job.Namespace,
  1264  		},
  1265  	}
  1266  
  1267  	// Fetch the response
  1268  	var resp structs.JobRegisterResponse
  1269  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil {
  1270  		t.Fatalf("err: %v", err)
  1271  	}
  1272  	if resp.Index == 0 {
  1273  		t.Fatalf("bad index: %d", resp.Index)
  1274  	}
  1275  
  1276  	// Force a re-evaluation
  1277  	reEval := &structs.JobEvaluateRequest{
  1278  		JobID: job.ID,
  1279  		WriteRequest: structs.WriteRequest{
  1280  			Region:    "global",
  1281  			Namespace: job.Namespace,
  1282  		},
  1283  	}
  1284  
  1285  	// Fetch the response
  1286  	if err := msgpackrpc.CallWithCodec(codec, "Job.Evaluate", reEval, &resp); err != nil {
  1287  		t.Fatalf("err: %v", err)
  1288  	}
  1289  	if resp.Index == 0 {
  1290  		t.Fatalf("bad index: %d", resp.Index)
  1291  	}
  1292  
  1293  	// Lookup the evaluation
  1294  	state := s1.fsm.State()
  1295  	ws := memdb.NewWatchSet()
  1296  	eval, err := state.EvalByID(ws, resp.EvalID)
  1297  	if err != nil {
  1298  		t.Fatalf("err: %v", err)
  1299  	}
  1300  	if eval == nil {
  1301  		t.Fatalf("expected eval")
  1302  	}
  1303  	if eval.CreateIndex != resp.EvalCreateIndex {
  1304  		t.Fatalf("index mis-match")
  1305  	}
  1306  
  1307  	if eval.Priority != job.Priority {
  1308  		t.Fatalf("bad: %#v", eval)
  1309  	}
  1310  	if eval.Type != job.Type {
  1311  		t.Fatalf("bad: %#v", eval)
  1312  	}
  1313  	if eval.TriggeredBy != structs.EvalTriggerJobRegister {
  1314  		t.Fatalf("bad: %#v", eval)
  1315  	}
  1316  	if eval.JobID != job.ID {
  1317  		t.Fatalf("bad: %#v", eval)
  1318  	}
  1319  	if eval.JobModifyIndex != resp.JobModifyIndex {
  1320  		t.Fatalf("bad: %#v", eval)
  1321  	}
  1322  	if eval.Status != structs.EvalStatusPending {
  1323  		t.Fatalf("bad: %#v", eval)
  1324  	}
  1325  }
  1326  
  1327  func TestJobEndpoint_ForceRescheduleEvaluate(t *testing.T) {
  1328  	require := require.New(t)
  1329  	t.Parallel()
  1330  	s1 := TestServer(t, func(c *Config) {
  1331  		c.NumSchedulers = 0 // Prevent automatic dequeue
  1332  	})
  1333  	defer s1.Shutdown()
  1334  	codec := rpcClient(t, s1)
  1335  	testutil.WaitForLeader(t, s1.RPC)
  1336  
  1337  	// Create the register request
  1338  	job := mock.Job()
  1339  	req := &structs.JobRegisterRequest{
  1340  		Job: job,
  1341  		WriteRequest: structs.WriteRequest{
  1342  			Region:    "global",
  1343  			Namespace: job.Namespace,
  1344  		},
  1345  	}
  1346  
  1347  	// Fetch the response
  1348  	var resp structs.JobRegisterResponse
  1349  	err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp)
  1350  	require.Nil(err)
  1351  	require.NotEqual(0, resp.Index)
  1352  
  1353  	state := s1.fsm.State()
  1354  	job, err = state.JobByID(nil, structs.DefaultNamespace, job.ID)
  1355  	require.Nil(err)
  1356  
  1357  	// Create a failed alloc
  1358  	alloc := mock.Alloc()
  1359  	alloc.Job = job
  1360  	alloc.JobID = job.ID
  1361  	alloc.TaskGroup = job.TaskGroups[0].Name
  1362  	alloc.Namespace = job.Namespace
  1363  	alloc.ClientStatus = structs.AllocClientStatusFailed
  1364  	err = s1.State().UpsertAllocs(resp.Index+1, []*structs.Allocation{alloc})
  1365  	require.Nil(err)
  1366  
  1367  	// Force a re-evaluation
  1368  	reEval := &structs.JobEvaluateRequest{
  1369  		JobID:       job.ID,
  1370  		EvalOptions: structs.EvalOptions{ForceReschedule: true},
  1371  		WriteRequest: structs.WriteRequest{
  1372  			Region:    "global",
  1373  			Namespace: job.Namespace,
  1374  		},
  1375  	}
  1376  
  1377  	// Fetch the response
  1378  	err = msgpackrpc.CallWithCodec(codec, "Job.Evaluate", reEval, &resp)
  1379  	require.Nil(err)
  1380  	require.NotEqual(0, resp.Index)
  1381  
  1382  	// Lookup the evaluation
  1383  	ws := memdb.NewWatchSet()
  1384  	eval, err := state.EvalByID(ws, resp.EvalID)
  1385  	require.Nil(err)
  1386  	require.NotNil(eval)
  1387  	require.Equal(eval.CreateIndex, resp.EvalCreateIndex)
  1388  	require.Equal(eval.Priority, job.Priority)
  1389  	require.Equal(eval.Type, job.Type)
  1390  	require.Equal(eval.TriggeredBy, structs.EvalTriggerJobRegister)
  1391  	require.Equal(eval.JobID, job.ID)
  1392  	require.Equal(eval.JobModifyIndex, resp.JobModifyIndex)
  1393  	require.Equal(eval.Status, structs.EvalStatusPending)
  1394  
  1395  	// Lookup the alloc, verify DesiredTransition ForceReschedule
  1396  	alloc, err = state.AllocByID(ws, alloc.ID)
  1397  	require.NotNil(alloc)
  1398  	require.Nil(err)
  1399  	require.True(*alloc.DesiredTransition.ForceReschedule)
  1400  }
  1401  
  1402  func TestJobEndpoint_Evaluate_ACL(t *testing.T) {
  1403  	t.Parallel()
  1404  	require := require.New(t)
  1405  
  1406  	s1, root := TestACLServer(t, func(c *Config) {
  1407  		c.NumSchedulers = 0 // Prevent automatic dequeue
  1408  	})
  1409  	defer s1.Shutdown()
  1410  	codec := rpcClient(t, s1)
  1411  	testutil.WaitForLeader(t, s1.RPC)
  1412  	state := s1.fsm.State()
  1413  
  1414  	// Create the job
  1415  	job := mock.Job()
  1416  	err := state.UpsertJob(300, job)
  1417  	require.Nil(err)
  1418  
  1419  	// Force a re-evaluation
  1420  	reEval := &structs.JobEvaluateRequest{
  1421  		JobID: job.ID,
  1422  		WriteRequest: structs.WriteRequest{
  1423  			Region:    "global",
  1424  			Namespace: job.Namespace,
  1425  		},
  1426  	}
  1427  
  1428  	// Attempt to fetch the response without a token
  1429  	var resp structs.JobRegisterResponse
  1430  	err = msgpackrpc.CallWithCodec(codec, "Job.Evaluate", reEval, &resp)
  1431  	require.NotNil(err)
  1432  	require.Contains(err.Error(), "Permission denied")
  1433  
  1434  	// Attempt to fetch the response with an invalid token
  1435  	invalidToken := mock.CreatePolicyAndToken(t, state, 1003, "test-invalid",
  1436  		mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityListJobs}))
  1437  
  1438  	reEval.AuthToken = invalidToken.SecretID
  1439  	var invalidResp structs.JobRegisterResponse
  1440  	err = msgpackrpc.CallWithCodec(codec, "Job.Evaluate", reEval, &invalidResp)
  1441  	require.NotNil(err)
  1442  	require.Contains(err.Error(), "Permission denied")
  1443  
  1444  	// Fetch the response with a valid management token
  1445  	reEval.AuthToken = root.SecretID
  1446  	var validResp structs.JobRegisterResponse
  1447  	err = msgpackrpc.CallWithCodec(codec, "Job.Evaluate", reEval, &validResp)
  1448  	require.Nil(err)
  1449  
  1450  	// Fetch the response with a valid token
  1451  	validToken := mock.CreatePolicyAndToken(t, state, 1005, "test-valid",
  1452  		mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityReadJob}))
  1453  
  1454  	reEval.AuthToken = validToken.SecretID
  1455  	var validResp2 structs.JobRegisterResponse
  1456  	err = msgpackrpc.CallWithCodec(codec, "Job.Evaluate", reEval, &validResp2)
  1457  	require.Nil(err)
  1458  
  1459  	// Lookup the evaluation
  1460  	ws := memdb.NewWatchSet()
  1461  	eval, err := state.EvalByID(ws, validResp2.EvalID)
  1462  	require.Nil(err)
  1463  	require.NotNil(eval)
  1464  
  1465  	require.Equal(eval.CreateIndex, validResp2.EvalCreateIndex)
  1466  	require.Equal(eval.Priority, job.Priority)
  1467  	require.Equal(eval.Type, job.Type)
  1468  	require.Equal(eval.TriggeredBy, structs.EvalTriggerJobRegister)
  1469  	require.Equal(eval.JobID, job.ID)
  1470  	require.Equal(eval.JobModifyIndex, validResp2.JobModifyIndex)
  1471  	require.Equal(eval.Status, structs.EvalStatusPending)
  1472  }
  1473  
  1474  func TestJobEndpoint_Evaluate_Periodic(t *testing.T) {
  1475  	t.Parallel()
  1476  	s1 := TestServer(t, func(c *Config) {
  1477  		c.NumSchedulers = 0 // Prevent automatic dequeue
  1478  	})
  1479  	defer s1.Shutdown()
  1480  	codec := rpcClient(t, s1)
  1481  	testutil.WaitForLeader(t, s1.RPC)
  1482  
  1483  	// Create the register request
  1484  	job := mock.PeriodicJob()
  1485  	req := &structs.JobRegisterRequest{
  1486  		Job: job,
  1487  		WriteRequest: structs.WriteRequest{
  1488  			Region:    "global",
  1489  			Namespace: job.Namespace,
  1490  		},
  1491  	}
  1492  
  1493  	// Fetch the response
  1494  	var resp structs.JobRegisterResponse
  1495  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil {
  1496  		t.Fatalf("err: %v", err)
  1497  	}
  1498  	if resp.JobModifyIndex == 0 {
  1499  		t.Fatalf("bad index: %d", resp.Index)
  1500  	}
  1501  
  1502  	// Force a re-evaluation
  1503  	reEval := &structs.JobEvaluateRequest{
  1504  		JobID: job.ID,
  1505  		WriteRequest: structs.WriteRequest{
  1506  			Region:    "global",
  1507  			Namespace: job.Namespace,
  1508  		},
  1509  	}
  1510  
  1511  	// Fetch the response
  1512  	if err := msgpackrpc.CallWithCodec(codec, "Job.Evaluate", reEval, &resp); err == nil {
  1513  		t.Fatal("expect an err")
  1514  	}
  1515  }
  1516  
  1517  func TestJobEndpoint_Evaluate_ParameterizedJob(t *testing.T) {
  1518  	t.Parallel()
  1519  	s1 := TestServer(t, func(c *Config) {
  1520  		c.NumSchedulers = 0 // Prevent automatic dequeue
  1521  	})
  1522  	defer s1.Shutdown()
  1523  	codec := rpcClient(t, s1)
  1524  	testutil.WaitForLeader(t, s1.RPC)
  1525  
  1526  	// Create the register request
  1527  	job := mock.BatchJob()
  1528  	job.ParameterizedJob = &structs.ParameterizedJobConfig{}
  1529  	req := &structs.JobRegisterRequest{
  1530  		Job: job,
  1531  		WriteRequest: structs.WriteRequest{
  1532  			Region:    "global",
  1533  			Namespace: job.Namespace,
  1534  		},
  1535  	}
  1536  
  1537  	// Fetch the response
  1538  	var resp structs.JobRegisterResponse
  1539  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil {
  1540  		t.Fatalf("err: %v", err)
  1541  	}
  1542  	if resp.JobModifyIndex == 0 {
  1543  		t.Fatalf("bad index: %d", resp.Index)
  1544  	}
  1545  
  1546  	// Force a re-evaluation
  1547  	reEval := &structs.JobEvaluateRequest{
  1548  		JobID: job.ID,
  1549  		WriteRequest: structs.WriteRequest{
  1550  			Region:    "global",
  1551  			Namespace: job.Namespace,
  1552  		},
  1553  	}
  1554  
  1555  	// Fetch the response
  1556  	if err := msgpackrpc.CallWithCodec(codec, "Job.Evaluate", reEval, &resp); err == nil {
  1557  		t.Fatal("expect an err")
  1558  	}
  1559  }
  1560  
  1561  func TestJobEndpoint_Deregister(t *testing.T) {
  1562  	t.Parallel()
  1563  	require := require.New(t)
  1564  	s1 := TestServer(t, func(c *Config) {
  1565  		c.NumSchedulers = 0 // Prevent automatic dequeue
  1566  	})
  1567  	defer s1.Shutdown()
  1568  	codec := rpcClient(t, s1)
  1569  	testutil.WaitForLeader(t, s1.RPC)
  1570  
  1571  	// Create the register requests
  1572  	job := mock.Job()
  1573  	reg := &structs.JobRegisterRequest{
  1574  		Job: job,
  1575  		WriteRequest: structs.WriteRequest{
  1576  			Region:    "global",
  1577  			Namespace: job.Namespace,
  1578  		},
  1579  	}
  1580  
  1581  	// Fetch the response
  1582  	var resp structs.JobRegisterResponse
  1583  	require.Nil(msgpackrpc.CallWithCodec(codec, "Job.Register", reg, &resp))
  1584  
  1585  	// Deregister but don't purge
  1586  	dereg := &structs.JobDeregisterRequest{
  1587  		JobID: job.ID,
  1588  		Purge: false,
  1589  		WriteRequest: structs.WriteRequest{
  1590  			Region:    "global",
  1591  			Namespace: job.Namespace,
  1592  		},
  1593  	}
  1594  	var resp2 structs.JobDeregisterResponse
  1595  	require.Nil(msgpackrpc.CallWithCodec(codec, "Job.Deregister", dereg, &resp2))
  1596  	require.NotZero(resp2.Index)
  1597  
  1598  	// Check for the job in the FSM
  1599  	state := s1.fsm.State()
  1600  	out, err := state.JobByID(nil, job.Namespace, job.ID)
  1601  	require.Nil(err)
  1602  	require.NotNil(out)
  1603  	require.True(out.Stop)
  1604  
  1605  	// Lookup the evaluation
  1606  	eval, err := state.EvalByID(nil, resp2.EvalID)
  1607  	require.Nil(err)
  1608  	require.NotNil(eval)
  1609  	require.EqualValues(resp2.EvalCreateIndex, eval.CreateIndex)
  1610  	require.Equal(job.Priority, eval.Priority)
  1611  	require.Equal(job.Type, eval.Type)
  1612  	require.Equal(structs.EvalTriggerJobDeregister, eval.TriggeredBy)
  1613  	require.Equal(job.ID, eval.JobID)
  1614  	require.Equal(structs.EvalStatusPending, eval.Status)
  1615  
  1616  	// Deregister and purge
  1617  	dereg2 := &structs.JobDeregisterRequest{
  1618  		JobID: job.ID,
  1619  		Purge: true,
  1620  		WriteRequest: structs.WriteRequest{
  1621  			Region:    "global",
  1622  			Namespace: job.Namespace,
  1623  		},
  1624  	}
  1625  	var resp3 structs.JobDeregisterResponse
  1626  	require.Nil(msgpackrpc.CallWithCodec(codec, "Job.Deregister", dereg2, &resp3))
  1627  	require.NotZero(resp3.Index)
  1628  
  1629  	// Check for the job in the FSM
  1630  	out, err = state.JobByID(nil, job.Namespace, job.ID)
  1631  	require.Nil(err)
  1632  	require.Nil(out)
  1633  
  1634  	// Lookup the evaluation
  1635  	eval, err = state.EvalByID(nil, resp3.EvalID)
  1636  	require.Nil(err)
  1637  	require.NotNil(eval)
  1638  
  1639  	require.EqualValues(resp3.EvalCreateIndex, eval.CreateIndex)
  1640  	require.Equal(job.Priority, eval.Priority)
  1641  	require.Equal(job.Type, eval.Type)
  1642  	require.Equal(structs.EvalTriggerJobDeregister, eval.TriggeredBy)
  1643  	require.Equal(job.ID, eval.JobID)
  1644  	require.Equal(structs.EvalStatusPending, eval.Status)
  1645  }
  1646  
  1647  func TestJobEndpoint_Deregister_ACL(t *testing.T) {
  1648  	t.Parallel()
  1649  	require := require.New(t)
  1650  
  1651  	s1, root := TestACLServer(t, func(c *Config) {
  1652  		c.NumSchedulers = 0 // Prevent automatic dequeue
  1653  	})
  1654  	defer s1.Shutdown()
  1655  	codec := rpcClient(t, s1)
  1656  	testutil.WaitForLeader(t, s1.RPC)
  1657  	state := s1.fsm.State()
  1658  
  1659  	// Create and register a job
  1660  	job := mock.Job()
  1661  	err := state.UpsertJob(100, job)
  1662  
  1663  	// Deregister and purge
  1664  	req := &structs.JobDeregisterRequest{
  1665  		JobID: job.ID,
  1666  		Purge: true,
  1667  		WriteRequest: structs.WriteRequest{
  1668  			Region:    "global",
  1669  			Namespace: job.Namespace,
  1670  		},
  1671  	}
  1672  
  1673  	// Expect failure for request without a token
  1674  	var resp structs.JobDeregisterResponse
  1675  	err = msgpackrpc.CallWithCodec(codec, "Job.Deregister", req, &resp)
  1676  	require.NotNil(err)
  1677  	require.Contains(err.Error(), "Permission denied")
  1678  
  1679  	// Expect failure for request with an invalid token
  1680  	invalidToken := mock.CreatePolicyAndToken(t, state, 1003, "test-invalid",
  1681  		mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityListJobs}))
  1682  	req.AuthToken = invalidToken.SecretID
  1683  
  1684  	var invalidResp structs.JobDeregisterResponse
  1685  	err = msgpackrpc.CallWithCodec(codec, "Job.Deregister", req, &invalidResp)
  1686  	require.NotNil(err)
  1687  	require.Contains(err.Error(), "Permission denied")
  1688  
  1689  	// Expect success with a valid management token
  1690  	req.AuthToken = root.SecretID
  1691  
  1692  	var validResp structs.JobDeregisterResponse
  1693  	err = msgpackrpc.CallWithCodec(codec, "Job.Deregister", req, &validResp)
  1694  	require.Nil(err)
  1695  	require.NotEqual(validResp.Index, 0)
  1696  
  1697  	// Expect success with a valid token
  1698  	validToken := mock.CreatePolicyAndToken(t, state, 1005, "test-valid",
  1699  		mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilitySubmitJob}))
  1700  	req.AuthToken = validToken.SecretID
  1701  
  1702  	var validResp2 structs.JobDeregisterResponse
  1703  	err = msgpackrpc.CallWithCodec(codec, "Job.Deregister", req, &validResp2)
  1704  	require.Nil(err)
  1705  	require.NotEqual(validResp2.Index, 0)
  1706  
  1707  	// Check for the job in the FSM
  1708  	out, err := state.JobByID(nil, job.Namespace, job.ID)
  1709  	require.Nil(err)
  1710  	require.Nil(out)
  1711  
  1712  	// Lookup the evaluation
  1713  	eval, err := state.EvalByID(nil, validResp2.EvalID)
  1714  	require.Nil(err)
  1715  	require.NotNil(eval, nil)
  1716  
  1717  	require.Equal(eval.CreateIndex, validResp2.EvalCreateIndex)
  1718  	require.Equal(eval.Priority, structs.JobDefaultPriority)
  1719  	require.Equal(eval.Type, structs.JobTypeService)
  1720  	require.Equal(eval.TriggeredBy, structs.EvalTriggerJobDeregister)
  1721  	require.Equal(eval.JobID, job.ID)
  1722  	require.Equal(eval.JobModifyIndex, validResp2.JobModifyIndex)
  1723  	require.Equal(eval.Status, structs.EvalStatusPending)
  1724  }
  1725  
  1726  func TestJobEndpoint_Deregister_Nonexistent(t *testing.T) {
  1727  	t.Parallel()
  1728  	s1 := TestServer(t, func(c *Config) {
  1729  		c.NumSchedulers = 0 // Prevent automatic dequeue
  1730  	})
  1731  	defer s1.Shutdown()
  1732  	codec := rpcClient(t, s1)
  1733  	testutil.WaitForLeader(t, s1.RPC)
  1734  
  1735  	// Deregister
  1736  	jobID := "foo"
  1737  	dereg := &structs.JobDeregisterRequest{
  1738  		JobID: jobID,
  1739  		WriteRequest: structs.WriteRequest{
  1740  			Region:    "global",
  1741  			Namespace: structs.DefaultNamespace,
  1742  		},
  1743  	}
  1744  	var resp2 structs.JobDeregisterResponse
  1745  	if err := msgpackrpc.CallWithCodec(codec, "Job.Deregister", dereg, &resp2); err != nil {
  1746  		t.Fatalf("err: %v", err)
  1747  	}
  1748  	if resp2.JobModifyIndex == 0 {
  1749  		t.Fatalf("bad index: %d", resp2.Index)
  1750  	}
  1751  
  1752  	// Lookup the evaluation
  1753  	state := s1.fsm.State()
  1754  	ws := memdb.NewWatchSet()
  1755  	eval, err := state.EvalByID(ws, resp2.EvalID)
  1756  	if err != nil {
  1757  		t.Fatalf("err: %v", err)
  1758  	}
  1759  	if eval == nil {
  1760  		t.Fatalf("expected eval")
  1761  	}
  1762  	if eval.CreateIndex != resp2.EvalCreateIndex {
  1763  		t.Fatalf("index mis-match")
  1764  	}
  1765  
  1766  	if eval.Priority != structs.JobDefaultPriority {
  1767  		t.Fatalf("bad: %#v", eval)
  1768  	}
  1769  	if eval.Type != structs.JobTypeService {
  1770  		t.Fatalf("bad: %#v", eval)
  1771  	}
  1772  	if eval.TriggeredBy != structs.EvalTriggerJobDeregister {
  1773  		t.Fatalf("bad: %#v", eval)
  1774  	}
  1775  	if eval.JobID != jobID {
  1776  		t.Fatalf("bad: %#v", eval)
  1777  	}
  1778  	if eval.JobModifyIndex != resp2.JobModifyIndex {
  1779  		t.Fatalf("bad: %#v", eval)
  1780  	}
  1781  	if eval.Status != structs.EvalStatusPending {
  1782  		t.Fatalf("bad: %#v", eval)
  1783  	}
  1784  }
  1785  
  1786  func TestJobEndpoint_Deregister_Periodic(t *testing.T) {
  1787  	t.Parallel()
  1788  	s1 := TestServer(t, func(c *Config) {
  1789  		c.NumSchedulers = 0 // Prevent automatic dequeue
  1790  	})
  1791  	defer s1.Shutdown()
  1792  	codec := rpcClient(t, s1)
  1793  	testutil.WaitForLeader(t, s1.RPC)
  1794  
  1795  	// Create the register request
  1796  	job := mock.PeriodicJob()
  1797  	reg := &structs.JobRegisterRequest{
  1798  		Job: job,
  1799  		WriteRequest: structs.WriteRequest{
  1800  			Region:    "global",
  1801  			Namespace: job.Namespace,
  1802  		},
  1803  	}
  1804  
  1805  	// Fetch the response
  1806  	var resp structs.JobRegisterResponse
  1807  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", reg, &resp); err != nil {
  1808  		t.Fatalf("err: %v", err)
  1809  	}
  1810  
  1811  	// Deregister
  1812  	dereg := &structs.JobDeregisterRequest{
  1813  		JobID: job.ID,
  1814  		Purge: true,
  1815  		WriteRequest: structs.WriteRequest{
  1816  			Region:    "global",
  1817  			Namespace: job.Namespace,
  1818  		},
  1819  	}
  1820  	var resp2 structs.JobDeregisterResponse
  1821  	if err := msgpackrpc.CallWithCodec(codec, "Job.Deregister", dereg, &resp2); err != nil {
  1822  		t.Fatalf("err: %v", err)
  1823  	}
  1824  	if resp2.JobModifyIndex == 0 {
  1825  		t.Fatalf("bad index: %d", resp2.Index)
  1826  	}
  1827  
  1828  	// Check for the node in the FSM
  1829  	state := s1.fsm.State()
  1830  	ws := memdb.NewWatchSet()
  1831  	out, err := state.JobByID(ws, job.Namespace, job.ID)
  1832  	if err != nil {
  1833  		t.Fatalf("err: %v", err)
  1834  	}
  1835  	if out != nil {
  1836  		t.Fatalf("unexpected job")
  1837  	}
  1838  
  1839  	if resp.EvalID != "" {
  1840  		t.Fatalf("Deregister created an eval for a periodic job")
  1841  	}
  1842  }
  1843  
  1844  func TestJobEndpoint_Deregister_ParameterizedJob(t *testing.T) {
  1845  	t.Parallel()
  1846  	s1 := TestServer(t, func(c *Config) {
  1847  		c.NumSchedulers = 0 // Prevent automatic dequeue
  1848  	})
  1849  	defer s1.Shutdown()
  1850  	codec := rpcClient(t, s1)
  1851  	testutil.WaitForLeader(t, s1.RPC)
  1852  
  1853  	// Create the register request
  1854  	job := mock.BatchJob()
  1855  	job.ParameterizedJob = &structs.ParameterizedJobConfig{}
  1856  	reg := &structs.JobRegisterRequest{
  1857  		Job: job,
  1858  		WriteRequest: structs.WriteRequest{
  1859  			Region:    "global",
  1860  			Namespace: job.Namespace,
  1861  		},
  1862  	}
  1863  
  1864  	// Fetch the response
  1865  	var resp structs.JobRegisterResponse
  1866  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", reg, &resp); err != nil {
  1867  		t.Fatalf("err: %v", err)
  1868  	}
  1869  
  1870  	// Deregister
  1871  	dereg := &structs.JobDeregisterRequest{
  1872  		JobID: job.ID,
  1873  		Purge: true,
  1874  		WriteRequest: structs.WriteRequest{
  1875  			Region:    "global",
  1876  			Namespace: job.Namespace,
  1877  		},
  1878  	}
  1879  	var resp2 structs.JobDeregisterResponse
  1880  	if err := msgpackrpc.CallWithCodec(codec, "Job.Deregister", dereg, &resp2); err != nil {
  1881  		t.Fatalf("err: %v", err)
  1882  	}
  1883  	if resp2.JobModifyIndex == 0 {
  1884  		t.Fatalf("bad index: %d", resp2.Index)
  1885  	}
  1886  
  1887  	// Check for the node in the FSM
  1888  	state := s1.fsm.State()
  1889  	ws := memdb.NewWatchSet()
  1890  	out, err := state.JobByID(ws, job.Namespace, job.ID)
  1891  	if err != nil {
  1892  		t.Fatalf("err: %v", err)
  1893  	}
  1894  	if out != nil {
  1895  		t.Fatalf("unexpected job")
  1896  	}
  1897  
  1898  	if resp.EvalID != "" {
  1899  		t.Fatalf("Deregister created an eval for a parameterized job")
  1900  	}
  1901  }
  1902  
  1903  func TestJobEndpoint_BatchDeregister(t *testing.T) {
  1904  	t.Parallel()
  1905  	require := require.New(t)
  1906  	s1 := TestServer(t, func(c *Config) {
  1907  		c.NumSchedulers = 0 // Prevent automatic dequeue
  1908  	})
  1909  	defer s1.Shutdown()
  1910  	codec := rpcClient(t, s1)
  1911  	testutil.WaitForLeader(t, s1.RPC)
  1912  
  1913  	// Create the register requests
  1914  	job := mock.Job()
  1915  	reg := &structs.JobRegisterRequest{
  1916  		Job: job,
  1917  		WriteRequest: structs.WriteRequest{
  1918  			Region:    "global",
  1919  			Namespace: job.Namespace,
  1920  		},
  1921  	}
  1922  
  1923  	// Fetch the response
  1924  	var resp structs.JobRegisterResponse
  1925  	require.Nil(msgpackrpc.CallWithCodec(codec, "Job.Register", reg, &resp))
  1926  
  1927  	job2 := mock.Job()
  1928  	job2.Priority = 1
  1929  	reg2 := &structs.JobRegisterRequest{
  1930  		Job: job2,
  1931  		WriteRequest: structs.WriteRequest{
  1932  			Region:    "global",
  1933  			Namespace: job2.Namespace,
  1934  		},
  1935  	}
  1936  
  1937  	// Fetch the response
  1938  	require.Nil(msgpackrpc.CallWithCodec(codec, "Job.Register", reg2, &resp))
  1939  
  1940  	// Deregister
  1941  	dereg := &structs.JobBatchDeregisterRequest{
  1942  		Jobs: map[structs.NamespacedID]*structs.JobDeregisterOptions{
  1943  			{
  1944  				ID:        job.ID,
  1945  				Namespace: job.Namespace,
  1946  			}: {},
  1947  			{
  1948  				ID:        job2.ID,
  1949  				Namespace: job2.Namespace,
  1950  			}: {
  1951  				Purge: true,
  1952  			},
  1953  		},
  1954  		WriteRequest: structs.WriteRequest{
  1955  			Region:    "global",
  1956  			Namespace: job.Namespace,
  1957  		},
  1958  	}
  1959  	var resp2 structs.JobBatchDeregisterResponse
  1960  	require.Nil(msgpackrpc.CallWithCodec(codec, "Job.BatchDeregister", dereg, &resp2))
  1961  	require.NotZero(resp2.Index)
  1962  
  1963  	// Check for the job in the FSM
  1964  	state := s1.fsm.State()
  1965  	out, err := state.JobByID(nil, job.Namespace, job.ID)
  1966  	require.Nil(err)
  1967  	require.NotNil(out)
  1968  	require.True(out.Stop)
  1969  
  1970  	out, err = state.JobByID(nil, job2.Namespace, job2.ID)
  1971  	require.Nil(err)
  1972  	require.Nil(out)
  1973  
  1974  	// Lookup the evaluation
  1975  	for jobNS, eval := range resp2.JobEvals {
  1976  		expectedJob := job
  1977  		if jobNS.ID != job.ID {
  1978  			expectedJob = job2
  1979  		}
  1980  
  1981  		eval, err := state.EvalByID(nil, eval)
  1982  		require.Nil(err)
  1983  		require.NotNil(eval)
  1984  		require.EqualValues(resp2.Index, eval.CreateIndex)
  1985  		require.Equal(expectedJob.Priority, eval.Priority)
  1986  		require.Equal(expectedJob.Type, eval.Type)
  1987  		require.Equal(structs.EvalTriggerJobDeregister, eval.TriggeredBy)
  1988  		require.Equal(expectedJob.ID, eval.JobID)
  1989  		require.Equal(structs.EvalStatusPending, eval.Status)
  1990  	}
  1991  }
  1992  
  1993  func TestJobEndpoint_BatchDeregister_ACL(t *testing.T) {
  1994  	t.Parallel()
  1995  	require := require.New(t)
  1996  
  1997  	s1, root := TestACLServer(t, func(c *Config) {
  1998  		c.NumSchedulers = 0 // Prevent automatic dequeue
  1999  	})
  2000  	defer s1.Shutdown()
  2001  	codec := rpcClient(t, s1)
  2002  	testutil.WaitForLeader(t, s1.RPC)
  2003  	state := s1.fsm.State()
  2004  
  2005  	// Create and register a job
  2006  	job, job2 := mock.Job(), mock.Job()
  2007  	require.Nil(state.UpsertJob(100, job))
  2008  	require.Nil(state.UpsertJob(101, job2))
  2009  
  2010  	// Deregister
  2011  	req := &structs.JobBatchDeregisterRequest{
  2012  		Jobs: map[structs.NamespacedID]*structs.JobDeregisterOptions{
  2013  			{
  2014  				ID:        job.ID,
  2015  				Namespace: job.Namespace,
  2016  			}: {},
  2017  			{
  2018  				ID:        job2.ID,
  2019  				Namespace: job2.Namespace,
  2020  			}: {},
  2021  		},
  2022  		WriteRequest: structs.WriteRequest{
  2023  			Region: "global",
  2024  		},
  2025  	}
  2026  
  2027  	// Expect failure for request without a token
  2028  	var resp structs.JobBatchDeregisterResponse
  2029  	err := msgpackrpc.CallWithCodec(codec, "Job.BatchDeregister", req, &resp)
  2030  	require.NotNil(err)
  2031  	require.True(structs.IsErrPermissionDenied(err))
  2032  
  2033  	// Expect failure for request with an invalid token
  2034  	invalidToken := mock.CreatePolicyAndToken(t, state, 1003, "test-invalid",
  2035  		mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityListJobs}))
  2036  	req.AuthToken = invalidToken.SecretID
  2037  
  2038  	var invalidResp structs.JobDeregisterResponse
  2039  	err = msgpackrpc.CallWithCodec(codec, "Job.BatchDeregister", req, &invalidResp)
  2040  	require.NotNil(err)
  2041  	require.True(structs.IsErrPermissionDenied(err))
  2042  
  2043  	// Expect success with a valid management token
  2044  	req.AuthToken = root.SecretID
  2045  
  2046  	var validResp structs.JobDeregisterResponse
  2047  	err = msgpackrpc.CallWithCodec(codec, "Job.BatchDeregister", req, &validResp)
  2048  	require.Nil(err)
  2049  	require.NotEqual(validResp.Index, 0)
  2050  
  2051  	// Expect success with a valid token
  2052  	validToken := mock.CreatePolicyAndToken(t, state, 1005, "test-valid",
  2053  		mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilitySubmitJob}))
  2054  	req.AuthToken = validToken.SecretID
  2055  
  2056  	var validResp2 structs.JobDeregisterResponse
  2057  	err = msgpackrpc.CallWithCodec(codec, "Job.BatchDeregister", req, &validResp2)
  2058  	require.Nil(err)
  2059  	require.NotEqual(validResp2.Index, 0)
  2060  }
  2061  
  2062  func TestJobEndpoint_GetJob(t *testing.T) {
  2063  	t.Parallel()
  2064  	s1 := TestServer(t, nil)
  2065  	defer s1.Shutdown()
  2066  	codec := rpcClient(t, s1)
  2067  	testutil.WaitForLeader(t, s1.RPC)
  2068  
  2069  	// Create the register request
  2070  	job := mock.Job()
  2071  	reg := &structs.JobRegisterRequest{
  2072  		Job: job,
  2073  		WriteRequest: structs.WriteRequest{
  2074  			Region:    "global",
  2075  			Namespace: job.Namespace,
  2076  		},
  2077  	}
  2078  
  2079  	// Fetch the response
  2080  	var resp structs.JobRegisterResponse
  2081  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", reg, &resp); err != nil {
  2082  		t.Fatalf("err: %v", err)
  2083  	}
  2084  	job.CreateIndex = resp.JobModifyIndex
  2085  	job.ModifyIndex = resp.JobModifyIndex
  2086  	job.JobModifyIndex = resp.JobModifyIndex
  2087  
  2088  	// Lookup the job
  2089  	get := &structs.JobSpecificRequest{
  2090  		JobID: job.ID,
  2091  		QueryOptions: structs.QueryOptions{
  2092  			Region:    "global",
  2093  			Namespace: job.Namespace,
  2094  		},
  2095  	}
  2096  	var resp2 structs.SingleJobResponse
  2097  	if err := msgpackrpc.CallWithCodec(codec, "Job.GetJob", get, &resp2); err != nil {
  2098  		t.Fatalf("err: %v", err)
  2099  	}
  2100  	if resp2.Index != resp.JobModifyIndex {
  2101  		t.Fatalf("Bad index: %d %d", resp2.Index, resp.Index)
  2102  	}
  2103  
  2104  	// Make a copy of the origin job and change the service name so that we can
  2105  	// do a deep equal with the response from the GET JOB Api
  2106  	j := job
  2107  	j.TaskGroups[0].Tasks[0].Services[0].Name = "web-frontend"
  2108  	for tgix, tg := range j.TaskGroups {
  2109  		for tidx, t := range tg.Tasks {
  2110  			for sidx, service := range t.Services {
  2111  				for cidx, check := range service.Checks {
  2112  					check.Name = resp2.Job.TaskGroups[tgix].Tasks[tidx].Services[sidx].Checks[cidx].Name
  2113  				}
  2114  			}
  2115  		}
  2116  	}
  2117  
  2118  	// Clear the submit times
  2119  	j.SubmitTime = 0
  2120  	resp2.Job.SubmitTime = 0
  2121  
  2122  	if !reflect.DeepEqual(j, resp2.Job) {
  2123  		t.Fatalf("bad: %#v %#v", job, resp2.Job)
  2124  	}
  2125  
  2126  	// Lookup non-existing job
  2127  	get.JobID = "foobarbaz"
  2128  	if err := msgpackrpc.CallWithCodec(codec, "Job.GetJob", get, &resp2); err != nil {
  2129  		t.Fatalf("err: %v", err)
  2130  	}
  2131  	if resp2.Index != resp.JobModifyIndex {
  2132  		t.Fatalf("Bad index: %d %d", resp2.Index, resp.Index)
  2133  	}
  2134  	if resp2.Job != nil {
  2135  		t.Fatalf("unexpected job")
  2136  	}
  2137  }
  2138  
  2139  func TestJobEndpoint_GetJob_ACL(t *testing.T) {
  2140  	t.Parallel()
  2141  	require := require.New(t)
  2142  
  2143  	s1, root := TestACLServer(t, nil)
  2144  	defer s1.Shutdown()
  2145  	codec := rpcClient(t, s1)
  2146  	testutil.WaitForLeader(t, s1.RPC)
  2147  	state := s1.fsm.State()
  2148  
  2149  	// Create the job
  2150  	job := mock.Job()
  2151  	err := state.UpsertJob(1000, job)
  2152  	require.Nil(err)
  2153  
  2154  	// Lookup the job
  2155  	get := &structs.JobSpecificRequest{
  2156  		JobID: job.ID,
  2157  		QueryOptions: structs.QueryOptions{
  2158  			Region:    "global",
  2159  			Namespace: job.Namespace,
  2160  		},
  2161  	}
  2162  
  2163  	// Looking up the job without a token should fail
  2164  	var resp structs.SingleJobResponse
  2165  	err = msgpackrpc.CallWithCodec(codec, "Job.GetJob", get, &resp)
  2166  	require.NotNil(err)
  2167  	require.Contains(err.Error(), "Permission denied")
  2168  
  2169  	// Expect failure for request with an invalid token
  2170  	invalidToken := mock.CreatePolicyAndToken(t, state, 1003, "test-invalid",
  2171  		mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityListJobs}))
  2172  
  2173  	get.AuthToken = invalidToken.SecretID
  2174  	var invalidResp structs.SingleJobResponse
  2175  	err = msgpackrpc.CallWithCodec(codec, "Job.GetJob", get, &invalidResp)
  2176  	require.NotNil(err)
  2177  	require.Contains(err.Error(), "Permission denied")
  2178  
  2179  	// Looking up the job with a management token should succeed
  2180  	get.AuthToken = root.SecretID
  2181  	var validResp structs.SingleJobResponse
  2182  	err = msgpackrpc.CallWithCodec(codec, "Job.GetJob", get, &validResp)
  2183  	require.Nil(err)
  2184  	require.Equal(job.ID, validResp.Job.ID)
  2185  
  2186  	// Looking up the job with a valid token should succeed
  2187  	validToken := mock.CreatePolicyAndToken(t, state, 1005, "test-valid",
  2188  		mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityReadJob}))
  2189  
  2190  	get.AuthToken = validToken.SecretID
  2191  	var validResp2 structs.SingleJobResponse
  2192  	err = msgpackrpc.CallWithCodec(codec, "Job.GetJob", get, &validResp2)
  2193  	require.Nil(err)
  2194  	require.Equal(job.ID, validResp2.Job.ID)
  2195  }
  2196  
  2197  func TestJobEndpoint_GetJob_Blocking(t *testing.T) {
  2198  	t.Parallel()
  2199  	s1 := TestServer(t, nil)
  2200  	defer s1.Shutdown()
  2201  	state := s1.fsm.State()
  2202  	codec := rpcClient(t, s1)
  2203  	testutil.WaitForLeader(t, s1.RPC)
  2204  
  2205  	// Create the jobs
  2206  	job1 := mock.Job()
  2207  	job2 := mock.Job()
  2208  
  2209  	// Upsert a job we are not interested in first.
  2210  	time.AfterFunc(100*time.Millisecond, func() {
  2211  		if err := state.UpsertJob(100, job1); err != nil {
  2212  			t.Fatalf("err: %v", err)
  2213  		}
  2214  	})
  2215  
  2216  	// Upsert another job later which should trigger the watch.
  2217  	time.AfterFunc(200*time.Millisecond, func() {
  2218  		if err := state.UpsertJob(200, job2); err != nil {
  2219  			t.Fatalf("err: %v", err)
  2220  		}
  2221  	})
  2222  
  2223  	req := &structs.JobSpecificRequest{
  2224  		JobID: job2.ID,
  2225  		QueryOptions: structs.QueryOptions{
  2226  			Region:        "global",
  2227  			Namespace:     job2.Namespace,
  2228  			MinQueryIndex: 150,
  2229  		},
  2230  	}
  2231  	start := time.Now()
  2232  	var resp structs.SingleJobResponse
  2233  	if err := msgpackrpc.CallWithCodec(codec, "Job.GetJob", req, &resp); err != nil {
  2234  		t.Fatalf("err: %v", err)
  2235  	}
  2236  
  2237  	if elapsed := time.Since(start); elapsed < 200*time.Millisecond {
  2238  		t.Fatalf("should block (returned in %s) %#v", elapsed, resp)
  2239  	}
  2240  	if resp.Index != 200 {
  2241  		t.Fatalf("Bad index: %d %d", resp.Index, 200)
  2242  	}
  2243  	if resp.Job == nil || resp.Job.ID != job2.ID {
  2244  		t.Fatalf("bad: %#v", resp.Job)
  2245  	}
  2246  
  2247  	// Job delete fires watches
  2248  	time.AfterFunc(100*time.Millisecond, func() {
  2249  		if err := state.DeleteJob(300, job2.Namespace, job2.ID); err != nil {
  2250  			t.Fatalf("err: %v", err)
  2251  		}
  2252  	})
  2253  
  2254  	req.QueryOptions.MinQueryIndex = 250
  2255  	start = time.Now()
  2256  
  2257  	var resp2 structs.SingleJobResponse
  2258  	if err := msgpackrpc.CallWithCodec(codec, "Job.GetJob", req, &resp2); err != nil {
  2259  		t.Fatalf("err: %v", err)
  2260  	}
  2261  
  2262  	if elapsed := time.Since(start); elapsed < 100*time.Millisecond {
  2263  		t.Fatalf("should block (returned in %s) %#v", elapsed, resp2)
  2264  	}
  2265  	if resp2.Index != 300 {
  2266  		t.Fatalf("Bad index: %d %d", resp2.Index, 300)
  2267  	}
  2268  	if resp2.Job != nil {
  2269  		t.Fatalf("bad: %#v", resp2.Job)
  2270  	}
  2271  }
  2272  
  2273  func TestJobEndpoint_GetJobVersions(t *testing.T) {
  2274  	t.Parallel()
  2275  	s1 := TestServer(t, nil)
  2276  	defer s1.Shutdown()
  2277  	codec := rpcClient(t, s1)
  2278  	testutil.WaitForLeader(t, s1.RPC)
  2279  
  2280  	// Create the register request
  2281  	job := mock.Job()
  2282  	job.Priority = 88
  2283  	reg := &structs.JobRegisterRequest{
  2284  		Job: job,
  2285  		WriteRequest: structs.WriteRequest{
  2286  			Region:    "global",
  2287  			Namespace: job.Namespace,
  2288  		},
  2289  	}
  2290  
  2291  	// Fetch the response
  2292  	var resp structs.JobRegisterResponse
  2293  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", reg, &resp); err != nil {
  2294  		t.Fatalf("err: %v", err)
  2295  	}
  2296  
  2297  	// Register the job again to create another version
  2298  	job.Priority = 100
  2299  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", reg, &resp); err != nil {
  2300  		t.Fatalf("err: %v", err)
  2301  	}
  2302  
  2303  	// Lookup the job
  2304  	get := &structs.JobVersionsRequest{
  2305  		JobID: job.ID,
  2306  		QueryOptions: structs.QueryOptions{
  2307  			Region:    "global",
  2308  			Namespace: job.Namespace,
  2309  		},
  2310  	}
  2311  	var versionsResp structs.JobVersionsResponse
  2312  	if err := msgpackrpc.CallWithCodec(codec, "Job.GetJobVersions", get, &versionsResp); err != nil {
  2313  		t.Fatalf("err: %v", err)
  2314  	}
  2315  	if versionsResp.Index != resp.JobModifyIndex {
  2316  		t.Fatalf("Bad index: %d %d", versionsResp.Index, resp.Index)
  2317  	}
  2318  
  2319  	// Make sure there are two job versions
  2320  	versions := versionsResp.Versions
  2321  	if l := len(versions); l != 2 {
  2322  		t.Fatalf("Got %d versions; want 2", l)
  2323  	}
  2324  
  2325  	if v := versions[0]; v.Priority != 100 || v.ID != job.ID || v.Version != 1 {
  2326  		t.Fatalf("bad: %+v", v)
  2327  	}
  2328  	if v := versions[1]; v.Priority != 88 || v.ID != job.ID || v.Version != 0 {
  2329  		t.Fatalf("bad: %+v", v)
  2330  	}
  2331  
  2332  	// Lookup non-existing job
  2333  	get.JobID = "foobarbaz"
  2334  	if err := msgpackrpc.CallWithCodec(codec, "Job.GetJobVersions", get, &versionsResp); err != nil {
  2335  		t.Fatalf("err: %v", err)
  2336  	}
  2337  	if versionsResp.Index != resp.JobModifyIndex {
  2338  		t.Fatalf("Bad index: %d %d", versionsResp.Index, resp.Index)
  2339  	}
  2340  	if l := len(versionsResp.Versions); l != 0 {
  2341  		t.Fatalf("unexpected versions: %d", l)
  2342  	}
  2343  }
  2344  
  2345  func TestJobEndpoint_GetJobVersions_ACL(t *testing.T) {
  2346  	t.Parallel()
  2347  	require := require.New(t)
  2348  
  2349  	s1, root := TestACLServer(t, nil)
  2350  	defer s1.Shutdown()
  2351  	codec := rpcClient(t, s1)
  2352  	testutil.WaitForLeader(t, s1.RPC)
  2353  	state := s1.fsm.State()
  2354  
  2355  	// Create two versions of a job with different priorities
  2356  	job := mock.Job()
  2357  	job.Priority = 88
  2358  	err := state.UpsertJob(10, job)
  2359  	require.Nil(err)
  2360  
  2361  	job.Priority = 100
  2362  	err = state.UpsertJob(100, job)
  2363  	require.Nil(err)
  2364  
  2365  	// Lookup the job
  2366  	get := &structs.JobVersionsRequest{
  2367  		JobID: job.ID,
  2368  		QueryOptions: structs.QueryOptions{
  2369  			Region:    "global",
  2370  			Namespace: job.Namespace,
  2371  		},
  2372  	}
  2373  
  2374  	// Attempt to fetch without a token should fail
  2375  	var resp structs.JobVersionsResponse
  2376  	err = msgpackrpc.CallWithCodec(codec, "Job.GetJobVersions", get, &resp)
  2377  	require.NotNil(err)
  2378  	require.Contains(err.Error(), "Permission denied")
  2379  
  2380  	// Expect failure for request with an invalid token
  2381  	invalidToken := mock.CreatePolicyAndToken(t, state, 1003, "test-invalid",
  2382  		mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityListJobs}))
  2383  
  2384  	get.AuthToken = invalidToken.SecretID
  2385  	var invalidResp structs.JobVersionsResponse
  2386  	err = msgpackrpc.CallWithCodec(codec, "Job.GetJobVersions", get, &invalidResp)
  2387  	require.NotNil(err)
  2388  	require.Contains(err.Error(), "Permission denied")
  2389  
  2390  	// Expect success for request with a valid management token
  2391  	get.AuthToken = root.SecretID
  2392  	var validResp structs.JobVersionsResponse
  2393  	err = msgpackrpc.CallWithCodec(codec, "Job.GetJobVersions", get, &validResp)
  2394  	require.Nil(err)
  2395  
  2396  	// Expect success for request with a valid token
  2397  	validToken := mock.CreatePolicyAndToken(t, state, 1005, "test-valid",
  2398  		mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityReadJob}))
  2399  
  2400  	get.AuthToken = validToken.SecretID
  2401  	var validResp2 structs.JobVersionsResponse
  2402  	err = msgpackrpc.CallWithCodec(codec, "Job.GetJobVersions", get, &validResp2)
  2403  	require.Nil(err)
  2404  
  2405  	// Make sure there are two job versions
  2406  	versions := validResp2.Versions
  2407  	require.Equal(2, len(versions))
  2408  	require.Equal(versions[0].ID, job.ID)
  2409  	require.Equal(versions[1].ID, job.ID)
  2410  }
  2411  
  2412  func TestJobEndpoint_GetJobVersions_Diff(t *testing.T) {
  2413  	t.Parallel()
  2414  	s1 := TestServer(t, nil)
  2415  	defer s1.Shutdown()
  2416  	codec := rpcClient(t, s1)
  2417  	testutil.WaitForLeader(t, s1.RPC)
  2418  
  2419  	// Create the register request
  2420  	job := mock.Job()
  2421  	job.Priority = 88
  2422  	reg := &structs.JobRegisterRequest{
  2423  		Job: job,
  2424  		WriteRequest: structs.WriteRequest{
  2425  			Region:    "global",
  2426  			Namespace: job.Namespace,
  2427  		},
  2428  	}
  2429  
  2430  	// Fetch the response
  2431  	var resp structs.JobRegisterResponse
  2432  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", reg, &resp); err != nil {
  2433  		t.Fatalf("err: %v", err)
  2434  	}
  2435  
  2436  	// Register the job again to create another version
  2437  	job.Priority = 90
  2438  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", reg, &resp); err != nil {
  2439  		t.Fatalf("err: %v", err)
  2440  	}
  2441  
  2442  	// Register the job again to create another version
  2443  	job.Priority = 100
  2444  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", reg, &resp); err != nil {
  2445  		t.Fatalf("err: %v", err)
  2446  	}
  2447  
  2448  	// Lookup the job
  2449  	get := &structs.JobVersionsRequest{
  2450  		JobID: job.ID,
  2451  		Diffs: true,
  2452  		QueryOptions: structs.QueryOptions{
  2453  			Region:    "global",
  2454  			Namespace: job.Namespace,
  2455  		},
  2456  	}
  2457  	var versionsResp structs.JobVersionsResponse
  2458  	if err := msgpackrpc.CallWithCodec(codec, "Job.GetJobVersions", get, &versionsResp); err != nil {
  2459  		t.Fatalf("err: %v", err)
  2460  	}
  2461  	if versionsResp.Index != resp.JobModifyIndex {
  2462  		t.Fatalf("Bad index: %d %d", versionsResp.Index, resp.Index)
  2463  	}
  2464  
  2465  	// Make sure there are two job versions
  2466  	versions := versionsResp.Versions
  2467  	if l := len(versions); l != 3 {
  2468  		t.Fatalf("Got %d versions; want 3", l)
  2469  	}
  2470  
  2471  	if v := versions[0]; v.Priority != 100 || v.ID != job.ID || v.Version != 2 {
  2472  		t.Fatalf("bad: %+v", v)
  2473  	}
  2474  	if v := versions[1]; v.Priority != 90 || v.ID != job.ID || v.Version != 1 {
  2475  		t.Fatalf("bad: %+v", v)
  2476  	}
  2477  	if v := versions[2]; v.Priority != 88 || v.ID != job.ID || v.Version != 0 {
  2478  		t.Fatalf("bad: %+v", v)
  2479  	}
  2480  
  2481  	// Ensure we got diffs
  2482  	diffs := versionsResp.Diffs
  2483  	if l := len(diffs); l != 2 {
  2484  		t.Fatalf("Got %d diffs; want 2", l)
  2485  	}
  2486  	d1 := diffs[0]
  2487  	if len(d1.Fields) != 1 {
  2488  		t.Fatalf("Got too many diffs: %#v", d1)
  2489  	}
  2490  	if d1.Fields[0].Name != "Priority" {
  2491  		t.Fatalf("Got wrong field: %#v", d1)
  2492  	}
  2493  	if d1.Fields[0].Old != "90" && d1.Fields[0].New != "100" {
  2494  		t.Fatalf("Got wrong field values: %#v", d1)
  2495  	}
  2496  	d2 := diffs[1]
  2497  	if len(d2.Fields) != 1 {
  2498  		t.Fatalf("Got too many diffs: %#v", d2)
  2499  	}
  2500  	if d2.Fields[0].Name != "Priority" {
  2501  		t.Fatalf("Got wrong field: %#v", d2)
  2502  	}
  2503  	if d2.Fields[0].Old != "88" && d1.Fields[0].New != "90" {
  2504  		t.Fatalf("Got wrong field values: %#v", d2)
  2505  	}
  2506  }
  2507  
  2508  func TestJobEndpoint_GetJobVersions_Blocking(t *testing.T) {
  2509  	t.Parallel()
  2510  	s1 := TestServer(t, nil)
  2511  	defer s1.Shutdown()
  2512  	state := s1.fsm.State()
  2513  	codec := rpcClient(t, s1)
  2514  	testutil.WaitForLeader(t, s1.RPC)
  2515  
  2516  	// Create the jobs
  2517  	job1 := mock.Job()
  2518  	job2 := mock.Job()
  2519  	job3 := mock.Job()
  2520  	job3.ID = job2.ID
  2521  	job3.Priority = 1
  2522  
  2523  	// Upsert a job we are not interested in first.
  2524  	time.AfterFunc(100*time.Millisecond, func() {
  2525  		if err := state.UpsertJob(100, job1); err != nil {
  2526  			t.Fatalf("err: %v", err)
  2527  		}
  2528  	})
  2529  
  2530  	// Upsert another job later which should trigger the watch.
  2531  	time.AfterFunc(200*time.Millisecond, func() {
  2532  		if err := state.UpsertJob(200, job2); err != nil {
  2533  			t.Fatalf("err: %v", err)
  2534  		}
  2535  	})
  2536  
  2537  	req := &structs.JobVersionsRequest{
  2538  		JobID: job2.ID,
  2539  		QueryOptions: structs.QueryOptions{
  2540  			Region:        "global",
  2541  			Namespace:     job2.Namespace,
  2542  			MinQueryIndex: 150,
  2543  		},
  2544  	}
  2545  	start := time.Now()
  2546  	var resp structs.JobVersionsResponse
  2547  	if err := msgpackrpc.CallWithCodec(codec, "Job.GetJobVersions", req, &resp); err != nil {
  2548  		t.Fatalf("err: %v", err)
  2549  	}
  2550  
  2551  	if elapsed := time.Since(start); elapsed < 200*time.Millisecond {
  2552  		t.Fatalf("should block (returned in %s) %#v", elapsed, resp)
  2553  	}
  2554  	if resp.Index != 200 {
  2555  		t.Fatalf("Bad index: %d %d", resp.Index, 200)
  2556  	}
  2557  	if len(resp.Versions) != 1 || resp.Versions[0].ID != job2.ID {
  2558  		t.Fatalf("bad: %#v", resp.Versions)
  2559  	}
  2560  
  2561  	// Upsert the job again which should trigger the watch.
  2562  	time.AfterFunc(100*time.Millisecond, func() {
  2563  		if err := state.UpsertJob(300, job3); err != nil {
  2564  			t.Fatalf("err: %v", err)
  2565  		}
  2566  	})
  2567  
  2568  	req2 := &structs.JobVersionsRequest{
  2569  		JobID: job3.ID,
  2570  		QueryOptions: structs.QueryOptions{
  2571  			Region:        "global",
  2572  			Namespace:     job3.Namespace,
  2573  			MinQueryIndex: 250,
  2574  		},
  2575  	}
  2576  	var resp2 structs.JobVersionsResponse
  2577  	start = time.Now()
  2578  	if err := msgpackrpc.CallWithCodec(codec, "Job.GetJobVersions", req2, &resp2); err != nil {
  2579  		t.Fatalf("err: %v", err)
  2580  	}
  2581  
  2582  	if elapsed := time.Since(start); elapsed < 100*time.Millisecond {
  2583  		t.Fatalf("should block (returned in %s) %#v", elapsed, resp)
  2584  	}
  2585  	if resp2.Index != 300 {
  2586  		t.Fatalf("Bad index: %d %d", resp.Index, 300)
  2587  	}
  2588  	if len(resp2.Versions) != 2 {
  2589  		t.Fatalf("bad: %#v", resp2.Versions)
  2590  	}
  2591  }
  2592  
  2593  func TestJobEndpoint_GetJobSummary(t *testing.T) {
  2594  	t.Parallel()
  2595  	s1 := TestServer(t, func(c *Config) {
  2596  		c.NumSchedulers = 0 // Prevent automatic dequeue
  2597  	})
  2598  
  2599  	defer s1.Shutdown()
  2600  	codec := rpcClient(t, s1)
  2601  	testutil.WaitForLeader(t, s1.RPC)
  2602  
  2603  	// Create the register request
  2604  	job := mock.Job()
  2605  	reg := &structs.JobRegisterRequest{
  2606  		Job: job,
  2607  		WriteRequest: structs.WriteRequest{
  2608  			Region:    "global",
  2609  			Namespace: job.Namespace,
  2610  		},
  2611  	}
  2612  
  2613  	// Fetch the response
  2614  	var resp structs.JobRegisterResponse
  2615  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", reg, &resp); err != nil {
  2616  		t.Fatalf("err: %v", err)
  2617  	}
  2618  	job.CreateIndex = resp.JobModifyIndex
  2619  	job.ModifyIndex = resp.JobModifyIndex
  2620  	job.JobModifyIndex = resp.JobModifyIndex
  2621  
  2622  	// Lookup the job summary
  2623  	get := &structs.JobSummaryRequest{
  2624  		JobID: job.ID,
  2625  		QueryOptions: structs.QueryOptions{
  2626  			Region:    "global",
  2627  			Namespace: job.Namespace,
  2628  		},
  2629  	}
  2630  	var resp2 structs.JobSummaryResponse
  2631  	if err := msgpackrpc.CallWithCodec(codec, "Job.Summary", get, &resp2); err != nil {
  2632  		t.Fatalf("err: %v", err)
  2633  	}
  2634  	if resp2.Index != resp.JobModifyIndex {
  2635  		t.Fatalf("Bad index: %d %d", resp2.Index, resp.Index)
  2636  	}
  2637  
  2638  	expectedJobSummary := structs.JobSummary{
  2639  		JobID:     job.ID,
  2640  		Namespace: job.Namespace,
  2641  		Summary: map[string]structs.TaskGroupSummary{
  2642  			"web": {},
  2643  		},
  2644  		Children:    new(structs.JobChildrenSummary),
  2645  		CreateIndex: job.CreateIndex,
  2646  		ModifyIndex: job.CreateIndex,
  2647  	}
  2648  
  2649  	if !reflect.DeepEqual(resp2.JobSummary, &expectedJobSummary) {
  2650  		t.Fatalf("expected: %v, actual: %v", expectedJobSummary, resp2.JobSummary)
  2651  	}
  2652  }
  2653  
  2654  func TestJobEndpoint_Summary_ACL(t *testing.T) {
  2655  	require := require.New(t)
  2656  	t.Parallel()
  2657  
  2658  	srv, root := TestACLServer(t, func(c *Config) {
  2659  		c.NumSchedulers = 0 // Prevent automatic dequeue
  2660  	})
  2661  	defer srv.Shutdown()
  2662  	codec := rpcClient(t, srv)
  2663  	testutil.WaitForLeader(t, srv.RPC)
  2664  
  2665  	// Create the job
  2666  	job := mock.Job()
  2667  	reg := &structs.JobRegisterRequest{
  2668  		Job: job,
  2669  		WriteRequest: structs.WriteRequest{
  2670  			Region:    "global",
  2671  			Namespace: job.Namespace,
  2672  		},
  2673  	}
  2674  	reg.AuthToken = root.SecretID
  2675  
  2676  	var err error
  2677  
  2678  	// Register the job with a valid token
  2679  	var regResp structs.JobRegisterResponse
  2680  	err = msgpackrpc.CallWithCodec(codec, "Job.Register", reg, &regResp)
  2681  	require.Nil(err)
  2682  
  2683  	job.CreateIndex = regResp.JobModifyIndex
  2684  	job.ModifyIndex = regResp.JobModifyIndex
  2685  	job.JobModifyIndex = regResp.JobModifyIndex
  2686  
  2687  	req := &structs.JobSummaryRequest{
  2688  		JobID: job.ID,
  2689  		QueryOptions: structs.QueryOptions{
  2690  			Region:    "global",
  2691  			Namespace: job.Namespace,
  2692  		},
  2693  	}
  2694  
  2695  	// Expect failure for request without a token
  2696  	var resp structs.JobSummaryResponse
  2697  	err = msgpackrpc.CallWithCodec(codec, "Job.Summary", req, &resp)
  2698  	require.NotNil(err)
  2699  
  2700  	expectedJobSummary := &structs.JobSummary{
  2701  		JobID:     job.ID,
  2702  		Namespace: job.Namespace,
  2703  		Summary: map[string]structs.TaskGroupSummary{
  2704  			"web": {},
  2705  		},
  2706  		Children:    new(structs.JobChildrenSummary),
  2707  		CreateIndex: job.CreateIndex,
  2708  		ModifyIndex: job.ModifyIndex,
  2709  	}
  2710  
  2711  	// Expect success when using a management token
  2712  	req.AuthToken = root.SecretID
  2713  	var mgmtResp structs.JobSummaryResponse
  2714  	err = msgpackrpc.CallWithCodec(codec, "Job.Summary", req, &mgmtResp)
  2715  	require.Nil(err)
  2716  	require.Equal(expectedJobSummary, mgmtResp.JobSummary)
  2717  
  2718  	// Create the namespace policy and tokens
  2719  	state := srv.fsm.State()
  2720  
  2721  	// Expect failure for request with an invalid token
  2722  	invalidToken := mock.CreatePolicyAndToken(t, state, 1003, "test-invalid",
  2723  		mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityListJobs}))
  2724  
  2725  	req.AuthToken = invalidToken.SecretID
  2726  	var invalidResp structs.JobSummaryResponse
  2727  	err = msgpackrpc.CallWithCodec(codec, "Job.Summary", req, &invalidResp)
  2728  	require.NotNil(err)
  2729  
  2730  	// Try with a valid token
  2731  	validToken := mock.CreatePolicyAndToken(t, state, 1001, "test-valid",
  2732  		mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityReadJob}))
  2733  
  2734  	req.AuthToken = validToken.SecretID
  2735  	var authResp structs.JobSummaryResponse
  2736  	err = msgpackrpc.CallWithCodec(codec, "Job.Summary", req, &authResp)
  2737  	require.Nil(err)
  2738  	require.Equal(expectedJobSummary, authResp.JobSummary)
  2739  }
  2740  
  2741  func TestJobEndpoint_GetJobSummary_Blocking(t *testing.T) {
  2742  	t.Parallel()
  2743  	s1 := TestServer(t, nil)
  2744  	defer s1.Shutdown()
  2745  	state := s1.fsm.State()
  2746  	codec := rpcClient(t, s1)
  2747  	testutil.WaitForLeader(t, s1.RPC)
  2748  
  2749  	// Create a job and insert it
  2750  	job1 := mock.Job()
  2751  	time.AfterFunc(200*time.Millisecond, func() {
  2752  		if err := state.UpsertJob(100, job1); err != nil {
  2753  			t.Fatalf("err: %v", err)
  2754  		}
  2755  	})
  2756  
  2757  	// Ensure the job summary request gets fired
  2758  	req := &structs.JobSummaryRequest{
  2759  		JobID: job1.ID,
  2760  		QueryOptions: structs.QueryOptions{
  2761  			Region:        "global",
  2762  			Namespace:     job1.Namespace,
  2763  			MinQueryIndex: 50,
  2764  		},
  2765  	}
  2766  	var resp structs.JobSummaryResponse
  2767  	start := time.Now()
  2768  	if err := msgpackrpc.CallWithCodec(codec, "Job.Summary", req, &resp); err != nil {
  2769  		t.Fatalf("err: %v", err)
  2770  	}
  2771  	if elapsed := time.Since(start); elapsed < 200*time.Millisecond {
  2772  		t.Fatalf("should block (returned in %s) %#v", elapsed, resp)
  2773  	}
  2774  
  2775  	// Upsert an allocation for the job which should trigger the watch.
  2776  	time.AfterFunc(200*time.Millisecond, func() {
  2777  		alloc := mock.Alloc()
  2778  		alloc.JobID = job1.ID
  2779  		alloc.Job = job1
  2780  		if err := state.UpsertAllocs(200, []*structs.Allocation{alloc}); err != nil {
  2781  			t.Fatalf("err: %v", err)
  2782  		}
  2783  	})
  2784  	req = &structs.JobSummaryRequest{
  2785  		JobID: job1.ID,
  2786  		QueryOptions: structs.QueryOptions{
  2787  			Region:        "global",
  2788  			Namespace:     job1.Namespace,
  2789  			MinQueryIndex: 199,
  2790  		},
  2791  	}
  2792  	start = time.Now()
  2793  	var resp1 structs.JobSummaryResponse
  2794  	if err := msgpackrpc.CallWithCodec(codec, "Job.Summary", req, &resp1); err != nil {
  2795  		t.Fatalf("err: %v", err)
  2796  	}
  2797  
  2798  	if elapsed := time.Since(start); elapsed < 200*time.Millisecond {
  2799  		t.Fatalf("should block (returned in %s) %#v", elapsed, resp)
  2800  	}
  2801  	if resp1.Index != 200 {
  2802  		t.Fatalf("Bad index: %d %d", resp.Index, 200)
  2803  	}
  2804  	if resp1.JobSummary == nil {
  2805  		t.Fatalf("bad: %#v", resp)
  2806  	}
  2807  
  2808  	// Job delete fires watches
  2809  	time.AfterFunc(100*time.Millisecond, func() {
  2810  		if err := state.DeleteJob(300, job1.Namespace, job1.ID); err != nil {
  2811  			t.Fatalf("err: %v", err)
  2812  		}
  2813  	})
  2814  
  2815  	req.QueryOptions.MinQueryIndex = 250
  2816  	start = time.Now()
  2817  
  2818  	var resp2 structs.SingleJobResponse
  2819  	if err := msgpackrpc.CallWithCodec(codec, "Job.Summary", req, &resp2); err != nil {
  2820  		t.Fatalf("err: %v", err)
  2821  	}
  2822  
  2823  	if elapsed := time.Since(start); elapsed < 100*time.Millisecond {
  2824  		t.Fatalf("should block (returned in %s) %#v", elapsed, resp2)
  2825  	}
  2826  	if resp2.Index != 300 {
  2827  		t.Fatalf("Bad index: %d %d", resp2.Index, 300)
  2828  	}
  2829  	if resp2.Job != nil {
  2830  		t.Fatalf("bad: %#v", resp2.Job)
  2831  	}
  2832  }
  2833  
  2834  func TestJobEndpoint_ListJobs(t *testing.T) {
  2835  	t.Parallel()
  2836  	s1 := TestServer(t, nil)
  2837  	defer s1.Shutdown()
  2838  	codec := rpcClient(t, s1)
  2839  	testutil.WaitForLeader(t, s1.RPC)
  2840  
  2841  	// Create the register request
  2842  	job := mock.Job()
  2843  	state := s1.fsm.State()
  2844  	err := state.UpsertJob(1000, job)
  2845  	if err != nil {
  2846  		t.Fatalf("err: %v", err)
  2847  	}
  2848  
  2849  	// Lookup the jobs
  2850  	get := &structs.JobListRequest{
  2851  		QueryOptions: structs.QueryOptions{
  2852  			Region:    "global",
  2853  			Namespace: job.Namespace,
  2854  		},
  2855  	}
  2856  	var resp2 structs.JobListResponse
  2857  	if err := msgpackrpc.CallWithCodec(codec, "Job.List", get, &resp2); err != nil {
  2858  		t.Fatalf("err: %v", err)
  2859  	}
  2860  	if resp2.Index != 1000 {
  2861  		t.Fatalf("Bad index: %d %d", resp2.Index, 1000)
  2862  	}
  2863  
  2864  	if len(resp2.Jobs) != 1 {
  2865  		t.Fatalf("bad: %#v", resp2.Jobs)
  2866  	}
  2867  	if resp2.Jobs[0].ID != job.ID {
  2868  		t.Fatalf("bad: %#v", resp2.Jobs[0])
  2869  	}
  2870  
  2871  	// Lookup the jobs by prefix
  2872  	get = &structs.JobListRequest{
  2873  		QueryOptions: structs.QueryOptions{
  2874  			Region:    "global",
  2875  			Namespace: job.Namespace,
  2876  			Prefix:    resp2.Jobs[0].ID[:4],
  2877  		},
  2878  	}
  2879  	var resp3 structs.JobListResponse
  2880  	if err := msgpackrpc.CallWithCodec(codec, "Job.List", get, &resp3); err != nil {
  2881  		t.Fatalf("err: %v", err)
  2882  	}
  2883  	if resp3.Index != 1000 {
  2884  		t.Fatalf("Bad index: %d %d", resp3.Index, 1000)
  2885  	}
  2886  
  2887  	if len(resp3.Jobs) != 1 {
  2888  		t.Fatalf("bad: %#v", resp3.Jobs)
  2889  	}
  2890  	if resp3.Jobs[0].ID != job.ID {
  2891  		t.Fatalf("bad: %#v", resp3.Jobs[0])
  2892  	}
  2893  }
  2894  
  2895  func TestJobEndpoint_ListJobs_WithACL(t *testing.T) {
  2896  	require := require.New(t)
  2897  	t.Parallel()
  2898  
  2899  	srv, root := TestACLServer(t, func(c *Config) {
  2900  		c.NumSchedulers = 0 // Prevent automatic dequeue
  2901  	})
  2902  	defer srv.Shutdown()
  2903  	codec := rpcClient(t, srv)
  2904  	testutil.WaitForLeader(t, srv.RPC)
  2905  	state := srv.fsm.State()
  2906  
  2907  	var err error
  2908  
  2909  	// Create the register request
  2910  	job := mock.Job()
  2911  	err = state.UpsertJob(1000, job)
  2912  	require.Nil(err)
  2913  
  2914  	req := &structs.JobListRequest{
  2915  		QueryOptions: structs.QueryOptions{
  2916  			Region:    "global",
  2917  			Namespace: job.Namespace,
  2918  		},
  2919  	}
  2920  
  2921  	// Expect failure for request without a token
  2922  	var resp structs.JobListResponse
  2923  	err = msgpackrpc.CallWithCodec(codec, "Job.List", req, &resp)
  2924  	require.NotNil(err)
  2925  
  2926  	// Expect success for request with a management token
  2927  	var mgmtResp structs.JobListResponse
  2928  	req.AuthToken = root.SecretID
  2929  	err = msgpackrpc.CallWithCodec(codec, "Job.List", req, &mgmtResp)
  2930  	require.Nil(err)
  2931  	require.Equal(1, len(mgmtResp.Jobs))
  2932  	require.Equal(job.ID, mgmtResp.Jobs[0].ID)
  2933  
  2934  	// Expect failure for request with a token that has incorrect permissions
  2935  	invalidToken := mock.CreatePolicyAndToken(t, state, 1003, "test-invalid",
  2936  		mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityReadJob}))
  2937  
  2938  	req.AuthToken = invalidToken.SecretID
  2939  	var invalidResp structs.JobListResponse
  2940  	err = msgpackrpc.CallWithCodec(codec, "Job.List", req, &invalidResp)
  2941  	require.NotNil(err)
  2942  
  2943  	// Try with a valid token with correct permissions
  2944  	validToken := mock.CreatePolicyAndToken(t, state, 1001, "test-valid",
  2945  		mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityListJobs}))
  2946  	var validResp structs.JobListResponse
  2947  	req.AuthToken = validToken.SecretID
  2948  
  2949  	err = msgpackrpc.CallWithCodec(codec, "Job.List", req, &validResp)
  2950  	require.Nil(err)
  2951  	require.Equal(1, len(validResp.Jobs))
  2952  	require.Equal(job.ID, validResp.Jobs[0].ID)
  2953  }
  2954  
  2955  func TestJobEndpoint_ListJobs_Blocking(t *testing.T) {
  2956  	t.Parallel()
  2957  	s1 := TestServer(t, nil)
  2958  	defer s1.Shutdown()
  2959  	state := s1.fsm.State()
  2960  	codec := rpcClient(t, s1)
  2961  	testutil.WaitForLeader(t, s1.RPC)
  2962  
  2963  	// Create the job
  2964  	job := mock.Job()
  2965  
  2966  	// Upsert job triggers watches
  2967  	time.AfterFunc(100*time.Millisecond, func() {
  2968  		if err := state.UpsertJob(100, job); err != nil {
  2969  			t.Fatalf("err: %v", err)
  2970  		}
  2971  	})
  2972  
  2973  	req := &structs.JobListRequest{
  2974  		QueryOptions: structs.QueryOptions{
  2975  			Region:        "global",
  2976  			Namespace:     job.Namespace,
  2977  			MinQueryIndex: 50,
  2978  		},
  2979  	}
  2980  	start := time.Now()
  2981  	var resp structs.JobListResponse
  2982  	if err := msgpackrpc.CallWithCodec(codec, "Job.List", req, &resp); err != nil {
  2983  		t.Fatalf("err: %v", err)
  2984  	}
  2985  
  2986  	if elapsed := time.Since(start); elapsed < 100*time.Millisecond {
  2987  		t.Fatalf("should block (returned in %s) %#v", elapsed, resp)
  2988  	}
  2989  	if resp.Index != 100 {
  2990  		t.Fatalf("Bad index: %d %d", resp.Index, 100)
  2991  	}
  2992  	if len(resp.Jobs) != 1 || resp.Jobs[0].ID != job.ID {
  2993  		t.Fatalf("bad: %#v", resp)
  2994  	}
  2995  
  2996  	// Job deletion triggers watches
  2997  	time.AfterFunc(100*time.Millisecond, func() {
  2998  		if err := state.DeleteJob(200, job.Namespace, job.ID); err != nil {
  2999  			t.Fatalf("err: %v", err)
  3000  		}
  3001  	})
  3002  
  3003  	req.MinQueryIndex = 150
  3004  	start = time.Now()
  3005  	var resp2 structs.JobListResponse
  3006  	if err := msgpackrpc.CallWithCodec(codec, "Job.List", req, &resp2); err != nil {
  3007  		t.Fatalf("err: %v", err)
  3008  	}
  3009  
  3010  	if elapsed := time.Since(start); elapsed < 100*time.Millisecond {
  3011  		t.Fatalf("should block (returned in %s) %#v", elapsed, resp2)
  3012  	}
  3013  	if resp2.Index != 200 {
  3014  		t.Fatalf("Bad index: %d %d", resp2.Index, 200)
  3015  	}
  3016  	if len(resp2.Jobs) != 0 {
  3017  		t.Fatalf("bad: %#v", resp2)
  3018  	}
  3019  }
  3020  
  3021  func TestJobEndpoint_Allocations(t *testing.T) {
  3022  	t.Parallel()
  3023  	s1 := TestServer(t, nil)
  3024  	defer s1.Shutdown()
  3025  	codec := rpcClient(t, s1)
  3026  	testutil.WaitForLeader(t, s1.RPC)
  3027  
  3028  	// Create the register request
  3029  	alloc1 := mock.Alloc()
  3030  	alloc2 := mock.Alloc()
  3031  	alloc2.JobID = alloc1.JobID
  3032  	state := s1.fsm.State()
  3033  	state.UpsertJobSummary(998, mock.JobSummary(alloc1.JobID))
  3034  	state.UpsertJobSummary(999, mock.JobSummary(alloc2.JobID))
  3035  	err := state.UpsertAllocs(1000,
  3036  		[]*structs.Allocation{alloc1, alloc2})
  3037  	if err != nil {
  3038  		t.Fatalf("err: %v", err)
  3039  	}
  3040  
  3041  	// Lookup the jobs
  3042  	get := &structs.JobSpecificRequest{
  3043  		JobID: alloc1.JobID,
  3044  		QueryOptions: structs.QueryOptions{
  3045  			Region:    "global",
  3046  			Namespace: alloc1.Job.Namespace,
  3047  		},
  3048  	}
  3049  	var resp2 structs.JobAllocationsResponse
  3050  	if err := msgpackrpc.CallWithCodec(codec, "Job.Allocations", get, &resp2); err != nil {
  3051  		t.Fatalf("err: %v", err)
  3052  	}
  3053  	if resp2.Index != 1000 {
  3054  		t.Fatalf("Bad index: %d %d", resp2.Index, 1000)
  3055  	}
  3056  
  3057  	if len(resp2.Allocations) != 2 {
  3058  		t.Fatalf("bad: %#v", resp2.Allocations)
  3059  	}
  3060  }
  3061  
  3062  func TestJobEndpoint_Allocations_ACL(t *testing.T) {
  3063  	t.Parallel()
  3064  	require := require.New(t)
  3065  
  3066  	s1, root := TestACLServer(t, nil)
  3067  	defer s1.Shutdown()
  3068  	codec := rpcClient(t, s1)
  3069  	testutil.WaitForLeader(t, s1.RPC)
  3070  	state := s1.fsm.State()
  3071  
  3072  	// Create allocations for a job
  3073  	alloc1 := mock.Alloc()
  3074  	alloc2 := mock.Alloc()
  3075  	alloc2.JobID = alloc1.JobID
  3076  	state.UpsertJobSummary(998, mock.JobSummary(alloc1.JobID))
  3077  	state.UpsertJobSummary(999, mock.JobSummary(alloc2.JobID))
  3078  	err := state.UpsertAllocs(1000,
  3079  		[]*structs.Allocation{alloc1, alloc2})
  3080  	require.Nil(err)
  3081  
  3082  	// Look up allocations for that job
  3083  	get := &structs.JobSpecificRequest{
  3084  		JobID: alloc1.JobID,
  3085  		QueryOptions: structs.QueryOptions{
  3086  			Region:    "global",
  3087  			Namespace: alloc1.Job.Namespace,
  3088  		},
  3089  	}
  3090  
  3091  	// Attempt to fetch the response without a token should fail
  3092  	var resp structs.JobAllocationsResponse
  3093  	err = msgpackrpc.CallWithCodec(codec, "Job.Allocations", get, &resp)
  3094  	require.NotNil(err)
  3095  	require.Contains(err.Error(), "Permission denied")
  3096  
  3097  	// Attempt to fetch the response with an invalid token should fail
  3098  	invalidToken := mock.CreatePolicyAndToken(t, state, 1001, "test-invalid",
  3099  		mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityListJobs}))
  3100  
  3101  	get.AuthToken = invalidToken.SecretID
  3102  	var invalidResp structs.JobAllocationsResponse
  3103  	err = msgpackrpc.CallWithCodec(codec, "Job.Allocations", get, &invalidResp)
  3104  	require.NotNil(err)
  3105  	require.Contains(err.Error(), "Permission denied")
  3106  
  3107  	// Attempt to fetch the response with valid management token should succeed
  3108  	get.AuthToken = root.SecretID
  3109  	var validResp structs.JobAllocationsResponse
  3110  	err = msgpackrpc.CallWithCodec(codec, "Job.Allocations", get, &validResp)
  3111  	require.Nil(err)
  3112  
  3113  	// Attempt to fetch the response with valid management token should succeed
  3114  	validToken := mock.CreatePolicyAndToken(t, state, 1005, "test-valid",
  3115  		mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityReadJob}))
  3116  
  3117  	get.AuthToken = validToken.SecretID
  3118  	var validResp2 structs.JobAllocationsResponse
  3119  	err = msgpackrpc.CallWithCodec(codec, "Job.Allocations", get, &validResp2)
  3120  	require.Nil(err)
  3121  
  3122  	require.Equal(2, len(validResp2.Allocations))
  3123  }
  3124  
  3125  func TestJobEndpoint_Allocations_Blocking(t *testing.T) {
  3126  	t.Parallel()
  3127  	s1 := TestServer(t, nil)
  3128  	defer s1.Shutdown()
  3129  	codec := rpcClient(t, s1)
  3130  	testutil.WaitForLeader(t, s1.RPC)
  3131  
  3132  	// Create the register request
  3133  	alloc1 := mock.Alloc()
  3134  	alloc2 := mock.Alloc()
  3135  	alloc2.JobID = "job1"
  3136  	state := s1.fsm.State()
  3137  
  3138  	// First upsert an unrelated alloc
  3139  	time.AfterFunc(100*time.Millisecond, func() {
  3140  		state.UpsertJobSummary(99, mock.JobSummary(alloc1.JobID))
  3141  		err := state.UpsertAllocs(100, []*structs.Allocation{alloc1})
  3142  		if err != nil {
  3143  			t.Fatalf("err: %v", err)
  3144  		}
  3145  	})
  3146  
  3147  	// Upsert an alloc for the job we are interested in later
  3148  	time.AfterFunc(200*time.Millisecond, func() {
  3149  		state.UpsertJobSummary(199, mock.JobSummary(alloc2.JobID))
  3150  		err := state.UpsertAllocs(200, []*structs.Allocation{alloc2})
  3151  		if err != nil {
  3152  			t.Fatalf("err: %v", err)
  3153  		}
  3154  	})
  3155  
  3156  	// Lookup the jobs
  3157  	get := &structs.JobSpecificRequest{
  3158  		JobID: "job1",
  3159  		QueryOptions: structs.QueryOptions{
  3160  			Region:        "global",
  3161  			Namespace:     alloc1.Job.Namespace,
  3162  			MinQueryIndex: 150,
  3163  		},
  3164  	}
  3165  	var resp structs.JobAllocationsResponse
  3166  	start := time.Now()
  3167  	if err := msgpackrpc.CallWithCodec(codec, "Job.Allocations", get, &resp); err != nil {
  3168  		t.Fatalf("err: %v", err)
  3169  	}
  3170  
  3171  	if elapsed := time.Since(start); elapsed < 200*time.Millisecond {
  3172  		t.Fatalf("should block (returned in %s) %#v", elapsed, resp)
  3173  	}
  3174  	if resp.Index != 200 {
  3175  		t.Fatalf("Bad index: %d %d", resp.Index, 200)
  3176  	}
  3177  	if len(resp.Allocations) != 1 || resp.Allocations[0].JobID != "job1" {
  3178  		t.Fatalf("bad: %#v", resp.Allocations)
  3179  	}
  3180  }
  3181  
  3182  func TestJobEndpoint_Evaluations(t *testing.T) {
  3183  	t.Parallel()
  3184  	s1 := TestServer(t, nil)
  3185  	defer s1.Shutdown()
  3186  	codec := rpcClient(t, s1)
  3187  	testutil.WaitForLeader(t, s1.RPC)
  3188  
  3189  	// Create the register request
  3190  	eval1 := mock.Eval()
  3191  	eval2 := mock.Eval()
  3192  	eval2.JobID = eval1.JobID
  3193  	state := s1.fsm.State()
  3194  	err := state.UpsertEvals(1000,
  3195  		[]*structs.Evaluation{eval1, eval2})
  3196  	if err != nil {
  3197  		t.Fatalf("err: %v", err)
  3198  	}
  3199  
  3200  	// Lookup the jobs
  3201  	get := &structs.JobSpecificRequest{
  3202  		JobID: eval1.JobID,
  3203  		QueryOptions: structs.QueryOptions{
  3204  			Region:    "global",
  3205  			Namespace: eval1.Namespace,
  3206  		},
  3207  	}
  3208  	var resp2 structs.JobEvaluationsResponse
  3209  	if err := msgpackrpc.CallWithCodec(codec, "Job.Evaluations", get, &resp2); err != nil {
  3210  		t.Fatalf("err: %v", err)
  3211  	}
  3212  	if resp2.Index != 1000 {
  3213  		t.Fatalf("Bad index: %d %d", resp2.Index, 1000)
  3214  	}
  3215  
  3216  	if len(resp2.Evaluations) != 2 {
  3217  		t.Fatalf("bad: %#v", resp2.Evaluations)
  3218  	}
  3219  }
  3220  
  3221  func TestJobEndpoint_Evaluations_ACL(t *testing.T) {
  3222  	t.Parallel()
  3223  	require := require.New(t)
  3224  
  3225  	s1, root := TestACLServer(t, nil)
  3226  	defer s1.Shutdown()
  3227  	codec := rpcClient(t, s1)
  3228  	testutil.WaitForLeader(t, s1.RPC)
  3229  	state := s1.fsm.State()
  3230  
  3231  	// Create evaluations for the same job
  3232  	eval1 := mock.Eval()
  3233  	eval2 := mock.Eval()
  3234  	eval2.JobID = eval1.JobID
  3235  	err := state.UpsertEvals(1000,
  3236  		[]*structs.Evaluation{eval1, eval2})
  3237  	require.Nil(err)
  3238  
  3239  	// Lookup the jobs
  3240  	get := &structs.JobSpecificRequest{
  3241  		JobID: eval1.JobID,
  3242  		QueryOptions: structs.QueryOptions{
  3243  			Region:    "global",
  3244  			Namespace: eval1.Namespace,
  3245  		},
  3246  	}
  3247  
  3248  	// Attempt to fetch without providing a token
  3249  	var resp structs.JobEvaluationsResponse
  3250  	err = msgpackrpc.CallWithCodec(codec, "Job.Evaluations", get, &resp)
  3251  	require.NotNil(err)
  3252  	require.Contains(err.Error(), "Permission denied")
  3253  
  3254  	// Attempt to fetch the response with an invalid token
  3255  	invalidToken := mock.CreatePolicyAndToken(t, state, 1001, "test-invalid",
  3256  		mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityListJobs}))
  3257  
  3258  	get.AuthToken = invalidToken.SecretID
  3259  	var invalidResp structs.JobEvaluationsResponse
  3260  	err = msgpackrpc.CallWithCodec(codec, "Job.Evaluations", get, &invalidResp)
  3261  	require.NotNil(err)
  3262  	require.Contains(err.Error(), "Permission denied")
  3263  
  3264  	// Attempt to fetch with valid management token should succeed
  3265  	get.AuthToken = root.SecretID
  3266  	var validResp structs.JobEvaluationsResponse
  3267  	err = msgpackrpc.CallWithCodec(codec, "Job.Evaluations", get, &validResp)
  3268  	require.Nil(err)
  3269  	require.Equal(2, len(validResp.Evaluations))
  3270  
  3271  	// Attempt to fetch with valid token should succeed
  3272  	validToken := mock.CreatePolicyAndToken(t, state, 1003, "test-valid",
  3273  		mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityReadJob}))
  3274  
  3275  	get.AuthToken = validToken.SecretID
  3276  	var validResp2 structs.JobEvaluationsResponse
  3277  	err = msgpackrpc.CallWithCodec(codec, "Job.Evaluations", get, &validResp2)
  3278  	require.Nil(err)
  3279  	require.Equal(2, len(validResp2.Evaluations))
  3280  }
  3281  
  3282  func TestJobEndpoint_Evaluations_Blocking(t *testing.T) {
  3283  	t.Parallel()
  3284  	s1 := TestServer(t, nil)
  3285  	defer s1.Shutdown()
  3286  	codec := rpcClient(t, s1)
  3287  	testutil.WaitForLeader(t, s1.RPC)
  3288  
  3289  	// Create the register request
  3290  	eval1 := mock.Eval()
  3291  	eval2 := mock.Eval()
  3292  	eval2.JobID = "job1"
  3293  	state := s1.fsm.State()
  3294  
  3295  	// First upsert an unrelated eval
  3296  	time.AfterFunc(100*time.Millisecond, func() {
  3297  		err := state.UpsertEvals(100, []*structs.Evaluation{eval1})
  3298  		if err != nil {
  3299  			t.Fatalf("err: %v", err)
  3300  		}
  3301  	})
  3302  
  3303  	// Upsert an eval for the job we are interested in later
  3304  	time.AfterFunc(200*time.Millisecond, func() {
  3305  		err := state.UpsertEvals(200, []*structs.Evaluation{eval2})
  3306  		if err != nil {
  3307  			t.Fatalf("err: %v", err)
  3308  		}
  3309  	})
  3310  
  3311  	// Lookup the jobs
  3312  	get := &structs.JobSpecificRequest{
  3313  		JobID: "job1",
  3314  		QueryOptions: structs.QueryOptions{
  3315  			Region:        "global",
  3316  			Namespace:     eval1.Namespace,
  3317  			MinQueryIndex: 150,
  3318  		},
  3319  	}
  3320  	var resp structs.JobEvaluationsResponse
  3321  	start := time.Now()
  3322  	if err := msgpackrpc.CallWithCodec(codec, "Job.Evaluations", get, &resp); err != nil {
  3323  		t.Fatalf("err: %v", err)
  3324  	}
  3325  
  3326  	if elapsed := time.Since(start); elapsed < 200*time.Millisecond {
  3327  		t.Fatalf("should block (returned in %s) %#v", elapsed, resp)
  3328  	}
  3329  	if resp.Index != 200 {
  3330  		t.Fatalf("Bad index: %d %d", resp.Index, 200)
  3331  	}
  3332  	if len(resp.Evaluations) != 1 || resp.Evaluations[0].JobID != "job1" {
  3333  		t.Fatalf("bad: %#v", resp.Evaluations)
  3334  	}
  3335  }
  3336  
  3337  func TestJobEndpoint_Deployments(t *testing.T) {
  3338  	t.Parallel()
  3339  	s1 := TestServer(t, nil)
  3340  	defer s1.Shutdown()
  3341  	codec := rpcClient(t, s1)
  3342  	testutil.WaitForLeader(t, s1.RPC)
  3343  	state := s1.fsm.State()
  3344  	require := require.New(t)
  3345  
  3346  	// Create the register request
  3347  	j := mock.Job()
  3348  	d1 := mock.Deployment()
  3349  	d2 := mock.Deployment()
  3350  	d1.JobID = j.ID
  3351  	d2.JobID = j.ID
  3352  	require.Nil(state.UpsertJob(1000, j), "UpsertJob")
  3353  	require.Nil(state.UpsertDeployment(1001, d1), "UpsertDeployment")
  3354  	require.Nil(state.UpsertDeployment(1002, d2), "UpsertDeployment")
  3355  
  3356  	// Lookup the jobs
  3357  	get := &structs.JobSpecificRequest{
  3358  		JobID: j.ID,
  3359  		QueryOptions: structs.QueryOptions{
  3360  			Region:    "global",
  3361  			Namespace: j.Namespace,
  3362  		},
  3363  	}
  3364  	var resp structs.DeploymentListResponse
  3365  	require.Nil(msgpackrpc.CallWithCodec(codec, "Job.Deployments", get, &resp), "RPC")
  3366  	require.EqualValues(1002, resp.Index, "response index")
  3367  	require.Len(resp.Deployments, 2, "deployments for job")
  3368  }
  3369  
  3370  func TestJobEndpoint_Deployments_ACL(t *testing.T) {
  3371  	t.Parallel()
  3372  	require := require.New(t)
  3373  
  3374  	s1, root := TestACLServer(t, nil)
  3375  	defer s1.Shutdown()
  3376  	codec := rpcClient(t, s1)
  3377  	testutil.WaitForLeader(t, s1.RPC)
  3378  	state := s1.fsm.State()
  3379  
  3380  	// Create a job and corresponding deployments
  3381  	j := mock.Job()
  3382  	d1 := mock.Deployment()
  3383  	d2 := mock.Deployment()
  3384  	d1.JobID = j.ID
  3385  	d2.JobID = j.ID
  3386  	require.Nil(state.UpsertJob(1000, j), "UpsertJob")
  3387  	require.Nil(state.UpsertDeployment(1001, d1), "UpsertDeployment")
  3388  	require.Nil(state.UpsertDeployment(1002, d2), "UpsertDeployment")
  3389  
  3390  	// Lookup the jobs
  3391  	get := &structs.JobSpecificRequest{
  3392  		JobID: j.ID,
  3393  		QueryOptions: structs.QueryOptions{
  3394  			Region:    "global",
  3395  			Namespace: j.Namespace,
  3396  		},
  3397  	}
  3398  	// Lookup with no token should fail
  3399  	var resp structs.DeploymentListResponse
  3400  	err := msgpackrpc.CallWithCodec(codec, "Job.Deployments", get, &resp)
  3401  	require.NotNil(err)
  3402  	require.Contains(err.Error(), "Permission denied")
  3403  
  3404  	// Attempt to fetch the response with an invalid token
  3405  	invalidToken := mock.CreatePolicyAndToken(t, state, 1001, "test-invalid",
  3406  		mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityListJobs}))
  3407  
  3408  	get.AuthToken = invalidToken.SecretID
  3409  	var invalidResp structs.DeploymentListResponse
  3410  	err = msgpackrpc.CallWithCodec(codec, "Job.Deployments", get, &invalidResp)
  3411  	require.NotNil(err)
  3412  	require.Contains(err.Error(), "Permission denied")
  3413  
  3414  	// Lookup with valid management token should succeed
  3415  	get.AuthToken = root.SecretID
  3416  	var validResp structs.DeploymentListResponse
  3417  	require.Nil(msgpackrpc.CallWithCodec(codec, "Job.Deployments", get, &validResp), "RPC")
  3418  	require.EqualValues(1002, validResp.Index, "response index")
  3419  	require.Len(validResp.Deployments, 2, "deployments for job")
  3420  
  3421  	// Lookup with valid token should succeed
  3422  	validToken := mock.CreatePolicyAndToken(t, state, 1005, "test-valid",
  3423  		mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityReadJob}))
  3424  
  3425  	get.AuthToken = validToken.SecretID
  3426  	var validResp2 structs.DeploymentListResponse
  3427  	require.Nil(msgpackrpc.CallWithCodec(codec, "Job.Deployments", get, &validResp2), "RPC")
  3428  	require.EqualValues(1002, validResp2.Index, "response index")
  3429  	require.Len(validResp2.Deployments, 2, "deployments for job")
  3430  }
  3431  
  3432  func TestJobEndpoint_Deployments_Blocking(t *testing.T) {
  3433  	t.Parallel()
  3434  	s1 := TestServer(t, nil)
  3435  	defer s1.Shutdown()
  3436  	codec := rpcClient(t, s1)
  3437  	testutil.WaitForLeader(t, s1.RPC)
  3438  	state := s1.fsm.State()
  3439  	require := require.New(t)
  3440  
  3441  	// Create the register request
  3442  	j := mock.Job()
  3443  	d1 := mock.Deployment()
  3444  	d2 := mock.Deployment()
  3445  	d2.JobID = j.ID
  3446  	require.Nil(state.UpsertJob(50, j), "UpsertJob")
  3447  
  3448  	// First upsert an unrelated eval
  3449  	time.AfterFunc(100*time.Millisecond, func() {
  3450  		require.Nil(state.UpsertDeployment(100, d1), "UpsertDeployment")
  3451  	})
  3452  
  3453  	// Upsert an eval for the job we are interested in later
  3454  	time.AfterFunc(200*time.Millisecond, func() {
  3455  		require.Nil(state.UpsertDeployment(200, d2), "UpsertDeployment")
  3456  	})
  3457  
  3458  	// Lookup the jobs
  3459  	get := &structs.JobSpecificRequest{
  3460  		JobID: d2.JobID,
  3461  		QueryOptions: structs.QueryOptions{
  3462  			Region:        "global",
  3463  			Namespace:     d2.Namespace,
  3464  			MinQueryIndex: 150,
  3465  		},
  3466  	}
  3467  	var resp structs.DeploymentListResponse
  3468  	start := time.Now()
  3469  	require.Nil(msgpackrpc.CallWithCodec(codec, "Job.Deployments", get, &resp), "RPC")
  3470  	require.EqualValues(200, resp.Index, "response index")
  3471  	require.Len(resp.Deployments, 1, "deployments for job")
  3472  	require.Equal(d2.ID, resp.Deployments[0].ID, "returned deployment")
  3473  	if elapsed := time.Since(start); elapsed < 200*time.Millisecond {
  3474  		t.Fatalf("should block (returned in %s) %#v", elapsed, resp)
  3475  	}
  3476  }
  3477  
  3478  func TestJobEndpoint_LatestDeployment(t *testing.T) {
  3479  	t.Parallel()
  3480  	s1 := TestServer(t, nil)
  3481  	defer s1.Shutdown()
  3482  	codec := rpcClient(t, s1)
  3483  	testutil.WaitForLeader(t, s1.RPC)
  3484  	state := s1.fsm.State()
  3485  	require := require.New(t)
  3486  
  3487  	// Create the register request
  3488  	j := mock.Job()
  3489  	d1 := mock.Deployment()
  3490  	d2 := mock.Deployment()
  3491  	d1.JobID = j.ID
  3492  	d2.JobID = j.ID
  3493  	d2.CreateIndex = d1.CreateIndex + 100
  3494  	d2.ModifyIndex = d2.CreateIndex + 100
  3495  	require.Nil(state.UpsertJob(1000, j), "UpsertJob")
  3496  	require.Nil(state.UpsertDeployment(1001, d1), "UpsertDeployment")
  3497  	require.Nil(state.UpsertDeployment(1002, d2), "UpsertDeployment")
  3498  
  3499  	// Lookup the jobs
  3500  	get := &structs.JobSpecificRequest{
  3501  		JobID: j.ID,
  3502  		QueryOptions: structs.QueryOptions{
  3503  			Region:    "global",
  3504  			Namespace: j.Namespace,
  3505  		},
  3506  	}
  3507  	var resp structs.SingleDeploymentResponse
  3508  	require.Nil(msgpackrpc.CallWithCodec(codec, "Job.LatestDeployment", get, &resp), "RPC")
  3509  	require.EqualValues(1002, resp.Index, "response index")
  3510  	require.NotNil(resp.Deployment, "want a deployment")
  3511  	require.Equal(d2.ID, resp.Deployment.ID, "latest deployment for job")
  3512  }
  3513  
  3514  func TestJobEndpoint_LatestDeployment_ACL(t *testing.T) {
  3515  	t.Parallel()
  3516  	require := require.New(t)
  3517  
  3518  	s1, root := TestACLServer(t, nil)
  3519  	defer s1.Shutdown()
  3520  	codec := rpcClient(t, s1)
  3521  	testutil.WaitForLeader(t, s1.RPC)
  3522  	state := s1.fsm.State()
  3523  
  3524  	// Create a job and deployments
  3525  	j := mock.Job()
  3526  	d1 := mock.Deployment()
  3527  	d2 := mock.Deployment()
  3528  	d1.JobID = j.ID
  3529  	d2.JobID = j.ID
  3530  	d2.CreateIndex = d1.CreateIndex + 100
  3531  	d2.ModifyIndex = d2.CreateIndex + 100
  3532  	require.Nil(state.UpsertJob(1000, j), "UpsertJob")
  3533  	require.Nil(state.UpsertDeployment(1001, d1), "UpsertDeployment")
  3534  	require.Nil(state.UpsertDeployment(1002, d2), "UpsertDeployment")
  3535  
  3536  	// Lookup the jobs
  3537  	get := &structs.JobSpecificRequest{
  3538  		JobID: j.ID,
  3539  		QueryOptions: structs.QueryOptions{
  3540  			Region:    "global",
  3541  			Namespace: j.Namespace,
  3542  		},
  3543  	}
  3544  
  3545  	// Attempt to fetch the response without a token should fail
  3546  	var resp structs.SingleDeploymentResponse
  3547  	err := msgpackrpc.CallWithCodec(codec, "Job.LatestDeployment", get, &resp)
  3548  	require.NotNil(err)
  3549  	require.Contains(err.Error(), "Permission denied")
  3550  
  3551  	// Attempt to fetch the response with an invalid token should fail
  3552  	invalidToken := mock.CreatePolicyAndToken(t, state, 1001, "test-invalid",
  3553  		mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityListJobs}))
  3554  
  3555  	get.AuthToken = invalidToken.SecretID
  3556  	var invalidResp structs.SingleDeploymentResponse
  3557  	err = msgpackrpc.CallWithCodec(codec, "Job.LatestDeployment", get, &invalidResp)
  3558  	require.NotNil(err)
  3559  	require.Contains(err.Error(), "Permission denied")
  3560  
  3561  	// Fetching latest deployment with a valid management token should succeed
  3562  	get.AuthToken = root.SecretID
  3563  	var validResp structs.SingleDeploymentResponse
  3564  	require.Nil(msgpackrpc.CallWithCodec(codec, "Job.LatestDeployment", get, &validResp), "RPC")
  3565  	require.EqualValues(1002, validResp.Index, "response index")
  3566  	require.NotNil(validResp.Deployment, "want a deployment")
  3567  	require.Equal(d2.ID, validResp.Deployment.ID, "latest deployment for job")
  3568  
  3569  	// Fetching latest deployment with a valid token should succeed
  3570  	validToken := mock.CreatePolicyAndToken(t, state, 1004, "test-valid",
  3571  		mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityReadJob}))
  3572  
  3573  	get.AuthToken = validToken.SecretID
  3574  	var validResp2 structs.SingleDeploymentResponse
  3575  	require.Nil(msgpackrpc.CallWithCodec(codec, "Job.LatestDeployment", get, &validResp2), "RPC")
  3576  	require.EqualValues(1002, validResp2.Index, "response index")
  3577  	require.NotNil(validResp2.Deployment, "want a deployment")
  3578  	require.Equal(d2.ID, validResp2.Deployment.ID, "latest deployment for job")
  3579  }
  3580  
  3581  func TestJobEndpoint_LatestDeployment_Blocking(t *testing.T) {
  3582  	t.Parallel()
  3583  	s1 := TestServer(t, nil)
  3584  	defer s1.Shutdown()
  3585  	codec := rpcClient(t, s1)
  3586  	testutil.WaitForLeader(t, s1.RPC)
  3587  	state := s1.fsm.State()
  3588  	require := require.New(t)
  3589  
  3590  	// Create the register request
  3591  	j := mock.Job()
  3592  	d1 := mock.Deployment()
  3593  	d2 := mock.Deployment()
  3594  	d2.JobID = j.ID
  3595  	require.Nil(state.UpsertJob(50, j), "UpsertJob")
  3596  
  3597  	// First upsert an unrelated eval
  3598  	time.AfterFunc(100*time.Millisecond, func() {
  3599  		require.Nil(state.UpsertDeployment(100, d1), "UpsertDeployment")
  3600  	})
  3601  
  3602  	// Upsert an eval for the job we are interested in later
  3603  	time.AfterFunc(200*time.Millisecond, func() {
  3604  		require.Nil(state.UpsertDeployment(200, d2), "UpsertDeployment")
  3605  	})
  3606  
  3607  	// Lookup the jobs
  3608  	get := &structs.JobSpecificRequest{
  3609  		JobID: d2.JobID,
  3610  		QueryOptions: structs.QueryOptions{
  3611  			Region:        "global",
  3612  			Namespace:     d2.Namespace,
  3613  			MinQueryIndex: 150,
  3614  		},
  3615  	}
  3616  	var resp structs.SingleDeploymentResponse
  3617  	start := time.Now()
  3618  	require.Nil(msgpackrpc.CallWithCodec(codec, "Job.LatestDeployment", get, &resp), "RPC")
  3619  	require.EqualValues(200, resp.Index, "response index")
  3620  	require.NotNil(resp.Deployment, "deployment for job")
  3621  	require.Equal(d2.ID, resp.Deployment.ID, "returned deployment")
  3622  	if elapsed := time.Since(start); elapsed < 200*time.Millisecond {
  3623  		t.Fatalf("should block (returned in %s) %#v", elapsed, resp)
  3624  	}
  3625  }
  3626  
  3627  func TestJobEndpoint_Plan_ACL(t *testing.T) {
  3628  	t.Parallel()
  3629  	s1, root := TestACLServer(t, func(c *Config) {
  3630  		c.NumSchedulers = 0 // Prevent automatic dequeue
  3631  	})
  3632  	defer s1.Shutdown()
  3633  	codec := rpcClient(t, s1)
  3634  	testutil.WaitForLeader(t, s1.RPC)
  3635  
  3636  	// Create a plan request
  3637  	job := mock.Job()
  3638  	planReq := &structs.JobPlanRequest{
  3639  		Job:  job,
  3640  		Diff: true,
  3641  		WriteRequest: structs.WriteRequest{
  3642  			Region:    "global",
  3643  			Namespace: job.Namespace,
  3644  		},
  3645  	}
  3646  
  3647  	// Try without a token, expect failure
  3648  	var planResp structs.JobPlanResponse
  3649  	if err := msgpackrpc.CallWithCodec(codec, "Job.Plan", planReq, &planResp); err == nil {
  3650  		t.Fatalf("expected error")
  3651  	}
  3652  
  3653  	// Try with a token
  3654  	planReq.AuthToken = root.SecretID
  3655  	if err := msgpackrpc.CallWithCodec(codec, "Job.Plan", planReq, &planResp); err != nil {
  3656  		t.Fatalf("err: %v", err)
  3657  	}
  3658  }
  3659  
  3660  func TestJobEndpoint_Plan_WithDiff(t *testing.T) {
  3661  	t.Parallel()
  3662  	s1 := TestServer(t, func(c *Config) {
  3663  		c.NumSchedulers = 0 // Prevent automatic dequeue
  3664  	})
  3665  	defer s1.Shutdown()
  3666  	codec := rpcClient(t, s1)
  3667  	testutil.WaitForLeader(t, s1.RPC)
  3668  
  3669  	// Create the register request
  3670  	job := mock.Job()
  3671  	req := &structs.JobRegisterRequest{
  3672  		Job: job,
  3673  		WriteRequest: structs.WriteRequest{
  3674  			Region:    "global",
  3675  			Namespace: job.Namespace,
  3676  		},
  3677  	}
  3678  
  3679  	// Fetch the response
  3680  	var resp structs.JobRegisterResponse
  3681  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil {
  3682  		t.Fatalf("err: %v", err)
  3683  	}
  3684  	if resp.Index == 0 {
  3685  		t.Fatalf("bad index: %d", resp.Index)
  3686  	}
  3687  
  3688  	// Create a plan request
  3689  	planReq := &structs.JobPlanRequest{
  3690  		Job:  job,
  3691  		Diff: true,
  3692  		WriteRequest: structs.WriteRequest{
  3693  			Region:    "global",
  3694  			Namespace: job.Namespace,
  3695  		},
  3696  	}
  3697  
  3698  	// Fetch the response
  3699  	var planResp structs.JobPlanResponse
  3700  	if err := msgpackrpc.CallWithCodec(codec, "Job.Plan", planReq, &planResp); err != nil {
  3701  		t.Fatalf("err: %v", err)
  3702  	}
  3703  
  3704  	// Check the response
  3705  	if planResp.JobModifyIndex == 0 {
  3706  		t.Fatalf("bad cas: %d", planResp.JobModifyIndex)
  3707  	}
  3708  	if planResp.Annotations == nil {
  3709  		t.Fatalf("no annotations")
  3710  	}
  3711  	if planResp.Diff == nil {
  3712  		t.Fatalf("no diff")
  3713  	}
  3714  	if len(planResp.FailedTGAllocs) == 0 {
  3715  		t.Fatalf("no failed task group alloc metrics")
  3716  	}
  3717  }
  3718  
  3719  func TestJobEndpoint_Plan_NoDiff(t *testing.T) {
  3720  	t.Parallel()
  3721  	s1 := TestServer(t, func(c *Config) {
  3722  		c.NumSchedulers = 0 // Prevent automatic dequeue
  3723  	})
  3724  	defer s1.Shutdown()
  3725  	codec := rpcClient(t, s1)
  3726  	testutil.WaitForLeader(t, s1.RPC)
  3727  
  3728  	// Create the register request
  3729  	job := mock.Job()
  3730  	req := &structs.JobRegisterRequest{
  3731  		Job: job,
  3732  		WriteRequest: structs.WriteRequest{
  3733  			Region:    "global",
  3734  			Namespace: job.Namespace,
  3735  		},
  3736  	}
  3737  
  3738  	// Fetch the response
  3739  	var resp structs.JobRegisterResponse
  3740  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil {
  3741  		t.Fatalf("err: %v", err)
  3742  	}
  3743  	if resp.Index == 0 {
  3744  		t.Fatalf("bad index: %d", resp.Index)
  3745  	}
  3746  
  3747  	// Create a plan request
  3748  	planReq := &structs.JobPlanRequest{
  3749  		Job:  job,
  3750  		Diff: false,
  3751  		WriteRequest: structs.WriteRequest{
  3752  			Region:    "global",
  3753  			Namespace: job.Namespace,
  3754  		},
  3755  	}
  3756  
  3757  	// Fetch the response
  3758  	var planResp structs.JobPlanResponse
  3759  	if err := msgpackrpc.CallWithCodec(codec, "Job.Plan", planReq, &planResp); err != nil {
  3760  		t.Fatalf("err: %v", err)
  3761  	}
  3762  
  3763  	// Check the response
  3764  	if planResp.JobModifyIndex == 0 {
  3765  		t.Fatalf("bad cas: %d", planResp.JobModifyIndex)
  3766  	}
  3767  	if planResp.Annotations == nil {
  3768  		t.Fatalf("no annotations")
  3769  	}
  3770  	if planResp.Diff != nil {
  3771  		t.Fatalf("got diff")
  3772  	}
  3773  	if len(planResp.FailedTGAllocs) == 0 {
  3774  		t.Fatalf("no failed task group alloc metrics")
  3775  	}
  3776  }
  3777  
  3778  func TestJobEndpoint_ImplicitConstraints_Vault(t *testing.T) {
  3779  	t.Parallel()
  3780  	s1 := TestServer(t, func(c *Config) {
  3781  		c.NumSchedulers = 0 // Prevent automatic dequeue
  3782  	})
  3783  	defer s1.Shutdown()
  3784  	codec := rpcClient(t, s1)
  3785  	testutil.WaitForLeader(t, s1.RPC)
  3786  
  3787  	// Enable vault
  3788  	tr, f := true, false
  3789  	s1.config.VaultConfig.Enabled = &tr
  3790  	s1.config.VaultConfig.AllowUnauthenticated = &f
  3791  
  3792  	// Replace the Vault Client on the server
  3793  	tvc := &TestVaultClient{}
  3794  	s1.vault = tvc
  3795  
  3796  	policy := "foo"
  3797  	goodToken := uuid.Generate()
  3798  	goodPolicies := []string{"foo", "bar", "baz"}
  3799  	tvc.SetLookupTokenAllowedPolicies(goodToken, goodPolicies)
  3800  
  3801  	// Create the register request with a job asking for a vault policy
  3802  	job := mock.Job()
  3803  	job.VaultToken = goodToken
  3804  	job.TaskGroups[0].Tasks[0].Vault = &structs.Vault{
  3805  		Policies:   []string{policy},
  3806  		ChangeMode: structs.VaultChangeModeRestart,
  3807  	}
  3808  	req := &structs.JobRegisterRequest{
  3809  		Job: job,
  3810  		WriteRequest: structs.WriteRequest{
  3811  			Region:    "global",
  3812  			Namespace: job.Namespace,
  3813  		},
  3814  	}
  3815  
  3816  	// Fetch the response
  3817  	var resp structs.JobRegisterResponse
  3818  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil {
  3819  		t.Fatalf("bad: %v", err)
  3820  	}
  3821  
  3822  	// Check for the job in the FSM
  3823  	state := s1.fsm.State()
  3824  	ws := memdb.NewWatchSet()
  3825  	out, err := state.JobByID(ws, job.Namespace, job.ID)
  3826  	if err != nil {
  3827  		t.Fatalf("err: %v", err)
  3828  	}
  3829  	if out == nil {
  3830  		t.Fatalf("expected job")
  3831  	}
  3832  	if out.CreateIndex != resp.JobModifyIndex {
  3833  		t.Fatalf("index mis-match")
  3834  	}
  3835  
  3836  	// Check that there is an implicit vault constraint
  3837  	constraints := out.TaskGroups[0].Constraints
  3838  	if len(constraints) != 1 {
  3839  		t.Fatalf("Expected an implicit constraint")
  3840  	}
  3841  
  3842  	if !constraints[0].Equal(vaultConstraint) {
  3843  		t.Fatalf("Expected implicit vault constraint")
  3844  	}
  3845  }
  3846  
  3847  func TestJobEndpoint_ImplicitConstraints_Signals(t *testing.T) {
  3848  	t.Parallel()
  3849  	s1 := TestServer(t, func(c *Config) {
  3850  		c.NumSchedulers = 0 // Prevent automatic dequeue
  3851  	})
  3852  	defer s1.Shutdown()
  3853  	codec := rpcClient(t, s1)
  3854  	testutil.WaitForLeader(t, s1.RPC)
  3855  
  3856  	// Create the register request with a job asking for a template that sends a
  3857  	// signal
  3858  	job := mock.Job()
  3859  	signal1 := "SIGUSR1"
  3860  	signal2 := "SIGHUP"
  3861  	job.TaskGroups[0].Tasks[0].Templates = []*structs.Template{
  3862  		{
  3863  			SourcePath:   "foo",
  3864  			DestPath:     "bar",
  3865  			ChangeMode:   structs.TemplateChangeModeSignal,
  3866  			ChangeSignal: signal1,
  3867  		},
  3868  		{
  3869  			SourcePath:   "foo",
  3870  			DestPath:     "baz",
  3871  			ChangeMode:   structs.TemplateChangeModeSignal,
  3872  			ChangeSignal: signal2,
  3873  		},
  3874  	}
  3875  	req := &structs.JobRegisterRequest{
  3876  		Job: job,
  3877  		WriteRequest: structs.WriteRequest{
  3878  			Region:    "global",
  3879  			Namespace: job.Namespace,
  3880  		},
  3881  	}
  3882  
  3883  	// Fetch the response
  3884  	var resp structs.JobRegisterResponse
  3885  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil {
  3886  		t.Fatalf("bad: %v", err)
  3887  	}
  3888  
  3889  	// Check for the job in the FSM
  3890  	state := s1.fsm.State()
  3891  	ws := memdb.NewWatchSet()
  3892  	out, err := state.JobByID(ws, job.Namespace, job.ID)
  3893  	if err != nil {
  3894  		t.Fatalf("err: %v", err)
  3895  	}
  3896  	if out == nil {
  3897  		t.Fatalf("expected job")
  3898  	}
  3899  	if out.CreateIndex != resp.JobModifyIndex {
  3900  		t.Fatalf("index mis-match")
  3901  	}
  3902  
  3903  	// Check that there is an implicit signal constraint
  3904  	constraints := out.TaskGroups[0].Constraints
  3905  	if len(constraints) != 1 {
  3906  		t.Fatalf("Expected an implicit constraint")
  3907  	}
  3908  
  3909  	sigConstraint := getSignalConstraint([]string{signal1, signal2})
  3910  	if !strings.HasPrefix(sigConstraint.RTarget, "SIGHUP") {
  3911  		t.Fatalf("signals not sorted: %v", sigConstraint.RTarget)
  3912  	}
  3913  
  3914  	if !constraints[0].Equal(sigConstraint) {
  3915  		t.Fatalf("Expected implicit vault constraint")
  3916  	}
  3917  }
  3918  
  3919  func TestJobEndpoint_ValidateJob_InvalidDriverConf(t *testing.T) {
  3920  	t.Parallel()
  3921  	// Create a mock job with an invalid config
  3922  	job := mock.Job()
  3923  	job.TaskGroups[0].Tasks[0].Config = map[string]interface{}{
  3924  		"foo": "bar",
  3925  	}
  3926  
  3927  	err, warnings := validateJob(job)
  3928  	if err == nil || !strings.Contains(err.Error(), "-> config") {
  3929  		t.Fatalf("Expected config error; got %v", err)
  3930  	}
  3931  
  3932  	if warnings != nil {
  3933  		t.Fatalf("got unexpected warnings: %v", warnings)
  3934  	}
  3935  }
  3936  
  3937  func TestJobEndpoint_ValidateJob_InvalidSignals(t *testing.T) {
  3938  	t.Parallel()
  3939  	// Create a mock job that wants to send a signal to a driver that can't
  3940  	job := mock.Job()
  3941  	job.TaskGroups[0].Tasks[0].Driver = "qemu"
  3942  	job.TaskGroups[0].Tasks[0].Vault = &structs.Vault{
  3943  		Policies:     []string{"foo"},
  3944  		ChangeMode:   structs.VaultChangeModeSignal,
  3945  		ChangeSignal: "SIGUSR1",
  3946  	}
  3947  
  3948  	err, warnings := validateJob(job)
  3949  	if err == nil || !strings.Contains(err.Error(), "support sending signals") {
  3950  		t.Fatalf("Expected signal feasibility error; got %v", err)
  3951  	}
  3952  
  3953  	if warnings != nil {
  3954  		t.Fatalf("got unexpected warnings: %v", warnings)
  3955  	}
  3956  }
  3957  
  3958  func TestJobEndpoint_ValidateJob_KillSignal(t *testing.T) {
  3959  	require := require.New(t)
  3960  	t.Parallel()
  3961  
  3962  	// test validate fails if the driver does not support sending signals, but a
  3963  	// stop_signal has been specified
  3964  	{
  3965  		job := mock.Job()
  3966  		job.TaskGroups[0].Tasks[0].Driver = "qemu" // qemu does not support sending signals
  3967  		job.TaskGroups[0].Tasks[0].KillSignal = "SIGINT"
  3968  
  3969  		err, warnings := validateJob(job)
  3970  		require.NotNil(err)
  3971  		require.True(strings.Contains(err.Error(), "support sending signals"))
  3972  		require.Nil(warnings)
  3973  	}
  3974  
  3975  	// test validate succeeds if the driver does support sending signals, and
  3976  	// a stop_signal has been specified
  3977  	{
  3978  		job := mock.Job()
  3979  		job.TaskGroups[0].Tasks[0].KillSignal = "SIGINT"
  3980  
  3981  		err, warnings := validateJob(job)
  3982  		require.Nil(err)
  3983  		require.Nil(warnings)
  3984  	}
  3985  }
  3986  
  3987  func TestJobEndpoint_ValidateJobUpdate(t *testing.T) {
  3988  	t.Parallel()
  3989  	require := require.New(t)
  3990  	old := mock.Job()
  3991  	new := mock.Job()
  3992  
  3993  	if err := validateJobUpdate(old, new); err != nil {
  3994  		t.Errorf("expected update to be valid but got: %v", err)
  3995  	}
  3996  
  3997  	new.Type = "batch"
  3998  	if err := validateJobUpdate(old, new); err == nil {
  3999  		t.Errorf("expected err when setting new job to a different type")
  4000  	} else {
  4001  		t.Log(err)
  4002  	}
  4003  
  4004  	new = mock.Job()
  4005  	new.Periodic = &structs.PeriodicConfig{Enabled: true}
  4006  	if err := validateJobUpdate(old, new); err == nil {
  4007  		t.Errorf("expected err when setting new job to periodic")
  4008  	} else {
  4009  		t.Log(err)
  4010  	}
  4011  
  4012  	new = mock.Job()
  4013  	new.ParameterizedJob = &structs.ParameterizedJobConfig{}
  4014  	if err := validateJobUpdate(old, new); err == nil {
  4015  		t.Errorf("expected err when setting new job to parameterized")
  4016  	} else {
  4017  		t.Log(err)
  4018  	}
  4019  
  4020  	new = mock.Job()
  4021  	new.Dispatched = true
  4022  	require.Error(validateJobUpdate(old, new),
  4023  		"expected err when setting new job to dispatched")
  4024  	require.Error(validateJobUpdate(nil, new),
  4025  		"expected err when setting new job to dispatched")
  4026  	require.Error(validateJobUpdate(new, old),
  4027  		"expected err when setting dispatched to false")
  4028  	require.NoError(validateJobUpdate(nil, old))
  4029  }
  4030  
  4031  func TestJobEndpoint_ValidateJobUpdate_ACL(t *testing.T) {
  4032  	t.Parallel()
  4033  	require := require.New(t)
  4034  
  4035  	s1, root := TestACLServer(t, func(c *Config) {
  4036  		c.NumSchedulers = 0 // Prevent automatic dequeue
  4037  	})
  4038  	defer s1.Shutdown()
  4039  	codec := rpcClient(t, s1)
  4040  	testutil.WaitForLeader(t, s1.RPC)
  4041  
  4042  	job := mock.Job()
  4043  
  4044  	req := &structs.JobValidateRequest{
  4045  		Job: job,
  4046  		WriteRequest: structs.WriteRequest{
  4047  			Region:    "global",
  4048  			Namespace: job.Namespace,
  4049  		},
  4050  	}
  4051  
  4052  	// Attempt to update without providing a valid token
  4053  	var resp structs.JobValidateResponse
  4054  	err := msgpackrpc.CallWithCodec(codec, "Job.Validate", req, &resp)
  4055  	require.NotNil(err)
  4056  
  4057  	// Update with a valid token
  4058  	req.AuthToken = root.SecretID
  4059  	var validResp structs.JobValidateResponse
  4060  	err = msgpackrpc.CallWithCodec(codec, "Job.Validate", req, &validResp)
  4061  	require.Nil(err)
  4062  
  4063  	require.Equal("", validResp.Error)
  4064  	require.Equal("", validResp.Warnings)
  4065  }
  4066  
  4067  func TestJobEndpoint_Dispatch_ACL(t *testing.T) {
  4068  	t.Parallel()
  4069  	require := require.New(t)
  4070  
  4071  	s1, root := TestACLServer(t, func(c *Config) {
  4072  		c.NumSchedulers = 0 // Prevent automatic dequeue
  4073  	})
  4074  
  4075  	defer s1.Shutdown()
  4076  	codec := rpcClient(t, s1)
  4077  	testutil.WaitForLeader(t, s1.RPC)
  4078  	state := s1.fsm.State()
  4079  
  4080  	// Create a parameterized job
  4081  	job := mock.BatchJob()
  4082  	job.ParameterizedJob = &structs.ParameterizedJobConfig{}
  4083  	err := state.UpsertJob(400, job)
  4084  	require.Nil(err)
  4085  
  4086  	req := &structs.JobDispatchRequest{
  4087  		JobID: job.ID,
  4088  		WriteRequest: structs.WriteRequest{
  4089  			Region:    "global",
  4090  			Namespace: job.Namespace,
  4091  		},
  4092  	}
  4093  
  4094  	// Attempt to fetch the response without a token should fail
  4095  	var resp structs.JobDispatchResponse
  4096  	err = msgpackrpc.CallWithCodec(codec, "Job.Dispatch", req, &resp)
  4097  	require.NotNil(err)
  4098  	require.Contains(err.Error(), "Permission denied")
  4099  
  4100  	// Attempt to fetch the response with an invalid token should fail
  4101  	invalidToken := mock.CreatePolicyAndToken(t, state, 1001, "test-invalid",
  4102  		mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityListJobs}))
  4103  	req.AuthToken = invalidToken.SecretID
  4104  
  4105  	var invalidResp structs.JobDispatchResponse
  4106  	err = msgpackrpc.CallWithCodec(codec, "Job.Dispatch", req, &invalidResp)
  4107  	require.NotNil(err)
  4108  	require.Contains(err.Error(), "Permission denied")
  4109  
  4110  	// Dispatch with a valid management token should succeed
  4111  	req.AuthToken = root.SecretID
  4112  
  4113  	var validResp structs.JobDispatchResponse
  4114  	err = msgpackrpc.CallWithCodec(codec, "Job.Dispatch", req, &validResp)
  4115  	require.Nil(err)
  4116  	require.NotNil(validResp.EvalID)
  4117  	require.NotNil(validResp.DispatchedJobID)
  4118  	require.NotEqual(validResp.DispatchedJobID, "")
  4119  
  4120  	// Dispatch with a valid token should succeed
  4121  	validToken := mock.CreatePolicyAndToken(t, state, 1003, "test-valid",
  4122  		mock.NamespacePolicy(structs.DefaultNamespace, "", []string{acl.NamespaceCapabilityDispatchJob}))
  4123  	req.AuthToken = validToken.SecretID
  4124  
  4125  	var validResp2 structs.JobDispatchResponse
  4126  	err = msgpackrpc.CallWithCodec(codec, "Job.Dispatch", req, &validResp2)
  4127  	require.Nil(err)
  4128  	require.NotNil(validResp2.EvalID)
  4129  	require.NotNil(validResp2.DispatchedJobID)
  4130  	require.NotEqual(validResp2.DispatchedJobID, "")
  4131  
  4132  	ws := memdb.NewWatchSet()
  4133  	out, err := state.JobByID(ws, job.Namespace, validResp2.DispatchedJobID)
  4134  	require.Nil(err)
  4135  	require.NotNil(out)
  4136  	require.Equal(out.ParentID, job.ID)
  4137  
  4138  	// Look up the evaluation
  4139  	eval, err := state.EvalByID(ws, validResp2.EvalID)
  4140  	require.Nil(err)
  4141  	require.NotNil(eval)
  4142  	require.Equal(eval.CreateIndex, validResp2.EvalCreateIndex)
  4143  }
  4144  
  4145  func TestJobEndpoint_Dispatch(t *testing.T) {
  4146  	t.Parallel()
  4147  
  4148  	// No requirements
  4149  	d1 := mock.BatchJob()
  4150  	d1.ParameterizedJob = &structs.ParameterizedJobConfig{}
  4151  
  4152  	// Require input data
  4153  	d2 := mock.BatchJob()
  4154  	d2.ParameterizedJob = &structs.ParameterizedJobConfig{
  4155  		Payload: structs.DispatchPayloadRequired,
  4156  	}
  4157  
  4158  	// Disallow input data
  4159  	d3 := mock.BatchJob()
  4160  	d3.ParameterizedJob = &structs.ParameterizedJobConfig{
  4161  		Payload: structs.DispatchPayloadForbidden,
  4162  	}
  4163  
  4164  	// Require meta
  4165  	d4 := mock.BatchJob()
  4166  	d4.ParameterizedJob = &structs.ParameterizedJobConfig{
  4167  		MetaRequired: []string{"foo", "bar"},
  4168  	}
  4169  
  4170  	// Optional meta
  4171  	d5 := mock.BatchJob()
  4172  	d5.ParameterizedJob = &structs.ParameterizedJobConfig{
  4173  		MetaOptional: []string{"foo", "bar"},
  4174  	}
  4175  
  4176  	// Periodic dispatch job
  4177  	d6 := mock.PeriodicJob()
  4178  	d6.ParameterizedJob = &structs.ParameterizedJobConfig{}
  4179  
  4180  	d7 := mock.BatchJob()
  4181  	d7.ParameterizedJob = &structs.ParameterizedJobConfig{}
  4182  	d7.Stop = true
  4183  
  4184  	reqNoInputNoMeta := &structs.JobDispatchRequest{}
  4185  	reqInputDataNoMeta := &structs.JobDispatchRequest{
  4186  		Payload: []byte("hello world"),
  4187  	}
  4188  	reqNoInputDataMeta := &structs.JobDispatchRequest{
  4189  		Meta: map[string]string{
  4190  			"foo": "f1",
  4191  			"bar": "f2",
  4192  		},
  4193  	}
  4194  	reqInputDataMeta := &structs.JobDispatchRequest{
  4195  		Payload: []byte("hello world"),
  4196  		Meta: map[string]string{
  4197  			"foo": "f1",
  4198  			"bar": "f2",
  4199  		},
  4200  	}
  4201  	reqBadMeta := &structs.JobDispatchRequest{
  4202  		Payload: []byte("hello world"),
  4203  		Meta: map[string]string{
  4204  			"foo": "f1",
  4205  			"bar": "f2",
  4206  			"baz": "f3",
  4207  		},
  4208  	}
  4209  	reqInputDataTooLarge := &structs.JobDispatchRequest{
  4210  		Payload: make([]byte, DispatchPayloadSizeLimit+100),
  4211  	}
  4212  
  4213  	type testCase struct {
  4214  		name             string
  4215  		parameterizedJob *structs.Job
  4216  		dispatchReq      *structs.JobDispatchRequest
  4217  		noEval           bool
  4218  		err              bool
  4219  		errStr           string
  4220  	}
  4221  	cases := []testCase{
  4222  		{
  4223  			name:             "optional input data w/ data",
  4224  			parameterizedJob: d1,
  4225  			dispatchReq:      reqInputDataNoMeta,
  4226  			err:              false,
  4227  		},
  4228  		{
  4229  			name:             "optional input data w/o data",
  4230  			parameterizedJob: d1,
  4231  			dispatchReq:      reqNoInputNoMeta,
  4232  			err:              false,
  4233  		},
  4234  		{
  4235  			name:             "require input data w/ data",
  4236  			parameterizedJob: d2,
  4237  			dispatchReq:      reqInputDataNoMeta,
  4238  			err:              false,
  4239  		},
  4240  		{
  4241  			name:             "require input data w/o data",
  4242  			parameterizedJob: d2,
  4243  			dispatchReq:      reqNoInputNoMeta,
  4244  			err:              true,
  4245  			errStr:           "not provided but required",
  4246  		},
  4247  		{
  4248  			name:             "disallow input data w/o data",
  4249  			parameterizedJob: d3,
  4250  			dispatchReq:      reqNoInputNoMeta,
  4251  			err:              false,
  4252  		},
  4253  		{
  4254  			name:             "disallow input data w/ data",
  4255  			parameterizedJob: d3,
  4256  			dispatchReq:      reqInputDataNoMeta,
  4257  			err:              true,
  4258  			errStr:           "provided but forbidden",
  4259  		},
  4260  		{
  4261  			name:             "require meta w/ meta",
  4262  			parameterizedJob: d4,
  4263  			dispatchReq:      reqInputDataMeta,
  4264  			err:              false,
  4265  		},
  4266  		{
  4267  			name:             "require meta w/o meta",
  4268  			parameterizedJob: d4,
  4269  			dispatchReq:      reqNoInputNoMeta,
  4270  			err:              true,
  4271  			errStr:           "did not provide required meta keys",
  4272  		},
  4273  		{
  4274  			name:             "optional meta w/ meta",
  4275  			parameterizedJob: d5,
  4276  			dispatchReq:      reqNoInputDataMeta,
  4277  			err:              false,
  4278  		},
  4279  		{
  4280  			name:             "optional meta w/o meta",
  4281  			parameterizedJob: d5,
  4282  			dispatchReq:      reqNoInputNoMeta,
  4283  			err:              false,
  4284  		},
  4285  		{
  4286  			name:             "optional meta w/ bad meta",
  4287  			parameterizedJob: d5,
  4288  			dispatchReq:      reqBadMeta,
  4289  			err:              true,
  4290  			errStr:           "unpermitted metadata keys",
  4291  		},
  4292  		{
  4293  			name:             "optional input w/ too big of input",
  4294  			parameterizedJob: d1,
  4295  			dispatchReq:      reqInputDataTooLarge,
  4296  			err:              true,
  4297  			errStr:           "Payload exceeds maximum size",
  4298  		},
  4299  		{
  4300  			name:             "periodic job dispatched, ensure no eval",
  4301  			parameterizedJob: d6,
  4302  			dispatchReq:      reqNoInputNoMeta,
  4303  			noEval:           true,
  4304  		},
  4305  		{
  4306  			name:             "periodic job stopped, ensure error",
  4307  			parameterizedJob: d7,
  4308  			dispatchReq:      reqNoInputNoMeta,
  4309  			err:              true,
  4310  			errStr:           "stopped",
  4311  		},
  4312  	}
  4313  
  4314  	for _, tc := range cases {
  4315  		t.Run(tc.name, func(t *testing.T) {
  4316  			s1 := TestServer(t, func(c *Config) {
  4317  				c.NumSchedulers = 0 // Prevent automatic dequeue
  4318  			})
  4319  			defer s1.Shutdown()
  4320  			codec := rpcClient(t, s1)
  4321  			testutil.WaitForLeader(t, s1.RPC)
  4322  
  4323  			// Create the register request
  4324  			regReq := &structs.JobRegisterRequest{
  4325  				Job: tc.parameterizedJob,
  4326  				WriteRequest: structs.WriteRequest{
  4327  					Region:    "global",
  4328  					Namespace: tc.parameterizedJob.Namespace,
  4329  				},
  4330  			}
  4331  
  4332  			// Fetch the response
  4333  			var regResp structs.JobRegisterResponse
  4334  			if err := msgpackrpc.CallWithCodec(codec, "Job.Register", regReq, &regResp); err != nil {
  4335  				t.Fatalf("err: %v", err)
  4336  			}
  4337  
  4338  			// Now try to dispatch
  4339  			tc.dispatchReq.JobID = tc.parameterizedJob.ID
  4340  			tc.dispatchReq.WriteRequest = structs.WriteRequest{
  4341  				Region:    "global",
  4342  				Namespace: tc.parameterizedJob.Namespace,
  4343  			}
  4344  
  4345  			var dispatchResp structs.JobDispatchResponse
  4346  			dispatchErr := msgpackrpc.CallWithCodec(codec, "Job.Dispatch", tc.dispatchReq, &dispatchResp)
  4347  
  4348  			if dispatchErr == nil {
  4349  				if tc.err {
  4350  					t.Fatalf("Expected error: %v", dispatchErr)
  4351  				}
  4352  
  4353  				// Check that we got an eval and job id back
  4354  				switch dispatchResp.EvalID {
  4355  				case "":
  4356  					if !tc.noEval {
  4357  						t.Fatalf("Bad response")
  4358  					}
  4359  				default:
  4360  					if tc.noEval {
  4361  						t.Fatalf("Got eval %q", dispatchResp.EvalID)
  4362  					}
  4363  				}
  4364  
  4365  				if dispatchResp.DispatchedJobID == "" {
  4366  					t.Fatalf("Bad response")
  4367  				}
  4368  
  4369  				state := s1.fsm.State()
  4370  				ws := memdb.NewWatchSet()
  4371  				out, err := state.JobByID(ws, tc.parameterizedJob.Namespace, dispatchResp.DispatchedJobID)
  4372  				if err != nil {
  4373  					t.Fatalf("err: %v", err)
  4374  				}
  4375  				if out == nil {
  4376  					t.Fatalf("expected job")
  4377  				}
  4378  				if out.CreateIndex != dispatchResp.JobCreateIndex {
  4379  					t.Fatalf("index mis-match")
  4380  				}
  4381  				if out.ParentID != tc.parameterizedJob.ID {
  4382  					t.Fatalf("bad parent ID")
  4383  				}
  4384  				if !out.Dispatched {
  4385  					t.Fatal("expected dispatched job")
  4386  				}
  4387  				if out.IsParameterized() {
  4388  					t.Fatal("dispatched job should not be parameterized")
  4389  				}
  4390  				if out.ParameterizedJob == nil {
  4391  					t.Fatal("parameter job config should exist")
  4392  				}
  4393  
  4394  				if tc.noEval {
  4395  					return
  4396  				}
  4397  
  4398  				// Lookup the evaluation
  4399  				eval, err := state.EvalByID(ws, dispatchResp.EvalID)
  4400  				if err != nil {
  4401  					t.Fatalf("err: %v", err)
  4402  				}
  4403  
  4404  				if eval == nil {
  4405  					t.Fatalf("expected eval")
  4406  				}
  4407  				if eval.CreateIndex != dispatchResp.EvalCreateIndex {
  4408  					t.Fatalf("index mis-match")
  4409  				}
  4410  			} else {
  4411  				if !tc.err {
  4412  					t.Fatalf("Got unexpected error: %v", dispatchErr)
  4413  				} else if !strings.Contains(dispatchErr.Error(), tc.errStr) {
  4414  					t.Fatalf("Expected err to include %q; got %v", tc.errStr, dispatchErr)
  4415  				}
  4416  			}
  4417  		})
  4418  	}
  4419  }