github.com/blixtra/nomad@v0.7.2-0.20171221000451-da9a1d7bb050/command/agent/job_endpoint_test.go (about)

     1  package agent
     2  
     3  import (
     4  	"net/http"
     5  	"net/http/httptest"
     6  	"reflect"
     7  	"strings"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/golang/snappy"
    12  	"github.com/hashicorp/nomad/api"
    13  	"github.com/hashicorp/nomad/helper"
    14  	"github.com/hashicorp/nomad/nomad/mock"
    15  	"github.com/hashicorp/nomad/nomad/structs"
    16  	"github.com/kr/pretty"
    17  	"github.com/stretchr/testify/assert"
    18  )
    19  
    20  func TestHTTP_JobsList(t *testing.T) {
    21  	t.Parallel()
    22  	httpTest(t, nil, func(s *TestAgent) {
    23  		for i := 0; i < 3; i++ {
    24  			// Create the job
    25  			job := mock.Job()
    26  			args := structs.JobRegisterRequest{
    27  				Job: job,
    28  				WriteRequest: structs.WriteRequest{
    29  					Region:    "global",
    30  					Namespace: structs.DefaultNamespace,
    31  				},
    32  			}
    33  			var resp structs.JobRegisterResponse
    34  			if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil {
    35  				t.Fatalf("err: %v", err)
    36  			}
    37  		}
    38  
    39  		// Make the HTTP request
    40  		req, err := http.NewRequest("GET", "/v1/jobs", nil)
    41  		if err != nil {
    42  			t.Fatalf("err: %v", err)
    43  		}
    44  		respW := httptest.NewRecorder()
    45  
    46  		// Make the request
    47  		obj, err := s.Server.JobsRequest(respW, req)
    48  		if err != nil {
    49  			t.Fatalf("err: %v", err)
    50  		}
    51  
    52  		// Check for the index
    53  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
    54  			t.Fatalf("missing index")
    55  		}
    56  		if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" {
    57  			t.Fatalf("missing known leader")
    58  		}
    59  		if respW.HeaderMap.Get("X-Nomad-LastContact") == "" {
    60  			t.Fatalf("missing last contact")
    61  		}
    62  
    63  		// Check the job
    64  		j := obj.([]*structs.JobListStub)
    65  		if len(j) != 3 {
    66  			t.Fatalf("bad: %#v", j)
    67  		}
    68  	})
    69  }
    70  
    71  func TestHTTP_PrefixJobsList(t *testing.T) {
    72  	ids := []string{
    73  		"aaaaaaaa-e8f7-fd38-c855-ab94ceb89706",
    74  		"aabbbbbb-e8f7-fd38-c855-ab94ceb89706",
    75  		"aabbcccc-e8f7-fd38-c855-ab94ceb89706",
    76  	}
    77  	t.Parallel()
    78  	httpTest(t, nil, func(s *TestAgent) {
    79  		for i := 0; i < 3; i++ {
    80  			// Create the job
    81  			job := mock.Job()
    82  			job.ID = ids[i]
    83  			job.TaskGroups[0].Count = 1
    84  			args := structs.JobRegisterRequest{
    85  				Job: job,
    86  				WriteRequest: structs.WriteRequest{
    87  					Region:    "global",
    88  					Namespace: structs.DefaultNamespace,
    89  				},
    90  			}
    91  			var resp structs.JobRegisterResponse
    92  			if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil {
    93  				t.Fatalf("err: %v", err)
    94  			}
    95  		}
    96  
    97  		// Make the HTTP request
    98  		req, err := http.NewRequest("GET", "/v1/jobs?prefix=aabb", nil)
    99  		if err != nil {
   100  			t.Fatalf("err: %v", err)
   101  		}
   102  		respW := httptest.NewRecorder()
   103  
   104  		// Make the request
   105  		obj, err := s.Server.JobsRequest(respW, req)
   106  		if err != nil {
   107  			t.Fatalf("err: %v", err)
   108  		}
   109  
   110  		// Check for the index
   111  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
   112  			t.Fatalf("missing index")
   113  		}
   114  		if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" {
   115  			t.Fatalf("missing known leader")
   116  		}
   117  		if respW.HeaderMap.Get("X-Nomad-LastContact") == "" {
   118  			t.Fatalf("missing last contact")
   119  		}
   120  
   121  		// Check the job
   122  		j := obj.([]*structs.JobListStub)
   123  		if len(j) != 2 {
   124  			t.Fatalf("bad: %#v", j)
   125  		}
   126  	})
   127  }
   128  
   129  func TestHTTP_JobsRegister(t *testing.T) {
   130  	t.Parallel()
   131  	httpTest(t, nil, func(s *TestAgent) {
   132  		// Create the job
   133  		job := api.MockJob()
   134  		args := api.JobRegisterRequest{
   135  			Job:          job,
   136  			WriteRequest: api.WriteRequest{Region: "global"},
   137  		}
   138  		buf := encodeReq(args)
   139  
   140  		// Make the HTTP request
   141  		req, err := http.NewRequest("PUT", "/v1/jobs", buf)
   142  		if err != nil {
   143  			t.Fatalf("err: %v", err)
   144  		}
   145  		respW := httptest.NewRecorder()
   146  
   147  		// Make the request
   148  		obj, err := s.Server.JobsRequest(respW, req)
   149  		if err != nil {
   150  			t.Fatalf("err: %v", err)
   151  		}
   152  
   153  		// Check the response
   154  		dereg := obj.(structs.JobRegisterResponse)
   155  		if dereg.EvalID == "" {
   156  			t.Fatalf("bad: %v", dereg)
   157  		}
   158  
   159  		// Check for the index
   160  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
   161  			t.Fatalf("missing index")
   162  		}
   163  
   164  		// Check the job is registered
   165  		getReq := structs.JobSpecificRequest{
   166  			JobID: *job.ID,
   167  			QueryOptions: structs.QueryOptions{
   168  				Region:    "global",
   169  				Namespace: structs.DefaultNamespace,
   170  			},
   171  		}
   172  		var getResp structs.SingleJobResponse
   173  		if err := s.Agent.RPC("Job.GetJob", &getReq, &getResp); err != nil {
   174  			t.Fatalf("err: %v", err)
   175  		}
   176  
   177  		if getResp.Job == nil {
   178  			t.Fatalf("job does not exist")
   179  		}
   180  	})
   181  }
   182  
   183  // Test that ACL token is properly threaded through to the RPC endpoint
   184  func TestHTTP_JobsRegister_ACL(t *testing.T) {
   185  	t.Parallel()
   186  	httpACLTest(t, nil, func(s *TestAgent) {
   187  		// Create the job
   188  		job := api.MockJob()
   189  		args := api.JobRegisterRequest{
   190  			Job: job,
   191  			WriteRequest: api.WriteRequest{
   192  				Region: "global",
   193  			},
   194  		}
   195  		buf := encodeReq(args)
   196  
   197  		// Make the HTTP request
   198  		req, err := http.NewRequest("PUT", "/v1/jobs", buf)
   199  		if err != nil {
   200  			t.Fatalf("err: %v", err)
   201  		}
   202  		respW := httptest.NewRecorder()
   203  		setToken(req, s.RootToken)
   204  
   205  		// Make the request
   206  		obj, err := s.Server.JobsRequest(respW, req)
   207  		if err != nil {
   208  			t.Fatalf("err: %v", err)
   209  		}
   210  		assert.NotNil(t, obj)
   211  	})
   212  }
   213  
   214  func TestHTTP_JobsRegister_Defaulting(t *testing.T) {
   215  	t.Parallel()
   216  	httpTest(t, nil, func(s *TestAgent) {
   217  		// Create the job
   218  		job := api.MockJob()
   219  
   220  		// Do not set its priority
   221  		job.Priority = nil
   222  
   223  		args := api.JobRegisterRequest{
   224  			Job:          job,
   225  			WriteRequest: api.WriteRequest{Region: "global"},
   226  		}
   227  		buf := encodeReq(args)
   228  
   229  		// Make the HTTP request
   230  		req, err := http.NewRequest("PUT", "/v1/jobs", buf)
   231  		if err != nil {
   232  			t.Fatalf("err: %v", err)
   233  		}
   234  		respW := httptest.NewRecorder()
   235  
   236  		// Make the request
   237  		obj, err := s.Server.JobsRequest(respW, req)
   238  		if err != nil {
   239  			t.Fatalf("err: %v", err)
   240  		}
   241  
   242  		// Check the response
   243  		dereg := obj.(structs.JobRegisterResponse)
   244  		if dereg.EvalID == "" {
   245  			t.Fatalf("bad: %v", dereg)
   246  		}
   247  
   248  		// Check for the index
   249  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
   250  			t.Fatalf("missing index")
   251  		}
   252  
   253  		// Check the job is registered
   254  		getReq := structs.JobSpecificRequest{
   255  			JobID: *job.ID,
   256  			QueryOptions: structs.QueryOptions{
   257  				Region:    "global",
   258  				Namespace: structs.DefaultNamespace,
   259  			},
   260  		}
   261  		var getResp structs.SingleJobResponse
   262  		if err := s.Agent.RPC("Job.GetJob", &getReq, &getResp); err != nil {
   263  			t.Fatalf("err: %v", err)
   264  		}
   265  
   266  		if getResp.Job == nil {
   267  			t.Fatalf("job does not exist")
   268  		}
   269  		if getResp.Job.Priority != 50 {
   270  			t.Fatalf("job didn't get defaulted")
   271  		}
   272  	})
   273  }
   274  
   275  func TestHTTP_JobQuery(t *testing.T) {
   276  	t.Parallel()
   277  	httpTest(t, nil, func(s *TestAgent) {
   278  		// Create the job
   279  		job := mock.Job()
   280  		args := structs.JobRegisterRequest{
   281  			Job: job,
   282  			WriteRequest: structs.WriteRequest{
   283  				Region:    "global",
   284  				Namespace: structs.DefaultNamespace,
   285  			},
   286  		}
   287  		var resp structs.JobRegisterResponse
   288  		if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil {
   289  			t.Fatalf("err: %v", err)
   290  		}
   291  
   292  		// Make the HTTP request
   293  		req, err := http.NewRequest("GET", "/v1/job/"+job.ID, nil)
   294  		if err != nil {
   295  			t.Fatalf("err: %v", err)
   296  		}
   297  		respW := httptest.NewRecorder()
   298  
   299  		// Make the request
   300  		obj, err := s.Server.JobSpecificRequest(respW, req)
   301  		if err != nil {
   302  			t.Fatalf("err: %v", err)
   303  		}
   304  
   305  		// Check for the index
   306  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
   307  			t.Fatalf("missing index")
   308  		}
   309  		if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" {
   310  			t.Fatalf("missing known leader")
   311  		}
   312  		if respW.HeaderMap.Get("X-Nomad-LastContact") == "" {
   313  			t.Fatalf("missing last contact")
   314  		}
   315  
   316  		// Check the job
   317  		j := obj.(*structs.Job)
   318  		if j.ID != job.ID {
   319  			t.Fatalf("bad: %#v", j)
   320  		}
   321  	})
   322  }
   323  
   324  func TestHTTP_JobQuery_Payload(t *testing.T) {
   325  	t.Parallel()
   326  	httpTest(t, nil, func(s *TestAgent) {
   327  		// Create the job
   328  		job := mock.Job()
   329  
   330  		// Insert Payload compressed
   331  		expected := []byte("hello world")
   332  		compressed := snappy.Encode(nil, expected)
   333  		job.Payload = compressed
   334  
   335  		// Directly manipulate the state
   336  		state := s.Agent.server.State()
   337  		if err := state.UpsertJob(1000, job); err != nil {
   338  			t.Fatalf("Failed to upsert job: %v", err)
   339  		}
   340  
   341  		// Make the HTTP request
   342  		req, err := http.NewRequest("GET", "/v1/job/"+job.ID, nil)
   343  		if err != nil {
   344  			t.Fatalf("err: %v", err)
   345  		}
   346  		respW := httptest.NewRecorder()
   347  
   348  		// Make the request
   349  		obj, err := s.Server.JobSpecificRequest(respW, req)
   350  		if err != nil {
   351  			t.Fatalf("err: %v", err)
   352  		}
   353  
   354  		// Check for the index
   355  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
   356  			t.Fatalf("missing index")
   357  		}
   358  		if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" {
   359  			t.Fatalf("missing known leader")
   360  		}
   361  		if respW.HeaderMap.Get("X-Nomad-LastContact") == "" {
   362  			t.Fatalf("missing last contact")
   363  		}
   364  
   365  		// Check the job
   366  		j := obj.(*structs.Job)
   367  		if j.ID != job.ID {
   368  			t.Fatalf("bad: %#v", j)
   369  		}
   370  
   371  		// Check the payload is decompressed
   372  		if !reflect.DeepEqual(j.Payload, expected) {
   373  			t.Fatalf("Payload not decompressed properly; got %#v; want %#v", j.Payload, expected)
   374  		}
   375  	})
   376  }
   377  
   378  func TestHTTP_JobUpdate(t *testing.T) {
   379  	t.Parallel()
   380  	httpTest(t, nil, func(s *TestAgent) {
   381  		// Create the job
   382  		job := api.MockJob()
   383  		args := api.JobRegisterRequest{
   384  			Job: job,
   385  			WriteRequest: api.WriteRequest{
   386  				Region:    "global",
   387  				Namespace: api.DefaultNamespace,
   388  			},
   389  		}
   390  		buf := encodeReq(args)
   391  
   392  		// Make the HTTP request
   393  		req, err := http.NewRequest("PUT", "/v1/job/"+*job.ID, buf)
   394  		if err != nil {
   395  			t.Fatalf("err: %v", err)
   396  		}
   397  		respW := httptest.NewRecorder()
   398  
   399  		// Make the request
   400  		obj, err := s.Server.JobSpecificRequest(respW, req)
   401  		if err != nil {
   402  			t.Fatalf("err: %v", err)
   403  		}
   404  
   405  		// Check the response
   406  		dereg := obj.(structs.JobRegisterResponse)
   407  		if dereg.EvalID == "" {
   408  			t.Fatalf("bad: %v", dereg)
   409  		}
   410  
   411  		// Check for the index
   412  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
   413  			t.Fatalf("missing index")
   414  		}
   415  
   416  		// Check the job is registered
   417  		getReq := structs.JobSpecificRequest{
   418  			JobID: *job.ID,
   419  			QueryOptions: structs.QueryOptions{
   420  				Region:    "global",
   421  				Namespace: structs.DefaultNamespace,
   422  			},
   423  		}
   424  		var getResp structs.SingleJobResponse
   425  		if err := s.Agent.RPC("Job.GetJob", &getReq, &getResp); err != nil {
   426  			t.Fatalf("err: %v", err)
   427  		}
   428  
   429  		if getResp.Job == nil {
   430  			t.Fatalf("job does not exist")
   431  		}
   432  	})
   433  }
   434  
   435  func TestHTTP_JobDelete(t *testing.T) {
   436  	t.Parallel()
   437  	httpTest(t, nil, func(s *TestAgent) {
   438  		// Create the job
   439  		job := mock.Job()
   440  		args := structs.JobRegisterRequest{
   441  			Job: job,
   442  			WriteRequest: structs.WriteRequest{
   443  				Region:    "global",
   444  				Namespace: structs.DefaultNamespace,
   445  			},
   446  		}
   447  		var resp structs.JobRegisterResponse
   448  		if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil {
   449  			t.Fatalf("err: %v", err)
   450  		}
   451  
   452  		// Make the HTTP request to do a soft delete
   453  		req, err := http.NewRequest("DELETE", "/v1/job/"+job.ID, nil)
   454  		if err != nil {
   455  			t.Fatalf("err: %v", err)
   456  		}
   457  		respW := httptest.NewRecorder()
   458  
   459  		// Make the request
   460  		obj, err := s.Server.JobSpecificRequest(respW, req)
   461  		if err != nil {
   462  			t.Fatalf("err: %v", err)
   463  		}
   464  
   465  		// Check the response
   466  		dereg := obj.(structs.JobDeregisterResponse)
   467  		if dereg.EvalID == "" {
   468  			t.Fatalf("bad: %v", dereg)
   469  		}
   470  
   471  		// Check for the index
   472  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
   473  			t.Fatalf("missing index")
   474  		}
   475  
   476  		// Check the job is still queryable
   477  		getReq1 := structs.JobSpecificRequest{
   478  			JobID: job.ID,
   479  			QueryOptions: structs.QueryOptions{
   480  				Region:    "global",
   481  				Namespace: structs.DefaultNamespace,
   482  			},
   483  		}
   484  		var getResp1 structs.SingleJobResponse
   485  		if err := s.Agent.RPC("Job.GetJob", &getReq1, &getResp1); err != nil {
   486  			t.Fatalf("err: %v", err)
   487  		}
   488  		if getResp1.Job == nil {
   489  			t.Fatalf("job doesn't exists")
   490  		}
   491  		if !getResp1.Job.Stop {
   492  			t.Fatalf("job should be marked as stop")
   493  		}
   494  
   495  		// Make the HTTP request to do a purge delete
   496  		req2, err := http.NewRequest("DELETE", "/v1/job/"+job.ID+"?purge=true", nil)
   497  		if err != nil {
   498  			t.Fatalf("err: %v", err)
   499  		}
   500  		respW.Flush()
   501  
   502  		// Make the request
   503  		obj, err = s.Server.JobSpecificRequest(respW, req2)
   504  		if err != nil {
   505  			t.Fatalf("err: %v", err)
   506  		}
   507  
   508  		// Check the response
   509  		dereg = obj.(structs.JobDeregisterResponse)
   510  		if dereg.EvalID == "" {
   511  			t.Fatalf("bad: %v", dereg)
   512  		}
   513  
   514  		// Check for the index
   515  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
   516  			t.Fatalf("missing index")
   517  		}
   518  
   519  		// Check the job is gone
   520  		getReq2 := structs.JobSpecificRequest{
   521  			JobID: job.ID,
   522  			QueryOptions: structs.QueryOptions{
   523  				Region:    "global",
   524  				Namespace: structs.DefaultNamespace,
   525  			},
   526  		}
   527  		var getResp2 structs.SingleJobResponse
   528  		if err := s.Agent.RPC("Job.GetJob", &getReq2, &getResp2); err != nil {
   529  			t.Fatalf("err: %v", err)
   530  		}
   531  		if getResp2.Job != nil {
   532  			t.Fatalf("job still exists")
   533  		}
   534  	})
   535  }
   536  
   537  func TestHTTP_JobForceEvaluate(t *testing.T) {
   538  	t.Parallel()
   539  	httpTest(t, nil, func(s *TestAgent) {
   540  		// Create the job
   541  		job := mock.Job()
   542  		args := structs.JobRegisterRequest{
   543  			Job: job,
   544  			WriteRequest: structs.WriteRequest{
   545  				Region:    "global",
   546  				Namespace: structs.DefaultNamespace,
   547  			},
   548  		}
   549  		var resp structs.JobRegisterResponse
   550  		if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil {
   551  			t.Fatalf("err: %v", err)
   552  		}
   553  
   554  		// Make the HTTP request
   555  		req, err := http.NewRequest("POST", "/v1/job/"+job.ID+"/evaluate", nil)
   556  		if err != nil {
   557  			t.Fatalf("err: %v", err)
   558  		}
   559  		respW := httptest.NewRecorder()
   560  
   561  		// Make the request
   562  		obj, err := s.Server.JobSpecificRequest(respW, req)
   563  		if err != nil {
   564  			t.Fatalf("err: %v", err)
   565  		}
   566  
   567  		// Check the response
   568  		reg := obj.(structs.JobRegisterResponse)
   569  		if reg.EvalID == "" {
   570  			t.Fatalf("bad: %v", reg)
   571  		}
   572  
   573  		// Check for the index
   574  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
   575  			t.Fatalf("missing index")
   576  		}
   577  	})
   578  }
   579  
   580  func TestHTTP_JobEvaluations(t *testing.T) {
   581  	t.Parallel()
   582  	httpTest(t, nil, func(s *TestAgent) {
   583  		// Create the job
   584  		job := mock.Job()
   585  		args := structs.JobRegisterRequest{
   586  			Job: job,
   587  			WriteRequest: structs.WriteRequest{
   588  				Region:    "global",
   589  				Namespace: structs.DefaultNamespace,
   590  			},
   591  		}
   592  		var resp structs.JobRegisterResponse
   593  		if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil {
   594  			t.Fatalf("err: %v", err)
   595  		}
   596  
   597  		// Make the HTTP request
   598  		req, err := http.NewRequest("GET", "/v1/job/"+job.ID+"/evaluations", nil)
   599  		if err != nil {
   600  			t.Fatalf("err: %v", err)
   601  		}
   602  		respW := httptest.NewRecorder()
   603  
   604  		// Make the request
   605  		obj, err := s.Server.JobSpecificRequest(respW, req)
   606  		if err != nil {
   607  			t.Fatalf("err: %v", err)
   608  		}
   609  
   610  		// Check the response
   611  		evals := obj.([]*structs.Evaluation)
   612  		// Can be multiple evals, use the last one, since they are in order
   613  		idx := len(evals) - 1
   614  		if len(evals) < 0 || evals[idx].ID != resp.EvalID {
   615  			t.Fatalf("bad: %v", evals)
   616  		}
   617  
   618  		// Check for the index
   619  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
   620  			t.Fatalf("missing index")
   621  		}
   622  		if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" {
   623  			t.Fatalf("missing known leader")
   624  		}
   625  		if respW.HeaderMap.Get("X-Nomad-LastContact") == "" {
   626  			t.Fatalf("missing last contact")
   627  		}
   628  	})
   629  }
   630  
   631  func TestHTTP_JobAllocations(t *testing.T) {
   632  	t.Parallel()
   633  	httpTest(t, nil, func(s *TestAgent) {
   634  		// Create the job
   635  		alloc1 := mock.Alloc()
   636  		args := structs.JobRegisterRequest{
   637  			Job: alloc1.Job,
   638  			WriteRequest: structs.WriteRequest{
   639  				Region:    "global",
   640  				Namespace: structs.DefaultNamespace,
   641  			},
   642  		}
   643  		var resp structs.JobRegisterResponse
   644  		if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil {
   645  			t.Fatalf("err: %v", err)
   646  		}
   647  
   648  		// Directly manipulate the state
   649  		expectedDisplayMsg := "test message"
   650  		testEvent := structs.NewTaskEvent("test event").SetMessage(expectedDisplayMsg)
   651  		var events []*structs.TaskEvent
   652  		events = append(events, testEvent)
   653  		taskState := &structs.TaskState{Events: events}
   654  		alloc1.TaskStates = make(map[string]*structs.TaskState)
   655  		alloc1.TaskStates["test"] = taskState
   656  		state := s.Agent.server.State()
   657  		err := state.UpsertAllocs(1000, []*structs.Allocation{alloc1})
   658  		if err != nil {
   659  			t.Fatalf("err: %v", err)
   660  		}
   661  
   662  		// Make the HTTP request
   663  		req, err := http.NewRequest("GET", "/v1/job/"+alloc1.Job.ID+"/allocations?all=true", nil)
   664  		if err != nil {
   665  			t.Fatalf("err: %v", err)
   666  		}
   667  		respW := httptest.NewRecorder()
   668  
   669  		// Make the request
   670  		obj, err := s.Server.JobSpecificRequest(respW, req)
   671  		if err != nil {
   672  			t.Fatalf("err: %v", err)
   673  		}
   674  
   675  		// Check the response
   676  		allocs := obj.([]*structs.AllocListStub)
   677  		if len(allocs) != 1 && allocs[0].ID != alloc1.ID {
   678  			t.Fatalf("bad: %v", allocs)
   679  		}
   680  		displayMsg := allocs[0].TaskStates["test"].Events[0].DisplayMessage
   681  		assert.Equal(t, expectedDisplayMsg, displayMsg)
   682  
   683  		// Check for the index
   684  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
   685  			t.Fatalf("missing index")
   686  		}
   687  		if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" {
   688  			t.Fatalf("missing known leader")
   689  		}
   690  		if respW.HeaderMap.Get("X-Nomad-LastContact") == "" {
   691  			t.Fatalf("missing last contact")
   692  		}
   693  	})
   694  }
   695  
   696  func TestHTTP_JobDeployments(t *testing.T) {
   697  	assert := assert.New(t)
   698  	t.Parallel()
   699  	httpTest(t, nil, func(s *TestAgent) {
   700  		// Create the job
   701  		j := mock.Job()
   702  		args := structs.JobRegisterRequest{
   703  			Job: j,
   704  			WriteRequest: structs.WriteRequest{
   705  				Region:    "global",
   706  				Namespace: structs.DefaultNamespace,
   707  			},
   708  		}
   709  		var resp structs.JobRegisterResponse
   710  		assert.Nil(s.Agent.RPC("Job.Register", &args, &resp), "JobRegister")
   711  
   712  		// Directly manipulate the state
   713  		state := s.Agent.server.State()
   714  		d := mock.Deployment()
   715  		d.JobID = j.ID
   716  		assert.Nil(state.UpsertDeployment(1000, d), "UpsertDeployment")
   717  
   718  		// Make the HTTP request
   719  		req, err := http.NewRequest("GET", "/v1/job/"+j.ID+"/deployments", nil)
   720  		assert.Nil(err, "HTTP")
   721  		respW := httptest.NewRecorder()
   722  
   723  		// Make the request
   724  		obj, err := s.Server.JobSpecificRequest(respW, req)
   725  		assert.Nil(err, "JobSpecificRequest")
   726  
   727  		// Check the response
   728  		deploys := obj.([]*structs.Deployment)
   729  		assert.Len(deploys, 1, "deployments")
   730  		assert.Equal(d.ID, deploys[0].ID, "deployment id")
   731  
   732  		assert.NotZero(respW.HeaderMap.Get("X-Nomad-Index"), "missing index")
   733  		assert.Equal("true", respW.HeaderMap.Get("X-Nomad-KnownLeader"), "missing known leader")
   734  		assert.NotZero(respW.HeaderMap.Get("X-Nomad-LastContact"), "missing last contact")
   735  	})
   736  }
   737  
   738  func TestHTTP_JobDeployment(t *testing.T) {
   739  	assert := assert.New(t)
   740  	t.Parallel()
   741  	httpTest(t, nil, func(s *TestAgent) {
   742  		// Create the job
   743  		j := mock.Job()
   744  		args := structs.JobRegisterRequest{
   745  			Job: j,
   746  			WriteRequest: structs.WriteRequest{
   747  				Region:    "global",
   748  				Namespace: structs.DefaultNamespace,
   749  			},
   750  		}
   751  		var resp structs.JobRegisterResponse
   752  		assert.Nil(s.Agent.RPC("Job.Register", &args, &resp), "JobRegister")
   753  
   754  		// Directly manipulate the state
   755  		state := s.Agent.server.State()
   756  		d := mock.Deployment()
   757  		d.JobID = j.ID
   758  		assert.Nil(state.UpsertDeployment(1000, d), "UpsertDeployment")
   759  
   760  		// Make the HTTP request
   761  		req, err := http.NewRequest("GET", "/v1/job/"+j.ID+"/deployment", nil)
   762  		assert.Nil(err, "HTTP")
   763  		respW := httptest.NewRecorder()
   764  
   765  		// Make the request
   766  		obj, err := s.Server.JobSpecificRequest(respW, req)
   767  		assert.Nil(err, "JobSpecificRequest")
   768  
   769  		// Check the response
   770  		out := obj.(*structs.Deployment)
   771  		assert.NotNil(out, "deployment")
   772  		assert.Equal(d.ID, out.ID, "deployment id")
   773  
   774  		assert.NotZero(respW.HeaderMap.Get("X-Nomad-Index"), "missing index")
   775  		assert.Equal("true", respW.HeaderMap.Get("X-Nomad-KnownLeader"), "missing known leader")
   776  		assert.NotZero(respW.HeaderMap.Get("X-Nomad-LastContact"), "missing last contact")
   777  	})
   778  }
   779  
   780  func TestHTTP_JobVersions(t *testing.T) {
   781  	t.Parallel()
   782  	httpTest(t, nil, func(s *TestAgent) {
   783  		// Create the job
   784  		job := mock.Job()
   785  		args := structs.JobRegisterRequest{
   786  			Job: job,
   787  			WriteRequest: structs.WriteRequest{
   788  				Region:    "global",
   789  				Namespace: structs.DefaultNamespace,
   790  			},
   791  		}
   792  		var resp structs.JobRegisterResponse
   793  		if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil {
   794  			t.Fatalf("err: %v", err)
   795  		}
   796  
   797  		job2 := mock.Job()
   798  		job2.ID = job.ID
   799  		job2.Priority = 100
   800  
   801  		args2 := structs.JobRegisterRequest{
   802  			Job: job2,
   803  			WriteRequest: structs.WriteRequest{
   804  				Region:    "global",
   805  				Namespace: structs.DefaultNamespace,
   806  			},
   807  		}
   808  		var resp2 structs.JobRegisterResponse
   809  		if err := s.Agent.RPC("Job.Register", &args2, &resp2); err != nil {
   810  			t.Fatalf("err: %v", err)
   811  		}
   812  
   813  		// Make the HTTP request
   814  		req, err := http.NewRequest("GET", "/v1/job/"+job.ID+"/versions?diffs=true", nil)
   815  		if err != nil {
   816  			t.Fatalf("err: %v", err)
   817  		}
   818  		respW := httptest.NewRecorder()
   819  
   820  		// Make the request
   821  		obj, err := s.Server.JobSpecificRequest(respW, req)
   822  		if err != nil {
   823  			t.Fatalf("err: %v", err)
   824  		}
   825  
   826  		// Check the response
   827  		vResp := obj.(structs.JobVersionsResponse)
   828  		versions := vResp.Versions
   829  		if len(versions) != 2 {
   830  			t.Fatalf("got %d versions; want 2", len(versions))
   831  		}
   832  
   833  		if v := versions[0]; v.Version != 1 || v.Priority != 100 {
   834  			t.Fatalf("bad %v", v)
   835  		}
   836  
   837  		if v := versions[1]; v.Version != 0 {
   838  			t.Fatalf("bad %v", v)
   839  		}
   840  
   841  		if len(vResp.Diffs) != 1 {
   842  			t.Fatalf("bad %v", vResp)
   843  		}
   844  
   845  		// Check for the index
   846  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
   847  			t.Fatalf("missing index")
   848  		}
   849  		if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" {
   850  			t.Fatalf("missing known leader")
   851  		}
   852  		if respW.HeaderMap.Get("X-Nomad-LastContact") == "" {
   853  			t.Fatalf("missing last contact")
   854  		}
   855  	})
   856  }
   857  
   858  func TestHTTP_PeriodicForce(t *testing.T) {
   859  	t.Parallel()
   860  	httpTest(t, nil, func(s *TestAgent) {
   861  		// Create and register a periodic job.
   862  		job := mock.PeriodicJob()
   863  		args := structs.JobRegisterRequest{
   864  			Job: job,
   865  			WriteRequest: structs.WriteRequest{
   866  				Region:    "global",
   867  				Namespace: structs.DefaultNamespace,
   868  			},
   869  		}
   870  		var resp structs.JobRegisterResponse
   871  		if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil {
   872  			t.Fatalf("err: %v", err)
   873  		}
   874  
   875  		// Make the HTTP request
   876  		req, err := http.NewRequest("POST", "/v1/job/"+job.ID+"/periodic/force", nil)
   877  		if err != nil {
   878  			t.Fatalf("err: %v", err)
   879  		}
   880  		respW := httptest.NewRecorder()
   881  
   882  		// Make the request
   883  		obj, err := s.Server.JobSpecificRequest(respW, req)
   884  		if err != nil {
   885  			t.Fatalf("err: %v", err)
   886  		}
   887  
   888  		// Check for the index
   889  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
   890  			t.Fatalf("missing index")
   891  		}
   892  
   893  		// Check the response
   894  		r := obj.(structs.PeriodicForceResponse)
   895  		if r.EvalID == "" {
   896  			t.Fatalf("bad: %#v", r)
   897  		}
   898  	})
   899  }
   900  
   901  func TestHTTP_JobPlan(t *testing.T) {
   902  	t.Parallel()
   903  	httpTest(t, nil, func(s *TestAgent) {
   904  		// Create the job
   905  		job := api.MockJob()
   906  		args := api.JobPlanRequest{
   907  			Job:  job,
   908  			Diff: true,
   909  			WriteRequest: api.WriteRequest{
   910  				Region:    "global",
   911  				Namespace: api.DefaultNamespace,
   912  			},
   913  		}
   914  		buf := encodeReq(args)
   915  
   916  		// Make the HTTP request
   917  		req, err := http.NewRequest("PUT", "/v1/job/"+*job.ID+"/plan", buf)
   918  		if err != nil {
   919  			t.Fatalf("err: %v", err)
   920  		}
   921  		respW := httptest.NewRecorder()
   922  
   923  		// Make the request
   924  		obj, err := s.Server.JobSpecificRequest(respW, req)
   925  		if err != nil {
   926  			t.Fatalf("err: %v", err)
   927  		}
   928  
   929  		// Check the response
   930  		plan := obj.(structs.JobPlanResponse)
   931  		if plan.Annotations == nil {
   932  			t.Fatalf("bad: %v", plan)
   933  		}
   934  
   935  		if plan.Diff == nil {
   936  			t.Fatalf("bad: %v", plan)
   937  		}
   938  	})
   939  }
   940  
   941  func TestHTTP_JobDispatch(t *testing.T) {
   942  	t.Parallel()
   943  	httpTest(t, nil, func(s *TestAgent) {
   944  		// Create the parameterized job
   945  		job := mock.Job()
   946  		job.Type = "batch"
   947  		job.ParameterizedJob = &structs.ParameterizedJobConfig{}
   948  
   949  		args := structs.JobRegisterRequest{
   950  			Job: job,
   951  			WriteRequest: structs.WriteRequest{
   952  				Region:    "global",
   953  				Namespace: structs.DefaultNamespace,
   954  			},
   955  		}
   956  		var resp structs.JobRegisterResponse
   957  		if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil {
   958  			t.Fatalf("err: %v", err)
   959  		}
   960  
   961  		// Make the request
   962  		respW := httptest.NewRecorder()
   963  		args2 := structs.JobDispatchRequest{
   964  			WriteRequest: structs.WriteRequest{
   965  				Region:    "global",
   966  				Namespace: structs.DefaultNamespace,
   967  			},
   968  		}
   969  		buf := encodeReq(args2)
   970  
   971  		// Make the HTTP request
   972  		req2, err := http.NewRequest("PUT", "/v1/job/"+job.ID+"/dispatch", buf)
   973  		if err != nil {
   974  			t.Fatalf("err: %v", err)
   975  		}
   976  		respW.Flush()
   977  
   978  		// Make the request
   979  		obj, err := s.Server.JobSpecificRequest(respW, req2)
   980  		if err != nil {
   981  			t.Fatalf("err: %v", err)
   982  		}
   983  
   984  		// Check the response
   985  		dispatch := obj.(structs.JobDispatchResponse)
   986  		if dispatch.EvalID == "" {
   987  			t.Fatalf("bad: %v", dispatch)
   988  		}
   989  
   990  		if dispatch.DispatchedJobID == "" {
   991  			t.Fatalf("bad: %v", dispatch)
   992  		}
   993  	})
   994  }
   995  
   996  func TestHTTP_JobRevert(t *testing.T) {
   997  	t.Parallel()
   998  	httpTest(t, nil, func(s *TestAgent) {
   999  		// Create the job and register it twice
  1000  		job := mock.Job()
  1001  		regReq := structs.JobRegisterRequest{
  1002  			Job: job,
  1003  			WriteRequest: structs.WriteRequest{
  1004  				Region:    "global",
  1005  				Namespace: structs.DefaultNamespace,
  1006  			},
  1007  		}
  1008  		var regResp structs.JobRegisterResponse
  1009  		if err := s.Agent.RPC("Job.Register", &regReq, &regResp); err != nil {
  1010  			t.Fatalf("err: %v", err)
  1011  		}
  1012  
  1013  		// Change the job to get a new version
  1014  		job.Datacenters = append(job.Datacenters, "foo")
  1015  		if err := s.Agent.RPC("Job.Register", &regReq, &regResp); err != nil {
  1016  			t.Fatalf("err: %v", err)
  1017  		}
  1018  
  1019  		args := structs.JobRevertRequest{
  1020  			JobID:      job.ID,
  1021  			JobVersion: 0,
  1022  			WriteRequest: structs.WriteRequest{
  1023  				Region:    "global",
  1024  				Namespace: structs.DefaultNamespace,
  1025  			},
  1026  		}
  1027  		buf := encodeReq(args)
  1028  
  1029  		// Make the HTTP request
  1030  		req, err := http.NewRequest("PUT", "/v1/job/"+job.ID+"/revert", buf)
  1031  		if err != nil {
  1032  			t.Fatalf("err: %v", err)
  1033  		}
  1034  		respW := httptest.NewRecorder()
  1035  
  1036  		// Make the request
  1037  		obj, err := s.Server.JobSpecificRequest(respW, req)
  1038  		if err != nil {
  1039  			t.Fatalf("err: %v", err)
  1040  		}
  1041  
  1042  		// Check the response
  1043  		revertResp := obj.(structs.JobRegisterResponse)
  1044  		if revertResp.EvalID == "" {
  1045  			t.Fatalf("bad: %v", revertResp)
  1046  		}
  1047  
  1048  		// Check for the index
  1049  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
  1050  			t.Fatalf("missing index")
  1051  		}
  1052  	})
  1053  }
  1054  
  1055  func TestHTTP_JobStable(t *testing.T) {
  1056  	t.Parallel()
  1057  	httpTest(t, nil, func(s *TestAgent) {
  1058  		// Create the job and register it twice
  1059  		job := mock.Job()
  1060  		regReq := structs.JobRegisterRequest{
  1061  			Job: job,
  1062  			WriteRequest: structs.WriteRequest{
  1063  				Region:    "global",
  1064  				Namespace: structs.DefaultNamespace,
  1065  			},
  1066  		}
  1067  		var regResp structs.JobRegisterResponse
  1068  		if err := s.Agent.RPC("Job.Register", &regReq, &regResp); err != nil {
  1069  			t.Fatalf("err: %v", err)
  1070  		}
  1071  
  1072  		if err := s.Agent.RPC("Job.Register", &regReq, &regResp); err != nil {
  1073  			t.Fatalf("err: %v", err)
  1074  		}
  1075  
  1076  		args := structs.JobStabilityRequest{
  1077  			JobID:      job.ID,
  1078  			JobVersion: 0,
  1079  			Stable:     true,
  1080  			WriteRequest: structs.WriteRequest{
  1081  				Region:    "global",
  1082  				Namespace: structs.DefaultNamespace,
  1083  			},
  1084  		}
  1085  		buf := encodeReq(args)
  1086  
  1087  		// Make the HTTP request
  1088  		req, err := http.NewRequest("PUT", "/v1/job/"+job.ID+"/stable", buf)
  1089  		if err != nil {
  1090  			t.Fatalf("err: %v", err)
  1091  		}
  1092  		respW := httptest.NewRecorder()
  1093  
  1094  		// Make the request
  1095  		obj, err := s.Server.JobSpecificRequest(respW, req)
  1096  		if err != nil {
  1097  			t.Fatalf("err: %v", err)
  1098  		}
  1099  
  1100  		// Check the response
  1101  		stableResp := obj.(structs.JobStabilityResponse)
  1102  		if stableResp.Index == 0 {
  1103  			t.Fatalf("bad: %v", stableResp)
  1104  		}
  1105  
  1106  		// Check for the index
  1107  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
  1108  			t.Fatalf("missing index")
  1109  		}
  1110  	})
  1111  }
  1112  
  1113  func TestJobs_ApiJobToStructsJob(t *testing.T) {
  1114  	apiJob := &api.Job{
  1115  		Stop:        helper.BoolToPtr(true),
  1116  		Region:      helper.StringToPtr("global"),
  1117  		Namespace:   helper.StringToPtr("foo"),
  1118  		ID:          helper.StringToPtr("foo"),
  1119  		ParentID:    helper.StringToPtr("lol"),
  1120  		Name:        helper.StringToPtr("name"),
  1121  		Type:        helper.StringToPtr("service"),
  1122  		Priority:    helper.IntToPtr(50),
  1123  		AllAtOnce:   helper.BoolToPtr(true),
  1124  		Datacenters: []string{"dc1", "dc2"},
  1125  		Constraints: []*api.Constraint{
  1126  			{
  1127  				LTarget: "a",
  1128  				RTarget: "b",
  1129  				Operand: "c",
  1130  			},
  1131  		},
  1132  		Update: &api.UpdateStrategy{
  1133  			Stagger:         helper.TimeToPtr(1 * time.Second),
  1134  			MaxParallel:     helper.IntToPtr(5),
  1135  			HealthCheck:     helper.StringToPtr(structs.UpdateStrategyHealthCheck_Manual),
  1136  			MinHealthyTime:  helper.TimeToPtr(1 * time.Minute),
  1137  			HealthyDeadline: helper.TimeToPtr(3 * time.Minute),
  1138  			AutoRevert:      helper.BoolToPtr(false),
  1139  			Canary:          helper.IntToPtr(1),
  1140  		},
  1141  		Periodic: &api.PeriodicConfig{
  1142  			Enabled:         helper.BoolToPtr(true),
  1143  			Spec:            helper.StringToPtr("spec"),
  1144  			SpecType:        helper.StringToPtr("cron"),
  1145  			ProhibitOverlap: helper.BoolToPtr(true),
  1146  			TimeZone:        helper.StringToPtr("test zone"),
  1147  		},
  1148  		ParameterizedJob: &api.ParameterizedJobConfig{
  1149  			Payload:      "payload",
  1150  			MetaRequired: []string{"a", "b"},
  1151  			MetaOptional: []string{"c", "d"},
  1152  		},
  1153  		Payload: []byte("payload"),
  1154  		Meta: map[string]string{
  1155  			"foo": "bar",
  1156  		},
  1157  		TaskGroups: []*api.TaskGroup{
  1158  			{
  1159  				Name:  helper.StringToPtr("group1"),
  1160  				Count: helper.IntToPtr(5),
  1161  				Constraints: []*api.Constraint{
  1162  					{
  1163  						LTarget: "x",
  1164  						RTarget: "y",
  1165  						Operand: "z",
  1166  					},
  1167  				},
  1168  				RestartPolicy: &api.RestartPolicy{
  1169  					Interval: helper.TimeToPtr(1 * time.Second),
  1170  					Attempts: helper.IntToPtr(5),
  1171  					Delay:    helper.TimeToPtr(10 * time.Second),
  1172  					Mode:     helper.StringToPtr("delay"),
  1173  				},
  1174  				EphemeralDisk: &api.EphemeralDisk{
  1175  					SizeMB:  helper.IntToPtr(100),
  1176  					Sticky:  helper.BoolToPtr(true),
  1177  					Migrate: helper.BoolToPtr(true),
  1178  				},
  1179  				Update: &api.UpdateStrategy{
  1180  					HealthCheck:     helper.StringToPtr(structs.UpdateStrategyHealthCheck_Checks),
  1181  					MinHealthyTime:  helper.TimeToPtr(2 * time.Minute),
  1182  					HealthyDeadline: helper.TimeToPtr(5 * time.Minute),
  1183  					AutoRevert:      helper.BoolToPtr(true),
  1184  				},
  1185  
  1186  				Meta: map[string]string{
  1187  					"key": "value",
  1188  				},
  1189  				Tasks: []*api.Task{
  1190  					{
  1191  						Name:   "task1",
  1192  						Leader: true,
  1193  						Driver: "docker",
  1194  						User:   "mary",
  1195  						Config: map[string]interface{}{
  1196  							"lol": "code",
  1197  						},
  1198  						Env: map[string]string{
  1199  							"hello": "world",
  1200  						},
  1201  						Constraints: []*api.Constraint{
  1202  							{
  1203  								LTarget: "x",
  1204  								RTarget: "y",
  1205  								Operand: "z",
  1206  							},
  1207  						},
  1208  
  1209  						Services: []*api.Service{
  1210  							{
  1211  								Id:        "id",
  1212  								Name:      "serviceA",
  1213  								Tags:      []string{"1", "2"},
  1214  								PortLabel: "foo",
  1215  								Checks: []api.ServiceCheck{
  1216  									{
  1217  										Id:            "hello",
  1218  										Name:          "bar",
  1219  										Type:          "http",
  1220  										Command:       "foo",
  1221  										Args:          []string{"a", "b"},
  1222  										Path:          "/check",
  1223  										Protocol:      "http",
  1224  										PortLabel:     "foo",
  1225  										AddressMode:   "driver",
  1226  										Interval:      4 * time.Second,
  1227  										Timeout:       2 * time.Second,
  1228  										InitialStatus: "ok",
  1229  										CheckRestart: &api.CheckRestart{
  1230  											Limit:          3,
  1231  											Grace:          helper.TimeToPtr(10 * time.Second),
  1232  											IgnoreWarnings: true,
  1233  										},
  1234  									},
  1235  								},
  1236  							},
  1237  						},
  1238  						Resources: &api.Resources{
  1239  							CPU:      helper.IntToPtr(100),
  1240  							MemoryMB: helper.IntToPtr(10),
  1241  							Networks: []*api.NetworkResource{
  1242  								{
  1243  									IP:    "10.10.11.1",
  1244  									MBits: helper.IntToPtr(10),
  1245  									ReservedPorts: []api.Port{
  1246  										{
  1247  											Label: "http",
  1248  											Value: 80,
  1249  										},
  1250  									},
  1251  									DynamicPorts: []api.Port{
  1252  										{
  1253  											Label: "ssh",
  1254  											Value: 2000,
  1255  										},
  1256  									},
  1257  								},
  1258  							},
  1259  						},
  1260  						Meta: map[string]string{
  1261  							"lol": "code",
  1262  						},
  1263  						KillTimeout: helper.TimeToPtr(10 * time.Second),
  1264  						KillSignal:  "SIGQUIT",
  1265  						LogConfig: &api.LogConfig{
  1266  							MaxFiles:      helper.IntToPtr(10),
  1267  							MaxFileSizeMB: helper.IntToPtr(100),
  1268  						},
  1269  						Artifacts: []*api.TaskArtifact{
  1270  							{
  1271  								GetterSource: helper.StringToPtr("source"),
  1272  								GetterOptions: map[string]string{
  1273  									"a": "b",
  1274  								},
  1275  								GetterMode:   helper.StringToPtr("dir"),
  1276  								RelativeDest: helper.StringToPtr("dest"),
  1277  							},
  1278  						},
  1279  						Vault: &api.Vault{
  1280  							Policies:     []string{"a", "b", "c"},
  1281  							Env:          helper.BoolToPtr(true),
  1282  							ChangeMode:   helper.StringToPtr("c"),
  1283  							ChangeSignal: helper.StringToPtr("sighup"),
  1284  						},
  1285  						Templates: []*api.Template{
  1286  							{
  1287  								SourcePath:   helper.StringToPtr("source"),
  1288  								DestPath:     helper.StringToPtr("dest"),
  1289  								EmbeddedTmpl: helper.StringToPtr("embedded"),
  1290  								ChangeMode:   helper.StringToPtr("change"),
  1291  								ChangeSignal: helper.StringToPtr("signal"),
  1292  								Splay:        helper.TimeToPtr(1 * time.Minute),
  1293  								Perms:        helper.StringToPtr("666"),
  1294  								LeftDelim:    helper.StringToPtr("abc"),
  1295  								RightDelim:   helper.StringToPtr("def"),
  1296  								Envvars:      helper.BoolToPtr(true),
  1297  								VaultGrace:   helper.TimeToPtr(3 * time.Second),
  1298  							},
  1299  						},
  1300  						DispatchPayload: &api.DispatchPayloadConfig{
  1301  							File: "fileA",
  1302  						},
  1303  					},
  1304  				},
  1305  			},
  1306  		},
  1307  		VaultToken:        helper.StringToPtr("token"),
  1308  		Status:            helper.StringToPtr("status"),
  1309  		StatusDescription: helper.StringToPtr("status_desc"),
  1310  		Version:           helper.Uint64ToPtr(10),
  1311  		CreateIndex:       helper.Uint64ToPtr(1),
  1312  		ModifyIndex:       helper.Uint64ToPtr(3),
  1313  		JobModifyIndex:    helper.Uint64ToPtr(5),
  1314  	}
  1315  
  1316  	expected := &structs.Job{
  1317  		Stop:        true,
  1318  		Region:      "global",
  1319  		Namespace:   "foo",
  1320  		ID:          "foo",
  1321  		ParentID:    "lol",
  1322  		Name:        "name",
  1323  		Type:        "service",
  1324  		Priority:    50,
  1325  		AllAtOnce:   true,
  1326  		Datacenters: []string{"dc1", "dc2"},
  1327  		Constraints: []*structs.Constraint{
  1328  			{
  1329  				LTarget: "a",
  1330  				RTarget: "b",
  1331  				Operand: "c",
  1332  			},
  1333  		},
  1334  		Update: structs.UpdateStrategy{
  1335  			Stagger:     1 * time.Second,
  1336  			MaxParallel: 5,
  1337  		},
  1338  		Periodic: &structs.PeriodicConfig{
  1339  			Enabled:         true,
  1340  			Spec:            "spec",
  1341  			SpecType:        "cron",
  1342  			ProhibitOverlap: true,
  1343  			TimeZone:        "test zone",
  1344  		},
  1345  		ParameterizedJob: &structs.ParameterizedJobConfig{
  1346  			Payload:      "payload",
  1347  			MetaRequired: []string{"a", "b"},
  1348  			MetaOptional: []string{"c", "d"},
  1349  		},
  1350  		Payload: []byte("payload"),
  1351  		Meta: map[string]string{
  1352  			"foo": "bar",
  1353  		},
  1354  		TaskGroups: []*structs.TaskGroup{
  1355  			{
  1356  				Name:  "group1",
  1357  				Count: 5,
  1358  				Constraints: []*structs.Constraint{
  1359  					{
  1360  						LTarget: "x",
  1361  						RTarget: "y",
  1362  						Operand: "z",
  1363  					},
  1364  				},
  1365  				RestartPolicy: &structs.RestartPolicy{
  1366  					Interval: 1 * time.Second,
  1367  					Attempts: 5,
  1368  					Delay:    10 * time.Second,
  1369  					Mode:     "delay",
  1370  				},
  1371  				EphemeralDisk: &structs.EphemeralDisk{
  1372  					SizeMB:  100,
  1373  					Sticky:  true,
  1374  					Migrate: true,
  1375  				},
  1376  				Update: &structs.UpdateStrategy{
  1377  					Stagger:         1 * time.Second,
  1378  					MaxParallel:     5,
  1379  					HealthCheck:     structs.UpdateStrategyHealthCheck_Checks,
  1380  					MinHealthyTime:  2 * time.Minute,
  1381  					HealthyDeadline: 5 * time.Minute,
  1382  					AutoRevert:      true,
  1383  					Canary:          1,
  1384  				},
  1385  				Meta: map[string]string{
  1386  					"key": "value",
  1387  				},
  1388  				Tasks: []*structs.Task{
  1389  					{
  1390  						Name:   "task1",
  1391  						Driver: "docker",
  1392  						Leader: true,
  1393  						User:   "mary",
  1394  						Config: map[string]interface{}{
  1395  							"lol": "code",
  1396  						},
  1397  						Constraints: []*structs.Constraint{
  1398  							{
  1399  								LTarget: "x",
  1400  								RTarget: "y",
  1401  								Operand: "z",
  1402  							},
  1403  						},
  1404  						Env: map[string]string{
  1405  							"hello": "world",
  1406  						},
  1407  						Services: []*structs.Service{
  1408  							{
  1409  								Name:        "serviceA",
  1410  								Tags:        []string{"1", "2"},
  1411  								PortLabel:   "foo",
  1412  								AddressMode: "auto",
  1413  								Checks: []*structs.ServiceCheck{
  1414  									{
  1415  										Name:          "bar",
  1416  										Type:          "http",
  1417  										Command:       "foo",
  1418  										Args:          []string{"a", "b"},
  1419  										Path:          "/check",
  1420  										Protocol:      "http",
  1421  										PortLabel:     "foo",
  1422  										AddressMode:   "driver",
  1423  										Interval:      4 * time.Second,
  1424  										Timeout:       2 * time.Second,
  1425  										InitialStatus: "ok",
  1426  										CheckRestart: &structs.CheckRestart{
  1427  											Limit:          3,
  1428  											Grace:          10 * time.Second,
  1429  											IgnoreWarnings: true,
  1430  										},
  1431  									},
  1432  								},
  1433  							},
  1434  						},
  1435  						Resources: &structs.Resources{
  1436  							CPU:      100,
  1437  							MemoryMB: 10,
  1438  							Networks: []*structs.NetworkResource{
  1439  								{
  1440  									IP:    "10.10.11.1",
  1441  									MBits: 10,
  1442  									ReservedPorts: []structs.Port{
  1443  										{
  1444  											Label: "http",
  1445  											Value: 80,
  1446  										},
  1447  									},
  1448  									DynamicPorts: []structs.Port{
  1449  										{
  1450  											Label: "ssh",
  1451  											Value: 2000,
  1452  										},
  1453  									},
  1454  								},
  1455  							},
  1456  						},
  1457  						Meta: map[string]string{
  1458  							"lol": "code",
  1459  						},
  1460  						KillTimeout: 10 * time.Second,
  1461  						KillSignal:  "SIGQUIT",
  1462  						LogConfig: &structs.LogConfig{
  1463  							MaxFiles:      10,
  1464  							MaxFileSizeMB: 100,
  1465  						},
  1466  						Artifacts: []*structs.TaskArtifact{
  1467  							{
  1468  								GetterSource: "source",
  1469  								GetterOptions: map[string]string{
  1470  									"a": "b",
  1471  								},
  1472  								GetterMode:   "dir",
  1473  								RelativeDest: "dest",
  1474  							},
  1475  						},
  1476  						Vault: &structs.Vault{
  1477  							Policies:     []string{"a", "b", "c"},
  1478  							Env:          true,
  1479  							ChangeMode:   "c",
  1480  							ChangeSignal: "sighup",
  1481  						},
  1482  						Templates: []*structs.Template{
  1483  							{
  1484  								SourcePath:   "source",
  1485  								DestPath:     "dest",
  1486  								EmbeddedTmpl: "embedded",
  1487  								ChangeMode:   "change",
  1488  								ChangeSignal: "SIGNAL",
  1489  								Splay:        1 * time.Minute,
  1490  								Perms:        "666",
  1491  								LeftDelim:    "abc",
  1492  								RightDelim:   "def",
  1493  								Envvars:      true,
  1494  								VaultGrace:   3 * time.Second,
  1495  							},
  1496  						},
  1497  						DispatchPayload: &structs.DispatchPayloadConfig{
  1498  							File: "fileA",
  1499  						},
  1500  					},
  1501  				},
  1502  			},
  1503  		},
  1504  
  1505  		VaultToken: "token",
  1506  	}
  1507  
  1508  	structsJob := ApiJobToStructJob(apiJob)
  1509  
  1510  	if diff := pretty.Diff(expected, structsJob); len(diff) > 0 {
  1511  		t.Fatalf("bad:\n%s", strings.Join(diff, "\n"))
  1512  	}
  1513  }