github.com/maier/nomad@v0.4.1-0.20161110003312-a9e3d0b8549d/nomad/job_endpoint_test.go (about)

     1  package nomad
     2  
     3  import (
     4  	"fmt"
     5  	"reflect"
     6  	"strings"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/hashicorp/net-rpc-msgpackrpc"
    11  	"github.com/hashicorp/nomad/nomad/mock"
    12  	"github.com/hashicorp/nomad/nomad/structs"
    13  	"github.com/hashicorp/nomad/testutil"
    14  )
    15  
    16  func TestJobEndpoint_Register(t *testing.T) {
    17  	s1 := testServer(t, func(c *Config) {
    18  		c.NumSchedulers = 0 // Prevent automatic dequeue
    19  	})
    20  	defer s1.Shutdown()
    21  	codec := rpcClient(t, s1)
    22  	testutil.WaitForLeader(t, s1.RPC)
    23  
    24  	// Create the register request
    25  	job := mock.Job()
    26  	req := &structs.JobRegisterRequest{
    27  		Job:          job,
    28  		WriteRequest: structs.WriteRequest{Region: "global"},
    29  	}
    30  
    31  	// Fetch the response
    32  	var resp structs.JobRegisterResponse
    33  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil {
    34  		t.Fatalf("err: %v", err)
    35  	}
    36  	if resp.Index == 0 {
    37  		t.Fatalf("bad index: %d", resp.Index)
    38  	}
    39  
    40  	// Check for the node in the FSM
    41  	state := s1.fsm.State()
    42  	out, err := state.JobByID(job.ID)
    43  	if err != nil {
    44  		t.Fatalf("err: %v", err)
    45  	}
    46  	if out == nil {
    47  		t.Fatalf("expected job")
    48  	}
    49  	if out.CreateIndex != resp.JobModifyIndex {
    50  		t.Fatalf("index mis-match")
    51  	}
    52  	serviceName := out.TaskGroups[0].Tasks[0].Services[0].Name
    53  	expectedServiceName := "web-frontend"
    54  	if serviceName != expectedServiceName {
    55  		t.Fatalf("Expected Service Name: %s, Actual: %s", expectedServiceName, serviceName)
    56  	}
    57  
    58  	// Lookup the evaluation
    59  	eval, err := state.EvalByID(resp.EvalID)
    60  	if err != nil {
    61  		t.Fatalf("err: %v", err)
    62  	}
    63  	if eval == nil {
    64  		t.Fatalf("expected eval")
    65  	}
    66  	if eval.CreateIndex != resp.EvalCreateIndex {
    67  		t.Fatalf("index mis-match")
    68  	}
    69  
    70  	if eval.Priority != job.Priority {
    71  		t.Fatalf("bad: %#v", eval)
    72  	}
    73  	if eval.Type != job.Type {
    74  		t.Fatalf("bad: %#v", eval)
    75  	}
    76  	if eval.TriggeredBy != structs.EvalTriggerJobRegister {
    77  		t.Fatalf("bad: %#v", eval)
    78  	}
    79  	if eval.JobID != job.ID {
    80  		t.Fatalf("bad: %#v", eval)
    81  	}
    82  	if eval.JobModifyIndex != resp.JobModifyIndex {
    83  		t.Fatalf("bad: %#v", eval)
    84  	}
    85  	if eval.Status != structs.EvalStatusPending {
    86  		t.Fatalf("bad: %#v", eval)
    87  	}
    88  }
    89  
    90  func TestJobEndpoint_Register_InvalidDriverConfig(t *testing.T) {
    91  	s1 := testServer(t, func(c *Config) {
    92  		c.NumSchedulers = 0 // Prevent automatic dequeue
    93  	})
    94  	defer s1.Shutdown()
    95  	codec := rpcClient(t, s1)
    96  	testutil.WaitForLeader(t, s1.RPC)
    97  
    98  	// Create the register request with a job containing an invalid driver
    99  	// config
   100  	job := mock.Job()
   101  	job.TaskGroups[0].Tasks[0].Config["foo"] = 1
   102  	req := &structs.JobRegisterRequest{
   103  		Job:          job,
   104  		WriteRequest: structs.WriteRequest{Region: "global"},
   105  	}
   106  
   107  	// Fetch the response
   108  	var resp structs.JobRegisterResponse
   109  	err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp)
   110  	if err == nil {
   111  		t.Fatalf("expected a validation error")
   112  	}
   113  
   114  	if !strings.Contains(err.Error(), "-> config:") {
   115  		t.Fatalf("expected a driver config validation error but got: %v", err)
   116  	}
   117  }
   118  
   119  func TestJobEndpoint_Register_Existing(t *testing.T) {
   120  	s1 := testServer(t, func(c *Config) {
   121  		c.NumSchedulers = 0 // Prevent automatic dequeue
   122  	})
   123  	defer s1.Shutdown()
   124  	codec := rpcClient(t, s1)
   125  	testutil.WaitForLeader(t, s1.RPC)
   126  
   127  	// Create the register request
   128  	job := mock.Job()
   129  	req := &structs.JobRegisterRequest{
   130  		Job:          job,
   131  		WriteRequest: structs.WriteRequest{Region: "global"},
   132  	}
   133  
   134  	// Fetch the response
   135  	var resp structs.JobRegisterResponse
   136  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil {
   137  		t.Fatalf("err: %v", err)
   138  	}
   139  	if resp.Index == 0 {
   140  		t.Fatalf("bad index: %d", resp.Index)
   141  	}
   142  
   143  	// Update the job definition
   144  	job2 := mock.Job()
   145  	job2.Priority = 100
   146  	job2.ID = job.ID
   147  	req.Job = job2
   148  
   149  	// Attempt update
   150  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil {
   151  		t.Fatalf("err: %v", err)
   152  	}
   153  	if resp.Index == 0 {
   154  		t.Fatalf("bad index: %d", resp.Index)
   155  	}
   156  
   157  	// Check for the node in the FSM
   158  	state := s1.fsm.State()
   159  	out, err := state.JobByID(job.ID)
   160  	if err != nil {
   161  		t.Fatalf("err: %v", err)
   162  	}
   163  	if out == nil {
   164  		t.Fatalf("expected job")
   165  	}
   166  	if out.ModifyIndex != resp.JobModifyIndex {
   167  		t.Fatalf("index mis-match")
   168  	}
   169  	if out.Priority != 100 {
   170  		t.Fatalf("expected update")
   171  	}
   172  
   173  	// Lookup the evaluation
   174  	eval, err := state.EvalByID(resp.EvalID)
   175  	if err != nil {
   176  		t.Fatalf("err: %v", err)
   177  	}
   178  	if eval == nil {
   179  		t.Fatalf("expected eval")
   180  	}
   181  	if eval.CreateIndex != resp.EvalCreateIndex {
   182  		t.Fatalf("index mis-match")
   183  	}
   184  
   185  	if eval.Priority != job2.Priority {
   186  		t.Fatalf("bad: %#v", eval)
   187  	}
   188  	if eval.Type != job2.Type {
   189  		t.Fatalf("bad: %#v", eval)
   190  	}
   191  	if eval.TriggeredBy != structs.EvalTriggerJobRegister {
   192  		t.Fatalf("bad: %#v", eval)
   193  	}
   194  	if eval.JobID != job2.ID {
   195  		t.Fatalf("bad: %#v", eval)
   196  	}
   197  	if eval.JobModifyIndex != resp.JobModifyIndex {
   198  		t.Fatalf("bad: %#v", eval)
   199  	}
   200  	if eval.Status != structs.EvalStatusPending {
   201  		t.Fatalf("bad: %#v", eval)
   202  	}
   203  }
   204  
   205  func TestJobEndpoint_Register_Periodic(t *testing.T) {
   206  	s1 := testServer(t, func(c *Config) {
   207  		c.NumSchedulers = 0 // Prevent automatic dequeue
   208  	})
   209  	defer s1.Shutdown()
   210  	codec := rpcClient(t, s1)
   211  	testutil.WaitForLeader(t, s1.RPC)
   212  
   213  	// Create the register request for a periodic job.
   214  	job := mock.PeriodicJob()
   215  	req := &structs.JobRegisterRequest{
   216  		Job:          job,
   217  		WriteRequest: structs.WriteRequest{Region: "global"},
   218  	}
   219  
   220  	// Fetch the response
   221  	var resp structs.JobRegisterResponse
   222  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil {
   223  		t.Fatalf("err: %v", err)
   224  	}
   225  	if resp.JobModifyIndex == 0 {
   226  		t.Fatalf("bad index: %d", resp.Index)
   227  	}
   228  
   229  	// Check for the node in the FSM
   230  	state := s1.fsm.State()
   231  	out, err := state.JobByID(job.ID)
   232  	if err != nil {
   233  		t.Fatalf("err: %v", err)
   234  	}
   235  	if out == nil {
   236  		t.Fatalf("expected job")
   237  	}
   238  	if out.CreateIndex != resp.JobModifyIndex {
   239  		t.Fatalf("index mis-match")
   240  	}
   241  	serviceName := out.TaskGroups[0].Tasks[0].Services[0].Name
   242  	expectedServiceName := "web-frontend"
   243  	if serviceName != expectedServiceName {
   244  		t.Fatalf("Expected Service Name: %s, Actual: %s", expectedServiceName, serviceName)
   245  	}
   246  
   247  	if resp.EvalID != "" {
   248  		t.Fatalf("Register created an eval for a periodic job")
   249  	}
   250  }
   251  
   252  func TestJobEndpoint_Register_EnforceIndex(t *testing.T) {
   253  	s1 := testServer(t, func(c *Config) {
   254  		c.NumSchedulers = 0 // Prevent automatic dequeue
   255  	})
   256  	defer s1.Shutdown()
   257  	codec := rpcClient(t, s1)
   258  	testutil.WaitForLeader(t, s1.RPC)
   259  
   260  	// Create the register request and enforcing an incorrect index
   261  	job := mock.Job()
   262  	req := &structs.JobRegisterRequest{
   263  		Job:            job,
   264  		EnforceIndex:   true,
   265  		JobModifyIndex: 100, // Not registered yet so not possible
   266  		WriteRequest:   structs.WriteRequest{Region: "global"},
   267  	}
   268  
   269  	// Fetch the response
   270  	var resp structs.JobRegisterResponse
   271  	err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp)
   272  	if err == nil || !strings.Contains(err.Error(), RegisterEnforceIndexErrPrefix) {
   273  		t.Fatalf("expected enforcement error")
   274  	}
   275  
   276  	// Create the register request and enforcing it is new
   277  	req = &structs.JobRegisterRequest{
   278  		Job:            job,
   279  		EnforceIndex:   true,
   280  		JobModifyIndex: 0,
   281  		WriteRequest:   structs.WriteRequest{Region: "global"},
   282  	}
   283  
   284  	// Fetch the response
   285  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil {
   286  		t.Fatalf("err: %v", err)
   287  	}
   288  	if resp.Index == 0 {
   289  		t.Fatalf("bad index: %d", resp.Index)
   290  	}
   291  
   292  	curIndex := resp.JobModifyIndex
   293  
   294  	// Check for the node in the FSM
   295  	state := s1.fsm.State()
   296  	out, err := state.JobByID(job.ID)
   297  	if err != nil {
   298  		t.Fatalf("err: %v", err)
   299  	}
   300  	if out == nil {
   301  		t.Fatalf("expected job")
   302  	}
   303  	if out.CreateIndex != resp.JobModifyIndex {
   304  		t.Fatalf("index mis-match")
   305  	}
   306  
   307  	// Reregister request and enforcing it be a new job
   308  	req = &structs.JobRegisterRequest{
   309  		Job:            job,
   310  		EnforceIndex:   true,
   311  		JobModifyIndex: 0,
   312  		WriteRequest:   structs.WriteRequest{Region: "global"},
   313  	}
   314  
   315  	// Fetch the response
   316  	err = msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp)
   317  	if err == nil || !strings.Contains(err.Error(), RegisterEnforceIndexErrPrefix) {
   318  		t.Fatalf("expected enforcement error")
   319  	}
   320  
   321  	// Reregister request and enforcing it be at an incorrect index
   322  	req = &structs.JobRegisterRequest{
   323  		Job:            job,
   324  		EnforceIndex:   true,
   325  		JobModifyIndex: curIndex - 1,
   326  		WriteRequest:   structs.WriteRequest{Region: "global"},
   327  	}
   328  
   329  	// Fetch the response
   330  	err = msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp)
   331  	if err == nil || !strings.Contains(err.Error(), RegisterEnforceIndexErrPrefix) {
   332  		t.Fatalf("expected enforcement error")
   333  	}
   334  
   335  	// Reregister request and enforcing it be at the correct index
   336  	job.Priority = job.Priority + 1
   337  	req = &structs.JobRegisterRequest{
   338  		Job:            job,
   339  		EnforceIndex:   true,
   340  		JobModifyIndex: curIndex,
   341  		WriteRequest:   structs.WriteRequest{Region: "global"},
   342  	}
   343  
   344  	// Fetch the response
   345  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil {
   346  		t.Fatalf("err: %v", err)
   347  	}
   348  	if resp.Index == 0 {
   349  		t.Fatalf("bad index: %d", resp.Index)
   350  	}
   351  
   352  	out, err = state.JobByID(job.ID)
   353  	if err != nil {
   354  		t.Fatalf("err: %v", err)
   355  	}
   356  	if out == nil {
   357  		t.Fatalf("expected job")
   358  	}
   359  	if out.Priority != job.Priority {
   360  		t.Fatalf("priority mis-match")
   361  	}
   362  }
   363  
   364  func TestJobEndpoint_Register_Vault_Disabled(t *testing.T) {
   365  	s1 := testServer(t, func(c *Config) {
   366  		c.NumSchedulers = 0 // Prevent automatic dequeue
   367  		f := false
   368  		c.VaultConfig.Enabled = &f
   369  	})
   370  	defer s1.Shutdown()
   371  	codec := rpcClient(t, s1)
   372  	testutil.WaitForLeader(t, s1.RPC)
   373  
   374  	// Create the register request with a job asking for a vault policy
   375  	job := mock.Job()
   376  	job.TaskGroups[0].Tasks[0].Vault = &structs.Vault{
   377  		Policies:   []string{"foo"},
   378  		ChangeMode: structs.VaultChangeModeRestart,
   379  	}
   380  	req := &structs.JobRegisterRequest{
   381  		Job:          job,
   382  		WriteRequest: structs.WriteRequest{Region: "global"},
   383  	}
   384  
   385  	// Fetch the response
   386  	var resp structs.JobRegisterResponse
   387  	err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp)
   388  	if err == nil || !strings.Contains(err.Error(), "Vault not enabled") {
   389  		t.Fatalf("expected Vault not enabled error: %v", err)
   390  	}
   391  }
   392  
   393  func TestJobEndpoint_Register_Vault_AllowUnauthenticated(t *testing.T) {
   394  	s1 := testServer(t, func(c *Config) {
   395  		c.NumSchedulers = 0 // Prevent automatic dequeue
   396  	})
   397  	defer s1.Shutdown()
   398  	codec := rpcClient(t, s1)
   399  	testutil.WaitForLeader(t, s1.RPC)
   400  
   401  	// Enable vault and allow authenticated
   402  	tr := true
   403  	s1.config.VaultConfig.Enabled = &tr
   404  	s1.config.VaultConfig.AllowUnauthenticated = &tr
   405  
   406  	// Replace the Vault Client on the server
   407  	s1.vault = &TestVaultClient{}
   408  
   409  	// Create the register request with a job asking for a vault policy
   410  	job := mock.Job()
   411  	job.TaskGroups[0].Tasks[0].Vault = &structs.Vault{
   412  		Policies:   []string{"foo"},
   413  		ChangeMode: structs.VaultChangeModeRestart,
   414  	}
   415  	req := &structs.JobRegisterRequest{
   416  		Job:          job,
   417  		WriteRequest: structs.WriteRequest{Region: "global"},
   418  	}
   419  
   420  	// Fetch the response
   421  	var resp structs.JobRegisterResponse
   422  	err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp)
   423  	if err != nil {
   424  		t.Fatalf("bad: %v", err)
   425  	}
   426  
   427  	// Check for the job in the FSM
   428  	state := s1.fsm.State()
   429  	out, err := state.JobByID(job.ID)
   430  	if err != nil {
   431  		t.Fatalf("err: %v", err)
   432  	}
   433  	if out == nil {
   434  		t.Fatalf("expected job")
   435  	}
   436  	if out.CreateIndex != resp.JobModifyIndex {
   437  		t.Fatalf("index mis-match")
   438  	}
   439  }
   440  
   441  func TestJobEndpoint_Register_Vault_NoToken(t *testing.T) {
   442  	s1 := testServer(t, func(c *Config) {
   443  		c.NumSchedulers = 0 // Prevent automatic dequeue
   444  	})
   445  	defer s1.Shutdown()
   446  	codec := rpcClient(t, s1)
   447  	testutil.WaitForLeader(t, s1.RPC)
   448  
   449  	// Enable vault
   450  	tr, f := true, false
   451  	s1.config.VaultConfig.Enabled = &tr
   452  	s1.config.VaultConfig.AllowUnauthenticated = &f
   453  
   454  	// Replace the Vault Client on the server
   455  	s1.vault = &TestVaultClient{}
   456  
   457  	// Create the register request with a job asking for a vault policy but
   458  	// don't send a Vault token
   459  	job := mock.Job()
   460  	job.TaskGroups[0].Tasks[0].Vault = &structs.Vault{
   461  		Policies:   []string{"foo"},
   462  		ChangeMode: structs.VaultChangeModeRestart,
   463  	}
   464  	req := &structs.JobRegisterRequest{
   465  		Job:          job,
   466  		WriteRequest: structs.WriteRequest{Region: "global"},
   467  	}
   468  
   469  	// Fetch the response
   470  	var resp structs.JobRegisterResponse
   471  	err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp)
   472  	if err == nil || !strings.Contains(err.Error(), "missing Vault Token") {
   473  		t.Fatalf("expected Vault not enabled error: %v", err)
   474  	}
   475  }
   476  
   477  func TestJobEndpoint_Register_Vault_Policies(t *testing.T) {
   478  	s1 := testServer(t, func(c *Config) {
   479  		c.NumSchedulers = 0 // Prevent automatic dequeue
   480  	})
   481  	defer s1.Shutdown()
   482  	codec := rpcClient(t, s1)
   483  	testutil.WaitForLeader(t, s1.RPC)
   484  
   485  	// Enable vault
   486  	tr, f := true, false
   487  	s1.config.VaultConfig.Enabled = &tr
   488  	s1.config.VaultConfig.AllowUnauthenticated = &f
   489  
   490  	// Replace the Vault Client on the server
   491  	tvc := &TestVaultClient{}
   492  	s1.vault = tvc
   493  
   494  	// Add three tokens: one that allows the requesting policy, one that does
   495  	// not and one that returns an error
   496  	policy := "foo"
   497  
   498  	badToken := structs.GenerateUUID()
   499  	badPolicies := []string{"a", "b", "c"}
   500  	tvc.SetLookupTokenAllowedPolicies(badToken, badPolicies)
   501  
   502  	goodToken := structs.GenerateUUID()
   503  	goodPolicies := []string{"foo", "bar", "baz"}
   504  	tvc.SetLookupTokenAllowedPolicies(goodToken, goodPolicies)
   505  
   506  	rootToken := structs.GenerateUUID()
   507  	rootPolicies := []string{"root"}
   508  	tvc.SetLookupTokenAllowedPolicies(rootToken, rootPolicies)
   509  
   510  	errToken := structs.GenerateUUID()
   511  	expectedErr := fmt.Errorf("return errors from vault")
   512  	tvc.SetLookupTokenError(errToken, expectedErr)
   513  
   514  	// Create the register request with a job asking for a vault policy but
   515  	// send the bad Vault token
   516  	job := mock.Job()
   517  	job.VaultToken = badToken
   518  	job.TaskGroups[0].Tasks[0].Vault = &structs.Vault{
   519  		Policies:   []string{policy},
   520  		ChangeMode: structs.VaultChangeModeRestart,
   521  	}
   522  	req := &structs.JobRegisterRequest{
   523  		Job:          job,
   524  		WriteRequest: structs.WriteRequest{Region: "global"},
   525  	}
   526  
   527  	// Fetch the response
   528  	var resp structs.JobRegisterResponse
   529  	err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp)
   530  	if err == nil || !strings.Contains(err.Error(),
   531  		"doesn't allow access to the following policies: "+policy) {
   532  		t.Fatalf("expected permission denied error: %v", err)
   533  	}
   534  
   535  	// Use the err token
   536  	job.VaultToken = errToken
   537  	err = msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp)
   538  	if err == nil || !strings.Contains(err.Error(), expectedErr.Error()) {
   539  		t.Fatalf("expected permission denied error: %v", err)
   540  	}
   541  
   542  	// Use the good token
   543  	job.VaultToken = goodToken
   544  
   545  	// Fetch the response
   546  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil {
   547  		t.Fatalf("bad: %v", err)
   548  	}
   549  
   550  	// Check for the job in the FSM
   551  	state := s1.fsm.State()
   552  	out, err := state.JobByID(job.ID)
   553  	if err != nil {
   554  		t.Fatalf("err: %v", err)
   555  	}
   556  	if out == nil {
   557  		t.Fatalf("expected job")
   558  	}
   559  	if out.CreateIndex != resp.JobModifyIndex {
   560  		t.Fatalf("index mis-match")
   561  	}
   562  	if out.VaultToken != "" {
   563  		t.Fatalf("vault token not cleared")
   564  	}
   565  
   566  	// Check that an implicit constraint was created
   567  	constraints := out.TaskGroups[0].Constraints
   568  	if l := len(constraints); l != 1 {
   569  		t.Fatalf("Unexpected number of tests: %v", l)
   570  	}
   571  
   572  	if !constraints[0].Equal(vaultConstraint) {
   573  		t.Fatalf("bad constraint; got %#v; want %#v", constraints[0], vaultConstraint)
   574  	}
   575  
   576  	// Create the register request with another job asking for a vault policy but
   577  	// send the root Vault token
   578  	job2 := mock.Job()
   579  	job2.VaultToken = rootToken
   580  	job.TaskGroups[0].Tasks[0].Vault = &structs.Vault{
   581  		Policies:   []string{policy},
   582  		ChangeMode: structs.VaultChangeModeRestart,
   583  	}
   584  	req = &structs.JobRegisterRequest{
   585  		Job:          job2,
   586  		WriteRequest: structs.WriteRequest{Region: "global"},
   587  	}
   588  
   589  	// Fetch the response
   590  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil {
   591  		t.Fatalf("bad: %v", err)
   592  	}
   593  
   594  	// Check for the job in the FSM
   595  	out, err = state.JobByID(job2.ID)
   596  	if err != nil {
   597  		t.Fatalf("err: %v", err)
   598  	}
   599  	if out == nil {
   600  		t.Fatalf("expected job")
   601  	}
   602  	if out.CreateIndex != resp.JobModifyIndex {
   603  		t.Fatalf("index mis-match")
   604  	}
   605  	if out.VaultToken != "" {
   606  		t.Fatalf("vault token not cleared")
   607  	}
   608  }
   609  
   610  func TestJobEndpoint_Evaluate(t *testing.T) {
   611  	s1 := testServer(t, func(c *Config) {
   612  		c.NumSchedulers = 0 // Prevent automatic dequeue
   613  	})
   614  	defer s1.Shutdown()
   615  	codec := rpcClient(t, s1)
   616  	testutil.WaitForLeader(t, s1.RPC)
   617  
   618  	// Create the register request
   619  	job := mock.Job()
   620  	req := &structs.JobRegisterRequest{
   621  		Job:          job,
   622  		WriteRequest: structs.WriteRequest{Region: "global"},
   623  	}
   624  
   625  	// Fetch the response
   626  	var resp structs.JobRegisterResponse
   627  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil {
   628  		t.Fatalf("err: %v", err)
   629  	}
   630  	if resp.Index == 0 {
   631  		t.Fatalf("bad index: %d", resp.Index)
   632  	}
   633  
   634  	// Force a re-evaluation
   635  	reEval := &structs.JobEvaluateRequest{
   636  		JobID:        job.ID,
   637  		WriteRequest: structs.WriteRequest{Region: "global"},
   638  	}
   639  
   640  	// Fetch the response
   641  	if err := msgpackrpc.CallWithCodec(codec, "Job.Evaluate", reEval, &resp); err != nil {
   642  		t.Fatalf("err: %v", err)
   643  	}
   644  	if resp.Index == 0 {
   645  		t.Fatalf("bad index: %d", resp.Index)
   646  	}
   647  
   648  	// Lookup the evaluation
   649  	state := s1.fsm.State()
   650  	eval, err := state.EvalByID(resp.EvalID)
   651  	if err != nil {
   652  		t.Fatalf("err: %v", err)
   653  	}
   654  	if eval == nil {
   655  		t.Fatalf("expected eval")
   656  	}
   657  	if eval.CreateIndex != resp.EvalCreateIndex {
   658  		t.Fatalf("index mis-match")
   659  	}
   660  
   661  	if eval.Priority != job.Priority {
   662  		t.Fatalf("bad: %#v", eval)
   663  	}
   664  	if eval.Type != job.Type {
   665  		t.Fatalf("bad: %#v", eval)
   666  	}
   667  	if eval.TriggeredBy != structs.EvalTriggerJobRegister {
   668  		t.Fatalf("bad: %#v", eval)
   669  	}
   670  	if eval.JobID != job.ID {
   671  		t.Fatalf("bad: %#v", eval)
   672  	}
   673  	if eval.JobModifyIndex != resp.JobModifyIndex {
   674  		t.Fatalf("bad: %#v", eval)
   675  	}
   676  	if eval.Status != structs.EvalStatusPending {
   677  		t.Fatalf("bad: %#v", eval)
   678  	}
   679  }
   680  
   681  func TestJobEndpoint_Evaluate_Periodic(t *testing.T) {
   682  	s1 := testServer(t, func(c *Config) {
   683  		c.NumSchedulers = 0 // Prevent automatic dequeue
   684  	})
   685  	defer s1.Shutdown()
   686  	codec := rpcClient(t, s1)
   687  	testutil.WaitForLeader(t, s1.RPC)
   688  
   689  	// Create the register request
   690  	job := mock.PeriodicJob()
   691  	req := &structs.JobRegisterRequest{
   692  		Job:          job,
   693  		WriteRequest: structs.WriteRequest{Region: "global"},
   694  	}
   695  
   696  	// Fetch the response
   697  	var resp structs.JobRegisterResponse
   698  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil {
   699  		t.Fatalf("err: %v", err)
   700  	}
   701  	if resp.JobModifyIndex == 0 {
   702  		t.Fatalf("bad index: %d", resp.Index)
   703  	}
   704  
   705  	// Force a re-evaluation
   706  	reEval := &structs.JobEvaluateRequest{
   707  		JobID:        job.ID,
   708  		WriteRequest: structs.WriteRequest{Region: "global"},
   709  	}
   710  
   711  	// Fetch the response
   712  	if err := msgpackrpc.CallWithCodec(codec, "Job.Evaluate", reEval, &resp); err == nil {
   713  		t.Fatal("expect an err")
   714  	}
   715  }
   716  
   717  func TestJobEndpoint_Deregister(t *testing.T) {
   718  	s1 := testServer(t, func(c *Config) {
   719  		c.NumSchedulers = 0 // Prevent automatic dequeue
   720  	})
   721  	defer s1.Shutdown()
   722  	codec := rpcClient(t, s1)
   723  	testutil.WaitForLeader(t, s1.RPC)
   724  
   725  	// Create the register request
   726  	job := mock.Job()
   727  	reg := &structs.JobRegisterRequest{
   728  		Job:          job,
   729  		WriteRequest: structs.WriteRequest{Region: "global"},
   730  	}
   731  
   732  	// Fetch the response
   733  	var resp structs.JobRegisterResponse
   734  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", reg, &resp); err != nil {
   735  		t.Fatalf("err: %v", err)
   736  	}
   737  
   738  	// Deregister
   739  	dereg := &structs.JobDeregisterRequest{
   740  		JobID:        job.ID,
   741  		WriteRequest: structs.WriteRequest{Region: "global"},
   742  	}
   743  	var resp2 structs.JobDeregisterResponse
   744  	if err := msgpackrpc.CallWithCodec(codec, "Job.Deregister", dereg, &resp2); err != nil {
   745  		t.Fatalf("err: %v", err)
   746  	}
   747  	if resp2.Index == 0 {
   748  		t.Fatalf("bad index: %d", resp2.Index)
   749  	}
   750  
   751  	// Check for the node in the FSM
   752  	state := s1.fsm.State()
   753  	out, err := state.JobByID(job.ID)
   754  	if err != nil {
   755  		t.Fatalf("err: %v", err)
   756  	}
   757  	if out != nil {
   758  		t.Fatalf("unexpected job")
   759  	}
   760  
   761  	// Lookup the evaluation
   762  	eval, err := state.EvalByID(resp2.EvalID)
   763  	if err != nil {
   764  		t.Fatalf("err: %v", err)
   765  	}
   766  	if eval == nil {
   767  		t.Fatalf("expected eval")
   768  	}
   769  	if eval.CreateIndex != resp2.EvalCreateIndex {
   770  		t.Fatalf("index mis-match")
   771  	}
   772  
   773  	if eval.Priority != structs.JobDefaultPriority {
   774  		t.Fatalf("bad: %#v", eval)
   775  	}
   776  	if eval.Type != structs.JobTypeService {
   777  		t.Fatalf("bad: %#v", eval)
   778  	}
   779  	if eval.TriggeredBy != structs.EvalTriggerJobDeregister {
   780  		t.Fatalf("bad: %#v", eval)
   781  	}
   782  	if eval.JobID != job.ID {
   783  		t.Fatalf("bad: %#v", eval)
   784  	}
   785  	if eval.JobModifyIndex != resp2.JobModifyIndex {
   786  		t.Fatalf("bad: %#v", eval)
   787  	}
   788  	if eval.Status != structs.EvalStatusPending {
   789  		t.Fatalf("bad: %#v", eval)
   790  	}
   791  }
   792  
   793  func TestJobEndpoint_Deregister_NonExistent(t *testing.T) {
   794  	s1 := testServer(t, func(c *Config) {
   795  		c.NumSchedulers = 0 // Prevent automatic dequeue
   796  	})
   797  	defer s1.Shutdown()
   798  	codec := rpcClient(t, s1)
   799  	testutil.WaitForLeader(t, s1.RPC)
   800  
   801  	// Deregister
   802  	jobID := "foo"
   803  	dereg := &structs.JobDeregisterRequest{
   804  		JobID:        jobID,
   805  		WriteRequest: structs.WriteRequest{Region: "global"},
   806  	}
   807  	var resp2 structs.JobDeregisterResponse
   808  	if err := msgpackrpc.CallWithCodec(codec, "Job.Deregister", dereg, &resp2); err != nil {
   809  		t.Fatalf("err: %v", err)
   810  	}
   811  	if resp2.JobModifyIndex == 0 {
   812  		t.Fatalf("bad index: %d", resp2.Index)
   813  	}
   814  
   815  	// Lookup the evaluation
   816  	state := s1.fsm.State()
   817  	eval, err := state.EvalByID(resp2.EvalID)
   818  	if err != nil {
   819  		t.Fatalf("err: %v", err)
   820  	}
   821  	if eval == nil {
   822  		t.Fatalf("expected eval")
   823  	}
   824  	if eval.CreateIndex != resp2.EvalCreateIndex {
   825  		t.Fatalf("index mis-match")
   826  	}
   827  
   828  	if eval.Priority != structs.JobDefaultPriority {
   829  		t.Fatalf("bad: %#v", eval)
   830  	}
   831  	if eval.Type != structs.JobTypeService {
   832  		t.Fatalf("bad: %#v", eval)
   833  	}
   834  	if eval.TriggeredBy != structs.EvalTriggerJobDeregister {
   835  		t.Fatalf("bad: %#v", eval)
   836  	}
   837  	if eval.JobID != jobID {
   838  		t.Fatalf("bad: %#v", eval)
   839  	}
   840  	if eval.JobModifyIndex != resp2.JobModifyIndex {
   841  		t.Fatalf("bad: %#v", eval)
   842  	}
   843  	if eval.Status != structs.EvalStatusPending {
   844  		t.Fatalf("bad: %#v", eval)
   845  	}
   846  }
   847  
   848  func TestJobEndpoint_Deregister_Periodic(t *testing.T) {
   849  	s1 := testServer(t, func(c *Config) {
   850  		c.NumSchedulers = 0 // Prevent automatic dequeue
   851  	})
   852  	defer s1.Shutdown()
   853  	codec := rpcClient(t, s1)
   854  	testutil.WaitForLeader(t, s1.RPC)
   855  
   856  	// Create the register request
   857  	job := mock.PeriodicJob()
   858  	reg := &structs.JobRegisterRequest{
   859  		Job:          job,
   860  		WriteRequest: structs.WriteRequest{Region: "global"},
   861  	}
   862  
   863  	// Fetch the response
   864  	var resp structs.JobRegisterResponse
   865  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", reg, &resp); err != nil {
   866  		t.Fatalf("err: %v", err)
   867  	}
   868  
   869  	// Deregister
   870  	dereg := &structs.JobDeregisterRequest{
   871  		JobID:        job.ID,
   872  		WriteRequest: structs.WriteRequest{Region: "global"},
   873  	}
   874  	var resp2 structs.JobDeregisterResponse
   875  	if err := msgpackrpc.CallWithCodec(codec, "Job.Deregister", dereg, &resp2); err != nil {
   876  		t.Fatalf("err: %v", err)
   877  	}
   878  	if resp2.JobModifyIndex == 0 {
   879  		t.Fatalf("bad index: %d", resp2.Index)
   880  	}
   881  
   882  	// Check for the node in the FSM
   883  	state := s1.fsm.State()
   884  	out, err := state.JobByID(job.ID)
   885  	if err != nil {
   886  		t.Fatalf("err: %v", err)
   887  	}
   888  	if out != nil {
   889  		t.Fatalf("unexpected job")
   890  	}
   891  
   892  	if resp.EvalID != "" {
   893  		t.Fatalf("Deregister created an eval for a periodic job")
   894  	}
   895  }
   896  
   897  func TestJobEndpoint_GetJob(t *testing.T) {
   898  	s1 := testServer(t, nil)
   899  	defer s1.Shutdown()
   900  	codec := rpcClient(t, s1)
   901  	testutil.WaitForLeader(t, s1.RPC)
   902  
   903  	// Create the register request
   904  	job := mock.Job()
   905  	reg := &structs.JobRegisterRequest{
   906  		Job:          job,
   907  		WriteRequest: structs.WriteRequest{Region: "global"},
   908  	}
   909  
   910  	// Fetch the response
   911  	var resp structs.JobRegisterResponse
   912  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", reg, &resp); err != nil {
   913  		t.Fatalf("err: %v", err)
   914  	}
   915  	job.CreateIndex = resp.JobModifyIndex
   916  	job.ModifyIndex = resp.JobModifyIndex
   917  	job.JobModifyIndex = resp.JobModifyIndex
   918  
   919  	// Lookup the job
   920  	get := &structs.JobSpecificRequest{
   921  		JobID:        job.ID,
   922  		QueryOptions: structs.QueryOptions{Region: "global"},
   923  	}
   924  	var resp2 structs.SingleJobResponse
   925  	if err := msgpackrpc.CallWithCodec(codec, "Job.GetJob", get, &resp2); err != nil {
   926  		t.Fatalf("err: %v", err)
   927  	}
   928  	if resp2.Index != resp.JobModifyIndex {
   929  		t.Fatalf("Bad index: %d %d", resp2.Index, resp.Index)
   930  	}
   931  
   932  	// Make a copy of the origin job and change the service name so that we can
   933  	// do a deep equal with the response from the GET JOB Api
   934  	j := job
   935  	j.TaskGroups[0].Tasks[0].Services[0].Name = "web-frontend"
   936  	for tgix, tg := range j.TaskGroups {
   937  		for tidx, t := range tg.Tasks {
   938  			for sidx, service := range t.Services {
   939  				for cidx, check := range service.Checks {
   940  					check.Name = resp2.Job.TaskGroups[tgix].Tasks[tidx].Services[sidx].Checks[cidx].Name
   941  				}
   942  			}
   943  		}
   944  	}
   945  
   946  	if !reflect.DeepEqual(j, resp2.Job) {
   947  		t.Fatalf("bad: %#v %#v", job, resp2.Job)
   948  	}
   949  
   950  	// Lookup non-existing job
   951  	get.JobID = "foobarbaz"
   952  	if err := msgpackrpc.CallWithCodec(codec, "Job.GetJob", get, &resp2); err != nil {
   953  		t.Fatalf("err: %v", err)
   954  	}
   955  	if resp2.Index != resp.JobModifyIndex {
   956  		t.Fatalf("Bad index: %d %d", resp2.Index, resp.Index)
   957  	}
   958  	if resp2.Job != nil {
   959  		t.Fatalf("unexpected job")
   960  	}
   961  }
   962  
   963  func TestJobEndpoint_GetJobSummary(t *testing.T) {
   964  	s1 := testServer(t, func(c *Config) {
   965  		c.NumSchedulers = 0 // Prevent automatic dequeue
   966  	})
   967  
   968  	defer s1.Shutdown()
   969  	codec := rpcClient(t, s1)
   970  	testutil.WaitForLeader(t, s1.RPC)
   971  
   972  	// Create the register request
   973  	job := mock.Job()
   974  	reg := &structs.JobRegisterRequest{
   975  		Job:          job,
   976  		WriteRequest: structs.WriteRequest{Region: "global"},
   977  	}
   978  
   979  	// Fetch the response
   980  	var resp structs.JobRegisterResponse
   981  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", reg, &resp); err != nil {
   982  		t.Fatalf("err: %v", err)
   983  	}
   984  	job.CreateIndex = resp.JobModifyIndex
   985  	job.ModifyIndex = resp.JobModifyIndex
   986  	job.JobModifyIndex = resp.JobModifyIndex
   987  
   988  	// Lookup the job summary
   989  	get := &structs.JobSummaryRequest{
   990  		JobID:        job.ID,
   991  		QueryOptions: structs.QueryOptions{Region: "global"},
   992  	}
   993  	var resp2 structs.JobSummaryResponse
   994  	if err := msgpackrpc.CallWithCodec(codec, "Job.Summary", get, &resp2); err != nil {
   995  		t.Fatalf("err: %v", err)
   996  	}
   997  	if resp2.Index != resp.JobModifyIndex {
   998  		t.Fatalf("Bad index: %d %d", resp2.Index, resp.Index)
   999  	}
  1000  
  1001  	expectedJobSummary := structs.JobSummary{
  1002  		JobID: job.ID,
  1003  		Summary: map[string]structs.TaskGroupSummary{
  1004  			"web": structs.TaskGroupSummary{},
  1005  		},
  1006  		CreateIndex: job.CreateIndex,
  1007  		ModifyIndex: job.CreateIndex,
  1008  	}
  1009  
  1010  	if !reflect.DeepEqual(resp2.JobSummary, &expectedJobSummary) {
  1011  		t.Fatalf("exptected: %v, actual: %v", expectedJobSummary, resp2.JobSummary)
  1012  	}
  1013  }
  1014  
  1015  func TestJobEndpoint_GetJobSummary_Blocking(t *testing.T) {
  1016  	s1 := testServer(t, nil)
  1017  	defer s1.Shutdown()
  1018  	state := s1.fsm.State()
  1019  	codec := rpcClient(t, s1)
  1020  	testutil.WaitForLeader(t, s1.RPC)
  1021  
  1022  	// Create a job and insert it
  1023  	job1 := mock.Job()
  1024  	time.AfterFunc(200*time.Millisecond, func() {
  1025  		if err := state.UpsertJob(100, job1); err != nil {
  1026  			t.Fatalf("err: %v", err)
  1027  		}
  1028  	})
  1029  
  1030  	// Ensure the job summary request gets fired
  1031  	req := &structs.JobSummaryRequest{
  1032  		JobID: job1.ID,
  1033  		QueryOptions: structs.QueryOptions{
  1034  			Region:        "global",
  1035  			MinQueryIndex: 50,
  1036  		},
  1037  	}
  1038  	var resp structs.JobSummaryResponse
  1039  	start := time.Now()
  1040  	if err := msgpackrpc.CallWithCodec(codec, "Job.Summary", req, &resp); err != nil {
  1041  		t.Fatalf("err: %v", err)
  1042  	}
  1043  	if elapsed := time.Since(start); elapsed < 200*time.Millisecond {
  1044  		t.Fatalf("should block (returned in %s) %#v", elapsed, resp)
  1045  	}
  1046  
  1047  	// Upsert an allocation for the job which should trigger the watch.
  1048  	time.AfterFunc(200*time.Millisecond, func() {
  1049  		alloc := mock.Alloc()
  1050  		alloc.JobID = job1.ID
  1051  		alloc.Job = job1
  1052  		if err := state.UpsertAllocs(200, []*structs.Allocation{alloc}); err != nil {
  1053  			t.Fatalf("err: %v", err)
  1054  		}
  1055  	})
  1056  	req = &structs.JobSummaryRequest{
  1057  		JobID: job1.ID,
  1058  		QueryOptions: structs.QueryOptions{
  1059  			Region:        "global",
  1060  			MinQueryIndex: 199,
  1061  		},
  1062  	}
  1063  	start = time.Now()
  1064  	var resp1 structs.JobSummaryResponse
  1065  	start = time.Now()
  1066  	if err := msgpackrpc.CallWithCodec(codec, "Job.Summary", req, &resp1); err != nil {
  1067  		t.Fatalf("err: %v", err)
  1068  	}
  1069  
  1070  	if elapsed := time.Since(start); elapsed < 200*time.Millisecond {
  1071  		t.Fatalf("should block (returned in %s) %#v", elapsed, resp)
  1072  	}
  1073  	if resp1.Index != 200 {
  1074  		t.Fatalf("Bad index: %d %d", resp.Index, 200)
  1075  	}
  1076  	if resp1.JobSummary == nil {
  1077  		t.Fatalf("bad: %#v", resp)
  1078  	}
  1079  
  1080  	// Job delete fires watches
  1081  	time.AfterFunc(100*time.Millisecond, func() {
  1082  		if err := state.DeleteJob(300, job1.ID); err != nil {
  1083  			t.Fatalf("err: %v", err)
  1084  		}
  1085  	})
  1086  
  1087  	req.QueryOptions.MinQueryIndex = 250
  1088  	start = time.Now()
  1089  
  1090  	var resp2 structs.SingleJobResponse
  1091  	if err := msgpackrpc.CallWithCodec(codec, "Job.Summary", req, &resp2); err != nil {
  1092  		t.Fatalf("err: %v", err)
  1093  	}
  1094  
  1095  	if elapsed := time.Since(start); elapsed < 100*time.Millisecond {
  1096  		t.Fatalf("should block (returned in %s) %#v", elapsed, resp2)
  1097  	}
  1098  	if resp2.Index != 300 {
  1099  		t.Fatalf("Bad index: %d %d", resp2.Index, 300)
  1100  	}
  1101  	if resp2.Job != nil {
  1102  		t.Fatalf("bad: %#v", resp2.Job)
  1103  	}
  1104  }
  1105  
  1106  func TestJobEndpoint_GetJob_Blocking(t *testing.T) {
  1107  	s1 := testServer(t, nil)
  1108  	defer s1.Shutdown()
  1109  	state := s1.fsm.State()
  1110  	codec := rpcClient(t, s1)
  1111  	testutil.WaitForLeader(t, s1.RPC)
  1112  
  1113  	// Create the jobs
  1114  	job1 := mock.Job()
  1115  	job2 := mock.Job()
  1116  
  1117  	// Upsert a job we are not interested in first.
  1118  	time.AfterFunc(100*time.Millisecond, func() {
  1119  		if err := state.UpsertJob(100, job1); err != nil {
  1120  			t.Fatalf("err: %v", err)
  1121  		}
  1122  	})
  1123  
  1124  	// Upsert another job later which should trigger the watch.
  1125  	time.AfterFunc(200*time.Millisecond, func() {
  1126  		if err := state.UpsertJob(200, job2); err != nil {
  1127  			t.Fatalf("err: %v", err)
  1128  		}
  1129  	})
  1130  
  1131  	req := &structs.JobSpecificRequest{
  1132  		JobID: job2.ID,
  1133  		QueryOptions: structs.QueryOptions{
  1134  			Region:        "global",
  1135  			MinQueryIndex: 50,
  1136  		},
  1137  	}
  1138  	start := time.Now()
  1139  	var resp structs.SingleJobResponse
  1140  	if err := msgpackrpc.CallWithCodec(codec, "Job.GetJob", req, &resp); err != nil {
  1141  		t.Fatalf("err: %v", err)
  1142  	}
  1143  
  1144  	if elapsed := time.Since(start); elapsed < 200*time.Millisecond {
  1145  		t.Fatalf("should block (returned in %s) %#v", elapsed, resp)
  1146  	}
  1147  	if resp.Index != 200 {
  1148  		t.Fatalf("Bad index: %d %d", resp.Index, 200)
  1149  	}
  1150  	if resp.Job == nil || resp.Job.ID != job2.ID {
  1151  		t.Fatalf("bad: %#v", resp.Job)
  1152  	}
  1153  
  1154  	// Job delete fires watches
  1155  	time.AfterFunc(100*time.Millisecond, func() {
  1156  		if err := state.DeleteJob(300, job2.ID); err != nil {
  1157  			t.Fatalf("err: %v", err)
  1158  		}
  1159  	})
  1160  
  1161  	req.QueryOptions.MinQueryIndex = 250
  1162  	start = time.Now()
  1163  
  1164  	var resp2 structs.SingleJobResponse
  1165  	if err := msgpackrpc.CallWithCodec(codec, "Job.GetJob", req, &resp2); err != nil {
  1166  		t.Fatalf("err: %v", err)
  1167  	}
  1168  
  1169  	if elapsed := time.Since(start); elapsed < 100*time.Millisecond {
  1170  		t.Fatalf("should block (returned in %s) %#v", elapsed, resp2)
  1171  	}
  1172  	if resp2.Index != 300 {
  1173  		t.Fatalf("Bad index: %d %d", resp2.Index, 300)
  1174  	}
  1175  	if resp2.Job != nil {
  1176  		t.Fatalf("bad: %#v", resp2.Job)
  1177  	}
  1178  }
  1179  
  1180  func TestJobEndpoint_ListJobs(t *testing.T) {
  1181  	s1 := testServer(t, nil)
  1182  	defer s1.Shutdown()
  1183  	codec := rpcClient(t, s1)
  1184  	testutil.WaitForLeader(t, s1.RPC)
  1185  
  1186  	// Create the register request
  1187  	job := mock.Job()
  1188  	state := s1.fsm.State()
  1189  	err := state.UpsertJob(1000, job)
  1190  	if err != nil {
  1191  		t.Fatalf("err: %v", err)
  1192  	}
  1193  
  1194  	// Lookup the jobs
  1195  	get := &structs.JobListRequest{
  1196  		QueryOptions: structs.QueryOptions{Region: "global"},
  1197  	}
  1198  	var resp2 structs.JobListResponse
  1199  	if err := msgpackrpc.CallWithCodec(codec, "Job.List", get, &resp2); err != nil {
  1200  		t.Fatalf("err: %v", err)
  1201  	}
  1202  	if resp2.Index != 1000 {
  1203  		t.Fatalf("Bad index: %d %d", resp2.Index, 1000)
  1204  	}
  1205  
  1206  	if len(resp2.Jobs) != 1 {
  1207  		t.Fatalf("bad: %#v", resp2.Jobs)
  1208  	}
  1209  	if resp2.Jobs[0].ID != job.ID {
  1210  		t.Fatalf("bad: %#v", resp2.Jobs[0])
  1211  	}
  1212  
  1213  	// Lookup the jobs by prefix
  1214  	get = &structs.JobListRequest{
  1215  		QueryOptions: structs.QueryOptions{Region: "global", Prefix: resp2.Jobs[0].ID[:4]},
  1216  	}
  1217  	var resp3 structs.JobListResponse
  1218  	if err := msgpackrpc.CallWithCodec(codec, "Job.List", get, &resp3); err != nil {
  1219  		t.Fatalf("err: %v", err)
  1220  	}
  1221  	if resp3.Index != 1000 {
  1222  		t.Fatalf("Bad index: %d %d", resp3.Index, 1000)
  1223  	}
  1224  
  1225  	if len(resp3.Jobs) != 1 {
  1226  		t.Fatalf("bad: %#v", resp3.Jobs)
  1227  	}
  1228  	if resp3.Jobs[0].ID != job.ID {
  1229  		t.Fatalf("bad: %#v", resp3.Jobs[0])
  1230  	}
  1231  }
  1232  
  1233  func TestJobEndpoint_ListJobs_Blocking(t *testing.T) {
  1234  	s1 := testServer(t, nil)
  1235  	defer s1.Shutdown()
  1236  	state := s1.fsm.State()
  1237  	codec := rpcClient(t, s1)
  1238  	testutil.WaitForLeader(t, s1.RPC)
  1239  
  1240  	// Create the job
  1241  	job := mock.Job()
  1242  
  1243  	// Upsert job triggers watches
  1244  	time.AfterFunc(100*time.Millisecond, func() {
  1245  		if err := state.UpsertJob(100, job); err != nil {
  1246  			t.Fatalf("err: %v", err)
  1247  		}
  1248  	})
  1249  
  1250  	req := &structs.JobListRequest{
  1251  		QueryOptions: structs.QueryOptions{
  1252  			Region:        "global",
  1253  			MinQueryIndex: 50,
  1254  		},
  1255  	}
  1256  	start := time.Now()
  1257  	var resp structs.JobListResponse
  1258  	if err := msgpackrpc.CallWithCodec(codec, "Job.List", req, &resp); err != nil {
  1259  		t.Fatalf("err: %v", err)
  1260  	}
  1261  
  1262  	if elapsed := time.Since(start); elapsed < 100*time.Millisecond {
  1263  		t.Fatalf("should block (returned in %s) %#v", elapsed, resp)
  1264  	}
  1265  	if resp.Index != 100 {
  1266  		t.Fatalf("Bad index: %d %d", resp.Index, 100)
  1267  	}
  1268  	if len(resp.Jobs) != 1 || resp.Jobs[0].ID != job.ID {
  1269  		t.Fatalf("bad: %#v", resp.Jobs)
  1270  	}
  1271  
  1272  	// Job deletion triggers watches
  1273  	time.AfterFunc(100*time.Millisecond, func() {
  1274  		if err := state.DeleteJob(200, job.ID); err != nil {
  1275  			t.Fatalf("err: %v", err)
  1276  		}
  1277  	})
  1278  
  1279  	req.MinQueryIndex = 150
  1280  	start = time.Now()
  1281  	var resp2 structs.JobListResponse
  1282  	if err := msgpackrpc.CallWithCodec(codec, "Job.List", req, &resp2); err != nil {
  1283  		t.Fatalf("err: %v", err)
  1284  	}
  1285  
  1286  	if elapsed := time.Since(start); elapsed < 100*time.Millisecond {
  1287  		t.Fatalf("should block (returned in %s) %#v", elapsed, resp2)
  1288  	}
  1289  	if resp2.Index != 200 {
  1290  		t.Fatalf("Bad index: %d %d", resp2.Index, 200)
  1291  	}
  1292  	if len(resp2.Jobs) != 0 {
  1293  		t.Fatalf("bad: %#v", resp2.Jobs)
  1294  	}
  1295  }
  1296  
  1297  func TestJobEndpoint_Allocations(t *testing.T) {
  1298  	s1 := testServer(t, nil)
  1299  	defer s1.Shutdown()
  1300  	codec := rpcClient(t, s1)
  1301  	testutil.WaitForLeader(t, s1.RPC)
  1302  
  1303  	// Create the register request
  1304  	alloc1 := mock.Alloc()
  1305  	alloc2 := mock.Alloc()
  1306  	alloc2.JobID = alloc1.JobID
  1307  	state := s1.fsm.State()
  1308  	state.UpsertJobSummary(998, mock.JobSummary(alloc1.JobID))
  1309  	state.UpsertJobSummary(999, mock.JobSummary(alloc2.JobID))
  1310  	err := state.UpsertAllocs(1000,
  1311  		[]*structs.Allocation{alloc1, alloc2})
  1312  	if err != nil {
  1313  		t.Fatalf("err: %v", err)
  1314  	}
  1315  
  1316  	// Lookup the jobs
  1317  	get := &structs.JobSpecificRequest{
  1318  		JobID:        alloc1.JobID,
  1319  		QueryOptions: structs.QueryOptions{Region: "global"},
  1320  	}
  1321  	var resp2 structs.JobAllocationsResponse
  1322  	if err := msgpackrpc.CallWithCodec(codec, "Job.Allocations", get, &resp2); err != nil {
  1323  		t.Fatalf("err: %v", err)
  1324  	}
  1325  	if resp2.Index != 1000 {
  1326  		t.Fatalf("Bad index: %d %d", resp2.Index, 1000)
  1327  	}
  1328  
  1329  	if len(resp2.Allocations) != 2 {
  1330  		t.Fatalf("bad: %#v", resp2.Allocations)
  1331  	}
  1332  }
  1333  
  1334  func TestJobEndpoint_Allocations_Blocking(t *testing.T) {
  1335  	s1 := testServer(t, nil)
  1336  	defer s1.Shutdown()
  1337  	codec := rpcClient(t, s1)
  1338  	testutil.WaitForLeader(t, s1.RPC)
  1339  
  1340  	// Create the register request
  1341  	alloc1 := mock.Alloc()
  1342  	alloc2 := mock.Alloc()
  1343  	alloc2.JobID = "job1"
  1344  	state := s1.fsm.State()
  1345  
  1346  	// First upsert an unrelated alloc
  1347  	time.AfterFunc(100*time.Millisecond, func() {
  1348  		state.UpsertJobSummary(99, mock.JobSummary(alloc1.JobID))
  1349  		err := state.UpsertAllocs(100, []*structs.Allocation{alloc1})
  1350  		if err != nil {
  1351  			t.Fatalf("err: %v", err)
  1352  		}
  1353  	})
  1354  
  1355  	// Upsert an alloc for the job we are interested in later
  1356  	time.AfterFunc(200*time.Millisecond, func() {
  1357  		state.UpsertJobSummary(199, mock.JobSummary(alloc2.JobID))
  1358  		err := state.UpsertAllocs(200, []*structs.Allocation{alloc2})
  1359  		if err != nil {
  1360  			t.Fatalf("err: %v", err)
  1361  		}
  1362  	})
  1363  
  1364  	// Lookup the jobs
  1365  	get := &structs.JobSpecificRequest{
  1366  		JobID: "job1",
  1367  		QueryOptions: structs.QueryOptions{
  1368  			Region:        "global",
  1369  			MinQueryIndex: 50,
  1370  		},
  1371  	}
  1372  	var resp structs.JobAllocationsResponse
  1373  	start := time.Now()
  1374  	if err := msgpackrpc.CallWithCodec(codec, "Job.Allocations", get, &resp); err != nil {
  1375  		t.Fatalf("err: %v", err)
  1376  	}
  1377  
  1378  	if elapsed := time.Since(start); elapsed < 200*time.Millisecond {
  1379  		t.Fatalf("should block (returned in %s) %#v", elapsed, resp)
  1380  	}
  1381  	if resp.Index != 200 {
  1382  		t.Fatalf("Bad index: %d %d", resp.Index, 200)
  1383  	}
  1384  	if len(resp.Allocations) != 1 || resp.Allocations[0].JobID != "job1" {
  1385  		t.Fatalf("bad: %#v", resp.Allocations)
  1386  	}
  1387  }
  1388  
  1389  func TestJobEndpoint_Evaluations(t *testing.T) {
  1390  	s1 := testServer(t, nil)
  1391  	defer s1.Shutdown()
  1392  	codec := rpcClient(t, s1)
  1393  	testutil.WaitForLeader(t, s1.RPC)
  1394  
  1395  	// Create the register request
  1396  	eval1 := mock.Eval()
  1397  	eval2 := mock.Eval()
  1398  	eval2.JobID = eval1.JobID
  1399  	state := s1.fsm.State()
  1400  	err := state.UpsertEvals(1000,
  1401  		[]*structs.Evaluation{eval1, eval2})
  1402  	if err != nil {
  1403  		t.Fatalf("err: %v", err)
  1404  	}
  1405  
  1406  	// Lookup the jobs
  1407  	get := &structs.JobSpecificRequest{
  1408  		JobID:        eval1.JobID,
  1409  		QueryOptions: structs.QueryOptions{Region: "global"},
  1410  	}
  1411  	var resp2 structs.JobEvaluationsResponse
  1412  	if err := msgpackrpc.CallWithCodec(codec, "Job.Evaluations", get, &resp2); err != nil {
  1413  		t.Fatalf("err: %v", err)
  1414  	}
  1415  	if resp2.Index != 1000 {
  1416  		t.Fatalf("Bad index: %d %d", resp2.Index, 1000)
  1417  	}
  1418  
  1419  	if len(resp2.Evaluations) != 2 {
  1420  		t.Fatalf("bad: %#v", resp2.Evaluations)
  1421  	}
  1422  }
  1423  
  1424  func TestJobEndpoint_Evaluations_Blocking(t *testing.T) {
  1425  	s1 := testServer(t, nil)
  1426  	defer s1.Shutdown()
  1427  	codec := rpcClient(t, s1)
  1428  	testutil.WaitForLeader(t, s1.RPC)
  1429  
  1430  	// Create the register request
  1431  	eval1 := mock.Eval()
  1432  	eval2 := mock.Eval()
  1433  	eval2.JobID = "job1"
  1434  	state := s1.fsm.State()
  1435  
  1436  	// First upsert an unrelated eval
  1437  	time.AfterFunc(100*time.Millisecond, func() {
  1438  		err := state.UpsertEvals(100, []*structs.Evaluation{eval1})
  1439  		if err != nil {
  1440  			t.Fatalf("err: %v", err)
  1441  		}
  1442  	})
  1443  
  1444  	// Upsert an eval for the job we are interested in later
  1445  	time.AfterFunc(200*time.Millisecond, func() {
  1446  		err := state.UpsertEvals(200, []*structs.Evaluation{eval2})
  1447  		if err != nil {
  1448  			t.Fatalf("err: %v", err)
  1449  		}
  1450  	})
  1451  
  1452  	// Lookup the jobs
  1453  	get := &structs.JobSpecificRequest{
  1454  		JobID: "job1",
  1455  		QueryOptions: structs.QueryOptions{
  1456  			Region:        "global",
  1457  			MinQueryIndex: 50,
  1458  		},
  1459  	}
  1460  	var resp structs.JobEvaluationsResponse
  1461  	start := time.Now()
  1462  	if err := msgpackrpc.CallWithCodec(codec, "Job.Evaluations", get, &resp); err != nil {
  1463  		t.Fatalf("err: %v", err)
  1464  	}
  1465  
  1466  	if elapsed := time.Since(start); elapsed < 200*time.Millisecond {
  1467  		t.Fatalf("should block (returned in %s) %#v", elapsed, resp)
  1468  	}
  1469  	if resp.Index != 200 {
  1470  		t.Fatalf("Bad index: %d %d", resp.Index, 200)
  1471  	}
  1472  	if len(resp.Evaluations) != 1 || resp.Evaluations[0].JobID != "job1" {
  1473  		t.Fatalf("bad: %#v", resp.Evaluations)
  1474  	}
  1475  }
  1476  
  1477  func TestJobEndpoint_Plan_WithDiff(t *testing.T) {
  1478  	s1 := testServer(t, func(c *Config) {
  1479  		c.NumSchedulers = 0 // Prevent automatic dequeue
  1480  	})
  1481  	defer s1.Shutdown()
  1482  	codec := rpcClient(t, s1)
  1483  	testutil.WaitForLeader(t, s1.RPC)
  1484  
  1485  	// Create the register request
  1486  	job := mock.Job()
  1487  	req := &structs.JobRegisterRequest{
  1488  		Job:          job,
  1489  		WriteRequest: structs.WriteRequest{Region: "global"},
  1490  	}
  1491  
  1492  	// Fetch the response
  1493  	var resp structs.JobRegisterResponse
  1494  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil {
  1495  		t.Fatalf("err: %v", err)
  1496  	}
  1497  	if resp.Index == 0 {
  1498  		t.Fatalf("bad index: %d", resp.Index)
  1499  	}
  1500  
  1501  	// Create a plan request
  1502  	planReq := &structs.JobPlanRequest{
  1503  		Job:          job,
  1504  		Diff:         true,
  1505  		WriteRequest: structs.WriteRequest{Region: "global"},
  1506  	}
  1507  
  1508  	// Fetch the response
  1509  	var planResp structs.JobPlanResponse
  1510  	if err := msgpackrpc.CallWithCodec(codec, "Job.Plan", planReq, &planResp); err != nil {
  1511  		t.Fatalf("err: %v", err)
  1512  	}
  1513  
  1514  	// Check the response
  1515  	if planResp.JobModifyIndex == 0 {
  1516  		t.Fatalf("bad cas: %d", planResp.JobModifyIndex)
  1517  	}
  1518  	if planResp.Annotations == nil {
  1519  		t.Fatalf("no annotations")
  1520  	}
  1521  	if planResp.Diff == nil {
  1522  		t.Fatalf("no diff")
  1523  	}
  1524  	if len(planResp.FailedTGAllocs) == 0 {
  1525  		t.Fatalf("no failed task group alloc metrics")
  1526  	}
  1527  }
  1528  
  1529  func TestJobEndpoint_Plan_NoDiff(t *testing.T) {
  1530  	s1 := testServer(t, func(c *Config) {
  1531  		c.NumSchedulers = 0 // Prevent automatic dequeue
  1532  	})
  1533  	defer s1.Shutdown()
  1534  	codec := rpcClient(t, s1)
  1535  	testutil.WaitForLeader(t, s1.RPC)
  1536  
  1537  	// Create the register request
  1538  	job := mock.Job()
  1539  	req := &structs.JobRegisterRequest{
  1540  		Job:          job,
  1541  		WriteRequest: structs.WriteRequest{Region: "global"},
  1542  	}
  1543  
  1544  	// Fetch the response
  1545  	var resp structs.JobRegisterResponse
  1546  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil {
  1547  		t.Fatalf("err: %v", err)
  1548  	}
  1549  	if resp.Index == 0 {
  1550  		t.Fatalf("bad index: %d", resp.Index)
  1551  	}
  1552  
  1553  	// Create a plan request
  1554  	planReq := &structs.JobPlanRequest{
  1555  		Job:          job,
  1556  		Diff:         false,
  1557  		WriteRequest: structs.WriteRequest{Region: "global"},
  1558  	}
  1559  
  1560  	// Fetch the response
  1561  	var planResp structs.JobPlanResponse
  1562  	if err := msgpackrpc.CallWithCodec(codec, "Job.Plan", planReq, &planResp); err != nil {
  1563  		t.Fatalf("err: %v", err)
  1564  	}
  1565  
  1566  	// Check the response
  1567  	if planResp.JobModifyIndex == 0 {
  1568  		t.Fatalf("bad cas: %d", planResp.JobModifyIndex)
  1569  	}
  1570  	if planResp.Annotations == nil {
  1571  		t.Fatalf("no annotations")
  1572  	}
  1573  	if planResp.Diff != nil {
  1574  		t.Fatalf("got diff")
  1575  	}
  1576  	if len(planResp.FailedTGAllocs) == 0 {
  1577  		t.Fatalf("no failed task group alloc metrics")
  1578  	}
  1579  }
  1580  
  1581  func TestJobEndpoint_ImplicitConstraints_Vault(t *testing.T) {
  1582  	s1 := testServer(t, func(c *Config) {
  1583  		c.NumSchedulers = 0 // Prevent automatic dequeue
  1584  	})
  1585  	defer s1.Shutdown()
  1586  	codec := rpcClient(t, s1)
  1587  	testutil.WaitForLeader(t, s1.RPC)
  1588  
  1589  	// Enable vault
  1590  	tr, f := true, false
  1591  	s1.config.VaultConfig.Enabled = &tr
  1592  	s1.config.VaultConfig.AllowUnauthenticated = &f
  1593  
  1594  	// Replace the Vault Client on the server
  1595  	tvc := &TestVaultClient{}
  1596  	s1.vault = tvc
  1597  
  1598  	policy := "foo"
  1599  	goodToken := structs.GenerateUUID()
  1600  	goodPolicies := []string{"foo", "bar", "baz"}
  1601  	tvc.SetLookupTokenAllowedPolicies(goodToken, goodPolicies)
  1602  
  1603  	// Create the register request with a job asking for a vault policy
  1604  	job := mock.Job()
  1605  	job.VaultToken = goodToken
  1606  	job.TaskGroups[0].Tasks[0].Vault = &structs.Vault{
  1607  		Policies:   []string{policy},
  1608  		ChangeMode: structs.VaultChangeModeRestart,
  1609  	}
  1610  	req := &structs.JobRegisterRequest{
  1611  		Job:          job,
  1612  		WriteRequest: structs.WriteRequest{Region: "global"},
  1613  	}
  1614  
  1615  	// Fetch the response
  1616  	var resp structs.JobRegisterResponse
  1617  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil {
  1618  		t.Fatalf("bad: %v", err)
  1619  	}
  1620  
  1621  	// Check for the job in the FSM
  1622  	state := s1.fsm.State()
  1623  	out, err := state.JobByID(job.ID)
  1624  	if err != nil {
  1625  		t.Fatalf("err: %v", err)
  1626  	}
  1627  	if out == nil {
  1628  		t.Fatalf("expected job")
  1629  	}
  1630  	if out.CreateIndex != resp.JobModifyIndex {
  1631  		t.Fatalf("index mis-match")
  1632  	}
  1633  
  1634  	// Check that there is an implicit vault constraint
  1635  	constraints := out.TaskGroups[0].Constraints
  1636  	if len(constraints) != 1 {
  1637  		t.Fatalf("Expected an implicit constraint")
  1638  	}
  1639  
  1640  	if !constraints[0].Equal(vaultConstraint) {
  1641  		t.Fatalf("Expected implicit vault constraint")
  1642  	}
  1643  }
  1644  
  1645  func TestJobEndpoint_ImplicitConstraints_Signals(t *testing.T) {
  1646  	s1 := testServer(t, func(c *Config) {
  1647  		c.NumSchedulers = 0 // Prevent automatic dequeue
  1648  	})
  1649  	defer s1.Shutdown()
  1650  	codec := rpcClient(t, s1)
  1651  	testutil.WaitForLeader(t, s1.RPC)
  1652  
  1653  	// Create the register request with a job asking for a template that sends a
  1654  	// signal
  1655  	job := mock.Job()
  1656  	signal := "SIGUSR1"
  1657  	job.TaskGroups[0].Tasks[0].Templates = []*structs.Template{
  1658  		&structs.Template{
  1659  			SourcePath:   "foo",
  1660  			DestPath:     "bar",
  1661  			ChangeMode:   structs.TemplateChangeModeSignal,
  1662  			ChangeSignal: signal,
  1663  		},
  1664  	}
  1665  	req := &structs.JobRegisterRequest{
  1666  		Job:          job,
  1667  		WriteRequest: structs.WriteRequest{Region: "global"},
  1668  	}
  1669  
  1670  	// Fetch the response
  1671  	var resp structs.JobRegisterResponse
  1672  	if err := msgpackrpc.CallWithCodec(codec, "Job.Register", req, &resp); err != nil {
  1673  		t.Fatalf("bad: %v", err)
  1674  	}
  1675  
  1676  	// Check for the job in the FSM
  1677  	state := s1.fsm.State()
  1678  	out, err := state.JobByID(job.ID)
  1679  	if err != nil {
  1680  		t.Fatalf("err: %v", err)
  1681  	}
  1682  	if out == nil {
  1683  		t.Fatalf("expected job")
  1684  	}
  1685  	if out.CreateIndex != resp.JobModifyIndex {
  1686  		t.Fatalf("index mis-match")
  1687  	}
  1688  
  1689  	// Check that there is an implicit signal constraint
  1690  	constraints := out.TaskGroups[0].Constraints
  1691  	if len(constraints) != 1 {
  1692  		t.Fatalf("Expected an implicit constraint")
  1693  	}
  1694  
  1695  	sigConstraint := getSignalConstraint([]string{signal})
  1696  
  1697  	if !constraints[0].Equal(sigConstraint) {
  1698  		t.Fatalf("Expected implicit vault constraint")
  1699  	}
  1700  }
  1701  
  1702  func TestJobEndpoint_ValidateJob_InvalidDriverConf(t *testing.T) {
  1703  	// Create a mock job with an invalid config
  1704  	job := mock.Job()
  1705  	job.TaskGroups[0].Tasks[0].Config = map[string]interface{}{
  1706  		"foo": "bar",
  1707  	}
  1708  
  1709  	if err := validateJob(job); err == nil || !strings.Contains(err.Error(), "-> config") {
  1710  		t.Fatalf("Expected config error; got %v", err)
  1711  	}
  1712  }
  1713  
  1714  func TestJobEndpoint_ValidateJob_InvalidSignals(t *testing.T) {
  1715  	// Create a mock job that wants to send a signal to a driver that can't
  1716  	job := mock.Job()
  1717  	job.TaskGroups[0].Tasks[0].Driver = "qemu"
  1718  	job.TaskGroups[0].Tasks[0].Vault = &structs.Vault{
  1719  		Policies:     []string{"foo"},
  1720  		ChangeMode:   structs.VaultChangeModeSignal,
  1721  		ChangeSignal: "SIGUSR1",
  1722  	}
  1723  
  1724  	if err := validateJob(job); err == nil || !strings.Contains(err.Error(), "support sending signals") {
  1725  		t.Fatalf("Expected signal feasibility error; got %v", err)
  1726  	}
  1727  }