github.com/jrxfive/nomad@v0.6.1-0.20170802162750-1fef470e89bf/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  	"github.com/hashicorp/net-rpc-msgpackrpc"
    12  	"github.com/hashicorp/nomad/helper"
    13  	"github.com/hashicorp/nomad/nomad/mock"
    14  	"github.com/hashicorp/nomad/nomad/structs"
    15  	"github.com/hashicorp/nomad/testutil"
    16  	"github.com/kr/pretty"
    17  	"github.com/stretchr/testify/assert"
    18  )
    19  
    20  func TestJobEndpoint_Register(t *testing.T) {
    21  	t.Parallel()
    22  	s1 := testServer(t, func(c *Config) {
    23  		c.NumSchedulers = 0 // Prevent automatic dequeue
    24  	})
    25  	defer s1.Shutdown()
    26  	codec := rpcClient(t, s1)
    27  	testutil.WaitForLeader(t, s1.RPC)
    28  
    29  	// Create the register request
    30  	job := mock.Job()
    31  	req := &structs.JobRegisterRequest{
    32  		Job:          job,
    33  		WriteRequest: structs.WriteRequest{Region: "global"},
    34  	}
    35  
    36  	// Fetch the response
    37  	var resp structs.JobRegisterResponse
    38  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil {
    39  		t.Fatalf("err: %v", err)
    40  	}
    41  	if resp.Index == 0 {
    42  		t.Fatalf("bad index: %d", resp.Index)
    43  	}
    44  
    45  	// Check for the node in the FSM
    46  	state := s1.fsm.State()
    47  	ws := memdb.NewWatchSet()
    48  	out, err := state.JobByID(ws, job.ID)
    49  	if err != nil {
    50  		t.Fatalf("err: %v", err)
    51  	}
    52  	if out == nil {
    53  		t.Fatalf("expected job")
    54  	}
    55  	if out.CreateIndex != resp.JobModifyIndex {
    56  		t.Fatalf("index mis-match")
    57  	}
    58  	serviceName := out.TaskGroups[0].Tasks[0].Services[0].Name
    59  	expectedServiceName := "web-frontend"
    60  	if serviceName != expectedServiceName {
    61  		t.Fatalf("Expected Service Name: %s, Actual: %s", expectedServiceName, serviceName)
    62  	}
    63  
    64  	// Lookup the evaluation
    65  	eval, err := state.EvalByID(ws, resp.EvalID)
    66  	if err != nil {
    67  		t.Fatalf("err: %v", err)
    68  	}
    69  	if eval == nil {
    70  		t.Fatalf("expected eval")
    71  	}
    72  	if eval.CreateIndex != resp.EvalCreateIndex {
    73  		t.Fatalf("index mis-match")
    74  	}
    75  
    76  	if eval.Priority != job.Priority {
    77  		t.Fatalf("bad: %#v", eval)
    78  	}
    79  	if eval.Type != job.Type {
    80  		t.Fatalf("bad: %#v", eval)
    81  	}
    82  	if eval.TriggeredBy != structs.EvalTriggerJobRegister {
    83  		t.Fatalf("bad: %#v", eval)
    84  	}
    85  	if eval.JobID != job.ID {
    86  		t.Fatalf("bad: %#v", eval)
    87  	}
    88  	if eval.JobModifyIndex != resp.JobModifyIndex {
    89  		t.Fatalf("bad: %#v", eval)
    90  	}
    91  	if eval.Status != structs.EvalStatusPending {
    92  		t.Fatalf("bad: %#v", eval)
    93  	}
    94  }
    95  
    96  func TestJobEndpoint_Register_InvalidDriverConfig(t *testing.T) {
    97  	t.Parallel()
    98  	s1 := testServer(t, func(c *Config) {
    99  		c.NumSchedulers = 0 // Prevent automatic dequeue
   100  	})
   101  	defer s1.Shutdown()
   102  	codec := rpcClient(t, s1)
   103  	testutil.WaitForLeader(t, s1.RPC)
   104  
   105  	// Create the register request with a job containing an invalid driver
   106  	// config
   107  	job := mock.Job()
   108  	job.TaskGroups[0].Tasks[0].Config["foo"] = 1
   109  	req := &structs.JobRegisterRequest{
   110  		Job:          job,
   111  		WriteRequest: structs.WriteRequest{Region: "global"},
   112  	}
   113  
   114  	// Fetch the response
   115  	var resp structs.JobRegisterResponse
   116  	err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp)
   117  	if err == nil {
   118  		t.Fatalf("expected a validation error")
   119  	}
   120  
   121  	if !strings.Contains(err.Error(), "-> config:") {
   122  		t.Fatalf("expected a driver config validation error but got: %v", err)
   123  	}
   124  }
   125  
   126  func TestJobEndpoint_Register_Payload(t *testing.T) {
   127  	t.Parallel()
   128  	s1 := testServer(t, func(c *Config) {
   129  		c.NumSchedulers = 0 // Prevent automatic dequeue
   130  	})
   131  	defer s1.Shutdown()
   132  	codec := rpcClient(t, s1)
   133  	testutil.WaitForLeader(t, s1.RPC)
   134  
   135  	// Create the register request with a job containing an invalid driver
   136  	// config
   137  	job := mock.Job()
   138  	job.Payload = []byte{0x1}
   139  	req := &structs.JobRegisterRequest{
   140  		Job:          job,
   141  		WriteRequest: structs.WriteRequest{Region: "global"},
   142  	}
   143  
   144  	// Fetch the response
   145  	var resp structs.JobRegisterResponse
   146  	err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp)
   147  	if err == nil {
   148  		t.Fatalf("expected a validation error")
   149  	}
   150  
   151  	if !strings.Contains(err.Error(), "payload") {
   152  		t.Fatalf("expected a payload error but got: %v", err)
   153  	}
   154  }
   155  
   156  func TestJobEndpoint_Register_Existing(t *testing.T) {
   157  	t.Parallel()
   158  	s1 := testServer(t, func(c *Config) {
   159  		c.NumSchedulers = 0 // Prevent automatic dequeue
   160  	})
   161  	defer s1.Shutdown()
   162  	codec := rpcClient(t, s1)
   163  	testutil.WaitForLeader(t, s1.RPC)
   164  
   165  	// Create the register request
   166  	job := mock.Job()
   167  	req := &structs.JobRegisterRequest{
   168  		Job:          job,
   169  		WriteRequest: structs.WriteRequest{Region: "global"},
   170  	}
   171  
   172  	// Fetch the response
   173  	var resp structs.JobRegisterResponse
   174  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil {
   175  		t.Fatalf("err: %v", err)
   176  	}
   177  	if resp.Index == 0 {
   178  		t.Fatalf("bad index: %d", resp.Index)
   179  	}
   180  
   181  	// Update the job definition
   182  	job2 := mock.Job()
   183  	job2.Priority = 100
   184  	job2.ID = job.ID
   185  	req.Job = job2
   186  
   187  	// Attempt update
   188  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil {
   189  		t.Fatalf("err: %v", err)
   190  	}
   191  	if resp.Index == 0 {
   192  		t.Fatalf("bad index: %d", resp.Index)
   193  	}
   194  
   195  	// Check for the node in the FSM
   196  	state := s1.fsm.State()
   197  	ws := memdb.NewWatchSet()
   198  	out, err := state.JobByID(ws, job.ID)
   199  	if err != nil {
   200  		t.Fatalf("err: %v", err)
   201  	}
   202  	if out == nil {
   203  		t.Fatalf("expected job")
   204  	}
   205  	if out.ModifyIndex != resp.JobModifyIndex {
   206  		t.Fatalf("index mis-match")
   207  	}
   208  	if out.Priority != 100 {
   209  		t.Fatalf("expected update")
   210  	}
   211  	if out.Version != 1 {
   212  		t.Fatalf("expected update")
   213  	}
   214  
   215  	// Lookup the evaluation
   216  	eval, err := state.EvalByID(ws, resp.EvalID)
   217  	if err != nil {
   218  		t.Fatalf("err: %v", err)
   219  	}
   220  	if eval == nil {
   221  		t.Fatalf("expected eval")
   222  	}
   223  	if eval.CreateIndex != resp.EvalCreateIndex {
   224  		t.Fatalf("index mis-match")
   225  	}
   226  
   227  	if eval.Priority != job2.Priority {
   228  		t.Fatalf("bad: %#v", eval)
   229  	}
   230  	if eval.Type != job2.Type {
   231  		t.Fatalf("bad: %#v", eval)
   232  	}
   233  	if eval.TriggeredBy != structs.EvalTriggerJobRegister {
   234  		t.Fatalf("bad: %#v", eval)
   235  	}
   236  	if eval.JobID != job2.ID {
   237  		t.Fatalf("bad: %#v", eval)
   238  	}
   239  	if eval.JobModifyIndex != resp.JobModifyIndex {
   240  		t.Fatalf("bad: %#v", eval)
   241  	}
   242  	if eval.Status != structs.EvalStatusPending {
   243  		t.Fatalf("bad: %#v", eval)
   244  	}
   245  
   246  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil {
   247  		t.Fatalf("err: %v", err)
   248  	}
   249  	if resp.Index == 0 {
   250  		t.Fatalf("bad index: %d", resp.Index)
   251  	}
   252  
   253  	// Check to ensure the job version didn't get bumped becasue we submitted
   254  	// the same job
   255  	state = s1.fsm.State()
   256  	ws = memdb.NewWatchSet()
   257  	out, err = state.JobByID(ws, job.ID)
   258  	if err != nil {
   259  		t.Fatalf("err: %v", err)
   260  	}
   261  	if out == nil {
   262  		t.Fatalf("expected job")
   263  	}
   264  	if out.Version != 1 {
   265  		t.Fatalf("expected no update; got %v; diff %v", out.Version, pretty.Diff(job2, out))
   266  	}
   267  }
   268  
   269  func TestJobEndpoint_Register_Periodic(t *testing.T) {
   270  	t.Parallel()
   271  	s1 := testServer(t, func(c *Config) {
   272  		c.NumSchedulers = 0 // Prevent automatic dequeue
   273  	})
   274  	defer s1.Shutdown()
   275  	codec := rpcClient(t, s1)
   276  	testutil.WaitForLeader(t, s1.RPC)
   277  
   278  	// Create the register request for a periodic job.
   279  	job := mock.PeriodicJob()
   280  	req := &structs.JobRegisterRequest{
   281  		Job:          job,
   282  		WriteRequest: structs.WriteRequest{Region: "global"},
   283  	}
   284  
   285  	// Fetch the response
   286  	var resp structs.JobRegisterResponse
   287  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil {
   288  		t.Fatalf("err: %v", err)
   289  	}
   290  	if resp.JobModifyIndex == 0 {
   291  		t.Fatalf("bad index: %d", resp.Index)
   292  	}
   293  
   294  	// Check for the node in the FSM
   295  	state := s1.fsm.State()
   296  	ws := memdb.NewWatchSet()
   297  	out, err := state.JobByID(ws, job.ID)
   298  	if err != nil {
   299  		t.Fatalf("err: %v", err)
   300  	}
   301  	if out == nil {
   302  		t.Fatalf("expected job")
   303  	}
   304  	if out.CreateIndex != resp.JobModifyIndex {
   305  		t.Fatalf("index mis-match")
   306  	}
   307  	serviceName := out.TaskGroups[0].Tasks[0].Services[0].Name
   308  	expectedServiceName := "web-frontend"
   309  	if serviceName != expectedServiceName {
   310  		t.Fatalf("Expected Service Name: %s, Actual: %s", expectedServiceName, serviceName)
   311  	}
   312  
   313  	if resp.EvalID != "" {
   314  		t.Fatalf("Register created an eval for a periodic job")
   315  	}
   316  }
   317  
   318  func TestJobEndpoint_Register_ParameterizedJob(t *testing.T) {
   319  	t.Parallel()
   320  	s1 := testServer(t, func(c *Config) {
   321  		c.NumSchedulers = 0 // Prevent automatic dequeue
   322  	})
   323  	defer s1.Shutdown()
   324  	codec := rpcClient(t, s1)
   325  	testutil.WaitForLeader(t, s1.RPC)
   326  
   327  	// Create the register request for a parameterized job.
   328  	job := mock.Job()
   329  	job.Type = structs.JobTypeBatch
   330  	job.ParameterizedJob = &structs.ParameterizedJobConfig{}
   331  	req := &structs.JobRegisterRequest{
   332  		Job:          job,
   333  		WriteRequest: structs.WriteRequest{Region: "global"},
   334  	}
   335  
   336  	// Fetch the response
   337  	var resp structs.JobRegisterResponse
   338  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil {
   339  		t.Fatalf("err: %v", err)
   340  	}
   341  	if resp.JobModifyIndex == 0 {
   342  		t.Fatalf("bad index: %d", resp.Index)
   343  	}
   344  
   345  	// Check for the job in the FSM
   346  	state := s1.fsm.State()
   347  	ws := memdb.NewWatchSet()
   348  	out, err := state.JobByID(ws, job.ID)
   349  	if err != nil {
   350  		t.Fatalf("err: %v", err)
   351  	}
   352  	if out == nil {
   353  		t.Fatalf("expected job")
   354  	}
   355  	if out.CreateIndex != resp.JobModifyIndex {
   356  		t.Fatalf("index mis-match")
   357  	}
   358  	if resp.EvalID != "" {
   359  		t.Fatalf("Register created an eval for a parameterized job")
   360  	}
   361  }
   362  
   363  func TestJobEndpoint_Register_EnforceIndex(t *testing.T) {
   364  	t.Parallel()
   365  	s1 := testServer(t, func(c *Config) {
   366  		c.NumSchedulers = 0 // Prevent automatic dequeue
   367  	})
   368  	defer s1.Shutdown()
   369  	codec := rpcClient(t, s1)
   370  	testutil.WaitForLeader(t, s1.RPC)
   371  
   372  	// Create the register request and enforcing an incorrect index
   373  	job := mock.Job()
   374  	req := &structs.JobRegisterRequest{
   375  		Job:            job,
   376  		EnforceIndex:   true,
   377  		JobModifyIndex: 100, // Not registered yet so not possible
   378  		WriteRequest:   structs.WriteRequest{Region: "global"},
   379  	}
   380  
   381  	// Fetch the response
   382  	var resp structs.JobRegisterResponse
   383  	err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp)
   384  	if err == nil || !strings.Contains(err.Error(), RegisterEnforceIndexErrPrefix) {
   385  		t.Fatalf("expected enforcement error")
   386  	}
   387  
   388  	// Create the register request and enforcing it is new
   389  	req = &structs.JobRegisterRequest{
   390  		Job:            job,
   391  		EnforceIndex:   true,
   392  		JobModifyIndex: 0,
   393  		WriteRequest:   structs.WriteRequest{Region: "global"},
   394  	}
   395  
   396  	// Fetch the response
   397  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil {
   398  		t.Fatalf("err: %v", err)
   399  	}
   400  	if resp.Index == 0 {
   401  		t.Fatalf("bad index: %d", resp.Index)
   402  	}
   403  
   404  	curIndex := resp.JobModifyIndex
   405  
   406  	// Check for the node in the FSM
   407  	state := s1.fsm.State()
   408  	ws := memdb.NewWatchSet()
   409  	out, err := state.JobByID(ws, job.ID)
   410  	if err != nil {
   411  		t.Fatalf("err: %v", err)
   412  	}
   413  	if out == nil {
   414  		t.Fatalf("expected job")
   415  	}
   416  	if out.CreateIndex != resp.JobModifyIndex {
   417  		t.Fatalf("index mis-match")
   418  	}
   419  
   420  	// Reregister request and enforcing it be a new job
   421  	req = &structs.JobRegisterRequest{
   422  		Job:            job,
   423  		EnforceIndex:   true,
   424  		JobModifyIndex: 0,
   425  		WriteRequest:   structs.WriteRequest{Region: "global"},
   426  	}
   427  
   428  	// Fetch the response
   429  	err = msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp)
   430  	if err == nil || !strings.Contains(err.Error(), RegisterEnforceIndexErrPrefix) {
   431  		t.Fatalf("expected enforcement error")
   432  	}
   433  
   434  	// Reregister request and enforcing it be at an incorrect index
   435  	req = &structs.JobRegisterRequest{
   436  		Job:            job,
   437  		EnforceIndex:   true,
   438  		JobModifyIndex: curIndex - 1,
   439  		WriteRequest:   structs.WriteRequest{Region: "global"},
   440  	}
   441  
   442  	// Fetch the response
   443  	err = msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp)
   444  	if err == nil || !strings.Contains(err.Error(), RegisterEnforceIndexErrPrefix) {
   445  		t.Fatalf("expected enforcement error")
   446  	}
   447  
   448  	// Reregister request and enforcing it be at the correct index
   449  	job.Priority = job.Priority + 1
   450  	req = &structs.JobRegisterRequest{
   451  		Job:            job,
   452  		EnforceIndex:   true,
   453  		JobModifyIndex: curIndex,
   454  		WriteRequest:   structs.WriteRequest{Region: "global"},
   455  	}
   456  
   457  	// Fetch the response
   458  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil {
   459  		t.Fatalf("err: %v", err)
   460  	}
   461  	if resp.Index == 0 {
   462  		t.Fatalf("bad index: %d", resp.Index)
   463  	}
   464  
   465  	out, err = state.JobByID(ws, job.ID)
   466  	if err != nil {
   467  		t.Fatalf("err: %v", err)
   468  	}
   469  	if out == nil {
   470  		t.Fatalf("expected job")
   471  	}
   472  	if out.Priority != job.Priority {
   473  		t.Fatalf("priority mis-match")
   474  	}
   475  }
   476  
   477  func TestJobEndpoint_Register_Vault_Disabled(t *testing.T) {
   478  	t.Parallel()
   479  	s1 := testServer(t, func(c *Config) {
   480  		c.NumSchedulers = 0 // Prevent automatic dequeue
   481  		f := false
   482  		c.VaultConfig.Enabled = &f
   483  	})
   484  	defer s1.Shutdown()
   485  	codec := rpcClient(t, s1)
   486  	testutil.WaitForLeader(t, s1.RPC)
   487  
   488  	// Create the register request with a job asking for a vault policy
   489  	job := mock.Job()
   490  	job.TaskGroups[0].Tasks[0].Vault = &structs.Vault{
   491  		Policies:   []string{"foo"},
   492  		ChangeMode: structs.VaultChangeModeRestart,
   493  	}
   494  	req := &structs.JobRegisterRequest{
   495  		Job:          job,
   496  		WriteRequest: structs.WriteRequest{Region: "global"},
   497  	}
   498  
   499  	// Fetch the response
   500  	var resp structs.JobRegisterResponse
   501  	err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp)
   502  	if err == nil || !strings.Contains(err.Error(), "Vault not enabled") {
   503  		t.Fatalf("expected Vault not enabled error: %v", err)
   504  	}
   505  }
   506  
   507  func TestJobEndpoint_Register_Vault_AllowUnauthenticated(t *testing.T) {
   508  	t.Parallel()
   509  	s1 := testServer(t, func(c *Config) {
   510  		c.NumSchedulers = 0 // Prevent automatic dequeue
   511  	})
   512  	defer s1.Shutdown()
   513  	codec := rpcClient(t, s1)
   514  	testutil.WaitForLeader(t, s1.RPC)
   515  
   516  	// Enable vault and allow authenticated
   517  	tr := true
   518  	s1.config.VaultConfig.Enabled = &tr
   519  	s1.config.VaultConfig.AllowUnauthenticated = &tr
   520  
   521  	// Replace the Vault Client on the server
   522  	s1.vault = &TestVaultClient{}
   523  
   524  	// Create the register request with a job asking for a vault policy
   525  	job := mock.Job()
   526  	job.TaskGroups[0].Tasks[0].Vault = &structs.Vault{
   527  		Policies:   []string{"foo"},
   528  		ChangeMode: structs.VaultChangeModeRestart,
   529  	}
   530  	req := &structs.JobRegisterRequest{
   531  		Job:          job,
   532  		WriteRequest: structs.WriteRequest{Region: "global"},
   533  	}
   534  
   535  	// Fetch the response
   536  	var resp structs.JobRegisterResponse
   537  	err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp)
   538  	if err != nil {
   539  		t.Fatalf("bad: %v", err)
   540  	}
   541  
   542  	// Check for the job in the FSM
   543  	state := s1.fsm.State()
   544  	ws := memdb.NewWatchSet()
   545  	out, err := state.JobByID(ws, job.ID)
   546  	if err != nil {
   547  		t.Fatalf("err: %v", err)
   548  	}
   549  	if out == nil {
   550  		t.Fatalf("expected job")
   551  	}
   552  	if out.CreateIndex != resp.JobModifyIndex {
   553  		t.Fatalf("index mis-match")
   554  	}
   555  }
   556  
   557  func TestJobEndpoint_Register_Vault_NoToken(t *testing.T) {
   558  	t.Parallel()
   559  	s1 := testServer(t, func(c *Config) {
   560  		c.NumSchedulers = 0 // Prevent automatic dequeue
   561  	})
   562  	defer s1.Shutdown()
   563  	codec := rpcClient(t, s1)
   564  	testutil.WaitForLeader(t, s1.RPC)
   565  
   566  	// Enable vault
   567  	tr, f := true, false
   568  	s1.config.VaultConfig.Enabled = &tr
   569  	s1.config.VaultConfig.AllowUnauthenticated = &f
   570  
   571  	// Replace the Vault Client on the server
   572  	s1.vault = &TestVaultClient{}
   573  
   574  	// Create the register request with a job asking for a vault policy but
   575  	// don't send a Vault token
   576  	job := mock.Job()
   577  	job.TaskGroups[0].Tasks[0].Vault = &structs.Vault{
   578  		Policies:   []string{"foo"},
   579  		ChangeMode: structs.VaultChangeModeRestart,
   580  	}
   581  	req := &structs.JobRegisterRequest{
   582  		Job:          job,
   583  		WriteRequest: structs.WriteRequest{Region: "global"},
   584  	}
   585  
   586  	// Fetch the response
   587  	var resp structs.JobRegisterResponse
   588  	err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp)
   589  	if err == nil || !strings.Contains(err.Error(), "missing Vault Token") {
   590  		t.Fatalf("expected Vault not enabled error: %v", err)
   591  	}
   592  }
   593  
   594  func TestJobEndpoint_Register_Vault_Policies(t *testing.T) {
   595  	t.Parallel()
   596  	s1 := testServer(t, func(c *Config) {
   597  		c.NumSchedulers = 0 // Prevent automatic dequeue
   598  	})
   599  	defer s1.Shutdown()
   600  	codec := rpcClient(t, s1)
   601  	testutil.WaitForLeader(t, s1.RPC)
   602  
   603  	// Enable vault
   604  	tr, f := true, false
   605  	s1.config.VaultConfig.Enabled = &tr
   606  	s1.config.VaultConfig.AllowUnauthenticated = &f
   607  
   608  	// Replace the Vault Client on the server
   609  	tvc := &TestVaultClient{}
   610  	s1.vault = tvc
   611  
   612  	// Add three tokens: one that allows the requesting policy, one that does
   613  	// not and one that returns an error
   614  	policy := "foo"
   615  
   616  	badToken := structs.GenerateUUID()
   617  	badPolicies := []string{"a", "b", "c"}
   618  	tvc.SetLookupTokenAllowedPolicies(badToken, badPolicies)
   619  
   620  	goodToken := structs.GenerateUUID()
   621  	goodPolicies := []string{"foo", "bar", "baz"}
   622  	tvc.SetLookupTokenAllowedPolicies(goodToken, goodPolicies)
   623  
   624  	rootToken := structs.GenerateUUID()
   625  	rootPolicies := []string{"root"}
   626  	tvc.SetLookupTokenAllowedPolicies(rootToken, rootPolicies)
   627  
   628  	errToken := structs.GenerateUUID()
   629  	expectedErr := fmt.Errorf("return errors from vault")
   630  	tvc.SetLookupTokenError(errToken, expectedErr)
   631  
   632  	// Create the register request with a job asking for a vault policy but
   633  	// send the bad Vault token
   634  	job := mock.Job()
   635  	job.VaultToken = badToken
   636  	job.TaskGroups[0].Tasks[0].Vault = &structs.Vault{
   637  		Policies:   []string{policy},
   638  		ChangeMode: structs.VaultChangeModeRestart,
   639  	}
   640  	req := &structs.JobRegisterRequest{
   641  		Job:          job,
   642  		WriteRequest: structs.WriteRequest{Region: "global"},
   643  	}
   644  
   645  	// Fetch the response
   646  	var resp structs.JobRegisterResponse
   647  	err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp)
   648  	if err == nil || !strings.Contains(err.Error(),
   649  		"doesn't allow access to the following policies: "+policy) {
   650  		t.Fatalf("expected permission denied error: %v", err)
   651  	}
   652  
   653  	// Use the err token
   654  	job.VaultToken = errToken
   655  	err = msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp)
   656  	if err == nil || !strings.Contains(err.Error(), expectedErr.Error()) {
   657  		t.Fatalf("expected permission denied error: %v", err)
   658  	}
   659  
   660  	// Use the good token
   661  	job.VaultToken = goodToken
   662  
   663  	// Fetch the response
   664  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil {
   665  		t.Fatalf("bad: %v", err)
   666  	}
   667  
   668  	// Check for the job in the FSM
   669  	state := s1.fsm.State()
   670  	ws := memdb.NewWatchSet()
   671  	out, err := state.JobByID(ws, job.ID)
   672  	if err != nil {
   673  		t.Fatalf("err: %v", err)
   674  	}
   675  	if out == nil {
   676  		t.Fatalf("expected job")
   677  	}
   678  	if out.CreateIndex != resp.JobModifyIndex {
   679  		t.Fatalf("index mis-match")
   680  	}
   681  	if out.VaultToken != "" {
   682  		t.Fatalf("vault token not cleared")
   683  	}
   684  
   685  	// Check that an implicit constraint was created
   686  	constraints := out.TaskGroups[0].Constraints
   687  	if l := len(constraints); l != 1 {
   688  		t.Fatalf("Unexpected number of tests: %v", l)
   689  	}
   690  
   691  	if !constraints[0].Equal(vaultConstraint) {
   692  		t.Fatalf("bad constraint; got %#v; want %#v", constraints[0], vaultConstraint)
   693  	}
   694  
   695  	// Create the register request with another job asking for a vault policy but
   696  	// send the root Vault token
   697  	job2 := mock.Job()
   698  	job2.VaultToken = rootToken
   699  	job.TaskGroups[0].Tasks[0].Vault = &structs.Vault{
   700  		Policies:   []string{policy},
   701  		ChangeMode: structs.VaultChangeModeRestart,
   702  	}
   703  	req = &structs.JobRegisterRequest{
   704  		Job:          job2,
   705  		WriteRequest: structs.WriteRequest{Region: "global"},
   706  	}
   707  
   708  	// Fetch the response
   709  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil {
   710  		t.Fatalf("bad: %v", err)
   711  	}
   712  
   713  	// Check for the job in the FSM
   714  	out, err = state.JobByID(ws, job2.ID)
   715  	if err != nil {
   716  		t.Fatalf("err: %v", err)
   717  	}
   718  	if out == nil {
   719  		t.Fatalf("expected job")
   720  	}
   721  	if out.CreateIndex != resp.JobModifyIndex {
   722  		t.Fatalf("index mis-match")
   723  	}
   724  	if out.VaultToken != "" {
   725  		t.Fatalf("vault token not cleared")
   726  	}
   727  }
   728  
   729  func TestJobEndpoint_Revert(t *testing.T) {
   730  	t.Parallel()
   731  	s1 := testServer(t, func(c *Config) {
   732  		c.NumSchedulers = 0 // Prevent automatic dequeue
   733  	})
   734  	defer s1.Shutdown()
   735  	codec := rpcClient(t, s1)
   736  	testutil.WaitForLeader(t, s1.RPC)
   737  
   738  	// Create the initial register request
   739  	job := mock.Job()
   740  	job.Priority = 100
   741  	req := &structs.JobRegisterRequest{
   742  		Job:          job,
   743  		WriteRequest: structs.WriteRequest{Region: "global"},
   744  	}
   745  
   746  	// Fetch the response
   747  	var resp structs.JobRegisterResponse
   748  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil {
   749  		t.Fatalf("err: %v", err)
   750  	}
   751  	if resp.Index == 0 {
   752  		t.Fatalf("bad index: %d", resp.Index)
   753  	}
   754  
   755  	// Reregister again to get another version
   756  	job2 := job.Copy()
   757  	job2.Priority = 1
   758  	req = &structs.JobRegisterRequest{
   759  		Job:          job2,
   760  		WriteRequest: structs.WriteRequest{Region: "global"},
   761  	}
   762  
   763  	// Fetch the response
   764  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil {
   765  		t.Fatalf("err: %v", err)
   766  	}
   767  	if resp.Index == 0 {
   768  		t.Fatalf("bad index: %d", resp.Index)
   769  	}
   770  
   771  	// Create revert request and enforcing it be at an incorrect version
   772  	revertReq := &structs.JobRevertRequest{
   773  		JobID:               job.ID,
   774  		JobVersion:          0,
   775  		EnforcePriorVersion: helper.Uint64ToPtr(10),
   776  		WriteRequest:        structs.WriteRequest{Region: "global"},
   777  	}
   778  
   779  	// Fetch the response
   780  	err := msgpackrpc.CallWithCodec(codec, "Job.Revert", revertReq, &resp)
   781  	if err == nil || !strings.Contains(err.Error(), "enforcing version 10") {
   782  		t.Fatalf("expected enforcement error")
   783  	}
   784  
   785  	// Create revert request and enforcing it be at the current version
   786  	revertReq = &structs.JobRevertRequest{
   787  		JobID:        job.ID,
   788  		JobVersion:   1,
   789  		WriteRequest: structs.WriteRequest{Region: "global"},
   790  	}
   791  
   792  	// Fetch the response
   793  	err = msgpackrpc.CallWithCodec(codec, "Job.Revert", revertReq, &resp)
   794  	if err == nil || !strings.Contains(err.Error(), "current version") {
   795  		t.Fatalf("expected current version err: %v", err)
   796  	}
   797  
   798  	// Create revert request and enforcing it be at version 1
   799  	revertReq = &structs.JobRevertRequest{
   800  		JobID:               job.ID,
   801  		JobVersion:          0,
   802  		EnforcePriorVersion: helper.Uint64ToPtr(1),
   803  		WriteRequest:        structs.WriteRequest{Region: "global"},
   804  	}
   805  
   806  	// Fetch the response
   807  	if err := msgpackrpc.CallWithCodec(codec, "Job.Revert", revertReq, &resp); err != nil {
   808  		t.Fatalf("err: %v", err)
   809  	}
   810  	if resp.Index == 0 {
   811  		t.Fatalf("bad index: %d", resp.Index)
   812  	}
   813  	if resp.EvalID == "" || resp.EvalCreateIndex == 0 {
   814  		t.Fatalf("bad created eval: %+v", resp)
   815  	}
   816  	if resp.JobModifyIndex == 0 {
   817  		t.Fatalf("bad job modify index: %d", resp.JobModifyIndex)
   818  	}
   819  
   820  	// Create revert request and don't enforce. We are at version 2 but it is
   821  	// the same as version 0
   822  	revertReq = &structs.JobRevertRequest{
   823  		JobID:        job.ID,
   824  		JobVersion:   0,
   825  		WriteRequest: structs.WriteRequest{Region: "global"},
   826  	}
   827  
   828  	// Fetch the response
   829  	if err := msgpackrpc.CallWithCodec(codec, "Job.Revert", revertReq, &resp); err != nil {
   830  		t.Fatalf("err: %v", err)
   831  	}
   832  	if resp.Index == 0 {
   833  		t.Fatalf("bad index: %d", resp.Index)
   834  	}
   835  	if resp.EvalID == "" || resp.EvalCreateIndex == 0 {
   836  		t.Fatalf("bad created eval: %+v", resp)
   837  	}
   838  	if resp.JobModifyIndex == 0 {
   839  		t.Fatalf("bad job modify index: %d", resp.JobModifyIndex)
   840  	}
   841  
   842  	// Check that the job is at the correct version and that the eval was
   843  	// created
   844  	state := s1.fsm.State()
   845  	ws := memdb.NewWatchSet()
   846  	out, err := state.JobByID(ws, job.ID)
   847  	if err != nil {
   848  		t.Fatalf("err: %v", err)
   849  	}
   850  	if out == nil {
   851  		t.Fatalf("expected job")
   852  	}
   853  	if out.Priority != job.Priority {
   854  		t.Fatalf("priority mis-match")
   855  	}
   856  	if out.Version != 2 {
   857  		t.Fatalf("got version %d; want %d", out.Version, 2)
   858  	}
   859  
   860  	eout, err := state.EvalByID(ws, resp.EvalID)
   861  	if err != nil {
   862  		t.Fatalf("err: %v", err)
   863  	}
   864  	if eout == nil {
   865  		t.Fatalf("expected eval")
   866  	}
   867  	if eout.JobID != job.ID {
   868  		t.Fatalf("job id mis-match")
   869  	}
   870  
   871  	versions, err := state.JobVersionsByID(ws, job.ID)
   872  	if err != nil {
   873  		t.Fatalf("err: %v", err)
   874  	}
   875  	if len(versions) != 3 {
   876  		t.Fatalf("got %d versions; want %d", len(versions), 3)
   877  	}
   878  }
   879  
   880  func TestJobEndpoint_Stable(t *testing.T) {
   881  	t.Parallel()
   882  	s1 := testServer(t, func(c *Config) {
   883  		c.NumSchedulers = 0 // Prevent automatic dequeue
   884  	})
   885  	defer s1.Shutdown()
   886  	codec := rpcClient(t, s1)
   887  	testutil.WaitForLeader(t, s1.RPC)
   888  
   889  	// Create the initial register request
   890  	job := mock.Job()
   891  	req := &structs.JobRegisterRequest{
   892  		Job:          job,
   893  		WriteRequest: structs.WriteRequest{Region: "global"},
   894  	}
   895  
   896  	// Fetch the response
   897  	var resp structs.JobRegisterResponse
   898  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil {
   899  		t.Fatalf("err: %v", err)
   900  	}
   901  	if resp.Index == 0 {
   902  		t.Fatalf("bad index: %d", resp.Index)
   903  	}
   904  
   905  	// Create stablility request
   906  	stableReq := &structs.JobStabilityRequest{
   907  		JobID:        job.ID,
   908  		JobVersion:   0,
   909  		Stable:       true,
   910  		WriteRequest: structs.WriteRequest{Region: "global"},
   911  	}
   912  
   913  	// Fetch the response
   914  	var stableResp structs.JobStabilityResponse
   915  	if err := msgpackrpc.CallWithCodec(codec, "Job.Stable", stableReq, &stableResp); err != nil {
   916  		t.Fatalf("err: %v", err)
   917  	}
   918  	if stableResp.Index == 0 {
   919  		t.Fatalf("bad index: %d", resp.Index)
   920  	}
   921  
   922  	// Check that the job is marked stable
   923  	state := s1.fsm.State()
   924  	ws := memdb.NewWatchSet()
   925  	out, err := state.JobByID(ws, job.ID)
   926  	if err != nil {
   927  		t.Fatalf("err: %v", err)
   928  	}
   929  	if out == nil {
   930  		t.Fatalf("expected job")
   931  	}
   932  	if !out.Stable {
   933  		t.Fatalf("Job is not marked stable")
   934  	}
   935  }
   936  
   937  func TestJobEndpoint_Evaluate(t *testing.T) {
   938  	t.Parallel()
   939  	s1 := testServer(t, func(c *Config) {
   940  		c.NumSchedulers = 0 // Prevent automatic dequeue
   941  	})
   942  	defer s1.Shutdown()
   943  	codec := rpcClient(t, s1)
   944  	testutil.WaitForLeader(t, s1.RPC)
   945  
   946  	// Create the register request
   947  	job := mock.Job()
   948  	req := &structs.JobRegisterRequest{
   949  		Job:          job,
   950  		WriteRequest: structs.WriteRequest{Region: "global"},
   951  	}
   952  
   953  	// Fetch the response
   954  	var resp structs.JobRegisterResponse
   955  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil {
   956  		t.Fatalf("err: %v", err)
   957  	}
   958  	if resp.Index == 0 {
   959  		t.Fatalf("bad index: %d", resp.Index)
   960  	}
   961  
   962  	// Force a re-evaluation
   963  	reEval := &structs.JobEvaluateRequest{
   964  		JobID:        job.ID,
   965  		WriteRequest: structs.WriteRequest{Region: "global"},
   966  	}
   967  
   968  	// Fetch the response
   969  	if err := msgpackrpc.CallWithCodec(codec, "Job.Evaluate", reEval, &resp); err != nil {
   970  		t.Fatalf("err: %v", err)
   971  	}
   972  	if resp.Index == 0 {
   973  		t.Fatalf("bad index: %d", resp.Index)
   974  	}
   975  
   976  	// Lookup the evaluation
   977  	state := s1.fsm.State()
   978  	ws := memdb.NewWatchSet()
   979  	eval, err := state.EvalByID(ws, resp.EvalID)
   980  	if err != nil {
   981  		t.Fatalf("err: %v", err)
   982  	}
   983  	if eval == nil {
   984  		t.Fatalf("expected eval")
   985  	}
   986  	if eval.CreateIndex != resp.EvalCreateIndex {
   987  		t.Fatalf("index mis-match")
   988  	}
   989  
   990  	if eval.Priority != job.Priority {
   991  		t.Fatalf("bad: %#v", eval)
   992  	}
   993  	if eval.Type != job.Type {
   994  		t.Fatalf("bad: %#v", eval)
   995  	}
   996  	if eval.TriggeredBy != structs.EvalTriggerJobRegister {
   997  		t.Fatalf("bad: %#v", eval)
   998  	}
   999  	if eval.JobID != job.ID {
  1000  		t.Fatalf("bad: %#v", eval)
  1001  	}
  1002  	if eval.JobModifyIndex != resp.JobModifyIndex {
  1003  		t.Fatalf("bad: %#v", eval)
  1004  	}
  1005  	if eval.Status != structs.EvalStatusPending {
  1006  		t.Fatalf("bad: %#v", eval)
  1007  	}
  1008  }
  1009  
  1010  func TestJobEndpoint_Evaluate_Periodic(t *testing.T) {
  1011  	t.Parallel()
  1012  	s1 := testServer(t, func(c *Config) {
  1013  		c.NumSchedulers = 0 // Prevent automatic dequeue
  1014  	})
  1015  	defer s1.Shutdown()
  1016  	codec := rpcClient(t, s1)
  1017  	testutil.WaitForLeader(t, s1.RPC)
  1018  
  1019  	// Create the register request
  1020  	job := mock.PeriodicJob()
  1021  	req := &structs.JobRegisterRequest{
  1022  		Job:          job,
  1023  		WriteRequest: structs.WriteRequest{Region: "global"},
  1024  	}
  1025  
  1026  	// Fetch the response
  1027  	var resp structs.JobRegisterResponse
  1028  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil {
  1029  		t.Fatalf("err: %v", err)
  1030  	}
  1031  	if resp.JobModifyIndex == 0 {
  1032  		t.Fatalf("bad index: %d", resp.Index)
  1033  	}
  1034  
  1035  	// Force a re-evaluation
  1036  	reEval := &structs.JobEvaluateRequest{
  1037  		JobID:        job.ID,
  1038  		WriteRequest: structs.WriteRequest{Region: "global"},
  1039  	}
  1040  
  1041  	// Fetch the response
  1042  	if err := msgpackrpc.CallWithCodec(codec, "Job.Evaluate", reEval, &resp); err == nil {
  1043  		t.Fatal("expect an err")
  1044  	}
  1045  }
  1046  
  1047  func TestJobEndpoint_Evaluate_ParameterizedJob(t *testing.T) {
  1048  	t.Parallel()
  1049  	s1 := testServer(t, func(c *Config) {
  1050  		c.NumSchedulers = 0 // Prevent automatic dequeue
  1051  	})
  1052  	defer s1.Shutdown()
  1053  	codec := rpcClient(t, s1)
  1054  	testutil.WaitForLeader(t, s1.RPC)
  1055  
  1056  	// Create the register request
  1057  	job := mock.Job()
  1058  	job.Type = structs.JobTypeBatch
  1059  	job.ParameterizedJob = &structs.ParameterizedJobConfig{}
  1060  	req := &structs.JobRegisterRequest{
  1061  		Job:          job,
  1062  		WriteRequest: structs.WriteRequest{Region: "global"},
  1063  	}
  1064  
  1065  	// Fetch the response
  1066  	var resp structs.JobRegisterResponse
  1067  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil {
  1068  		t.Fatalf("err: %v", err)
  1069  	}
  1070  	if resp.JobModifyIndex == 0 {
  1071  		t.Fatalf("bad index: %d", resp.Index)
  1072  	}
  1073  
  1074  	// Force a re-evaluation
  1075  	reEval := &structs.JobEvaluateRequest{
  1076  		JobID:        job.ID,
  1077  		WriteRequest: structs.WriteRequest{Region: "global"},
  1078  	}
  1079  
  1080  	// Fetch the response
  1081  	if err := msgpackrpc.CallWithCodec(codec, "Job.Evaluate", reEval, &resp); err == nil {
  1082  		t.Fatal("expect an err")
  1083  	}
  1084  }
  1085  
  1086  func TestJobEndpoint_Deregister(t *testing.T) {
  1087  	t.Parallel()
  1088  	s1 := testServer(t, func(c *Config) {
  1089  		c.NumSchedulers = 0 // Prevent automatic dequeue
  1090  	})
  1091  	defer s1.Shutdown()
  1092  	codec := rpcClient(t, s1)
  1093  	testutil.WaitForLeader(t, s1.RPC)
  1094  
  1095  	// Create the register request
  1096  	job := mock.Job()
  1097  	reg := &structs.JobRegisterRequest{
  1098  		Job:          job,
  1099  		WriteRequest: structs.WriteRequest{Region: "global"},
  1100  	}
  1101  
  1102  	// Fetch the response
  1103  	var resp structs.JobRegisterResponse
  1104  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", reg, &resp); err != nil {
  1105  		t.Fatalf("err: %v", err)
  1106  	}
  1107  
  1108  	// Deregister but don't purge
  1109  	dereg := &structs.JobDeregisterRequest{
  1110  		JobID:        job.ID,
  1111  		Purge:        false,
  1112  		WriteRequest: structs.WriteRequest{Region: "global"},
  1113  	}
  1114  	var resp2 structs.JobDeregisterResponse
  1115  	if err := msgpackrpc.CallWithCodec(codec, "Job.Deregister", dereg, &resp2); err != nil {
  1116  		t.Fatalf("err: %v", err)
  1117  	}
  1118  	if resp2.Index == 0 {
  1119  		t.Fatalf("bad index: %d", resp2.Index)
  1120  	}
  1121  
  1122  	// Check for the job in the FSM
  1123  	ws := memdb.NewWatchSet()
  1124  	state := s1.fsm.State()
  1125  	out, err := state.JobByID(ws, job.ID)
  1126  	if err != nil {
  1127  		t.Fatalf("err: %v", err)
  1128  	}
  1129  	if out == nil {
  1130  		t.Fatalf("job purged")
  1131  	}
  1132  	if !out.Stop {
  1133  		t.Fatalf("job not stopped")
  1134  	}
  1135  
  1136  	// Lookup the evaluation
  1137  	eval, err := state.EvalByID(ws, resp2.EvalID)
  1138  	if err != nil {
  1139  		t.Fatalf("err: %v", err)
  1140  	}
  1141  	if eval == nil {
  1142  		t.Fatalf("expected eval")
  1143  	}
  1144  	if eval.CreateIndex != resp2.EvalCreateIndex {
  1145  		t.Fatalf("index mis-match")
  1146  	}
  1147  
  1148  	if eval.Priority != structs.JobDefaultPriority {
  1149  		t.Fatalf("bad: %#v", eval)
  1150  	}
  1151  	if eval.Type != structs.JobTypeService {
  1152  		t.Fatalf("bad: %#v", eval)
  1153  	}
  1154  	if eval.TriggeredBy != structs.EvalTriggerJobDeregister {
  1155  		t.Fatalf("bad: %#v", eval)
  1156  	}
  1157  	if eval.JobID != job.ID {
  1158  		t.Fatalf("bad: %#v", eval)
  1159  	}
  1160  	if eval.JobModifyIndex != resp2.JobModifyIndex {
  1161  		t.Fatalf("bad: %#v", eval)
  1162  	}
  1163  	if eval.Status != structs.EvalStatusPending {
  1164  		t.Fatalf("bad: %#v", eval)
  1165  	}
  1166  
  1167  	// Deregister and purge
  1168  	dereg2 := &structs.JobDeregisterRequest{
  1169  		JobID:        job.ID,
  1170  		Purge:        true,
  1171  		WriteRequest: structs.WriteRequest{Region: "global"},
  1172  	}
  1173  	var resp3 structs.JobDeregisterResponse
  1174  	if err := msgpackrpc.CallWithCodec(codec, "Job.Deregister", dereg2, &resp3); err != nil {
  1175  		t.Fatalf("err: %v", err)
  1176  	}
  1177  	if resp3.Index == 0 {
  1178  		t.Fatalf("bad index: %d", resp3.Index)
  1179  	}
  1180  
  1181  	// Check for the job in the FSM
  1182  	out, err = state.JobByID(ws, job.ID)
  1183  	if err != nil {
  1184  		t.Fatalf("err: %v", err)
  1185  	}
  1186  	if out != nil {
  1187  		t.Fatalf("unexpected job")
  1188  	}
  1189  
  1190  	// Lookup the evaluation
  1191  	eval, err = state.EvalByID(ws, resp3.EvalID)
  1192  	if err != nil {
  1193  		t.Fatalf("err: %v", err)
  1194  	}
  1195  	if eval == nil {
  1196  		t.Fatalf("expected eval")
  1197  	}
  1198  	if eval.CreateIndex != resp3.EvalCreateIndex {
  1199  		t.Fatalf("index mis-match")
  1200  	}
  1201  
  1202  	if eval.Priority != structs.JobDefaultPriority {
  1203  		t.Fatalf("bad: %#v", eval)
  1204  	}
  1205  	if eval.Type != structs.JobTypeService {
  1206  		t.Fatalf("bad: %#v", eval)
  1207  	}
  1208  	if eval.TriggeredBy != structs.EvalTriggerJobDeregister {
  1209  		t.Fatalf("bad: %#v", eval)
  1210  	}
  1211  	if eval.JobID != job.ID {
  1212  		t.Fatalf("bad: %#v", eval)
  1213  	}
  1214  	if eval.JobModifyIndex != resp3.JobModifyIndex {
  1215  		t.Fatalf("bad: %#v", eval)
  1216  	}
  1217  	if eval.Status != structs.EvalStatusPending {
  1218  		t.Fatalf("bad: %#v", eval)
  1219  	}
  1220  }
  1221  
  1222  func TestJobEndpoint_Deregister_NonExistent(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  	// Deregister
  1232  	jobID := "foo"
  1233  	dereg := &structs.JobDeregisterRequest{
  1234  		JobID:        jobID,
  1235  		WriteRequest: structs.WriteRequest{Region: "global"},
  1236  	}
  1237  	var resp2 structs.JobDeregisterResponse
  1238  	if err := msgpackrpc.CallWithCodec(codec, "Job.Deregister", dereg, &resp2); err != nil {
  1239  		t.Fatalf("err: %v", err)
  1240  	}
  1241  	if resp2.JobModifyIndex == 0 {
  1242  		t.Fatalf("bad index: %d", resp2.Index)
  1243  	}
  1244  
  1245  	// Lookup the evaluation
  1246  	state := s1.fsm.State()
  1247  	ws := memdb.NewWatchSet()
  1248  	eval, err := state.EvalByID(ws, resp2.EvalID)
  1249  	if err != nil {
  1250  		t.Fatalf("err: %v", err)
  1251  	}
  1252  	if eval == nil {
  1253  		t.Fatalf("expected eval")
  1254  	}
  1255  	if eval.CreateIndex != resp2.EvalCreateIndex {
  1256  		t.Fatalf("index mis-match")
  1257  	}
  1258  
  1259  	if eval.Priority != structs.JobDefaultPriority {
  1260  		t.Fatalf("bad: %#v", eval)
  1261  	}
  1262  	if eval.Type != structs.JobTypeService {
  1263  		t.Fatalf("bad: %#v", eval)
  1264  	}
  1265  	if eval.TriggeredBy != structs.EvalTriggerJobDeregister {
  1266  		t.Fatalf("bad: %#v", eval)
  1267  	}
  1268  	if eval.JobID != jobID {
  1269  		t.Fatalf("bad: %#v", eval)
  1270  	}
  1271  	if eval.JobModifyIndex != resp2.JobModifyIndex {
  1272  		t.Fatalf("bad: %#v", eval)
  1273  	}
  1274  	if eval.Status != structs.EvalStatusPending {
  1275  		t.Fatalf("bad: %#v", eval)
  1276  	}
  1277  }
  1278  
  1279  func TestJobEndpoint_Deregister_Periodic(t *testing.T) {
  1280  	t.Parallel()
  1281  	s1 := testServer(t, func(c *Config) {
  1282  		c.NumSchedulers = 0 // Prevent automatic dequeue
  1283  	})
  1284  	defer s1.Shutdown()
  1285  	codec := rpcClient(t, s1)
  1286  	testutil.WaitForLeader(t, s1.RPC)
  1287  
  1288  	// Create the register request
  1289  	job := mock.PeriodicJob()
  1290  	reg := &structs.JobRegisterRequest{
  1291  		Job:          job,
  1292  		WriteRequest: structs.WriteRequest{Region: "global"},
  1293  	}
  1294  
  1295  	// Fetch the response
  1296  	var resp structs.JobRegisterResponse
  1297  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", reg, &resp); err != nil {
  1298  		t.Fatalf("err: %v", err)
  1299  	}
  1300  
  1301  	// Deregister
  1302  	dereg := &structs.JobDeregisterRequest{
  1303  		JobID:        job.ID,
  1304  		Purge:        true,
  1305  		WriteRequest: structs.WriteRequest{Region: "global"},
  1306  	}
  1307  	var resp2 structs.JobDeregisterResponse
  1308  	if err := msgpackrpc.CallWithCodec(codec, "Job.Deregister", dereg, &resp2); err != nil {
  1309  		t.Fatalf("err: %v", err)
  1310  	}
  1311  	if resp2.JobModifyIndex == 0 {
  1312  		t.Fatalf("bad index: %d", resp2.Index)
  1313  	}
  1314  
  1315  	// Check for the node in the FSM
  1316  	state := s1.fsm.State()
  1317  	ws := memdb.NewWatchSet()
  1318  	out, err := state.JobByID(ws, job.ID)
  1319  	if err != nil {
  1320  		t.Fatalf("err: %v", err)
  1321  	}
  1322  	if out != nil {
  1323  		t.Fatalf("unexpected job")
  1324  	}
  1325  
  1326  	if resp.EvalID != "" {
  1327  		t.Fatalf("Deregister created an eval for a periodic job")
  1328  	}
  1329  }
  1330  
  1331  func TestJobEndpoint_Deregister_ParameterizedJob(t *testing.T) {
  1332  	t.Parallel()
  1333  	s1 := testServer(t, func(c *Config) {
  1334  		c.NumSchedulers = 0 // Prevent automatic dequeue
  1335  	})
  1336  	defer s1.Shutdown()
  1337  	codec := rpcClient(t, s1)
  1338  	testutil.WaitForLeader(t, s1.RPC)
  1339  
  1340  	// Create the register request
  1341  	job := mock.Job()
  1342  	job.Type = structs.JobTypeBatch
  1343  	job.ParameterizedJob = &structs.ParameterizedJobConfig{}
  1344  	reg := &structs.JobRegisterRequest{
  1345  		Job:          job,
  1346  		WriteRequest: structs.WriteRequest{Region: "global"},
  1347  	}
  1348  
  1349  	// Fetch the response
  1350  	var resp structs.JobRegisterResponse
  1351  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", reg, &resp); err != nil {
  1352  		t.Fatalf("err: %v", err)
  1353  	}
  1354  
  1355  	// Deregister
  1356  	dereg := &structs.JobDeregisterRequest{
  1357  		JobID:        job.ID,
  1358  		Purge:        true,
  1359  		WriteRequest: structs.WriteRequest{Region: "global"},
  1360  	}
  1361  	var resp2 structs.JobDeregisterResponse
  1362  	if err := msgpackrpc.CallWithCodec(codec, "Job.Deregister", dereg, &resp2); err != nil {
  1363  		t.Fatalf("err: %v", err)
  1364  	}
  1365  	if resp2.JobModifyIndex == 0 {
  1366  		t.Fatalf("bad index: %d", resp2.Index)
  1367  	}
  1368  
  1369  	// Check for the node in the FSM
  1370  	state := s1.fsm.State()
  1371  	ws := memdb.NewWatchSet()
  1372  	out, err := state.JobByID(ws, job.ID)
  1373  	if err != nil {
  1374  		t.Fatalf("err: %v", err)
  1375  	}
  1376  	if out != nil {
  1377  		t.Fatalf("unexpected job")
  1378  	}
  1379  
  1380  	if resp.EvalID != "" {
  1381  		t.Fatalf("Deregister created an eval for a parameterized job")
  1382  	}
  1383  }
  1384  
  1385  func TestJobEndpoint_GetJob(t *testing.T) {
  1386  	t.Parallel()
  1387  	s1 := testServer(t, nil)
  1388  	defer s1.Shutdown()
  1389  	codec := rpcClient(t, s1)
  1390  	testutil.WaitForLeader(t, s1.RPC)
  1391  
  1392  	// Create the register request
  1393  	job := mock.Job()
  1394  	reg := &structs.JobRegisterRequest{
  1395  		Job:          job,
  1396  		WriteRequest: structs.WriteRequest{Region: "global"},
  1397  	}
  1398  
  1399  	// Fetch the response
  1400  	var resp structs.JobRegisterResponse
  1401  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", reg, &resp); err != nil {
  1402  		t.Fatalf("err: %v", err)
  1403  	}
  1404  	job.CreateIndex = resp.JobModifyIndex
  1405  	job.ModifyIndex = resp.JobModifyIndex
  1406  	job.JobModifyIndex = resp.JobModifyIndex
  1407  
  1408  	// Lookup the job
  1409  	get := &structs.JobSpecificRequest{
  1410  		JobID:        job.ID,
  1411  		QueryOptions: structs.QueryOptions{Region: "global"},
  1412  	}
  1413  	var resp2 structs.SingleJobResponse
  1414  	if err := msgpackrpc.CallWithCodec(codec, "Job.GetJob", get, &resp2); err != nil {
  1415  		t.Fatalf("err: %v", err)
  1416  	}
  1417  	if resp2.Index != resp.JobModifyIndex {
  1418  		t.Fatalf("Bad index: %d %d", resp2.Index, resp.Index)
  1419  	}
  1420  
  1421  	// Make a copy of the origin job and change the service name so that we can
  1422  	// do a deep equal with the response from the GET JOB Api
  1423  	j := job
  1424  	j.TaskGroups[0].Tasks[0].Services[0].Name = "web-frontend"
  1425  	for tgix, tg := range j.TaskGroups {
  1426  		for tidx, t := range tg.Tasks {
  1427  			for sidx, service := range t.Services {
  1428  				for cidx, check := range service.Checks {
  1429  					check.Name = resp2.Job.TaskGroups[tgix].Tasks[tidx].Services[sidx].Checks[cidx].Name
  1430  				}
  1431  			}
  1432  		}
  1433  	}
  1434  
  1435  	// Clear the submit times
  1436  	j.SubmitTime = 0
  1437  	resp2.Job.SubmitTime = 0
  1438  
  1439  	if !reflect.DeepEqual(j, resp2.Job) {
  1440  		t.Fatalf("bad: %#v %#v", job, resp2.Job)
  1441  	}
  1442  
  1443  	// Lookup non-existing job
  1444  	get.JobID = "foobarbaz"
  1445  	if err := msgpackrpc.CallWithCodec(codec, "Job.GetJob", get, &resp2); err != nil {
  1446  		t.Fatalf("err: %v", err)
  1447  	}
  1448  	if resp2.Index != resp.JobModifyIndex {
  1449  		t.Fatalf("Bad index: %d %d", resp2.Index, resp.Index)
  1450  	}
  1451  	if resp2.Job != nil {
  1452  		t.Fatalf("unexpected job")
  1453  	}
  1454  }
  1455  
  1456  func TestJobEndpoint_GetJob_Blocking(t *testing.T) {
  1457  	t.Parallel()
  1458  	s1 := testServer(t, nil)
  1459  	defer s1.Shutdown()
  1460  	state := s1.fsm.State()
  1461  	codec := rpcClient(t, s1)
  1462  	testutil.WaitForLeader(t, s1.RPC)
  1463  
  1464  	// Create the jobs
  1465  	job1 := mock.Job()
  1466  	job2 := mock.Job()
  1467  
  1468  	// Upsert a job we are not interested in first.
  1469  	time.AfterFunc(100*time.Millisecond, func() {
  1470  		if err := state.UpsertJob(100, job1); err != nil {
  1471  			t.Fatalf("err: %v", err)
  1472  		}
  1473  	})
  1474  
  1475  	// Upsert another job later which should trigger the watch.
  1476  	time.AfterFunc(200*time.Millisecond, func() {
  1477  		if err := state.UpsertJob(200, job2); err != nil {
  1478  			t.Fatalf("err: %v", err)
  1479  		}
  1480  	})
  1481  
  1482  	req := &structs.JobSpecificRequest{
  1483  		JobID: job2.ID,
  1484  		QueryOptions: structs.QueryOptions{
  1485  			Region:        "global",
  1486  			MinQueryIndex: 150,
  1487  		},
  1488  	}
  1489  	start := time.Now()
  1490  	var resp structs.SingleJobResponse
  1491  	if err := msgpackrpc.CallWithCodec(codec, "Job.GetJob", req, &resp); err != nil {
  1492  		t.Fatalf("err: %v", err)
  1493  	}
  1494  
  1495  	if elapsed := time.Since(start); elapsed < 200*time.Millisecond {
  1496  		t.Fatalf("should block (returned in %s) %#v", elapsed, resp)
  1497  	}
  1498  	if resp.Index != 200 {
  1499  		t.Fatalf("Bad index: %d %d", resp.Index, 200)
  1500  	}
  1501  	if resp.Job == nil || resp.Job.ID != job2.ID {
  1502  		t.Fatalf("bad: %#v", resp.Job)
  1503  	}
  1504  
  1505  	// Job delete fires watches
  1506  	time.AfterFunc(100*time.Millisecond, func() {
  1507  		if err := state.DeleteJob(300, job2.ID); err != nil {
  1508  			t.Fatalf("err: %v", err)
  1509  		}
  1510  	})
  1511  
  1512  	req.QueryOptions.MinQueryIndex = 250
  1513  	start = time.Now()
  1514  
  1515  	var resp2 structs.SingleJobResponse
  1516  	if err := msgpackrpc.CallWithCodec(codec, "Job.GetJob", req, &resp2); err != nil {
  1517  		t.Fatalf("err: %v", err)
  1518  	}
  1519  
  1520  	if elapsed := time.Since(start); elapsed < 100*time.Millisecond {
  1521  		t.Fatalf("should block (returned in %s) %#v", elapsed, resp2)
  1522  	}
  1523  	if resp2.Index != 300 {
  1524  		t.Fatalf("Bad index: %d %d", resp2.Index, 300)
  1525  	}
  1526  	if resp2.Job != nil {
  1527  		t.Fatalf("bad: %#v", resp2.Job)
  1528  	}
  1529  }
  1530  
  1531  func TestJobEndpoint_GetJobVersions(t *testing.T) {
  1532  	t.Parallel()
  1533  	s1 := testServer(t, nil)
  1534  	defer s1.Shutdown()
  1535  	codec := rpcClient(t, s1)
  1536  	testutil.WaitForLeader(t, s1.RPC)
  1537  
  1538  	// Create the register request
  1539  	job := mock.Job()
  1540  	job.Priority = 88
  1541  	reg := &structs.JobRegisterRequest{
  1542  		Job:          job,
  1543  		WriteRequest: structs.WriteRequest{Region: "global"},
  1544  	}
  1545  
  1546  	// Fetch the response
  1547  	var resp structs.JobRegisterResponse
  1548  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", reg, &resp); err != nil {
  1549  		t.Fatalf("err: %v", err)
  1550  	}
  1551  
  1552  	// Register the job again to create another version
  1553  	job.Priority = 100
  1554  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", reg, &resp); err != nil {
  1555  		t.Fatalf("err: %v", err)
  1556  	}
  1557  
  1558  	// Lookup the job
  1559  	get := &structs.JobVersionsRequest{
  1560  		JobID:        job.ID,
  1561  		QueryOptions: structs.QueryOptions{Region: "global"},
  1562  	}
  1563  	var versionsResp structs.JobVersionsResponse
  1564  	if err := msgpackrpc.CallWithCodec(codec, "Job.GetJobVersions", get, &versionsResp); err != nil {
  1565  		t.Fatalf("err: %v", err)
  1566  	}
  1567  	if versionsResp.Index != resp.JobModifyIndex {
  1568  		t.Fatalf("Bad index: %d %d", versionsResp.Index, resp.Index)
  1569  	}
  1570  
  1571  	// Make sure there are two job versions
  1572  	versions := versionsResp.Versions
  1573  	if l := len(versions); l != 2 {
  1574  		t.Fatalf("Got %d versions; want 2", l)
  1575  	}
  1576  
  1577  	if v := versions[0]; v.Priority != 100 || v.ID != job.ID || v.Version != 1 {
  1578  		t.Fatalf("bad: %+v", v)
  1579  	}
  1580  	if v := versions[1]; v.Priority != 88 || v.ID != job.ID || v.Version != 0 {
  1581  		t.Fatalf("bad: %+v", v)
  1582  	}
  1583  
  1584  	// Lookup non-existing job
  1585  	get.JobID = "foobarbaz"
  1586  	if err := msgpackrpc.CallWithCodec(codec, "Job.GetJobVersions", get, &versionsResp); err != nil {
  1587  		t.Fatalf("err: %v", err)
  1588  	}
  1589  	if versionsResp.Index != resp.JobModifyIndex {
  1590  		t.Fatalf("Bad index: %d %d", versionsResp.Index, resp.Index)
  1591  	}
  1592  	if l := len(versionsResp.Versions); l != 0 {
  1593  		t.Fatalf("unexpected versions: %d", l)
  1594  	}
  1595  }
  1596  
  1597  func TestJobEndpoint_GetJobVersions_Diff(t *testing.T) {
  1598  	t.Parallel()
  1599  	s1 := testServer(t, nil)
  1600  	defer s1.Shutdown()
  1601  	codec := rpcClient(t, s1)
  1602  	testutil.WaitForLeader(t, s1.RPC)
  1603  
  1604  	// Create the register request
  1605  	job := mock.Job()
  1606  	job.Priority = 88
  1607  	reg := &structs.JobRegisterRequest{
  1608  		Job:          job,
  1609  		WriteRequest: structs.WriteRequest{Region: "global"},
  1610  	}
  1611  
  1612  	// Fetch the response
  1613  	var resp structs.JobRegisterResponse
  1614  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", reg, &resp); err != nil {
  1615  		t.Fatalf("err: %v", err)
  1616  	}
  1617  
  1618  	// Register the job again to create another version
  1619  	job.Priority = 90
  1620  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", reg, &resp); err != nil {
  1621  		t.Fatalf("err: %v", err)
  1622  	}
  1623  
  1624  	// Register the job again to create another version
  1625  	job.Priority = 100
  1626  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", reg, &resp); err != nil {
  1627  		t.Fatalf("err: %v", err)
  1628  	}
  1629  
  1630  	// Lookup the job
  1631  	get := &structs.JobVersionsRequest{
  1632  		JobID:        job.ID,
  1633  		Diffs:        true,
  1634  		QueryOptions: structs.QueryOptions{Region: "global"},
  1635  	}
  1636  	var versionsResp structs.JobVersionsResponse
  1637  	if err := msgpackrpc.CallWithCodec(codec, "Job.GetJobVersions", get, &versionsResp); err != nil {
  1638  		t.Fatalf("err: %v", err)
  1639  	}
  1640  	if versionsResp.Index != resp.JobModifyIndex {
  1641  		t.Fatalf("Bad index: %d %d", versionsResp.Index, resp.Index)
  1642  	}
  1643  
  1644  	// Make sure there are two job versions
  1645  	versions := versionsResp.Versions
  1646  	if l := len(versions); l != 3 {
  1647  		t.Fatalf("Got %d versions; want 3", l)
  1648  	}
  1649  
  1650  	if v := versions[0]; v.Priority != 100 || v.ID != job.ID || v.Version != 2 {
  1651  		t.Fatalf("bad: %+v", v)
  1652  	}
  1653  	if v := versions[1]; v.Priority != 90 || v.ID != job.ID || v.Version != 1 {
  1654  		t.Fatalf("bad: %+v", v)
  1655  	}
  1656  	if v := versions[2]; v.Priority != 88 || v.ID != job.ID || v.Version != 0 {
  1657  		t.Fatalf("bad: %+v", v)
  1658  	}
  1659  
  1660  	// Ensure we got diffs
  1661  	diffs := versionsResp.Diffs
  1662  	if l := len(diffs); l != 2 {
  1663  		t.Fatalf("Got %d diffs; want 2", l)
  1664  	}
  1665  	d1 := diffs[0]
  1666  	if len(d1.Fields) != 1 {
  1667  		t.Fatalf("Got too many diffs: %#v", d1)
  1668  	}
  1669  	if d1.Fields[0].Name != "Priority" {
  1670  		t.Fatalf("Got wrong field: %#v", d1)
  1671  	}
  1672  	if d1.Fields[0].Old != "90" && d1.Fields[0].New != "100" {
  1673  		t.Fatalf("Got wrong field values: %#v", d1)
  1674  	}
  1675  	d2 := diffs[1]
  1676  	if len(d2.Fields) != 1 {
  1677  		t.Fatalf("Got too many diffs: %#v", d2)
  1678  	}
  1679  	if d2.Fields[0].Name != "Priority" {
  1680  		t.Fatalf("Got wrong field: %#v", d2)
  1681  	}
  1682  	if d2.Fields[0].Old != "88" && d1.Fields[0].New != "90" {
  1683  		t.Fatalf("Got wrong field values: %#v", d2)
  1684  	}
  1685  }
  1686  
  1687  func TestJobEndpoint_GetJobVersions_Blocking(t *testing.T) {
  1688  	t.Parallel()
  1689  	s1 := testServer(t, nil)
  1690  	defer s1.Shutdown()
  1691  	state := s1.fsm.State()
  1692  	codec := rpcClient(t, s1)
  1693  	testutil.WaitForLeader(t, s1.RPC)
  1694  
  1695  	// Create the jobs
  1696  	job1 := mock.Job()
  1697  	job2 := mock.Job()
  1698  	job3 := mock.Job()
  1699  	job3.ID = job2.ID
  1700  	job3.Priority = 1
  1701  
  1702  	// Upsert a job we are not interested in first.
  1703  	time.AfterFunc(100*time.Millisecond, func() {
  1704  		if err := state.UpsertJob(100, job1); err != nil {
  1705  			t.Fatalf("err: %v", err)
  1706  		}
  1707  	})
  1708  
  1709  	// Upsert another job later which should trigger the watch.
  1710  	time.AfterFunc(200*time.Millisecond, func() {
  1711  		if err := state.UpsertJob(200, job2); err != nil {
  1712  			t.Fatalf("err: %v", err)
  1713  		}
  1714  	})
  1715  
  1716  	req := &structs.JobVersionsRequest{
  1717  		JobID: job2.ID,
  1718  		QueryOptions: structs.QueryOptions{
  1719  			Region:        "global",
  1720  			MinQueryIndex: 150,
  1721  		},
  1722  	}
  1723  	start := time.Now()
  1724  	var resp structs.JobVersionsResponse
  1725  	if err := msgpackrpc.CallWithCodec(codec, "Job.GetJobVersions", req, &resp); err != nil {
  1726  		t.Fatalf("err: %v", err)
  1727  	}
  1728  
  1729  	if elapsed := time.Since(start); elapsed < 200*time.Millisecond {
  1730  		t.Fatalf("should block (returned in %s) %#v", elapsed, resp)
  1731  	}
  1732  	if resp.Index != 200 {
  1733  		t.Fatalf("Bad index: %d %d", resp.Index, 200)
  1734  	}
  1735  	if len(resp.Versions) != 1 || resp.Versions[0].ID != job2.ID {
  1736  		t.Fatalf("bad: %#v", resp.Versions)
  1737  	}
  1738  
  1739  	// Upsert the job again which should trigger the watch.
  1740  	time.AfterFunc(100*time.Millisecond, func() {
  1741  		if err := state.UpsertJob(300, job3); err != nil {
  1742  			t.Fatalf("err: %v", err)
  1743  		}
  1744  	})
  1745  
  1746  	req2 := &structs.JobVersionsRequest{
  1747  		JobID: job3.ID,
  1748  		QueryOptions: structs.QueryOptions{
  1749  			Region:        "global",
  1750  			MinQueryIndex: 250,
  1751  		},
  1752  	}
  1753  	var resp2 structs.JobVersionsResponse
  1754  	start = time.Now()
  1755  	if err := msgpackrpc.CallWithCodec(codec, "Job.GetJobVersions", req2, &resp2); err != nil {
  1756  		t.Fatalf("err: %v", err)
  1757  	}
  1758  
  1759  	if elapsed := time.Since(start); elapsed < 100*time.Millisecond {
  1760  		t.Fatalf("should block (returned in %s) %#v", elapsed, resp)
  1761  	}
  1762  	if resp2.Index != 300 {
  1763  		t.Fatalf("Bad index: %d %d", resp.Index, 300)
  1764  	}
  1765  	if len(resp2.Versions) != 2 {
  1766  		t.Fatalf("bad: %#v", resp2.Versions)
  1767  	}
  1768  }
  1769  
  1770  func TestJobEndpoint_GetJobSummary(t *testing.T) {
  1771  	t.Parallel()
  1772  	s1 := testServer(t, func(c *Config) {
  1773  		c.NumSchedulers = 0 // Prevent automatic dequeue
  1774  	})
  1775  
  1776  	defer s1.Shutdown()
  1777  	codec := rpcClient(t, s1)
  1778  	testutil.WaitForLeader(t, s1.RPC)
  1779  
  1780  	// Create the register request
  1781  	job := mock.Job()
  1782  	reg := &structs.JobRegisterRequest{
  1783  		Job:          job,
  1784  		WriteRequest: structs.WriteRequest{Region: "global"},
  1785  	}
  1786  
  1787  	// Fetch the response
  1788  	var resp structs.JobRegisterResponse
  1789  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", reg, &resp); err != nil {
  1790  		t.Fatalf("err: %v", err)
  1791  	}
  1792  	job.CreateIndex = resp.JobModifyIndex
  1793  	job.ModifyIndex = resp.JobModifyIndex
  1794  	job.JobModifyIndex = resp.JobModifyIndex
  1795  
  1796  	// Lookup the job summary
  1797  	get := &structs.JobSummaryRequest{
  1798  		JobID:        job.ID,
  1799  		QueryOptions: structs.QueryOptions{Region: "global"},
  1800  	}
  1801  	var resp2 structs.JobSummaryResponse
  1802  	if err := msgpackrpc.CallWithCodec(codec, "Job.Summary", get, &resp2); err != nil {
  1803  		t.Fatalf("err: %v", err)
  1804  	}
  1805  	if resp2.Index != resp.JobModifyIndex {
  1806  		t.Fatalf("Bad index: %d %d", resp2.Index, resp.Index)
  1807  	}
  1808  
  1809  	expectedJobSummary := structs.JobSummary{
  1810  		JobID: job.ID,
  1811  		Summary: map[string]structs.TaskGroupSummary{
  1812  			"web": structs.TaskGroupSummary{},
  1813  		},
  1814  		Children:    new(structs.JobChildrenSummary),
  1815  		CreateIndex: job.CreateIndex,
  1816  		ModifyIndex: job.CreateIndex,
  1817  	}
  1818  
  1819  	if !reflect.DeepEqual(resp2.JobSummary, &expectedJobSummary) {
  1820  		t.Fatalf("exptected: %v, actual: %v", expectedJobSummary, resp2.JobSummary)
  1821  	}
  1822  }
  1823  
  1824  func TestJobEndpoint_GetJobSummary_Blocking(t *testing.T) {
  1825  	t.Parallel()
  1826  	s1 := testServer(t, nil)
  1827  	defer s1.Shutdown()
  1828  	state := s1.fsm.State()
  1829  	codec := rpcClient(t, s1)
  1830  	testutil.WaitForLeader(t, s1.RPC)
  1831  
  1832  	// Create a job and insert it
  1833  	job1 := mock.Job()
  1834  	time.AfterFunc(200*time.Millisecond, func() {
  1835  		if err := state.UpsertJob(100, job1); err != nil {
  1836  			t.Fatalf("err: %v", err)
  1837  		}
  1838  	})
  1839  
  1840  	// Ensure the job summary request gets fired
  1841  	req := &structs.JobSummaryRequest{
  1842  		JobID: job1.ID,
  1843  		QueryOptions: structs.QueryOptions{
  1844  			Region:        "global",
  1845  			MinQueryIndex: 50,
  1846  		},
  1847  	}
  1848  	var resp structs.JobSummaryResponse
  1849  	start := time.Now()
  1850  	if err := msgpackrpc.CallWithCodec(codec, "Job.Summary", req, &resp); err != nil {
  1851  		t.Fatalf("err: %v", err)
  1852  	}
  1853  	if elapsed := time.Since(start); elapsed < 200*time.Millisecond {
  1854  		t.Fatalf("should block (returned in %s) %#v", elapsed, resp)
  1855  	}
  1856  
  1857  	// Upsert an allocation for the job which should trigger the watch.
  1858  	time.AfterFunc(200*time.Millisecond, func() {
  1859  		alloc := mock.Alloc()
  1860  		alloc.JobID = job1.ID
  1861  		alloc.Job = job1
  1862  		if err := state.UpsertAllocs(200, []*structs.Allocation{alloc}); err != nil {
  1863  			t.Fatalf("err: %v", err)
  1864  		}
  1865  	})
  1866  	req = &structs.JobSummaryRequest{
  1867  		JobID: job1.ID,
  1868  		QueryOptions: structs.QueryOptions{
  1869  			Region:        "global",
  1870  			MinQueryIndex: 199,
  1871  		},
  1872  	}
  1873  	start = time.Now()
  1874  	var resp1 structs.JobSummaryResponse
  1875  	start = time.Now()
  1876  	if err := msgpackrpc.CallWithCodec(codec, "Job.Summary", req, &resp1); err != nil {
  1877  		t.Fatalf("err: %v", err)
  1878  	}
  1879  
  1880  	if elapsed := time.Since(start); elapsed < 200*time.Millisecond {
  1881  		t.Fatalf("should block (returned in %s) %#v", elapsed, resp)
  1882  	}
  1883  	if resp1.Index != 200 {
  1884  		t.Fatalf("Bad index: %d %d", resp.Index, 200)
  1885  	}
  1886  	if resp1.JobSummary == nil {
  1887  		t.Fatalf("bad: %#v", resp)
  1888  	}
  1889  
  1890  	// Job delete fires watches
  1891  	time.AfterFunc(100*time.Millisecond, func() {
  1892  		if err := state.DeleteJob(300, job1.ID); err != nil {
  1893  			t.Fatalf("err: %v", err)
  1894  		}
  1895  	})
  1896  
  1897  	req.QueryOptions.MinQueryIndex = 250
  1898  	start = time.Now()
  1899  
  1900  	var resp2 structs.SingleJobResponse
  1901  	if err := msgpackrpc.CallWithCodec(codec, "Job.Summary", req, &resp2); err != nil {
  1902  		t.Fatalf("err: %v", err)
  1903  	}
  1904  
  1905  	if elapsed := time.Since(start); elapsed < 100*time.Millisecond {
  1906  		t.Fatalf("should block (returned in %s) %#v", elapsed, resp2)
  1907  	}
  1908  	if resp2.Index != 300 {
  1909  		t.Fatalf("Bad index: %d %d", resp2.Index, 300)
  1910  	}
  1911  	if resp2.Job != nil {
  1912  		t.Fatalf("bad: %#v", resp2.Job)
  1913  	}
  1914  }
  1915  
  1916  func TestJobEndpoint_ListJobs(t *testing.T) {
  1917  	t.Parallel()
  1918  	s1 := testServer(t, nil)
  1919  	defer s1.Shutdown()
  1920  	codec := rpcClient(t, s1)
  1921  	testutil.WaitForLeader(t, s1.RPC)
  1922  
  1923  	// Create the register request
  1924  	job := mock.Job()
  1925  	state := s1.fsm.State()
  1926  	err := state.UpsertJob(1000, job)
  1927  	if err != nil {
  1928  		t.Fatalf("err: %v", err)
  1929  	}
  1930  
  1931  	// Lookup the jobs
  1932  	get := &structs.JobListRequest{
  1933  		QueryOptions: structs.QueryOptions{Region: "global"},
  1934  	}
  1935  	var resp2 structs.JobListResponse
  1936  	if err := msgpackrpc.CallWithCodec(codec, "Job.List", get, &resp2); err != nil {
  1937  		t.Fatalf("err: %v", err)
  1938  	}
  1939  	if resp2.Index != 1000 {
  1940  		t.Fatalf("Bad index: %d %d", resp2.Index, 1000)
  1941  	}
  1942  
  1943  	if len(resp2.Jobs) != 1 {
  1944  		t.Fatalf("bad: %#v", resp2.Jobs)
  1945  	}
  1946  	if resp2.Jobs[0].ID != job.ID {
  1947  		t.Fatalf("bad: %#v", resp2.Jobs[0])
  1948  	}
  1949  
  1950  	// Lookup the jobs by prefix
  1951  	get = &structs.JobListRequest{
  1952  		QueryOptions: structs.QueryOptions{Region: "global", Prefix: resp2.Jobs[0].ID[:4]},
  1953  	}
  1954  	var resp3 structs.JobListResponse
  1955  	if err := msgpackrpc.CallWithCodec(codec, "Job.List", get, &resp3); err != nil {
  1956  		t.Fatalf("err: %v", err)
  1957  	}
  1958  	if resp3.Index != 1000 {
  1959  		t.Fatalf("Bad index: %d %d", resp3.Index, 1000)
  1960  	}
  1961  
  1962  	if len(resp3.Jobs) != 1 {
  1963  		t.Fatalf("bad: %#v", resp3.Jobs)
  1964  	}
  1965  	if resp3.Jobs[0].ID != job.ID {
  1966  		t.Fatalf("bad: %#v", resp3.Jobs[0])
  1967  	}
  1968  }
  1969  
  1970  func TestJobEndpoint_ListJobs_Blocking(t *testing.T) {
  1971  	t.Parallel()
  1972  	s1 := testServer(t, nil)
  1973  	defer s1.Shutdown()
  1974  	state := s1.fsm.State()
  1975  	codec := rpcClient(t, s1)
  1976  	testutil.WaitForLeader(t, s1.RPC)
  1977  
  1978  	// Create the job
  1979  	job := mock.Job()
  1980  
  1981  	// Upsert job triggers watches
  1982  	time.AfterFunc(100*time.Millisecond, func() {
  1983  		if err := state.UpsertJob(100, job); err != nil {
  1984  			t.Fatalf("err: %v", err)
  1985  		}
  1986  	})
  1987  
  1988  	req := &structs.JobListRequest{
  1989  		QueryOptions: structs.QueryOptions{
  1990  			Region:        "global",
  1991  			MinQueryIndex: 50,
  1992  		},
  1993  	}
  1994  	start := time.Now()
  1995  	var resp structs.JobListResponse
  1996  	if err := msgpackrpc.CallWithCodec(codec, "Job.List", req, &resp); err != nil {
  1997  		t.Fatalf("err: %v", err)
  1998  	}
  1999  
  2000  	if elapsed := time.Since(start); elapsed < 100*time.Millisecond {
  2001  		t.Fatalf("should block (returned in %s) %#v", elapsed, resp)
  2002  	}
  2003  	if resp.Index != 100 {
  2004  		t.Fatalf("Bad index: %d %d", resp.Index, 100)
  2005  	}
  2006  	if len(resp.Jobs) != 1 || resp.Jobs[0].ID != job.ID {
  2007  		t.Fatalf("bad: %#v", resp)
  2008  	}
  2009  
  2010  	// Job deletion triggers watches
  2011  	time.AfterFunc(100*time.Millisecond, func() {
  2012  		if err := state.DeleteJob(200, job.ID); err != nil {
  2013  			t.Fatalf("err: %v", err)
  2014  		}
  2015  	})
  2016  
  2017  	req.MinQueryIndex = 150
  2018  	start = time.Now()
  2019  	var resp2 structs.JobListResponse
  2020  	if err := msgpackrpc.CallWithCodec(codec, "Job.List", req, &resp2); err != nil {
  2021  		t.Fatalf("err: %v", err)
  2022  	}
  2023  
  2024  	if elapsed := time.Since(start); elapsed < 100*time.Millisecond {
  2025  		t.Fatalf("should block (returned in %s) %#v", elapsed, resp2)
  2026  	}
  2027  	if resp2.Index != 200 {
  2028  		t.Fatalf("Bad index: %d %d", resp2.Index, 200)
  2029  	}
  2030  	if len(resp2.Jobs) != 0 {
  2031  		t.Fatalf("bad: %#v", resp2)
  2032  	}
  2033  }
  2034  
  2035  func TestJobEndpoint_Allocations(t *testing.T) {
  2036  	t.Parallel()
  2037  	s1 := testServer(t, nil)
  2038  	defer s1.Shutdown()
  2039  	codec := rpcClient(t, s1)
  2040  	testutil.WaitForLeader(t, s1.RPC)
  2041  
  2042  	// Create the register request
  2043  	alloc1 := mock.Alloc()
  2044  	alloc2 := mock.Alloc()
  2045  	alloc2.JobID = alloc1.JobID
  2046  	state := s1.fsm.State()
  2047  	state.UpsertJobSummary(998, mock.JobSummary(alloc1.JobID))
  2048  	state.UpsertJobSummary(999, mock.JobSummary(alloc2.JobID))
  2049  	err := state.UpsertAllocs(1000,
  2050  		[]*structs.Allocation{alloc1, alloc2})
  2051  	if err != nil {
  2052  		t.Fatalf("err: %v", err)
  2053  	}
  2054  
  2055  	// Lookup the jobs
  2056  	get := &structs.JobSpecificRequest{
  2057  		JobID:        alloc1.JobID,
  2058  		QueryOptions: structs.QueryOptions{Region: "global"},
  2059  	}
  2060  	var resp2 structs.JobAllocationsResponse
  2061  	if err := msgpackrpc.CallWithCodec(codec, "Job.Allocations", get, &resp2); err != nil {
  2062  		t.Fatalf("err: %v", err)
  2063  	}
  2064  	if resp2.Index != 1000 {
  2065  		t.Fatalf("Bad index: %d %d", resp2.Index, 1000)
  2066  	}
  2067  
  2068  	if len(resp2.Allocations) != 2 {
  2069  		t.Fatalf("bad: %#v", resp2.Allocations)
  2070  	}
  2071  }
  2072  
  2073  func TestJobEndpoint_Allocations_Blocking(t *testing.T) {
  2074  	t.Parallel()
  2075  	s1 := testServer(t, nil)
  2076  	defer s1.Shutdown()
  2077  	codec := rpcClient(t, s1)
  2078  	testutil.WaitForLeader(t, s1.RPC)
  2079  
  2080  	// Create the register request
  2081  	alloc1 := mock.Alloc()
  2082  	alloc2 := mock.Alloc()
  2083  	alloc2.JobID = "job1"
  2084  	state := s1.fsm.State()
  2085  
  2086  	// First upsert an unrelated alloc
  2087  	time.AfterFunc(100*time.Millisecond, func() {
  2088  		state.UpsertJobSummary(99, mock.JobSummary(alloc1.JobID))
  2089  		err := state.UpsertAllocs(100, []*structs.Allocation{alloc1})
  2090  		if err != nil {
  2091  			t.Fatalf("err: %v", err)
  2092  		}
  2093  	})
  2094  
  2095  	// Upsert an alloc for the job we are interested in later
  2096  	time.AfterFunc(200*time.Millisecond, func() {
  2097  		state.UpsertJobSummary(199, mock.JobSummary(alloc2.JobID))
  2098  		err := state.UpsertAllocs(200, []*structs.Allocation{alloc2})
  2099  		if err != nil {
  2100  			t.Fatalf("err: %v", err)
  2101  		}
  2102  	})
  2103  
  2104  	// Lookup the jobs
  2105  	get := &structs.JobSpecificRequest{
  2106  		JobID: "job1",
  2107  		QueryOptions: structs.QueryOptions{
  2108  			Region:        "global",
  2109  			MinQueryIndex: 150,
  2110  		},
  2111  	}
  2112  	var resp structs.JobAllocationsResponse
  2113  	start := time.Now()
  2114  	if err := msgpackrpc.CallWithCodec(codec, "Job.Allocations", get, &resp); err != nil {
  2115  		t.Fatalf("err: %v", err)
  2116  	}
  2117  
  2118  	if elapsed := time.Since(start); elapsed < 200*time.Millisecond {
  2119  		t.Fatalf("should block (returned in %s) %#v", elapsed, resp)
  2120  	}
  2121  	if resp.Index != 200 {
  2122  		t.Fatalf("Bad index: %d %d", resp.Index, 200)
  2123  	}
  2124  	if len(resp.Allocations) != 1 || resp.Allocations[0].JobID != "job1" {
  2125  		t.Fatalf("bad: %#v", resp.Allocations)
  2126  	}
  2127  }
  2128  
  2129  func TestJobEndpoint_Evaluations(t *testing.T) {
  2130  	t.Parallel()
  2131  	s1 := testServer(t, nil)
  2132  	defer s1.Shutdown()
  2133  	codec := rpcClient(t, s1)
  2134  	testutil.WaitForLeader(t, s1.RPC)
  2135  
  2136  	// Create the register request
  2137  	eval1 := mock.Eval()
  2138  	eval2 := mock.Eval()
  2139  	eval2.JobID = eval1.JobID
  2140  	state := s1.fsm.State()
  2141  	err := state.UpsertEvals(1000,
  2142  		[]*structs.Evaluation{eval1, eval2})
  2143  	if err != nil {
  2144  		t.Fatalf("err: %v", err)
  2145  	}
  2146  
  2147  	// Lookup the jobs
  2148  	get := &structs.JobSpecificRequest{
  2149  		JobID:        eval1.JobID,
  2150  		QueryOptions: structs.QueryOptions{Region: "global"},
  2151  	}
  2152  	var resp2 structs.JobEvaluationsResponse
  2153  	if err := msgpackrpc.CallWithCodec(codec, "Job.Evaluations", get, &resp2); err != nil {
  2154  		t.Fatalf("err: %v", err)
  2155  	}
  2156  	if resp2.Index != 1000 {
  2157  		t.Fatalf("Bad index: %d %d", resp2.Index, 1000)
  2158  	}
  2159  
  2160  	if len(resp2.Evaluations) != 2 {
  2161  		t.Fatalf("bad: %#v", resp2.Evaluations)
  2162  	}
  2163  }
  2164  
  2165  func TestJobEndpoint_Evaluations_Blocking(t *testing.T) {
  2166  	t.Parallel()
  2167  	s1 := testServer(t, nil)
  2168  	defer s1.Shutdown()
  2169  	codec := rpcClient(t, s1)
  2170  	testutil.WaitForLeader(t, s1.RPC)
  2171  
  2172  	// Create the register request
  2173  	eval1 := mock.Eval()
  2174  	eval2 := mock.Eval()
  2175  	eval2.JobID = "job1"
  2176  	state := s1.fsm.State()
  2177  
  2178  	// First upsert an unrelated eval
  2179  	time.AfterFunc(100*time.Millisecond, func() {
  2180  		err := state.UpsertEvals(100, []*structs.Evaluation{eval1})
  2181  		if err != nil {
  2182  			t.Fatalf("err: %v", err)
  2183  		}
  2184  	})
  2185  
  2186  	// Upsert an eval for the job we are interested in later
  2187  	time.AfterFunc(200*time.Millisecond, func() {
  2188  		err := state.UpsertEvals(200, []*structs.Evaluation{eval2})
  2189  		if err != nil {
  2190  			t.Fatalf("err: %v", err)
  2191  		}
  2192  	})
  2193  
  2194  	// Lookup the jobs
  2195  	get := &structs.JobSpecificRequest{
  2196  		JobID: "job1",
  2197  		QueryOptions: structs.QueryOptions{
  2198  			Region:        "global",
  2199  			MinQueryIndex: 150,
  2200  		},
  2201  	}
  2202  	var resp structs.JobEvaluationsResponse
  2203  	start := time.Now()
  2204  	if err := msgpackrpc.CallWithCodec(codec, "Job.Evaluations", get, &resp); err != nil {
  2205  		t.Fatalf("err: %v", err)
  2206  	}
  2207  
  2208  	if elapsed := time.Since(start); elapsed < 200*time.Millisecond {
  2209  		t.Fatalf("should block (returned in %s) %#v", elapsed, resp)
  2210  	}
  2211  	if resp.Index != 200 {
  2212  		t.Fatalf("Bad index: %d %d", resp.Index, 200)
  2213  	}
  2214  	if len(resp.Evaluations) != 1 || resp.Evaluations[0].JobID != "job1" {
  2215  		t.Fatalf("bad: %#v", resp.Evaluations)
  2216  	}
  2217  }
  2218  
  2219  func TestJobEndpoint_Deployments(t *testing.T) {
  2220  	t.Parallel()
  2221  	s1 := testServer(t, nil)
  2222  	defer s1.Shutdown()
  2223  	codec := rpcClient(t, s1)
  2224  	testutil.WaitForLeader(t, s1.RPC)
  2225  	state := s1.fsm.State()
  2226  	assert := assert.New(t)
  2227  
  2228  	// Create the register request
  2229  	j := mock.Job()
  2230  	d1 := mock.Deployment()
  2231  	d2 := mock.Deployment()
  2232  	d1.JobID = j.ID
  2233  	d2.JobID = j.ID
  2234  	assert.Nil(state.UpsertJob(1000, j), "UpsertJob")
  2235  	assert.Nil(state.UpsertDeployment(1001, d1), "UpsertDeployment")
  2236  	assert.Nil(state.UpsertDeployment(1002, d2), "UpsertDeployment")
  2237  
  2238  	// Lookup the jobs
  2239  	get := &structs.JobSpecificRequest{
  2240  		JobID:        j.ID,
  2241  		QueryOptions: structs.QueryOptions{Region: "global"},
  2242  	}
  2243  	var resp structs.DeploymentListResponse
  2244  	assert.Nil(msgpackrpc.CallWithCodec(codec, "Job.Deployments", get, &resp), "RPC")
  2245  	assert.EqualValues(1002, resp.Index, "response index")
  2246  	assert.Len(resp.Deployments, 2, "deployments for job")
  2247  }
  2248  
  2249  func TestJobEndpoint_Deployments_Blocking(t *testing.T) {
  2250  	t.Parallel()
  2251  	s1 := testServer(t, nil)
  2252  	defer s1.Shutdown()
  2253  	codec := rpcClient(t, s1)
  2254  	testutil.WaitForLeader(t, s1.RPC)
  2255  	state := s1.fsm.State()
  2256  	assert := assert.New(t)
  2257  
  2258  	// Create the register request
  2259  	j := mock.Job()
  2260  	d1 := mock.Deployment()
  2261  	d2 := mock.Deployment()
  2262  	d2.JobID = j.ID
  2263  	assert.Nil(state.UpsertJob(50, j), "UpsertJob")
  2264  
  2265  	// First upsert an unrelated eval
  2266  	time.AfterFunc(100*time.Millisecond, func() {
  2267  		assert.Nil(state.UpsertDeployment(100, d1), "UpsertDeployment")
  2268  	})
  2269  
  2270  	// Upsert an eval for the job we are interested in later
  2271  	time.AfterFunc(200*time.Millisecond, func() {
  2272  		assert.Nil(state.UpsertDeployment(200, d2), "UpsertDeployment")
  2273  	})
  2274  
  2275  	// Lookup the jobs
  2276  	get := &structs.JobSpecificRequest{
  2277  		JobID: d2.JobID,
  2278  		QueryOptions: structs.QueryOptions{
  2279  			Region:        "global",
  2280  			MinQueryIndex: 150,
  2281  		},
  2282  	}
  2283  	var resp structs.DeploymentListResponse
  2284  	start := time.Now()
  2285  	assert.Nil(msgpackrpc.CallWithCodec(codec, "Job.Deployments", get, &resp), "RPC")
  2286  	assert.EqualValues(200, resp.Index, "response index")
  2287  	assert.Len(resp.Deployments, 1, "deployments for job")
  2288  	assert.Equal(d2.ID, resp.Deployments[0].ID, "returned deployment")
  2289  	if elapsed := time.Since(start); elapsed < 200*time.Millisecond {
  2290  		t.Fatalf("should block (returned in %s) %#v", elapsed, resp)
  2291  	}
  2292  }
  2293  
  2294  func TestJobEndpoint_LatestDeployment(t *testing.T) {
  2295  	t.Parallel()
  2296  	s1 := testServer(t, nil)
  2297  	defer s1.Shutdown()
  2298  	codec := rpcClient(t, s1)
  2299  	testutil.WaitForLeader(t, s1.RPC)
  2300  	state := s1.fsm.State()
  2301  	assert := assert.New(t)
  2302  
  2303  	// Create the register request
  2304  	j := mock.Job()
  2305  	d1 := mock.Deployment()
  2306  	d2 := mock.Deployment()
  2307  	d1.JobID = j.ID
  2308  	d2.JobID = j.ID
  2309  	d2.CreateIndex = d1.CreateIndex + 100
  2310  	d2.ModifyIndex = d2.CreateIndex + 100
  2311  	assert.Nil(state.UpsertJob(1000, j), "UpsertJob")
  2312  	assert.Nil(state.UpsertDeployment(1001, d1), "UpsertDeployment")
  2313  	assert.Nil(state.UpsertDeployment(1002, d2), "UpsertDeployment")
  2314  
  2315  	// Lookup the jobs
  2316  	get := &structs.JobSpecificRequest{
  2317  		JobID:        j.ID,
  2318  		QueryOptions: structs.QueryOptions{Region: "global"},
  2319  	}
  2320  	var resp structs.SingleDeploymentResponse
  2321  	assert.Nil(msgpackrpc.CallWithCodec(codec, "Job.LatestDeployment", get, &resp), "RPC")
  2322  	assert.EqualValues(1002, resp.Index, "response index")
  2323  	assert.NotNil(resp.Deployment, "want a deployment")
  2324  	assert.Equal(d2.ID, resp.Deployment.ID, "latest deployment for job")
  2325  }
  2326  
  2327  func TestJobEndpoint_LatestDeployment_Blocking(t *testing.T) {
  2328  	t.Parallel()
  2329  	s1 := testServer(t, nil)
  2330  	defer s1.Shutdown()
  2331  	codec := rpcClient(t, s1)
  2332  	testutil.WaitForLeader(t, s1.RPC)
  2333  	state := s1.fsm.State()
  2334  	assert := assert.New(t)
  2335  
  2336  	// Create the register request
  2337  	j := mock.Job()
  2338  	d1 := mock.Deployment()
  2339  	d2 := mock.Deployment()
  2340  	d2.JobID = j.ID
  2341  	assert.Nil(state.UpsertJob(50, j), "UpsertJob")
  2342  
  2343  	// First upsert an unrelated eval
  2344  	time.AfterFunc(100*time.Millisecond, func() {
  2345  		assert.Nil(state.UpsertDeployment(100, d1), "UpsertDeployment")
  2346  	})
  2347  
  2348  	// Upsert an eval for the job we are interested in later
  2349  	time.AfterFunc(200*time.Millisecond, func() {
  2350  		assert.Nil(state.UpsertDeployment(200, d2), "UpsertDeployment")
  2351  	})
  2352  
  2353  	// Lookup the jobs
  2354  	get := &structs.JobSpecificRequest{
  2355  		JobID: d2.JobID,
  2356  		QueryOptions: structs.QueryOptions{
  2357  			Region:        "global",
  2358  			MinQueryIndex: 150,
  2359  		},
  2360  	}
  2361  	var resp structs.SingleDeploymentResponse
  2362  	start := time.Now()
  2363  	assert.Nil(msgpackrpc.CallWithCodec(codec, "Job.LatestDeployment", get, &resp), "RPC")
  2364  	assert.EqualValues(200, resp.Index, "response index")
  2365  	assert.NotNil(resp.Deployment, "deployment for job")
  2366  	assert.Equal(d2.ID, resp.Deployment.ID, "returned deployment")
  2367  	if elapsed := time.Since(start); elapsed < 200*time.Millisecond {
  2368  		t.Fatalf("should block (returned in %s) %#v", elapsed, resp)
  2369  	}
  2370  }
  2371  
  2372  func TestJobEndpoint_Plan_WithDiff(t *testing.T) {
  2373  	t.Parallel()
  2374  	s1 := testServer(t, func(c *Config) {
  2375  		c.NumSchedulers = 0 // Prevent automatic dequeue
  2376  	})
  2377  	defer s1.Shutdown()
  2378  	codec := rpcClient(t, s1)
  2379  	testutil.WaitForLeader(t, s1.RPC)
  2380  
  2381  	// Create the register request
  2382  	job := mock.Job()
  2383  	req := &structs.JobRegisterRequest{
  2384  		Job:          job,
  2385  		WriteRequest: structs.WriteRequest{Region: "global"},
  2386  	}
  2387  
  2388  	// Fetch the response
  2389  	var resp structs.JobRegisterResponse
  2390  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil {
  2391  		t.Fatalf("err: %v", err)
  2392  	}
  2393  	if resp.Index == 0 {
  2394  		t.Fatalf("bad index: %d", resp.Index)
  2395  	}
  2396  
  2397  	// Create a plan request
  2398  	planReq := &structs.JobPlanRequest{
  2399  		Job:          job,
  2400  		Diff:         true,
  2401  		WriteRequest: structs.WriteRequest{Region: "global"},
  2402  	}
  2403  
  2404  	// Fetch the response
  2405  	var planResp structs.JobPlanResponse
  2406  	if err := msgpackrpc.CallWithCodec(codec, "Job.Plan", planReq, &planResp); err != nil {
  2407  		t.Fatalf("err: %v", err)
  2408  	}
  2409  
  2410  	// Check the response
  2411  	if planResp.JobModifyIndex == 0 {
  2412  		t.Fatalf("bad cas: %d", planResp.JobModifyIndex)
  2413  	}
  2414  	if planResp.Annotations == nil {
  2415  		t.Fatalf("no annotations")
  2416  	}
  2417  	if planResp.Diff == nil {
  2418  		t.Fatalf("no diff")
  2419  	}
  2420  	if len(planResp.FailedTGAllocs) == 0 {
  2421  		t.Fatalf("no failed task group alloc metrics")
  2422  	}
  2423  }
  2424  
  2425  func TestJobEndpoint_Plan_NoDiff(t *testing.T) {
  2426  	t.Parallel()
  2427  	s1 := testServer(t, func(c *Config) {
  2428  		c.NumSchedulers = 0 // Prevent automatic dequeue
  2429  	})
  2430  	defer s1.Shutdown()
  2431  	codec := rpcClient(t, s1)
  2432  	testutil.WaitForLeader(t, s1.RPC)
  2433  
  2434  	// Create the register request
  2435  	job := mock.Job()
  2436  	req := &structs.JobRegisterRequest{
  2437  		Job:          job,
  2438  		WriteRequest: structs.WriteRequest{Region: "global"},
  2439  	}
  2440  
  2441  	// Fetch the response
  2442  	var resp structs.JobRegisterResponse
  2443  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil {
  2444  		t.Fatalf("err: %v", err)
  2445  	}
  2446  	if resp.Index == 0 {
  2447  		t.Fatalf("bad index: %d", resp.Index)
  2448  	}
  2449  
  2450  	// Create a plan request
  2451  	planReq := &structs.JobPlanRequest{
  2452  		Job:          job,
  2453  		Diff:         false,
  2454  		WriteRequest: structs.WriteRequest{Region: "global"},
  2455  	}
  2456  
  2457  	// Fetch the response
  2458  	var planResp structs.JobPlanResponse
  2459  	if err := msgpackrpc.CallWithCodec(codec, "Job.Plan", planReq, &planResp); err != nil {
  2460  		t.Fatalf("err: %v", err)
  2461  	}
  2462  
  2463  	// Check the response
  2464  	if planResp.JobModifyIndex == 0 {
  2465  		t.Fatalf("bad cas: %d", planResp.JobModifyIndex)
  2466  	}
  2467  	if planResp.Annotations == nil {
  2468  		t.Fatalf("no annotations")
  2469  	}
  2470  	if planResp.Diff != nil {
  2471  		t.Fatalf("got diff")
  2472  	}
  2473  	if len(planResp.FailedTGAllocs) == 0 {
  2474  		t.Fatalf("no failed task group alloc metrics")
  2475  	}
  2476  }
  2477  
  2478  func TestJobEndpoint_ImplicitConstraints_Vault(t *testing.T) {
  2479  	t.Parallel()
  2480  	s1 := testServer(t, func(c *Config) {
  2481  		c.NumSchedulers = 0 // Prevent automatic dequeue
  2482  	})
  2483  	defer s1.Shutdown()
  2484  	codec := rpcClient(t, s1)
  2485  	testutil.WaitForLeader(t, s1.RPC)
  2486  
  2487  	// Enable vault
  2488  	tr, f := true, false
  2489  	s1.config.VaultConfig.Enabled = &tr
  2490  	s1.config.VaultConfig.AllowUnauthenticated = &f
  2491  
  2492  	// Replace the Vault Client on the server
  2493  	tvc := &TestVaultClient{}
  2494  	s1.vault = tvc
  2495  
  2496  	policy := "foo"
  2497  	goodToken := structs.GenerateUUID()
  2498  	goodPolicies := []string{"foo", "bar", "baz"}
  2499  	tvc.SetLookupTokenAllowedPolicies(goodToken, goodPolicies)
  2500  
  2501  	// Create the register request with a job asking for a vault policy
  2502  	job := mock.Job()
  2503  	job.VaultToken = goodToken
  2504  	job.TaskGroups[0].Tasks[0].Vault = &structs.Vault{
  2505  		Policies:   []string{policy},
  2506  		ChangeMode: structs.VaultChangeModeRestart,
  2507  	}
  2508  	req := &structs.JobRegisterRequest{
  2509  		Job:          job,
  2510  		WriteRequest: structs.WriteRequest{Region: "global"},
  2511  	}
  2512  
  2513  	// Fetch the response
  2514  	var resp structs.JobRegisterResponse
  2515  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil {
  2516  		t.Fatalf("bad: %v", err)
  2517  	}
  2518  
  2519  	// Check for the job in the FSM
  2520  	state := s1.fsm.State()
  2521  	ws := memdb.NewWatchSet()
  2522  	out, err := state.JobByID(ws, job.ID)
  2523  	if err != nil {
  2524  		t.Fatalf("err: %v", err)
  2525  	}
  2526  	if out == nil {
  2527  		t.Fatalf("expected job")
  2528  	}
  2529  	if out.CreateIndex != resp.JobModifyIndex {
  2530  		t.Fatalf("index mis-match")
  2531  	}
  2532  
  2533  	// Check that there is an implicit vault constraint
  2534  	constraints := out.TaskGroups[0].Constraints
  2535  	if len(constraints) != 1 {
  2536  		t.Fatalf("Expected an implicit constraint")
  2537  	}
  2538  
  2539  	if !constraints[0].Equal(vaultConstraint) {
  2540  		t.Fatalf("Expected implicit vault constraint")
  2541  	}
  2542  }
  2543  
  2544  func TestJobEndpoint_ImplicitConstraints_Signals(t *testing.T) {
  2545  	t.Parallel()
  2546  	s1 := testServer(t, func(c *Config) {
  2547  		c.NumSchedulers = 0 // Prevent automatic dequeue
  2548  	})
  2549  	defer s1.Shutdown()
  2550  	codec := rpcClient(t, s1)
  2551  	testutil.WaitForLeader(t, s1.RPC)
  2552  
  2553  	// Create the register request with a job asking for a template that sends a
  2554  	// signal
  2555  	job := mock.Job()
  2556  	signal := "SIGUSR1"
  2557  	job.TaskGroups[0].Tasks[0].Templates = []*structs.Template{
  2558  		&structs.Template{
  2559  			SourcePath:   "foo",
  2560  			DestPath:     "bar",
  2561  			ChangeMode:   structs.TemplateChangeModeSignal,
  2562  			ChangeSignal: signal,
  2563  		},
  2564  	}
  2565  	req := &structs.JobRegisterRequest{
  2566  		Job:          job,
  2567  		WriteRequest: structs.WriteRequest{Region: "global"},
  2568  	}
  2569  
  2570  	// Fetch the response
  2571  	var resp structs.JobRegisterResponse
  2572  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil {
  2573  		t.Fatalf("bad: %v", err)
  2574  	}
  2575  
  2576  	// Check for the job in the FSM
  2577  	state := s1.fsm.State()
  2578  	ws := memdb.NewWatchSet()
  2579  	out, err := state.JobByID(ws, job.ID)
  2580  	if err != nil {
  2581  		t.Fatalf("err: %v", err)
  2582  	}
  2583  	if out == nil {
  2584  		t.Fatalf("expected job")
  2585  	}
  2586  	if out.CreateIndex != resp.JobModifyIndex {
  2587  		t.Fatalf("index mis-match")
  2588  	}
  2589  
  2590  	// Check that there is an implicit signal constraint
  2591  	constraints := out.TaskGroups[0].Constraints
  2592  	if len(constraints) != 1 {
  2593  		t.Fatalf("Expected an implicit constraint")
  2594  	}
  2595  
  2596  	sigConstraint := getSignalConstraint([]string{signal})
  2597  
  2598  	if !constraints[0].Equal(sigConstraint) {
  2599  		t.Fatalf("Expected implicit vault constraint")
  2600  	}
  2601  }
  2602  
  2603  func TestJobEndpoint_ValidateJob_InvalidDriverConf(t *testing.T) {
  2604  	t.Parallel()
  2605  	// Create a mock job with an invalid config
  2606  	job := mock.Job()
  2607  	job.TaskGroups[0].Tasks[0].Config = map[string]interface{}{
  2608  		"foo": "bar",
  2609  	}
  2610  
  2611  	err, warnings := validateJob(job)
  2612  	if err == nil || !strings.Contains(err.Error(), "-> config") {
  2613  		t.Fatalf("Expected config error; got %v", err)
  2614  	}
  2615  
  2616  	if warnings != nil {
  2617  		t.Fatalf("got unexpected warnings: %v", warnings)
  2618  	}
  2619  }
  2620  
  2621  func TestJobEndpoint_ValidateJob_InvalidSignals(t *testing.T) {
  2622  	t.Parallel()
  2623  	// Create a mock job that wants to send a signal to a driver that can't
  2624  	job := mock.Job()
  2625  	job.TaskGroups[0].Tasks[0].Driver = "qemu"
  2626  	job.TaskGroups[0].Tasks[0].Vault = &structs.Vault{
  2627  		Policies:     []string{"foo"},
  2628  		ChangeMode:   structs.VaultChangeModeSignal,
  2629  		ChangeSignal: "SIGUSR1",
  2630  	}
  2631  
  2632  	err, warnings := validateJob(job)
  2633  	if err == nil || !strings.Contains(err.Error(), "support sending signals") {
  2634  		t.Fatalf("Expected signal feasibility error; got %v", err)
  2635  	}
  2636  
  2637  	if warnings != nil {
  2638  		t.Fatalf("got unexpected warnings: %v", warnings)
  2639  	}
  2640  }
  2641  
  2642  func TestJobEndpoint_ValidateJobUpdate(t *testing.T) {
  2643  	t.Parallel()
  2644  	old := mock.Job()
  2645  	new := mock.Job()
  2646  
  2647  	if err := validateJobUpdate(old, new); err != nil {
  2648  		t.Errorf("expected update to be valid but got: %v", err)
  2649  	}
  2650  
  2651  	new.Type = "batch"
  2652  	if err := validateJobUpdate(old, new); err == nil {
  2653  		t.Errorf("expected err when setting new job to a different type")
  2654  	} else {
  2655  		t.Log(err)
  2656  	}
  2657  
  2658  	new = mock.Job()
  2659  	new.Periodic = &structs.PeriodicConfig{Enabled: true}
  2660  	if err := validateJobUpdate(old, new); err == nil {
  2661  		t.Errorf("expected err when setting new job to periodic")
  2662  	} else {
  2663  		t.Log(err)
  2664  	}
  2665  
  2666  	new = mock.Job()
  2667  	new.ParameterizedJob = &structs.ParameterizedJobConfig{}
  2668  	if err := validateJobUpdate(old, new); err == nil {
  2669  		t.Errorf("expected err when setting new job to parameterized")
  2670  	} else {
  2671  		t.Log(err)
  2672  	}
  2673  }
  2674  
  2675  func TestJobEndpoint_Dispatch(t *testing.T) {
  2676  	t.Parallel()
  2677  
  2678  	// No requirements
  2679  	d1 := mock.Job()
  2680  	d1.Type = structs.JobTypeBatch
  2681  	d1.ParameterizedJob = &structs.ParameterizedJobConfig{}
  2682  
  2683  	// Require input data
  2684  	d2 := mock.Job()
  2685  	d2.Type = structs.JobTypeBatch
  2686  	d2.ParameterizedJob = &structs.ParameterizedJobConfig{
  2687  		Payload: structs.DispatchPayloadRequired,
  2688  	}
  2689  
  2690  	// Disallow input data
  2691  	d3 := mock.Job()
  2692  	d3.Type = structs.JobTypeBatch
  2693  	d3.ParameterizedJob = &structs.ParameterizedJobConfig{
  2694  		Payload: structs.DispatchPayloadForbidden,
  2695  	}
  2696  
  2697  	// Require meta
  2698  	d4 := mock.Job()
  2699  	d4.Type = structs.JobTypeBatch
  2700  	d4.ParameterizedJob = &structs.ParameterizedJobConfig{
  2701  		MetaRequired: []string{"foo", "bar"},
  2702  	}
  2703  
  2704  	// Optional meta
  2705  	d5 := mock.Job()
  2706  	d5.Type = structs.JobTypeBatch
  2707  	d5.ParameterizedJob = &structs.ParameterizedJobConfig{
  2708  		MetaOptional: []string{"foo", "bar"},
  2709  	}
  2710  
  2711  	// Periodic dispatch job
  2712  	d6 := mock.PeriodicJob()
  2713  	d6.ParameterizedJob = &structs.ParameterizedJobConfig{}
  2714  
  2715  	d7 := mock.Job()
  2716  	d7.Type = structs.JobTypeBatch
  2717  	d7.ParameterizedJob = &structs.ParameterizedJobConfig{}
  2718  	d7.Stop = true
  2719  
  2720  	reqNoInputNoMeta := &structs.JobDispatchRequest{}
  2721  	reqInputDataNoMeta := &structs.JobDispatchRequest{
  2722  		Payload: []byte("hello world"),
  2723  	}
  2724  	reqNoInputDataMeta := &structs.JobDispatchRequest{
  2725  		Meta: map[string]string{
  2726  			"foo": "f1",
  2727  			"bar": "f2",
  2728  		},
  2729  	}
  2730  	reqInputDataMeta := &structs.JobDispatchRequest{
  2731  		Payload: []byte("hello world"),
  2732  		Meta: map[string]string{
  2733  			"foo": "f1",
  2734  			"bar": "f2",
  2735  		},
  2736  	}
  2737  	reqBadMeta := &structs.JobDispatchRequest{
  2738  		Payload: []byte("hello world"),
  2739  		Meta: map[string]string{
  2740  			"foo": "f1",
  2741  			"bar": "f2",
  2742  			"baz": "f3",
  2743  		},
  2744  	}
  2745  	reqInputDataTooLarge := &structs.JobDispatchRequest{
  2746  		Payload: make([]byte, DispatchPayloadSizeLimit+100),
  2747  	}
  2748  
  2749  	type testCase struct {
  2750  		name             string
  2751  		parameterizedJob *structs.Job
  2752  		dispatchReq      *structs.JobDispatchRequest
  2753  		noEval           bool
  2754  		err              bool
  2755  		errStr           string
  2756  	}
  2757  	cases := []testCase{
  2758  		{
  2759  			name:             "optional input data w/ data",
  2760  			parameterizedJob: d1,
  2761  			dispatchReq:      reqInputDataNoMeta,
  2762  			err:              false,
  2763  		},
  2764  		{
  2765  			name:             "optional input data w/o data",
  2766  			parameterizedJob: d1,
  2767  			dispatchReq:      reqNoInputNoMeta,
  2768  			err:              false,
  2769  		},
  2770  		{
  2771  			name:             "require input data w/ data",
  2772  			parameterizedJob: d2,
  2773  			dispatchReq:      reqInputDataNoMeta,
  2774  			err:              false,
  2775  		},
  2776  		{
  2777  			name:             "require input data w/o data",
  2778  			parameterizedJob: d2,
  2779  			dispatchReq:      reqNoInputNoMeta,
  2780  			err:              true,
  2781  			errStr:           "not provided but required",
  2782  		},
  2783  		{
  2784  			name:             "disallow input data w/o data",
  2785  			parameterizedJob: d3,
  2786  			dispatchReq:      reqNoInputNoMeta,
  2787  			err:              false,
  2788  		},
  2789  		{
  2790  			name:             "disallow input data w/ data",
  2791  			parameterizedJob: d3,
  2792  			dispatchReq:      reqInputDataNoMeta,
  2793  			err:              true,
  2794  			errStr:           "provided but forbidden",
  2795  		},
  2796  		{
  2797  			name:             "require meta w/ meta",
  2798  			parameterizedJob: d4,
  2799  			dispatchReq:      reqInputDataMeta,
  2800  			err:              false,
  2801  		},
  2802  		{
  2803  			name:             "require meta w/o meta",
  2804  			parameterizedJob: d4,
  2805  			dispatchReq:      reqNoInputNoMeta,
  2806  			err:              true,
  2807  			errStr:           "did not provide required meta keys",
  2808  		},
  2809  		{
  2810  			name:             "optional meta w/ meta",
  2811  			parameterizedJob: d5,
  2812  			dispatchReq:      reqNoInputDataMeta,
  2813  			err:              false,
  2814  		},
  2815  		{
  2816  			name:             "optional meta w/o meta",
  2817  			parameterizedJob: d5,
  2818  			dispatchReq:      reqNoInputNoMeta,
  2819  			err:              false,
  2820  		},
  2821  		{
  2822  			name:             "optional meta w/ bad meta",
  2823  			parameterizedJob: d5,
  2824  			dispatchReq:      reqBadMeta,
  2825  			err:              true,
  2826  			errStr:           "unpermitted metadata keys",
  2827  		},
  2828  		{
  2829  			name:             "optional input w/ too big of input",
  2830  			parameterizedJob: d1,
  2831  			dispatchReq:      reqInputDataTooLarge,
  2832  			err:              true,
  2833  			errStr:           "Payload exceeds maximum size",
  2834  		},
  2835  		{
  2836  			name:             "periodic job dispatched, ensure no eval",
  2837  			parameterizedJob: d6,
  2838  			dispatchReq:      reqNoInputNoMeta,
  2839  			noEval:           true,
  2840  		},
  2841  		{
  2842  			name:             "periodic job stopped, ensure error",
  2843  			parameterizedJob: d7,
  2844  			dispatchReq:      reqNoInputNoMeta,
  2845  			err:              true,
  2846  			errStr:           "stopped",
  2847  		},
  2848  	}
  2849  
  2850  	for _, tc := range cases {
  2851  		t.Run(tc.name, func(t *testing.T) {
  2852  			s1 := testServer(t, func(c *Config) {
  2853  				c.NumSchedulers = 0 // Prevent automatic dequeue
  2854  			})
  2855  			defer s1.Shutdown()
  2856  			codec := rpcClient(t, s1)
  2857  			testutil.WaitForLeader(t, s1.RPC)
  2858  
  2859  			// Create the register request
  2860  			regReq := &structs.JobRegisterRequest{
  2861  				Job:          tc.parameterizedJob,
  2862  				WriteRequest: structs.WriteRequest{Region: "global"},
  2863  			}
  2864  
  2865  			// Fetch the response
  2866  			var regResp structs.JobRegisterResponse
  2867  			if err := msgpackrpc.CallWithCodec(codec, "Job.Register", regReq, &regResp); err != nil {
  2868  				t.Fatalf("err: %v", err)
  2869  			}
  2870  
  2871  			// Now try to dispatch
  2872  			tc.dispatchReq.JobID = tc.parameterizedJob.ID
  2873  			tc.dispatchReq.WriteRequest = structs.WriteRequest{Region: "global"}
  2874  
  2875  			var dispatchResp structs.JobDispatchResponse
  2876  			dispatchErr := msgpackrpc.CallWithCodec(codec, "Job.Dispatch", tc.dispatchReq, &dispatchResp)
  2877  
  2878  			if dispatchErr == nil {
  2879  				if tc.err {
  2880  					t.Fatalf("Expected error: %v", dispatchErr)
  2881  				}
  2882  
  2883  				// Check that we got an eval and job id back
  2884  				switch dispatchResp.EvalID {
  2885  				case "":
  2886  					if !tc.noEval {
  2887  						t.Fatalf("Bad response")
  2888  					}
  2889  				default:
  2890  					if tc.noEval {
  2891  						t.Fatalf("Got eval %q", dispatchResp.EvalID)
  2892  					}
  2893  				}
  2894  
  2895  				if dispatchResp.DispatchedJobID == "" {
  2896  					t.Fatalf("Bad response")
  2897  				}
  2898  
  2899  				state := s1.fsm.State()
  2900  				ws := memdb.NewWatchSet()
  2901  				out, err := state.JobByID(ws, dispatchResp.DispatchedJobID)
  2902  				if err != nil {
  2903  					t.Fatalf("err: %v", err)
  2904  				}
  2905  				if out == nil {
  2906  					t.Fatalf("expected job")
  2907  				}
  2908  				if out.CreateIndex != dispatchResp.JobCreateIndex {
  2909  					t.Fatalf("index mis-match")
  2910  				}
  2911  				if out.ParentID != tc.parameterizedJob.ID {
  2912  					t.Fatalf("bad parent ID")
  2913  				}
  2914  
  2915  				if tc.noEval {
  2916  					return
  2917  				}
  2918  
  2919  				// Lookup the evaluation
  2920  				eval, err := state.EvalByID(ws, dispatchResp.EvalID)
  2921  				if err != nil {
  2922  					t.Fatalf("err: %v", err)
  2923  				}
  2924  
  2925  				if eval == nil {
  2926  					t.Fatalf("expected eval")
  2927  				}
  2928  				if eval.CreateIndex != dispatchResp.EvalCreateIndex {
  2929  					t.Fatalf("index mis-match")
  2930  				}
  2931  			} else {
  2932  				if !tc.err {
  2933  					t.Fatalf("Got unexpected error: %v", dispatchErr)
  2934  				} else if !strings.Contains(dispatchErr.Error(), tc.errStr) {
  2935  					t.Fatalf("Expected err to include %q; got %v", tc.errStr, dispatchErr)
  2936  				}
  2937  			}
  2938  		})
  2939  	}
  2940  }