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