github.com/remilapeyre/nomad@v0.8.5/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_JobsParse(t *testing.T) {
   276  	t.Parallel()
   277  	httpTest(t, nil, func(s *TestAgent) {
   278  		buf := encodeReq(api.JobsParseRequest{JobHCL: mock.HCL()})
   279  		req, err := http.NewRequest("POST", "/v1/jobs/parse", buf)
   280  		if err != nil {
   281  			t.Fatalf("err: %v", err)
   282  		}
   283  
   284  		respW := httptest.NewRecorder()
   285  
   286  		obj, err := s.Server.JobsParseRequest(respW, req)
   287  		if err != nil {
   288  			t.Fatalf("err: %v", err)
   289  		}
   290  		if obj == nil {
   291  			t.Fatal("response should not be nil")
   292  		}
   293  
   294  		job := obj.(*api.Job)
   295  		expected := mock.Job()
   296  		if job.Name == nil || *job.Name != expected.Name {
   297  			t.Fatalf("job name is '%s', expected '%s'", *job.Name, expected.Name)
   298  		}
   299  
   300  		if job.Datacenters == nil ||
   301  			job.Datacenters[0] != expected.Datacenters[0] {
   302  			t.Fatalf("job datacenters is '%s', expected '%s'",
   303  				job.Datacenters[0], expected.Datacenters[0])
   304  		}
   305  	})
   306  }
   307  func TestHTTP_JobQuery(t *testing.T) {
   308  	t.Parallel()
   309  	httpTest(t, nil, func(s *TestAgent) {
   310  		// Create the job
   311  		job := mock.Job()
   312  		args := structs.JobRegisterRequest{
   313  			Job: job,
   314  			WriteRequest: structs.WriteRequest{
   315  				Region:    "global",
   316  				Namespace: structs.DefaultNamespace,
   317  			},
   318  		}
   319  		var resp structs.JobRegisterResponse
   320  		if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil {
   321  			t.Fatalf("err: %v", err)
   322  		}
   323  
   324  		// Make the HTTP request
   325  		req, err := http.NewRequest("GET", "/v1/job/"+job.ID, nil)
   326  		if err != nil {
   327  			t.Fatalf("err: %v", err)
   328  		}
   329  		respW := httptest.NewRecorder()
   330  
   331  		// Make the request
   332  		obj, err := s.Server.JobSpecificRequest(respW, req)
   333  		if err != nil {
   334  			t.Fatalf("err: %v", err)
   335  		}
   336  
   337  		// Check for the index
   338  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
   339  			t.Fatalf("missing index")
   340  		}
   341  		if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" {
   342  			t.Fatalf("missing known leader")
   343  		}
   344  		if respW.HeaderMap.Get("X-Nomad-LastContact") == "" {
   345  			t.Fatalf("missing last contact")
   346  		}
   347  
   348  		// Check the job
   349  		j := obj.(*structs.Job)
   350  		if j.ID != job.ID {
   351  			t.Fatalf("bad: %#v", j)
   352  		}
   353  	})
   354  }
   355  
   356  func TestHTTP_JobQuery_Payload(t *testing.T) {
   357  	t.Parallel()
   358  	httpTest(t, nil, func(s *TestAgent) {
   359  		// Create the job
   360  		job := mock.Job()
   361  
   362  		// Insert Payload compressed
   363  		expected := []byte("hello world")
   364  		compressed := snappy.Encode(nil, expected)
   365  		job.Payload = compressed
   366  
   367  		// Directly manipulate the state
   368  		state := s.Agent.server.State()
   369  		if err := state.UpsertJob(1000, job); err != nil {
   370  			t.Fatalf("Failed to upsert job: %v", err)
   371  		}
   372  
   373  		// Make the HTTP request
   374  		req, err := http.NewRequest("GET", "/v1/job/"+job.ID, nil)
   375  		if err != nil {
   376  			t.Fatalf("err: %v", err)
   377  		}
   378  		respW := httptest.NewRecorder()
   379  
   380  		// Make the request
   381  		obj, err := s.Server.JobSpecificRequest(respW, req)
   382  		if err != nil {
   383  			t.Fatalf("err: %v", err)
   384  		}
   385  
   386  		// Check for the index
   387  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
   388  			t.Fatalf("missing index")
   389  		}
   390  		if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" {
   391  			t.Fatalf("missing known leader")
   392  		}
   393  		if respW.HeaderMap.Get("X-Nomad-LastContact") == "" {
   394  			t.Fatalf("missing last contact")
   395  		}
   396  
   397  		// Check the job
   398  		j := obj.(*structs.Job)
   399  		if j.ID != job.ID {
   400  			t.Fatalf("bad: %#v", j)
   401  		}
   402  
   403  		// Check the payload is decompressed
   404  		if !reflect.DeepEqual(j.Payload, expected) {
   405  			t.Fatalf("Payload not decompressed properly; got %#v; want %#v", j.Payload, expected)
   406  		}
   407  	})
   408  }
   409  
   410  func TestHTTP_JobUpdate(t *testing.T) {
   411  	t.Parallel()
   412  	httpTest(t, nil, func(s *TestAgent) {
   413  		// Create the job
   414  		job := api.MockJob()
   415  		args := api.JobRegisterRequest{
   416  			Job: job,
   417  			WriteRequest: api.WriteRequest{
   418  				Region:    "global",
   419  				Namespace: api.DefaultNamespace,
   420  			},
   421  		}
   422  		buf := encodeReq(args)
   423  
   424  		// Make the HTTP request
   425  		req, err := http.NewRequest("PUT", "/v1/job/"+*job.ID, buf)
   426  		if err != nil {
   427  			t.Fatalf("err: %v", err)
   428  		}
   429  		respW := httptest.NewRecorder()
   430  
   431  		// Make the request
   432  		obj, err := s.Server.JobSpecificRequest(respW, req)
   433  		if err != nil {
   434  			t.Fatalf("err: %v", err)
   435  		}
   436  
   437  		// Check the response
   438  		dereg := obj.(structs.JobRegisterResponse)
   439  		if dereg.EvalID == "" {
   440  			t.Fatalf("bad: %v", dereg)
   441  		}
   442  
   443  		// Check for the index
   444  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
   445  			t.Fatalf("missing index")
   446  		}
   447  
   448  		// Check the job is registered
   449  		getReq := structs.JobSpecificRequest{
   450  			JobID: *job.ID,
   451  			QueryOptions: structs.QueryOptions{
   452  				Region:    "global",
   453  				Namespace: structs.DefaultNamespace,
   454  			},
   455  		}
   456  		var getResp structs.SingleJobResponse
   457  		if err := s.Agent.RPC("Job.GetJob", &getReq, &getResp); err != nil {
   458  			t.Fatalf("err: %v", err)
   459  		}
   460  
   461  		if getResp.Job == nil {
   462  			t.Fatalf("job does not exist")
   463  		}
   464  	})
   465  }
   466  
   467  func TestHTTP_JobDelete(t *testing.T) {
   468  	t.Parallel()
   469  	httpTest(t, nil, func(s *TestAgent) {
   470  		// Create the job
   471  		job := mock.Job()
   472  		args := structs.JobRegisterRequest{
   473  			Job: job,
   474  			WriteRequest: structs.WriteRequest{
   475  				Region:    "global",
   476  				Namespace: structs.DefaultNamespace,
   477  			},
   478  		}
   479  		var resp structs.JobRegisterResponse
   480  		if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil {
   481  			t.Fatalf("err: %v", err)
   482  		}
   483  
   484  		// Make the HTTP request to do a soft delete
   485  		req, err := http.NewRequest("DELETE", "/v1/job/"+job.ID, nil)
   486  		if err != nil {
   487  			t.Fatalf("err: %v", err)
   488  		}
   489  		respW := httptest.NewRecorder()
   490  
   491  		// Make the request
   492  		obj, err := s.Server.JobSpecificRequest(respW, req)
   493  		if err != nil {
   494  			t.Fatalf("err: %v", err)
   495  		}
   496  
   497  		// Check the response
   498  		dereg := obj.(structs.JobDeregisterResponse)
   499  		if dereg.EvalID == "" {
   500  			t.Fatalf("bad: %v", dereg)
   501  		}
   502  
   503  		// Check for the index
   504  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
   505  			t.Fatalf("missing index")
   506  		}
   507  
   508  		// Check the job is still queryable
   509  		getReq1 := structs.JobSpecificRequest{
   510  			JobID: job.ID,
   511  			QueryOptions: structs.QueryOptions{
   512  				Region:    "global",
   513  				Namespace: structs.DefaultNamespace,
   514  			},
   515  		}
   516  		var getResp1 structs.SingleJobResponse
   517  		if err := s.Agent.RPC("Job.GetJob", &getReq1, &getResp1); err != nil {
   518  			t.Fatalf("err: %v", err)
   519  		}
   520  		if getResp1.Job == nil {
   521  			t.Fatalf("job doesn't exists")
   522  		}
   523  		if !getResp1.Job.Stop {
   524  			t.Fatalf("job should be marked as stop")
   525  		}
   526  
   527  		// Make the HTTP request to do a purge delete
   528  		req2, err := http.NewRequest("DELETE", "/v1/job/"+job.ID+"?purge=true", nil)
   529  		if err != nil {
   530  			t.Fatalf("err: %v", err)
   531  		}
   532  		respW.Flush()
   533  
   534  		// Make the request
   535  		obj, err = s.Server.JobSpecificRequest(respW, req2)
   536  		if err != nil {
   537  			t.Fatalf("err: %v", err)
   538  		}
   539  
   540  		// Check the response
   541  		dereg = obj.(structs.JobDeregisterResponse)
   542  		if dereg.EvalID == "" {
   543  			t.Fatalf("bad: %v", dereg)
   544  		}
   545  
   546  		// Check for the index
   547  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
   548  			t.Fatalf("missing index")
   549  		}
   550  
   551  		// Check the job is gone
   552  		getReq2 := structs.JobSpecificRequest{
   553  			JobID: job.ID,
   554  			QueryOptions: structs.QueryOptions{
   555  				Region:    "global",
   556  				Namespace: structs.DefaultNamespace,
   557  			},
   558  		}
   559  		var getResp2 structs.SingleJobResponse
   560  		if err := s.Agent.RPC("Job.GetJob", &getReq2, &getResp2); err != nil {
   561  			t.Fatalf("err: %v", err)
   562  		}
   563  		if getResp2.Job != nil {
   564  			t.Fatalf("job still exists")
   565  		}
   566  	})
   567  }
   568  
   569  func TestHTTP_JobForceEvaluate(t *testing.T) {
   570  	t.Parallel()
   571  	httpTest(t, nil, func(s *TestAgent) {
   572  		// Create the job
   573  		job := mock.Job()
   574  		args := structs.JobRegisterRequest{
   575  			Job: job,
   576  			WriteRequest: structs.WriteRequest{
   577  				Region:    "global",
   578  				Namespace: structs.DefaultNamespace,
   579  			},
   580  		}
   581  		var resp structs.JobRegisterResponse
   582  		if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil {
   583  			t.Fatalf("err: %v", err)
   584  		}
   585  
   586  		// Make the HTTP request
   587  		req, err := http.NewRequest("POST", "/v1/job/"+job.ID+"/evaluate", nil)
   588  		if err != nil {
   589  			t.Fatalf("err: %v", err)
   590  		}
   591  		respW := httptest.NewRecorder()
   592  
   593  		// Make the request
   594  		obj, err := s.Server.JobSpecificRequest(respW, req)
   595  		if err != nil {
   596  			t.Fatalf("err: %v", err)
   597  		}
   598  
   599  		// Check the response
   600  		reg := obj.(structs.JobRegisterResponse)
   601  		if reg.EvalID == "" {
   602  			t.Fatalf("bad: %v", reg)
   603  		}
   604  
   605  		// Check for the index
   606  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
   607  			t.Fatalf("missing index")
   608  		}
   609  	})
   610  }
   611  
   612  func TestHTTP_JobEvaluate_ForceReschedule(t *testing.T) {
   613  	t.Parallel()
   614  	httpTest(t, nil, func(s *TestAgent) {
   615  		// Create the job
   616  		job := mock.Job()
   617  		args := structs.JobRegisterRequest{
   618  			Job: job,
   619  			WriteRequest: structs.WriteRequest{
   620  				Region:    "global",
   621  				Namespace: structs.DefaultNamespace,
   622  			},
   623  		}
   624  		var resp structs.JobRegisterResponse
   625  		if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil {
   626  			t.Fatalf("err: %v", err)
   627  		}
   628  		jobEvalReq := api.JobEvaluateRequest{
   629  			JobID: job.ID,
   630  			EvalOptions: api.EvalOptions{
   631  				ForceReschedule: true,
   632  			},
   633  		}
   634  
   635  		buf := encodeReq(jobEvalReq)
   636  
   637  		// Make the HTTP request
   638  		req, err := http.NewRequest("POST", "/v1/job/"+job.ID+"/evaluate", buf)
   639  		if err != nil {
   640  			t.Fatalf("err: %v", err)
   641  		}
   642  		respW := httptest.NewRecorder()
   643  
   644  		// Make the request
   645  		obj, err := s.Server.JobSpecificRequest(respW, req)
   646  		if err != nil {
   647  			t.Fatalf("err: %v", err)
   648  		}
   649  
   650  		// Check the response
   651  		reg := obj.(structs.JobRegisterResponse)
   652  		if reg.EvalID == "" {
   653  			t.Fatalf("bad: %v", reg)
   654  		}
   655  
   656  		// Check for the index
   657  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
   658  			t.Fatalf("missing index")
   659  		}
   660  	})
   661  }
   662  
   663  func TestHTTP_JobEvaluations(t *testing.T) {
   664  	t.Parallel()
   665  	httpTest(t, nil, func(s *TestAgent) {
   666  		// Create the job
   667  		job := mock.Job()
   668  		args := structs.JobRegisterRequest{
   669  			Job: job,
   670  			WriteRequest: structs.WriteRequest{
   671  				Region:    "global",
   672  				Namespace: structs.DefaultNamespace,
   673  			},
   674  		}
   675  		var resp structs.JobRegisterResponse
   676  		if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil {
   677  			t.Fatalf("err: %v", err)
   678  		}
   679  
   680  		// Make the HTTP request
   681  		req, err := http.NewRequest("GET", "/v1/job/"+job.ID+"/evaluations", nil)
   682  		if err != nil {
   683  			t.Fatalf("err: %v", err)
   684  		}
   685  		respW := httptest.NewRecorder()
   686  
   687  		// Make the request
   688  		obj, err := s.Server.JobSpecificRequest(respW, req)
   689  		if err != nil {
   690  			t.Fatalf("err: %v", err)
   691  		}
   692  
   693  		// Check the response
   694  		evals := obj.([]*structs.Evaluation)
   695  		// Can be multiple evals, use the last one, since they are in order
   696  		idx := len(evals) - 1
   697  		if len(evals) < 0 || evals[idx].ID != resp.EvalID {
   698  			t.Fatalf("bad: %v", evals)
   699  		}
   700  
   701  		// Check for the index
   702  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
   703  			t.Fatalf("missing index")
   704  		}
   705  		if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" {
   706  			t.Fatalf("missing known leader")
   707  		}
   708  		if respW.HeaderMap.Get("X-Nomad-LastContact") == "" {
   709  			t.Fatalf("missing last contact")
   710  		}
   711  	})
   712  }
   713  
   714  func TestHTTP_JobAllocations(t *testing.T) {
   715  	t.Parallel()
   716  	httpTest(t, nil, func(s *TestAgent) {
   717  		// Create the job
   718  		alloc1 := mock.Alloc()
   719  		args := structs.JobRegisterRequest{
   720  			Job: alloc1.Job,
   721  			WriteRequest: structs.WriteRequest{
   722  				Region:    "global",
   723  				Namespace: structs.DefaultNamespace,
   724  			},
   725  		}
   726  		var resp structs.JobRegisterResponse
   727  		if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil {
   728  			t.Fatalf("err: %v", err)
   729  		}
   730  
   731  		// Directly manipulate the state
   732  		expectedDisplayMsg := "test message"
   733  		testEvent := structs.NewTaskEvent("test event").SetMessage(expectedDisplayMsg)
   734  		var events []*structs.TaskEvent
   735  		events = append(events, testEvent)
   736  		taskState := &structs.TaskState{Events: events}
   737  		alloc1.TaskStates = make(map[string]*structs.TaskState)
   738  		alloc1.TaskStates["test"] = taskState
   739  		state := s.Agent.server.State()
   740  		err := state.UpsertAllocs(1000, []*structs.Allocation{alloc1})
   741  		if err != nil {
   742  			t.Fatalf("err: %v", err)
   743  		}
   744  
   745  		// Make the HTTP request
   746  		req, err := http.NewRequest("GET", "/v1/job/"+alloc1.Job.ID+"/allocations?all=true", nil)
   747  		if err != nil {
   748  			t.Fatalf("err: %v", err)
   749  		}
   750  		respW := httptest.NewRecorder()
   751  
   752  		// Make the request
   753  		obj, err := s.Server.JobSpecificRequest(respW, req)
   754  		if err != nil {
   755  			t.Fatalf("err: %v", err)
   756  		}
   757  
   758  		// Check the response
   759  		allocs := obj.([]*structs.AllocListStub)
   760  		if len(allocs) != 1 && allocs[0].ID != alloc1.ID {
   761  			t.Fatalf("bad: %v", allocs)
   762  		}
   763  		displayMsg := allocs[0].TaskStates["test"].Events[0].DisplayMessage
   764  		assert.Equal(t, expectedDisplayMsg, displayMsg)
   765  
   766  		// Check for the index
   767  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
   768  			t.Fatalf("missing index")
   769  		}
   770  		if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" {
   771  			t.Fatalf("missing known leader")
   772  		}
   773  		if respW.HeaderMap.Get("X-Nomad-LastContact") == "" {
   774  			t.Fatalf("missing last contact")
   775  		}
   776  	})
   777  }
   778  
   779  func TestHTTP_JobDeployments(t *testing.T) {
   780  	assert := assert.New(t)
   781  	t.Parallel()
   782  	httpTest(t, nil, func(s *TestAgent) {
   783  		// Create the job
   784  		j := mock.Job()
   785  		args := structs.JobRegisterRequest{
   786  			Job: j,
   787  			WriteRequest: structs.WriteRequest{
   788  				Region:    "global",
   789  				Namespace: structs.DefaultNamespace,
   790  			},
   791  		}
   792  		var resp structs.JobRegisterResponse
   793  		assert.Nil(s.Agent.RPC("Job.Register", &args, &resp), "JobRegister")
   794  
   795  		// Directly manipulate the state
   796  		state := s.Agent.server.State()
   797  		d := mock.Deployment()
   798  		d.JobID = j.ID
   799  		assert.Nil(state.UpsertDeployment(1000, d), "UpsertDeployment")
   800  
   801  		// Make the HTTP request
   802  		req, err := http.NewRequest("GET", "/v1/job/"+j.ID+"/deployments", nil)
   803  		assert.Nil(err, "HTTP")
   804  		respW := httptest.NewRecorder()
   805  
   806  		// Make the request
   807  		obj, err := s.Server.JobSpecificRequest(respW, req)
   808  		assert.Nil(err, "JobSpecificRequest")
   809  
   810  		// Check the response
   811  		deploys := obj.([]*structs.Deployment)
   812  		assert.Len(deploys, 1, "deployments")
   813  		assert.Equal(d.ID, deploys[0].ID, "deployment id")
   814  
   815  		assert.NotZero(respW.HeaderMap.Get("X-Nomad-Index"), "missing index")
   816  		assert.Equal("true", respW.HeaderMap.Get("X-Nomad-KnownLeader"), "missing known leader")
   817  		assert.NotZero(respW.HeaderMap.Get("X-Nomad-LastContact"), "missing last contact")
   818  	})
   819  }
   820  
   821  func TestHTTP_JobDeployment(t *testing.T) {
   822  	assert := assert.New(t)
   823  	t.Parallel()
   824  	httpTest(t, nil, func(s *TestAgent) {
   825  		// Create the job
   826  		j := mock.Job()
   827  		args := structs.JobRegisterRequest{
   828  			Job: j,
   829  			WriteRequest: structs.WriteRequest{
   830  				Region:    "global",
   831  				Namespace: structs.DefaultNamespace,
   832  			},
   833  		}
   834  		var resp structs.JobRegisterResponse
   835  		assert.Nil(s.Agent.RPC("Job.Register", &args, &resp), "JobRegister")
   836  
   837  		// Directly manipulate the state
   838  		state := s.Agent.server.State()
   839  		d := mock.Deployment()
   840  		d.JobID = j.ID
   841  		assert.Nil(state.UpsertDeployment(1000, d), "UpsertDeployment")
   842  
   843  		// Make the HTTP request
   844  		req, err := http.NewRequest("GET", "/v1/job/"+j.ID+"/deployment", nil)
   845  		assert.Nil(err, "HTTP")
   846  		respW := httptest.NewRecorder()
   847  
   848  		// Make the request
   849  		obj, err := s.Server.JobSpecificRequest(respW, req)
   850  		assert.Nil(err, "JobSpecificRequest")
   851  
   852  		// Check the response
   853  		out := obj.(*structs.Deployment)
   854  		assert.NotNil(out, "deployment")
   855  		assert.Equal(d.ID, out.ID, "deployment id")
   856  
   857  		assert.NotZero(respW.HeaderMap.Get("X-Nomad-Index"), "missing index")
   858  		assert.Equal("true", respW.HeaderMap.Get("X-Nomad-KnownLeader"), "missing known leader")
   859  		assert.NotZero(respW.HeaderMap.Get("X-Nomad-LastContact"), "missing last contact")
   860  	})
   861  }
   862  
   863  func TestHTTP_JobVersions(t *testing.T) {
   864  	t.Parallel()
   865  	httpTest(t, nil, func(s *TestAgent) {
   866  		// Create the job
   867  		job := mock.Job()
   868  		args := structs.JobRegisterRequest{
   869  			Job: job,
   870  			WriteRequest: structs.WriteRequest{
   871  				Region:    "global",
   872  				Namespace: structs.DefaultNamespace,
   873  			},
   874  		}
   875  		var resp structs.JobRegisterResponse
   876  		if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil {
   877  			t.Fatalf("err: %v", err)
   878  		}
   879  
   880  		job2 := mock.Job()
   881  		job2.ID = job.ID
   882  		job2.Priority = 100
   883  
   884  		args2 := structs.JobRegisterRequest{
   885  			Job: job2,
   886  			WriteRequest: structs.WriteRequest{
   887  				Region:    "global",
   888  				Namespace: structs.DefaultNamespace,
   889  			},
   890  		}
   891  		var resp2 structs.JobRegisterResponse
   892  		if err := s.Agent.RPC("Job.Register", &args2, &resp2); err != nil {
   893  			t.Fatalf("err: %v", err)
   894  		}
   895  
   896  		// Make the HTTP request
   897  		req, err := http.NewRequest("GET", "/v1/job/"+job.ID+"/versions?diffs=true", nil)
   898  		if err != nil {
   899  			t.Fatalf("err: %v", err)
   900  		}
   901  		respW := httptest.NewRecorder()
   902  
   903  		// Make the request
   904  		obj, err := s.Server.JobSpecificRequest(respW, req)
   905  		if err != nil {
   906  			t.Fatalf("err: %v", err)
   907  		}
   908  
   909  		// Check the response
   910  		vResp := obj.(structs.JobVersionsResponse)
   911  		versions := vResp.Versions
   912  		if len(versions) != 2 {
   913  			t.Fatalf("got %d versions; want 2", len(versions))
   914  		}
   915  
   916  		if v := versions[0]; v.Version != 1 || v.Priority != 100 {
   917  			t.Fatalf("bad %v", v)
   918  		}
   919  
   920  		if v := versions[1]; v.Version != 0 {
   921  			t.Fatalf("bad %v", v)
   922  		}
   923  
   924  		if len(vResp.Diffs) != 1 {
   925  			t.Fatalf("bad %v", vResp)
   926  		}
   927  
   928  		// Check for the index
   929  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
   930  			t.Fatalf("missing index")
   931  		}
   932  		if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" {
   933  			t.Fatalf("missing known leader")
   934  		}
   935  		if respW.HeaderMap.Get("X-Nomad-LastContact") == "" {
   936  			t.Fatalf("missing last contact")
   937  		}
   938  	})
   939  }
   940  
   941  func TestHTTP_PeriodicForce(t *testing.T) {
   942  	t.Parallel()
   943  	httpTest(t, nil, func(s *TestAgent) {
   944  		// Create and register a periodic job.
   945  		job := mock.PeriodicJob()
   946  		args := structs.JobRegisterRequest{
   947  			Job: job,
   948  			WriteRequest: structs.WriteRequest{
   949  				Region:    "global",
   950  				Namespace: structs.DefaultNamespace,
   951  			},
   952  		}
   953  		var resp structs.JobRegisterResponse
   954  		if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil {
   955  			t.Fatalf("err: %v", err)
   956  		}
   957  
   958  		// Make the HTTP request
   959  		req, err := http.NewRequest("POST", "/v1/job/"+job.ID+"/periodic/force", nil)
   960  		if err != nil {
   961  			t.Fatalf("err: %v", err)
   962  		}
   963  		respW := httptest.NewRecorder()
   964  
   965  		// Make the request
   966  		obj, err := s.Server.JobSpecificRequest(respW, req)
   967  		if err != nil {
   968  			t.Fatalf("err: %v", err)
   969  		}
   970  
   971  		// Check for the index
   972  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
   973  			t.Fatalf("missing index")
   974  		}
   975  
   976  		// Check the response
   977  		r := obj.(structs.PeriodicForceResponse)
   978  		if r.EvalID == "" {
   979  			t.Fatalf("bad: %#v", r)
   980  		}
   981  	})
   982  }
   983  
   984  func TestHTTP_JobPlan(t *testing.T) {
   985  	t.Parallel()
   986  	httpTest(t, nil, func(s *TestAgent) {
   987  		// Create the job
   988  		job := api.MockJob()
   989  		args := api.JobPlanRequest{
   990  			Job:  job,
   991  			Diff: true,
   992  			WriteRequest: api.WriteRequest{
   993  				Region:    "global",
   994  				Namespace: api.DefaultNamespace,
   995  			},
   996  		}
   997  		buf := encodeReq(args)
   998  
   999  		// Make the HTTP request
  1000  		req, err := http.NewRequest("PUT", "/v1/job/"+*job.ID+"/plan", buf)
  1001  		if err != nil {
  1002  			t.Fatalf("err: %v", err)
  1003  		}
  1004  		respW := httptest.NewRecorder()
  1005  
  1006  		// Make the request
  1007  		obj, err := s.Server.JobSpecificRequest(respW, req)
  1008  		if err != nil {
  1009  			t.Fatalf("err: %v", err)
  1010  		}
  1011  
  1012  		// Check the response
  1013  		plan := obj.(structs.JobPlanResponse)
  1014  		if plan.Annotations == nil {
  1015  			t.Fatalf("bad: %v", plan)
  1016  		}
  1017  
  1018  		if plan.Diff == nil {
  1019  			t.Fatalf("bad: %v", plan)
  1020  		}
  1021  	})
  1022  }
  1023  
  1024  func TestHTTP_JobDispatch(t *testing.T) {
  1025  	t.Parallel()
  1026  	httpTest(t, nil, func(s *TestAgent) {
  1027  		// Create the parameterized job
  1028  		job := mock.BatchJob()
  1029  		job.ParameterizedJob = &structs.ParameterizedJobConfig{}
  1030  
  1031  		args := structs.JobRegisterRequest{
  1032  			Job: job,
  1033  			WriteRequest: structs.WriteRequest{
  1034  				Region:    "global",
  1035  				Namespace: structs.DefaultNamespace,
  1036  			},
  1037  		}
  1038  		var resp structs.JobRegisterResponse
  1039  		if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil {
  1040  			t.Fatalf("err: %v", err)
  1041  		}
  1042  
  1043  		// Make the request
  1044  		respW := httptest.NewRecorder()
  1045  		args2 := structs.JobDispatchRequest{
  1046  			WriteRequest: structs.WriteRequest{
  1047  				Region:    "global",
  1048  				Namespace: structs.DefaultNamespace,
  1049  			},
  1050  		}
  1051  		buf := encodeReq(args2)
  1052  
  1053  		// Make the HTTP request
  1054  		req2, err := http.NewRequest("PUT", "/v1/job/"+job.ID+"/dispatch", buf)
  1055  		if err != nil {
  1056  			t.Fatalf("err: %v", err)
  1057  		}
  1058  		respW.Flush()
  1059  
  1060  		// Make the request
  1061  		obj, err := s.Server.JobSpecificRequest(respW, req2)
  1062  		if err != nil {
  1063  			t.Fatalf("err: %v", err)
  1064  		}
  1065  
  1066  		// Check the response
  1067  		dispatch := obj.(structs.JobDispatchResponse)
  1068  		if dispatch.EvalID == "" {
  1069  			t.Fatalf("bad: %v", dispatch)
  1070  		}
  1071  
  1072  		if dispatch.DispatchedJobID == "" {
  1073  			t.Fatalf("bad: %v", dispatch)
  1074  		}
  1075  	})
  1076  }
  1077  
  1078  func TestHTTP_JobRevert(t *testing.T) {
  1079  	t.Parallel()
  1080  	httpTest(t, nil, func(s *TestAgent) {
  1081  		// Create the job and register it twice
  1082  		job := mock.Job()
  1083  		regReq := structs.JobRegisterRequest{
  1084  			Job: job,
  1085  			WriteRequest: structs.WriteRequest{
  1086  				Region:    "global",
  1087  				Namespace: structs.DefaultNamespace,
  1088  			},
  1089  		}
  1090  		var regResp structs.JobRegisterResponse
  1091  		if err := s.Agent.RPC("Job.Register", &regReq, &regResp); err != nil {
  1092  			t.Fatalf("err: %v", err)
  1093  		}
  1094  
  1095  		// Change the job to get a new version
  1096  		job.Datacenters = append(job.Datacenters, "foo")
  1097  		if err := s.Agent.RPC("Job.Register", &regReq, &regResp); err != nil {
  1098  			t.Fatalf("err: %v", err)
  1099  		}
  1100  
  1101  		args := structs.JobRevertRequest{
  1102  			JobID:      job.ID,
  1103  			JobVersion: 0,
  1104  			WriteRequest: structs.WriteRequest{
  1105  				Region:    "global",
  1106  				Namespace: structs.DefaultNamespace,
  1107  			},
  1108  		}
  1109  		buf := encodeReq(args)
  1110  
  1111  		// Make the HTTP request
  1112  		req, err := http.NewRequest("PUT", "/v1/job/"+job.ID+"/revert", buf)
  1113  		if err != nil {
  1114  			t.Fatalf("err: %v", err)
  1115  		}
  1116  		respW := httptest.NewRecorder()
  1117  
  1118  		// Make the request
  1119  		obj, err := s.Server.JobSpecificRequest(respW, req)
  1120  		if err != nil {
  1121  			t.Fatalf("err: %v", err)
  1122  		}
  1123  
  1124  		// Check the response
  1125  		revertResp := obj.(structs.JobRegisterResponse)
  1126  		if revertResp.EvalID == "" {
  1127  			t.Fatalf("bad: %v", revertResp)
  1128  		}
  1129  
  1130  		// Check for the index
  1131  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
  1132  			t.Fatalf("missing index")
  1133  		}
  1134  	})
  1135  }
  1136  
  1137  func TestHTTP_JobStable(t *testing.T) {
  1138  	t.Parallel()
  1139  	httpTest(t, nil, func(s *TestAgent) {
  1140  		// Create the job and register it twice
  1141  		job := mock.Job()
  1142  		regReq := structs.JobRegisterRequest{
  1143  			Job: job,
  1144  			WriteRequest: structs.WriteRequest{
  1145  				Region:    "global",
  1146  				Namespace: structs.DefaultNamespace,
  1147  			},
  1148  		}
  1149  		var regResp structs.JobRegisterResponse
  1150  		if err := s.Agent.RPC("Job.Register", &regReq, &regResp); err != nil {
  1151  			t.Fatalf("err: %v", err)
  1152  		}
  1153  
  1154  		if err := s.Agent.RPC("Job.Register", &regReq, &regResp); err != nil {
  1155  			t.Fatalf("err: %v", err)
  1156  		}
  1157  
  1158  		args := structs.JobStabilityRequest{
  1159  			JobID:      job.ID,
  1160  			JobVersion: 0,
  1161  			Stable:     true,
  1162  			WriteRequest: structs.WriteRequest{
  1163  				Region:    "global",
  1164  				Namespace: structs.DefaultNamespace,
  1165  			},
  1166  		}
  1167  		buf := encodeReq(args)
  1168  
  1169  		// Make the HTTP request
  1170  		req, err := http.NewRequest("PUT", "/v1/job/"+job.ID+"/stable", buf)
  1171  		if err != nil {
  1172  			t.Fatalf("err: %v", err)
  1173  		}
  1174  		respW := httptest.NewRecorder()
  1175  
  1176  		// Make the request
  1177  		obj, err := s.Server.JobSpecificRequest(respW, req)
  1178  		if err != nil {
  1179  			t.Fatalf("err: %v", err)
  1180  		}
  1181  
  1182  		// Check the response
  1183  		stableResp := obj.(structs.JobStabilityResponse)
  1184  		if stableResp.Index == 0 {
  1185  			t.Fatalf("bad: %v", stableResp)
  1186  		}
  1187  
  1188  		// Check for the index
  1189  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
  1190  			t.Fatalf("missing index")
  1191  		}
  1192  	})
  1193  }
  1194  
  1195  func TestJobs_ApiJobToStructsJob(t *testing.T) {
  1196  	apiJob := &api.Job{
  1197  		Stop:        helper.BoolToPtr(true),
  1198  		Region:      helper.StringToPtr("global"),
  1199  		Namespace:   helper.StringToPtr("foo"),
  1200  		ID:          helper.StringToPtr("foo"),
  1201  		ParentID:    helper.StringToPtr("lol"),
  1202  		Name:        helper.StringToPtr("name"),
  1203  		Type:        helper.StringToPtr("service"),
  1204  		Priority:    helper.IntToPtr(50),
  1205  		AllAtOnce:   helper.BoolToPtr(true),
  1206  		Datacenters: []string{"dc1", "dc2"},
  1207  		Constraints: []*api.Constraint{
  1208  			{
  1209  				LTarget: "a",
  1210  				RTarget: "b",
  1211  				Operand: "c",
  1212  			},
  1213  		},
  1214  		Update: &api.UpdateStrategy{
  1215  			Stagger:          helper.TimeToPtr(1 * time.Second),
  1216  			MaxParallel:      helper.IntToPtr(5),
  1217  			HealthCheck:      helper.StringToPtr(structs.UpdateStrategyHealthCheck_Manual),
  1218  			MinHealthyTime:   helper.TimeToPtr(1 * time.Minute),
  1219  			HealthyDeadline:  helper.TimeToPtr(3 * time.Minute),
  1220  			ProgressDeadline: helper.TimeToPtr(3 * time.Minute),
  1221  			AutoRevert:       helper.BoolToPtr(false),
  1222  			Canary:           helper.IntToPtr(1),
  1223  		},
  1224  		Periodic: &api.PeriodicConfig{
  1225  			Enabled:         helper.BoolToPtr(true),
  1226  			Spec:            helper.StringToPtr("spec"),
  1227  			SpecType:        helper.StringToPtr("cron"),
  1228  			ProhibitOverlap: helper.BoolToPtr(true),
  1229  			TimeZone:        helper.StringToPtr("test zone"),
  1230  		},
  1231  		ParameterizedJob: &api.ParameterizedJobConfig{
  1232  			Payload:      "payload",
  1233  			MetaRequired: []string{"a", "b"},
  1234  			MetaOptional: []string{"c", "d"},
  1235  		},
  1236  		Payload: []byte("payload"),
  1237  		Meta: map[string]string{
  1238  			"foo": "bar",
  1239  		},
  1240  		TaskGroups: []*api.TaskGroup{
  1241  			{
  1242  				Name:  helper.StringToPtr("group1"),
  1243  				Count: helper.IntToPtr(5),
  1244  				Constraints: []*api.Constraint{
  1245  					{
  1246  						LTarget: "x",
  1247  						RTarget: "y",
  1248  						Operand: "z",
  1249  					},
  1250  				},
  1251  				RestartPolicy: &api.RestartPolicy{
  1252  					Interval: helper.TimeToPtr(1 * time.Second),
  1253  					Attempts: helper.IntToPtr(5),
  1254  					Delay:    helper.TimeToPtr(10 * time.Second),
  1255  					Mode:     helper.StringToPtr("delay"),
  1256  				},
  1257  				ReschedulePolicy: &api.ReschedulePolicy{
  1258  					Interval:      helper.TimeToPtr(12 * time.Hour),
  1259  					Attempts:      helper.IntToPtr(5),
  1260  					DelayFunction: helper.StringToPtr("constant"),
  1261  					Delay:         helper.TimeToPtr(30 * time.Second),
  1262  					Unlimited:     helper.BoolToPtr(true),
  1263  					MaxDelay:      helper.TimeToPtr(20 * time.Minute),
  1264  				},
  1265  				Migrate: &api.MigrateStrategy{
  1266  					MaxParallel:     helper.IntToPtr(12),
  1267  					HealthCheck:     helper.StringToPtr("task_events"),
  1268  					MinHealthyTime:  helper.TimeToPtr(12 * time.Hour),
  1269  					HealthyDeadline: helper.TimeToPtr(12 * time.Hour),
  1270  				},
  1271  				EphemeralDisk: &api.EphemeralDisk{
  1272  					SizeMB:  helper.IntToPtr(100),
  1273  					Sticky:  helper.BoolToPtr(true),
  1274  					Migrate: helper.BoolToPtr(true),
  1275  				},
  1276  				Update: &api.UpdateStrategy{
  1277  					HealthCheck:      helper.StringToPtr(structs.UpdateStrategyHealthCheck_Checks),
  1278  					MinHealthyTime:   helper.TimeToPtr(2 * time.Minute),
  1279  					HealthyDeadline:  helper.TimeToPtr(5 * time.Minute),
  1280  					ProgressDeadline: helper.TimeToPtr(5 * time.Minute),
  1281  					AutoRevert:       helper.BoolToPtr(true),
  1282  				},
  1283  
  1284  				Meta: map[string]string{
  1285  					"key": "value",
  1286  				},
  1287  				Tasks: []*api.Task{
  1288  					{
  1289  						Name:   "task1",
  1290  						Leader: true,
  1291  						Driver: "docker",
  1292  						User:   "mary",
  1293  						Config: map[string]interface{}{
  1294  							"lol": "code",
  1295  						},
  1296  						Env: map[string]string{
  1297  							"hello": "world",
  1298  						},
  1299  						Constraints: []*api.Constraint{
  1300  							{
  1301  								LTarget: "x",
  1302  								RTarget: "y",
  1303  								Operand: "z",
  1304  							},
  1305  						},
  1306  
  1307  						Services: []*api.Service{
  1308  							{
  1309  								Id:         "id",
  1310  								Name:       "serviceA",
  1311  								Tags:       []string{"1", "2"},
  1312  								CanaryTags: []string{"3", "4"},
  1313  								PortLabel:  "foo",
  1314  								CheckRestart: &api.CheckRestart{
  1315  									Limit: 4,
  1316  									Grace: helper.TimeToPtr(11 * time.Second),
  1317  								},
  1318  								Checks: []api.ServiceCheck{
  1319  									{
  1320  										Id:            "hello",
  1321  										Name:          "bar",
  1322  										Type:          "http",
  1323  										Command:       "foo",
  1324  										Args:          []string{"a", "b"},
  1325  										Path:          "/check",
  1326  										Protocol:      "http",
  1327  										PortLabel:     "foo",
  1328  										AddressMode:   "driver",
  1329  										GRPCService:   "foo.Bar",
  1330  										GRPCUseTLS:    true,
  1331  										Interval:      4 * time.Second,
  1332  										Timeout:       2 * time.Second,
  1333  										InitialStatus: "ok",
  1334  										CheckRestart: &api.CheckRestart{
  1335  											Limit:          3,
  1336  											IgnoreWarnings: true,
  1337  										},
  1338  									},
  1339  									{
  1340  										Id:        "check2id",
  1341  										Name:      "check2",
  1342  										Type:      "tcp",
  1343  										PortLabel: "foo",
  1344  										Interval:  4 * time.Second,
  1345  										Timeout:   2 * time.Second,
  1346  									},
  1347  								},
  1348  							},
  1349  						},
  1350  						Resources: &api.Resources{
  1351  							CPU:      helper.IntToPtr(100),
  1352  							MemoryMB: helper.IntToPtr(10),
  1353  							Networks: []*api.NetworkResource{
  1354  								{
  1355  									IP:    "10.10.11.1",
  1356  									MBits: helper.IntToPtr(10),
  1357  									ReservedPorts: []api.Port{
  1358  										{
  1359  											Label: "http",
  1360  											Value: 80,
  1361  										},
  1362  									},
  1363  									DynamicPorts: []api.Port{
  1364  										{
  1365  											Label: "ssh",
  1366  											Value: 2000,
  1367  										},
  1368  									},
  1369  								},
  1370  							},
  1371  						},
  1372  						Meta: map[string]string{
  1373  							"lol": "code",
  1374  						},
  1375  						KillTimeout: helper.TimeToPtr(10 * time.Second),
  1376  						KillSignal:  "SIGQUIT",
  1377  						LogConfig: &api.LogConfig{
  1378  							MaxFiles:      helper.IntToPtr(10),
  1379  							MaxFileSizeMB: helper.IntToPtr(100),
  1380  						},
  1381  						Artifacts: []*api.TaskArtifact{
  1382  							{
  1383  								GetterSource: helper.StringToPtr("source"),
  1384  								GetterOptions: map[string]string{
  1385  									"a": "b",
  1386  								},
  1387  								GetterMode:   helper.StringToPtr("dir"),
  1388  								RelativeDest: helper.StringToPtr("dest"),
  1389  							},
  1390  						},
  1391  						Vault: &api.Vault{
  1392  							Policies:     []string{"a", "b", "c"},
  1393  							Env:          helper.BoolToPtr(true),
  1394  							ChangeMode:   helper.StringToPtr("c"),
  1395  							ChangeSignal: helper.StringToPtr("sighup"),
  1396  						},
  1397  						Templates: []*api.Template{
  1398  							{
  1399  								SourcePath:   helper.StringToPtr("source"),
  1400  								DestPath:     helper.StringToPtr("dest"),
  1401  								EmbeddedTmpl: helper.StringToPtr("embedded"),
  1402  								ChangeMode:   helper.StringToPtr("change"),
  1403  								ChangeSignal: helper.StringToPtr("signal"),
  1404  								Splay:        helper.TimeToPtr(1 * time.Minute),
  1405  								Perms:        helper.StringToPtr("666"),
  1406  								LeftDelim:    helper.StringToPtr("abc"),
  1407  								RightDelim:   helper.StringToPtr("def"),
  1408  								Envvars:      helper.BoolToPtr(true),
  1409  								VaultGrace:   helper.TimeToPtr(3 * time.Second),
  1410  							},
  1411  						},
  1412  						DispatchPayload: &api.DispatchPayloadConfig{
  1413  							File: "fileA",
  1414  						},
  1415  					},
  1416  				},
  1417  			},
  1418  		},
  1419  		VaultToken:        helper.StringToPtr("token"),
  1420  		Status:            helper.StringToPtr("status"),
  1421  		StatusDescription: helper.StringToPtr("status_desc"),
  1422  		Version:           helper.Uint64ToPtr(10),
  1423  		CreateIndex:       helper.Uint64ToPtr(1),
  1424  		ModifyIndex:       helper.Uint64ToPtr(3),
  1425  		JobModifyIndex:    helper.Uint64ToPtr(5),
  1426  	}
  1427  
  1428  	expected := &structs.Job{
  1429  		Stop:        true,
  1430  		Region:      "global",
  1431  		Namespace:   "foo",
  1432  		ID:          "foo",
  1433  		ParentID:    "lol",
  1434  		Name:        "name",
  1435  		Type:        "service",
  1436  		Priority:    50,
  1437  		AllAtOnce:   true,
  1438  		Datacenters: []string{"dc1", "dc2"},
  1439  		Constraints: []*structs.Constraint{
  1440  			{
  1441  				LTarget: "a",
  1442  				RTarget: "b",
  1443  				Operand: "c",
  1444  			},
  1445  		},
  1446  		Update: structs.UpdateStrategy{
  1447  			Stagger:     1 * time.Second,
  1448  			MaxParallel: 5,
  1449  		},
  1450  		Periodic: &structs.PeriodicConfig{
  1451  			Enabled:         true,
  1452  			Spec:            "spec",
  1453  			SpecType:        "cron",
  1454  			ProhibitOverlap: true,
  1455  			TimeZone:        "test zone",
  1456  		},
  1457  		ParameterizedJob: &structs.ParameterizedJobConfig{
  1458  			Payload:      "payload",
  1459  			MetaRequired: []string{"a", "b"},
  1460  			MetaOptional: []string{"c", "d"},
  1461  		},
  1462  		Payload: []byte("payload"),
  1463  		Meta: map[string]string{
  1464  			"foo": "bar",
  1465  		},
  1466  		TaskGroups: []*structs.TaskGroup{
  1467  			{
  1468  				Name:  "group1",
  1469  				Count: 5,
  1470  				Constraints: []*structs.Constraint{
  1471  					{
  1472  						LTarget: "x",
  1473  						RTarget: "y",
  1474  						Operand: "z",
  1475  					},
  1476  				},
  1477  				RestartPolicy: &structs.RestartPolicy{
  1478  					Interval: 1 * time.Second,
  1479  					Attempts: 5,
  1480  					Delay:    10 * time.Second,
  1481  					Mode:     "delay",
  1482  				},
  1483  				ReschedulePolicy: &structs.ReschedulePolicy{
  1484  					Interval:      12 * time.Hour,
  1485  					Attempts:      5,
  1486  					DelayFunction: "constant",
  1487  					Delay:         30 * time.Second,
  1488  					Unlimited:     true,
  1489  					MaxDelay:      20 * time.Minute,
  1490  				},
  1491  				Migrate: &structs.MigrateStrategy{
  1492  					MaxParallel:     12,
  1493  					HealthCheck:     "task_events",
  1494  					MinHealthyTime:  12 * time.Hour,
  1495  					HealthyDeadline: 12 * time.Hour,
  1496  				},
  1497  				EphemeralDisk: &structs.EphemeralDisk{
  1498  					SizeMB:  100,
  1499  					Sticky:  true,
  1500  					Migrate: true,
  1501  				},
  1502  				Update: &structs.UpdateStrategy{
  1503  					Stagger:          1 * time.Second,
  1504  					MaxParallel:      5,
  1505  					HealthCheck:      structs.UpdateStrategyHealthCheck_Checks,
  1506  					MinHealthyTime:   2 * time.Minute,
  1507  					HealthyDeadline:  5 * time.Minute,
  1508  					ProgressDeadline: 5 * time.Minute,
  1509  					AutoRevert:       true,
  1510  					Canary:           1,
  1511  				},
  1512  				Meta: map[string]string{
  1513  					"key": "value",
  1514  				},
  1515  				Tasks: []*structs.Task{
  1516  					{
  1517  						Name:   "task1",
  1518  						Driver: "docker",
  1519  						Leader: true,
  1520  						User:   "mary",
  1521  						Config: map[string]interface{}{
  1522  							"lol": "code",
  1523  						},
  1524  						Constraints: []*structs.Constraint{
  1525  							{
  1526  								LTarget: "x",
  1527  								RTarget: "y",
  1528  								Operand: "z",
  1529  							},
  1530  						},
  1531  						Env: map[string]string{
  1532  							"hello": "world",
  1533  						},
  1534  						Services: []*structs.Service{
  1535  							{
  1536  								Name:        "serviceA",
  1537  								Tags:        []string{"1", "2"},
  1538  								CanaryTags:  []string{"3", "4"},
  1539  								PortLabel:   "foo",
  1540  								AddressMode: "auto",
  1541  								Checks: []*structs.ServiceCheck{
  1542  									{
  1543  										Name:          "bar",
  1544  										Type:          "http",
  1545  										Command:       "foo",
  1546  										Args:          []string{"a", "b"},
  1547  										Path:          "/check",
  1548  										Protocol:      "http",
  1549  										PortLabel:     "foo",
  1550  										AddressMode:   "driver",
  1551  										Interval:      4 * time.Second,
  1552  										Timeout:       2 * time.Second,
  1553  										InitialStatus: "ok",
  1554  										GRPCService:   "foo.Bar",
  1555  										GRPCUseTLS:    true,
  1556  										CheckRestart: &structs.CheckRestart{
  1557  											Limit:          3,
  1558  											Grace:          11 * time.Second,
  1559  											IgnoreWarnings: true,
  1560  										},
  1561  									},
  1562  									{
  1563  										Name:      "check2",
  1564  										Type:      "tcp",
  1565  										PortLabel: "foo",
  1566  										Interval:  4 * time.Second,
  1567  										Timeout:   2 * time.Second,
  1568  										CheckRestart: &structs.CheckRestart{
  1569  											Limit: 4,
  1570  											Grace: 11 * time.Second,
  1571  										},
  1572  									},
  1573  								},
  1574  							},
  1575  						},
  1576  						Resources: &structs.Resources{
  1577  							CPU:      100,
  1578  							MemoryMB: 10,
  1579  							Networks: []*structs.NetworkResource{
  1580  								{
  1581  									IP:    "10.10.11.1",
  1582  									MBits: 10,
  1583  									ReservedPorts: []structs.Port{
  1584  										{
  1585  											Label: "http",
  1586  											Value: 80,
  1587  										},
  1588  									},
  1589  									DynamicPorts: []structs.Port{
  1590  										{
  1591  											Label: "ssh",
  1592  											Value: 2000,
  1593  										},
  1594  									},
  1595  								},
  1596  							},
  1597  						},
  1598  						Meta: map[string]string{
  1599  							"lol": "code",
  1600  						},
  1601  						KillTimeout: 10 * time.Second,
  1602  						KillSignal:  "SIGQUIT",
  1603  						LogConfig: &structs.LogConfig{
  1604  							MaxFiles:      10,
  1605  							MaxFileSizeMB: 100,
  1606  						},
  1607  						Artifacts: []*structs.TaskArtifact{
  1608  							{
  1609  								GetterSource: "source",
  1610  								GetterOptions: map[string]string{
  1611  									"a": "b",
  1612  								},
  1613  								GetterMode:   "dir",
  1614  								RelativeDest: "dest",
  1615  							},
  1616  						},
  1617  						Vault: &structs.Vault{
  1618  							Policies:     []string{"a", "b", "c"},
  1619  							Env:          true,
  1620  							ChangeMode:   "c",
  1621  							ChangeSignal: "sighup",
  1622  						},
  1623  						Templates: []*structs.Template{
  1624  							{
  1625  								SourcePath:   "source",
  1626  								DestPath:     "dest",
  1627  								EmbeddedTmpl: "embedded",
  1628  								ChangeMode:   "change",
  1629  								ChangeSignal: "SIGNAL",
  1630  								Splay:        1 * time.Minute,
  1631  								Perms:        "666",
  1632  								LeftDelim:    "abc",
  1633  								RightDelim:   "def",
  1634  								Envvars:      true,
  1635  								VaultGrace:   3 * time.Second,
  1636  							},
  1637  						},
  1638  						DispatchPayload: &structs.DispatchPayloadConfig{
  1639  							File: "fileA",
  1640  						},
  1641  					},
  1642  				},
  1643  			},
  1644  		},
  1645  
  1646  		VaultToken: "token",
  1647  	}
  1648  
  1649  	structsJob := ApiJobToStructJob(apiJob)
  1650  
  1651  	if diff := pretty.Diff(expected, structsJob); len(diff) > 0 {
  1652  		t.Fatalf("bad:\n%s", strings.Join(diff, "\n"))
  1653  	}
  1654  
  1655  	systemAPIJob := &api.Job{
  1656  		Stop:        helper.BoolToPtr(true),
  1657  		Region:      helper.StringToPtr("global"),
  1658  		Namespace:   helper.StringToPtr("foo"),
  1659  		ID:          helper.StringToPtr("foo"),
  1660  		ParentID:    helper.StringToPtr("lol"),
  1661  		Name:        helper.StringToPtr("name"),
  1662  		Type:        helper.StringToPtr("system"),
  1663  		Priority:    helper.IntToPtr(50),
  1664  		AllAtOnce:   helper.BoolToPtr(true),
  1665  		Datacenters: []string{"dc1", "dc2"},
  1666  		Constraints: []*api.Constraint{
  1667  			{
  1668  				LTarget: "a",
  1669  				RTarget: "b",
  1670  				Operand: "c",
  1671  			},
  1672  		},
  1673  		TaskGroups: []*api.TaskGroup{
  1674  			{
  1675  				Name:  helper.StringToPtr("group1"),
  1676  				Count: helper.IntToPtr(5),
  1677  				Constraints: []*api.Constraint{
  1678  					{
  1679  						LTarget: "x",
  1680  						RTarget: "y",
  1681  						Operand: "z",
  1682  					},
  1683  				},
  1684  				RestartPolicy: &api.RestartPolicy{
  1685  					Interval: helper.TimeToPtr(1 * time.Second),
  1686  					Attempts: helper.IntToPtr(5),
  1687  					Delay:    helper.TimeToPtr(10 * time.Second),
  1688  					Mode:     helper.StringToPtr("delay"),
  1689  				},
  1690  				EphemeralDisk: &api.EphemeralDisk{
  1691  					SizeMB:  helper.IntToPtr(100),
  1692  					Sticky:  helper.BoolToPtr(true),
  1693  					Migrate: helper.BoolToPtr(true),
  1694  				},
  1695  				Meta: map[string]string{
  1696  					"key": "value",
  1697  				},
  1698  				Tasks: []*api.Task{
  1699  					{
  1700  						Name:   "task1",
  1701  						Leader: true,
  1702  						Driver: "docker",
  1703  						User:   "mary",
  1704  						Config: map[string]interface{}{
  1705  							"lol": "code",
  1706  						},
  1707  						Env: map[string]string{
  1708  							"hello": "world",
  1709  						},
  1710  						Constraints: []*api.Constraint{
  1711  							{
  1712  								LTarget: "x",
  1713  								RTarget: "y",
  1714  								Operand: "z",
  1715  							},
  1716  						},
  1717  						Resources: &api.Resources{
  1718  							CPU:      helper.IntToPtr(100),
  1719  							MemoryMB: helper.IntToPtr(10),
  1720  							Networks: []*api.NetworkResource{
  1721  								{
  1722  									IP:    "10.10.11.1",
  1723  									MBits: helper.IntToPtr(10),
  1724  									ReservedPorts: []api.Port{
  1725  										{
  1726  											Label: "http",
  1727  											Value: 80,
  1728  										},
  1729  									},
  1730  									DynamicPorts: []api.Port{
  1731  										{
  1732  											Label: "ssh",
  1733  											Value: 2000,
  1734  										},
  1735  									},
  1736  								},
  1737  							},
  1738  						},
  1739  						Meta: map[string]string{
  1740  							"lol": "code",
  1741  						},
  1742  						KillTimeout: helper.TimeToPtr(10 * time.Second),
  1743  						KillSignal:  "SIGQUIT",
  1744  						LogConfig: &api.LogConfig{
  1745  							MaxFiles:      helper.IntToPtr(10),
  1746  							MaxFileSizeMB: helper.IntToPtr(100),
  1747  						},
  1748  						Artifacts: []*api.TaskArtifact{
  1749  							{
  1750  								GetterSource: helper.StringToPtr("source"),
  1751  								GetterOptions: map[string]string{
  1752  									"a": "b",
  1753  								},
  1754  								GetterMode:   helper.StringToPtr("dir"),
  1755  								RelativeDest: helper.StringToPtr("dest"),
  1756  							},
  1757  						},
  1758  						DispatchPayload: &api.DispatchPayloadConfig{
  1759  							File: "fileA",
  1760  						},
  1761  					},
  1762  				},
  1763  			},
  1764  		},
  1765  		Status:            helper.StringToPtr("status"),
  1766  		StatusDescription: helper.StringToPtr("status_desc"),
  1767  		Version:           helper.Uint64ToPtr(10),
  1768  		CreateIndex:       helper.Uint64ToPtr(1),
  1769  		ModifyIndex:       helper.Uint64ToPtr(3),
  1770  		JobModifyIndex:    helper.Uint64ToPtr(5),
  1771  	}
  1772  
  1773  	expectedSystemJob := &structs.Job{
  1774  		Stop:        true,
  1775  		Region:      "global",
  1776  		Namespace:   "foo",
  1777  		ID:          "foo",
  1778  		ParentID:    "lol",
  1779  		Name:        "name",
  1780  		Type:        "system",
  1781  		Priority:    50,
  1782  		AllAtOnce:   true,
  1783  		Datacenters: []string{"dc1", "dc2"},
  1784  		Constraints: []*structs.Constraint{
  1785  			{
  1786  				LTarget: "a",
  1787  				RTarget: "b",
  1788  				Operand: "c",
  1789  			},
  1790  		},
  1791  		TaskGroups: []*structs.TaskGroup{
  1792  			{
  1793  				Name:  "group1",
  1794  				Count: 5,
  1795  				Constraints: []*structs.Constraint{
  1796  					{
  1797  						LTarget: "x",
  1798  						RTarget: "y",
  1799  						Operand: "z",
  1800  					},
  1801  				},
  1802  				RestartPolicy: &structs.RestartPolicy{
  1803  					Interval: 1 * time.Second,
  1804  					Attempts: 5,
  1805  					Delay:    10 * time.Second,
  1806  					Mode:     "delay",
  1807  				},
  1808  				EphemeralDisk: &structs.EphemeralDisk{
  1809  					SizeMB:  100,
  1810  					Sticky:  true,
  1811  					Migrate: true,
  1812  				},
  1813  				Meta: map[string]string{
  1814  					"key": "value",
  1815  				},
  1816  				Tasks: []*structs.Task{
  1817  					{
  1818  						Name:   "task1",
  1819  						Driver: "docker",
  1820  						Leader: true,
  1821  						User:   "mary",
  1822  						Config: map[string]interface{}{
  1823  							"lol": "code",
  1824  						},
  1825  						Constraints: []*structs.Constraint{
  1826  							{
  1827  								LTarget: "x",
  1828  								RTarget: "y",
  1829  								Operand: "z",
  1830  							},
  1831  						},
  1832  						Env: map[string]string{
  1833  							"hello": "world",
  1834  						},
  1835  						Resources: &structs.Resources{
  1836  							CPU:      100,
  1837  							MemoryMB: 10,
  1838  							Networks: []*structs.NetworkResource{
  1839  								{
  1840  									IP:    "10.10.11.1",
  1841  									MBits: 10,
  1842  									ReservedPorts: []structs.Port{
  1843  										{
  1844  											Label: "http",
  1845  											Value: 80,
  1846  										},
  1847  									},
  1848  									DynamicPorts: []structs.Port{
  1849  										{
  1850  											Label: "ssh",
  1851  											Value: 2000,
  1852  										},
  1853  									},
  1854  								},
  1855  							},
  1856  						},
  1857  						Meta: map[string]string{
  1858  							"lol": "code",
  1859  						},
  1860  						KillTimeout: 10 * time.Second,
  1861  						KillSignal:  "SIGQUIT",
  1862  						LogConfig: &structs.LogConfig{
  1863  							MaxFiles:      10,
  1864  							MaxFileSizeMB: 100,
  1865  						},
  1866  						Artifacts: []*structs.TaskArtifact{
  1867  							{
  1868  								GetterSource: "source",
  1869  								GetterOptions: map[string]string{
  1870  									"a": "b",
  1871  								},
  1872  								GetterMode:   "dir",
  1873  								RelativeDest: "dest",
  1874  							},
  1875  						},
  1876  						DispatchPayload: &structs.DispatchPayloadConfig{
  1877  							File: "fileA",
  1878  						},
  1879  					},
  1880  				},
  1881  			},
  1882  		},
  1883  	}
  1884  
  1885  	systemStructsJob := ApiJobToStructJob(systemAPIJob)
  1886  
  1887  	if diff := pretty.Diff(expectedSystemJob, systemStructsJob); len(diff) > 0 {
  1888  		t.Fatalf("bad:\n%s", strings.Join(diff, "\n"))
  1889  	}
  1890  }