github.com/smithx10/nomad@v0.9.1-rc1/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 := 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 := 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 := 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 := 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 := 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  		Affinities: []*api.Affinity{
  1215  			{
  1216  				LTarget: "a",
  1217  				RTarget: "b",
  1218  				Operand: "c",
  1219  				Weight:  helper.Int8ToPtr(50),
  1220  			},
  1221  		},
  1222  		Update: &api.UpdateStrategy{
  1223  			Stagger:          helper.TimeToPtr(1 * time.Second),
  1224  			MaxParallel:      helper.IntToPtr(5),
  1225  			HealthCheck:      helper.StringToPtr(structs.UpdateStrategyHealthCheck_Manual),
  1226  			MinHealthyTime:   helper.TimeToPtr(1 * time.Minute),
  1227  			HealthyDeadline:  helper.TimeToPtr(3 * time.Minute),
  1228  			ProgressDeadline: helper.TimeToPtr(3 * time.Minute),
  1229  			AutoRevert:       helper.BoolToPtr(false),
  1230  			Canary:           helper.IntToPtr(1),
  1231  		},
  1232  		Spreads: []*api.Spread{
  1233  			{
  1234  				Attribute: "${meta.rack}",
  1235  				Weight:    helper.Int8ToPtr(100),
  1236  				SpreadTarget: []*api.SpreadTarget{
  1237  					{
  1238  						Value:   "r1",
  1239  						Percent: 50,
  1240  					},
  1241  				},
  1242  			},
  1243  		},
  1244  		Periodic: &api.PeriodicConfig{
  1245  			Enabled:         helper.BoolToPtr(true),
  1246  			Spec:            helper.StringToPtr("spec"),
  1247  			SpecType:        helper.StringToPtr("cron"),
  1248  			ProhibitOverlap: helper.BoolToPtr(true),
  1249  			TimeZone:        helper.StringToPtr("test zone"),
  1250  		},
  1251  		ParameterizedJob: &api.ParameterizedJobConfig{
  1252  			Payload:      "payload",
  1253  			MetaRequired: []string{"a", "b"},
  1254  			MetaOptional: []string{"c", "d"},
  1255  		},
  1256  		Payload: []byte("payload"),
  1257  		Meta: map[string]string{
  1258  			"foo": "bar",
  1259  		},
  1260  		TaskGroups: []*api.TaskGroup{
  1261  			{
  1262  				Name:  helper.StringToPtr("group1"),
  1263  				Count: helper.IntToPtr(5),
  1264  				Constraints: []*api.Constraint{
  1265  					{
  1266  						LTarget: "x",
  1267  						RTarget: "y",
  1268  						Operand: "z",
  1269  					},
  1270  				},
  1271  				Affinities: []*api.Affinity{
  1272  					{
  1273  						LTarget: "x",
  1274  						RTarget: "y",
  1275  						Operand: "z",
  1276  						Weight:  helper.Int8ToPtr(100),
  1277  					},
  1278  				},
  1279  				RestartPolicy: &api.RestartPolicy{
  1280  					Interval: helper.TimeToPtr(1 * time.Second),
  1281  					Attempts: helper.IntToPtr(5),
  1282  					Delay:    helper.TimeToPtr(10 * time.Second),
  1283  					Mode:     helper.StringToPtr("delay"),
  1284  				},
  1285  				ReschedulePolicy: &api.ReschedulePolicy{
  1286  					Interval:      helper.TimeToPtr(12 * time.Hour),
  1287  					Attempts:      helper.IntToPtr(5),
  1288  					DelayFunction: helper.StringToPtr("constant"),
  1289  					Delay:         helper.TimeToPtr(30 * time.Second),
  1290  					Unlimited:     helper.BoolToPtr(true),
  1291  					MaxDelay:      helper.TimeToPtr(20 * time.Minute),
  1292  				},
  1293  				Migrate: &api.MigrateStrategy{
  1294  					MaxParallel:     helper.IntToPtr(12),
  1295  					HealthCheck:     helper.StringToPtr("task_events"),
  1296  					MinHealthyTime:  helper.TimeToPtr(12 * time.Hour),
  1297  					HealthyDeadline: helper.TimeToPtr(12 * time.Hour),
  1298  				},
  1299  				Spreads: []*api.Spread{
  1300  					{
  1301  						Attribute: "${node.datacenter}",
  1302  						Weight:    helper.Int8ToPtr(100),
  1303  						SpreadTarget: []*api.SpreadTarget{
  1304  							{
  1305  								Value:   "dc1",
  1306  								Percent: 100,
  1307  							},
  1308  						},
  1309  					},
  1310  				},
  1311  				EphemeralDisk: &api.EphemeralDisk{
  1312  					SizeMB:  helper.IntToPtr(100),
  1313  					Sticky:  helper.BoolToPtr(true),
  1314  					Migrate: helper.BoolToPtr(true),
  1315  				},
  1316  				Update: &api.UpdateStrategy{
  1317  					HealthCheck:      helper.StringToPtr(structs.UpdateStrategyHealthCheck_Checks),
  1318  					MinHealthyTime:   helper.TimeToPtr(2 * time.Minute),
  1319  					HealthyDeadline:  helper.TimeToPtr(5 * time.Minute),
  1320  					ProgressDeadline: helper.TimeToPtr(5 * time.Minute),
  1321  					AutoRevert:       helper.BoolToPtr(true),
  1322  				},
  1323  
  1324  				Meta: map[string]string{
  1325  					"key": "value",
  1326  				},
  1327  				Tasks: []*api.Task{
  1328  					{
  1329  						Name:   "task1",
  1330  						Leader: true,
  1331  						Driver: "docker",
  1332  						User:   "mary",
  1333  						Config: map[string]interface{}{
  1334  							"lol": "code",
  1335  						},
  1336  						Env: map[string]string{
  1337  							"hello": "world",
  1338  						},
  1339  						Constraints: []*api.Constraint{
  1340  							{
  1341  								LTarget: "x",
  1342  								RTarget: "y",
  1343  								Operand: "z",
  1344  							},
  1345  						},
  1346  						Affinities: []*api.Affinity{
  1347  							{
  1348  								LTarget: "a",
  1349  								RTarget: "b",
  1350  								Operand: "c",
  1351  								Weight:  helper.Int8ToPtr(50),
  1352  							},
  1353  						},
  1354  
  1355  						Services: []*api.Service{
  1356  							{
  1357  								Id:         "id",
  1358  								Name:       "serviceA",
  1359  								Tags:       []string{"1", "2"},
  1360  								CanaryTags: []string{"3", "4"},
  1361  								PortLabel:  "foo",
  1362  								CheckRestart: &api.CheckRestart{
  1363  									Limit: 4,
  1364  									Grace: helper.TimeToPtr(11 * time.Second),
  1365  								},
  1366  								Checks: []api.ServiceCheck{
  1367  									{
  1368  										Id:            "hello",
  1369  										Name:          "bar",
  1370  										Type:          "http",
  1371  										Command:       "foo",
  1372  										Args:          []string{"a", "b"},
  1373  										Path:          "/check",
  1374  										Protocol:      "http",
  1375  										PortLabel:     "foo",
  1376  										AddressMode:   "driver",
  1377  										GRPCService:   "foo.Bar",
  1378  										GRPCUseTLS:    true,
  1379  										Interval:      4 * time.Second,
  1380  										Timeout:       2 * time.Second,
  1381  										InitialStatus: "ok",
  1382  										CheckRestart: &api.CheckRestart{
  1383  											Limit:          3,
  1384  											IgnoreWarnings: true,
  1385  										},
  1386  									},
  1387  									{
  1388  										Id:        "check2id",
  1389  										Name:      "check2",
  1390  										Type:      "tcp",
  1391  										PortLabel: "foo",
  1392  										Interval:  4 * time.Second,
  1393  										Timeout:   2 * time.Second,
  1394  									},
  1395  								},
  1396  							},
  1397  						},
  1398  						Resources: &api.Resources{
  1399  							CPU:      helper.IntToPtr(100),
  1400  							MemoryMB: helper.IntToPtr(10),
  1401  							Networks: []*api.NetworkResource{
  1402  								{
  1403  									IP:    "10.10.11.1",
  1404  									MBits: helper.IntToPtr(10),
  1405  									ReservedPorts: []api.Port{
  1406  										{
  1407  											Label: "http",
  1408  											Value: 80,
  1409  										},
  1410  									},
  1411  									DynamicPorts: []api.Port{
  1412  										{
  1413  											Label: "ssh",
  1414  											Value: 2000,
  1415  										},
  1416  									},
  1417  								},
  1418  							},
  1419  							Devices: []*api.RequestedDevice{
  1420  								{
  1421  									Name:  "nvidia/gpu",
  1422  									Count: helper.Uint64ToPtr(4),
  1423  									Constraints: []*api.Constraint{
  1424  										{
  1425  											LTarget: "x",
  1426  											RTarget: "y",
  1427  											Operand: "z",
  1428  										},
  1429  									},
  1430  									Affinities: []*api.Affinity{
  1431  										{
  1432  											LTarget: "a",
  1433  											RTarget: "b",
  1434  											Operand: "c",
  1435  											Weight:  helper.Int8ToPtr(50),
  1436  										},
  1437  									},
  1438  								},
  1439  								{
  1440  									Name:  "gpu",
  1441  									Count: nil,
  1442  								},
  1443  							},
  1444  						},
  1445  						Meta: map[string]string{
  1446  							"lol": "code",
  1447  						},
  1448  						KillTimeout: helper.TimeToPtr(10 * time.Second),
  1449  						KillSignal:  "SIGQUIT",
  1450  						LogConfig: &api.LogConfig{
  1451  							MaxFiles:      helper.IntToPtr(10),
  1452  							MaxFileSizeMB: helper.IntToPtr(100),
  1453  						},
  1454  						Artifacts: []*api.TaskArtifact{
  1455  							{
  1456  								GetterSource: helper.StringToPtr("source"),
  1457  								GetterOptions: map[string]string{
  1458  									"a": "b",
  1459  								},
  1460  								GetterMode:   helper.StringToPtr("dir"),
  1461  								RelativeDest: helper.StringToPtr("dest"),
  1462  							},
  1463  						},
  1464  						Vault: &api.Vault{
  1465  							Policies:     []string{"a", "b", "c"},
  1466  							Env:          helper.BoolToPtr(true),
  1467  							ChangeMode:   helper.StringToPtr("c"),
  1468  							ChangeSignal: helper.StringToPtr("sighup"),
  1469  						},
  1470  						Templates: []*api.Template{
  1471  							{
  1472  								SourcePath:   helper.StringToPtr("source"),
  1473  								DestPath:     helper.StringToPtr("dest"),
  1474  								EmbeddedTmpl: helper.StringToPtr("embedded"),
  1475  								ChangeMode:   helper.StringToPtr("change"),
  1476  								ChangeSignal: helper.StringToPtr("signal"),
  1477  								Splay:        helper.TimeToPtr(1 * time.Minute),
  1478  								Perms:        helper.StringToPtr("666"),
  1479  								LeftDelim:    helper.StringToPtr("abc"),
  1480  								RightDelim:   helper.StringToPtr("def"),
  1481  								Envvars:      helper.BoolToPtr(true),
  1482  								VaultGrace:   helper.TimeToPtr(3 * time.Second),
  1483  							},
  1484  						},
  1485  						DispatchPayload: &api.DispatchPayloadConfig{
  1486  							File: "fileA",
  1487  						},
  1488  					},
  1489  				},
  1490  			},
  1491  		},
  1492  		VaultToken:        helper.StringToPtr("token"),
  1493  		Status:            helper.StringToPtr("status"),
  1494  		StatusDescription: helper.StringToPtr("status_desc"),
  1495  		Version:           helper.Uint64ToPtr(10),
  1496  		CreateIndex:       helper.Uint64ToPtr(1),
  1497  		ModifyIndex:       helper.Uint64ToPtr(3),
  1498  		JobModifyIndex:    helper.Uint64ToPtr(5),
  1499  	}
  1500  
  1501  	expected := &structs.Job{
  1502  		Stop:        true,
  1503  		Region:      "global",
  1504  		Namespace:   "foo",
  1505  		ID:          "foo",
  1506  		ParentID:    "lol",
  1507  		Name:        "name",
  1508  		Type:        "service",
  1509  		Priority:    50,
  1510  		AllAtOnce:   true,
  1511  		Datacenters: []string{"dc1", "dc2"},
  1512  		Constraints: []*structs.Constraint{
  1513  			{
  1514  				LTarget: "a",
  1515  				RTarget: "b",
  1516  				Operand: "c",
  1517  			},
  1518  		},
  1519  		Affinities: []*structs.Affinity{
  1520  			{
  1521  				LTarget: "a",
  1522  				RTarget: "b",
  1523  				Operand: "c",
  1524  				Weight:  50,
  1525  			},
  1526  		},
  1527  		Spreads: []*structs.Spread{
  1528  			{
  1529  				Attribute: "${meta.rack}",
  1530  				Weight:    100,
  1531  				SpreadTarget: []*structs.SpreadTarget{
  1532  					{
  1533  						Value:   "r1",
  1534  						Percent: 50,
  1535  					},
  1536  				},
  1537  			},
  1538  		},
  1539  		Update: structs.UpdateStrategy{
  1540  			Stagger:     1 * time.Second,
  1541  			MaxParallel: 5,
  1542  		},
  1543  		Periodic: &structs.PeriodicConfig{
  1544  			Enabled:         true,
  1545  			Spec:            "spec",
  1546  			SpecType:        "cron",
  1547  			ProhibitOverlap: true,
  1548  			TimeZone:        "test zone",
  1549  		},
  1550  		ParameterizedJob: &structs.ParameterizedJobConfig{
  1551  			Payload:      "payload",
  1552  			MetaRequired: []string{"a", "b"},
  1553  			MetaOptional: []string{"c", "d"},
  1554  		},
  1555  		Payload: []byte("payload"),
  1556  		Meta: map[string]string{
  1557  			"foo": "bar",
  1558  		},
  1559  		TaskGroups: []*structs.TaskGroup{
  1560  			{
  1561  				Name:  "group1",
  1562  				Count: 5,
  1563  				Constraints: []*structs.Constraint{
  1564  					{
  1565  						LTarget: "x",
  1566  						RTarget: "y",
  1567  						Operand: "z",
  1568  					},
  1569  				},
  1570  				Affinities: []*structs.Affinity{
  1571  					{
  1572  						LTarget: "x",
  1573  						RTarget: "y",
  1574  						Operand: "z",
  1575  						Weight:  100,
  1576  					},
  1577  				},
  1578  				RestartPolicy: &structs.RestartPolicy{
  1579  					Interval: 1 * time.Second,
  1580  					Attempts: 5,
  1581  					Delay:    10 * time.Second,
  1582  					Mode:     "delay",
  1583  				},
  1584  				Spreads: []*structs.Spread{
  1585  					{
  1586  						Attribute: "${node.datacenter}",
  1587  						Weight:    100,
  1588  						SpreadTarget: []*structs.SpreadTarget{
  1589  							{
  1590  								Value:   "dc1",
  1591  								Percent: 100,
  1592  							},
  1593  						},
  1594  					},
  1595  				},
  1596  				ReschedulePolicy: &structs.ReschedulePolicy{
  1597  					Interval:      12 * time.Hour,
  1598  					Attempts:      5,
  1599  					DelayFunction: "constant",
  1600  					Delay:         30 * time.Second,
  1601  					Unlimited:     true,
  1602  					MaxDelay:      20 * time.Minute,
  1603  				},
  1604  				Migrate: &structs.MigrateStrategy{
  1605  					MaxParallel:     12,
  1606  					HealthCheck:     "task_events",
  1607  					MinHealthyTime:  12 * time.Hour,
  1608  					HealthyDeadline: 12 * time.Hour,
  1609  				},
  1610  				EphemeralDisk: &structs.EphemeralDisk{
  1611  					SizeMB:  100,
  1612  					Sticky:  true,
  1613  					Migrate: true,
  1614  				},
  1615  				Update: &structs.UpdateStrategy{
  1616  					Stagger:          1 * time.Second,
  1617  					MaxParallel:      5,
  1618  					HealthCheck:      structs.UpdateStrategyHealthCheck_Checks,
  1619  					MinHealthyTime:   2 * time.Minute,
  1620  					HealthyDeadline:  5 * time.Minute,
  1621  					ProgressDeadline: 5 * time.Minute,
  1622  					AutoRevert:       true,
  1623  					Canary:           1,
  1624  				},
  1625  				Meta: map[string]string{
  1626  					"key": "value",
  1627  				},
  1628  				Tasks: []*structs.Task{
  1629  					{
  1630  						Name:   "task1",
  1631  						Driver: "docker",
  1632  						Leader: true,
  1633  						User:   "mary",
  1634  						Config: map[string]interface{}{
  1635  							"lol": "code",
  1636  						},
  1637  						Constraints: []*structs.Constraint{
  1638  							{
  1639  								LTarget: "x",
  1640  								RTarget: "y",
  1641  								Operand: "z",
  1642  							},
  1643  						},
  1644  						Affinities: []*structs.Affinity{
  1645  							{
  1646  								LTarget: "a",
  1647  								RTarget: "b",
  1648  								Operand: "c",
  1649  								Weight:  50,
  1650  							},
  1651  						},
  1652  						Env: map[string]string{
  1653  							"hello": "world",
  1654  						},
  1655  						Services: []*structs.Service{
  1656  							{
  1657  								Name:        "serviceA",
  1658  								Tags:        []string{"1", "2"},
  1659  								CanaryTags:  []string{"3", "4"},
  1660  								PortLabel:   "foo",
  1661  								AddressMode: "auto",
  1662  								Checks: []*structs.ServiceCheck{
  1663  									{
  1664  										Name:          "bar",
  1665  										Type:          "http",
  1666  										Command:       "foo",
  1667  										Args:          []string{"a", "b"},
  1668  										Path:          "/check",
  1669  										Protocol:      "http",
  1670  										PortLabel:     "foo",
  1671  										AddressMode:   "driver",
  1672  										Interval:      4 * time.Second,
  1673  										Timeout:       2 * time.Second,
  1674  										InitialStatus: "ok",
  1675  										GRPCService:   "foo.Bar",
  1676  										GRPCUseTLS:    true,
  1677  										CheckRestart: &structs.CheckRestart{
  1678  											Limit:          3,
  1679  											Grace:          11 * time.Second,
  1680  											IgnoreWarnings: true,
  1681  										},
  1682  									},
  1683  									{
  1684  										Name:      "check2",
  1685  										Type:      "tcp",
  1686  										PortLabel: "foo",
  1687  										Interval:  4 * time.Second,
  1688  										Timeout:   2 * time.Second,
  1689  										CheckRestart: &structs.CheckRestart{
  1690  											Limit: 4,
  1691  											Grace: 11 * time.Second,
  1692  										},
  1693  									},
  1694  								},
  1695  							},
  1696  						},
  1697  						Resources: &structs.Resources{
  1698  							CPU:      100,
  1699  							MemoryMB: 10,
  1700  							Networks: []*structs.NetworkResource{
  1701  								{
  1702  									IP:    "10.10.11.1",
  1703  									MBits: 10,
  1704  									ReservedPorts: []structs.Port{
  1705  										{
  1706  											Label: "http",
  1707  											Value: 80,
  1708  										},
  1709  									},
  1710  									DynamicPorts: []structs.Port{
  1711  										{
  1712  											Label: "ssh",
  1713  											Value: 2000,
  1714  										},
  1715  									},
  1716  								},
  1717  							},
  1718  							Devices: []*structs.RequestedDevice{
  1719  								{
  1720  									Name:  "nvidia/gpu",
  1721  									Count: 4,
  1722  									Constraints: []*structs.Constraint{
  1723  										{
  1724  											LTarget: "x",
  1725  											RTarget: "y",
  1726  											Operand: "z",
  1727  										},
  1728  									},
  1729  									Affinities: []*structs.Affinity{
  1730  										{
  1731  											LTarget: "a",
  1732  											RTarget: "b",
  1733  											Operand: "c",
  1734  											Weight:  50,
  1735  										},
  1736  									},
  1737  								},
  1738  								{
  1739  									Name:  "gpu",
  1740  									Count: 1,
  1741  								},
  1742  							},
  1743  						},
  1744  						Meta: map[string]string{
  1745  							"lol": "code",
  1746  						},
  1747  						KillTimeout: 10 * time.Second,
  1748  						KillSignal:  "SIGQUIT",
  1749  						LogConfig: &structs.LogConfig{
  1750  							MaxFiles:      10,
  1751  							MaxFileSizeMB: 100,
  1752  						},
  1753  						Artifacts: []*structs.TaskArtifact{
  1754  							{
  1755  								GetterSource: "source",
  1756  								GetterOptions: map[string]string{
  1757  									"a": "b",
  1758  								},
  1759  								GetterMode:   "dir",
  1760  								RelativeDest: "dest",
  1761  							},
  1762  						},
  1763  						Vault: &structs.Vault{
  1764  							Policies:     []string{"a", "b", "c"},
  1765  							Env:          true,
  1766  							ChangeMode:   "c",
  1767  							ChangeSignal: "sighup",
  1768  						},
  1769  						Templates: []*structs.Template{
  1770  							{
  1771  								SourcePath:   "source",
  1772  								DestPath:     "dest",
  1773  								EmbeddedTmpl: "embedded",
  1774  								ChangeMode:   "change",
  1775  								ChangeSignal: "SIGNAL",
  1776  								Splay:        1 * time.Minute,
  1777  								Perms:        "666",
  1778  								LeftDelim:    "abc",
  1779  								RightDelim:   "def",
  1780  								Envvars:      true,
  1781  								VaultGrace:   3 * time.Second,
  1782  							},
  1783  						},
  1784  						DispatchPayload: &structs.DispatchPayloadConfig{
  1785  							File: "fileA",
  1786  						},
  1787  					},
  1788  				},
  1789  			},
  1790  		},
  1791  
  1792  		VaultToken: "token",
  1793  	}
  1794  
  1795  	structsJob := ApiJobToStructJob(apiJob)
  1796  
  1797  	if diff := pretty.Diff(expected, structsJob); len(diff) > 0 {
  1798  		t.Fatalf("bad:\n%s", strings.Join(diff, "\n"))
  1799  	}
  1800  
  1801  	systemAPIJob := &api.Job{
  1802  		Stop:        helper.BoolToPtr(true),
  1803  		Region:      helper.StringToPtr("global"),
  1804  		Namespace:   helper.StringToPtr("foo"),
  1805  		ID:          helper.StringToPtr("foo"),
  1806  		ParentID:    helper.StringToPtr("lol"),
  1807  		Name:        helper.StringToPtr("name"),
  1808  		Type:        helper.StringToPtr("system"),
  1809  		Priority:    helper.IntToPtr(50),
  1810  		AllAtOnce:   helper.BoolToPtr(true),
  1811  		Datacenters: []string{"dc1", "dc2"},
  1812  		Constraints: []*api.Constraint{
  1813  			{
  1814  				LTarget: "a",
  1815  				RTarget: "b",
  1816  				Operand: "c",
  1817  			},
  1818  		},
  1819  		TaskGroups: []*api.TaskGroup{
  1820  			{
  1821  				Name:  helper.StringToPtr("group1"),
  1822  				Count: helper.IntToPtr(5),
  1823  				Constraints: []*api.Constraint{
  1824  					{
  1825  						LTarget: "x",
  1826  						RTarget: "y",
  1827  						Operand: "z",
  1828  					},
  1829  				},
  1830  				RestartPolicy: &api.RestartPolicy{
  1831  					Interval: helper.TimeToPtr(1 * time.Second),
  1832  					Attempts: helper.IntToPtr(5),
  1833  					Delay:    helper.TimeToPtr(10 * time.Second),
  1834  					Mode:     helper.StringToPtr("delay"),
  1835  				},
  1836  				EphemeralDisk: &api.EphemeralDisk{
  1837  					SizeMB:  helper.IntToPtr(100),
  1838  					Sticky:  helper.BoolToPtr(true),
  1839  					Migrate: helper.BoolToPtr(true),
  1840  				},
  1841  				Meta: map[string]string{
  1842  					"key": "value",
  1843  				},
  1844  				Tasks: []*api.Task{
  1845  					{
  1846  						Name:   "task1",
  1847  						Leader: true,
  1848  						Driver: "docker",
  1849  						User:   "mary",
  1850  						Config: map[string]interface{}{
  1851  							"lol": "code",
  1852  						},
  1853  						Env: map[string]string{
  1854  							"hello": "world",
  1855  						},
  1856  						Constraints: []*api.Constraint{
  1857  							{
  1858  								LTarget: "x",
  1859  								RTarget: "y",
  1860  								Operand: "z",
  1861  							},
  1862  						},
  1863  						Resources: &api.Resources{
  1864  							CPU:      helper.IntToPtr(100),
  1865  							MemoryMB: helper.IntToPtr(10),
  1866  							Networks: []*api.NetworkResource{
  1867  								{
  1868  									IP:    "10.10.11.1",
  1869  									MBits: helper.IntToPtr(10),
  1870  									ReservedPorts: []api.Port{
  1871  										{
  1872  											Label: "http",
  1873  											Value: 80,
  1874  										},
  1875  									},
  1876  									DynamicPorts: []api.Port{
  1877  										{
  1878  											Label: "ssh",
  1879  											Value: 2000,
  1880  										},
  1881  									},
  1882  								},
  1883  							},
  1884  						},
  1885  						Meta: map[string]string{
  1886  							"lol": "code",
  1887  						},
  1888  						KillTimeout: helper.TimeToPtr(10 * time.Second),
  1889  						KillSignal:  "SIGQUIT",
  1890  						LogConfig: &api.LogConfig{
  1891  							MaxFiles:      helper.IntToPtr(10),
  1892  							MaxFileSizeMB: helper.IntToPtr(100),
  1893  						},
  1894  						Artifacts: []*api.TaskArtifact{
  1895  							{
  1896  								GetterSource: helper.StringToPtr("source"),
  1897  								GetterOptions: map[string]string{
  1898  									"a": "b",
  1899  								},
  1900  								GetterMode:   helper.StringToPtr("dir"),
  1901  								RelativeDest: helper.StringToPtr("dest"),
  1902  							},
  1903  						},
  1904  						DispatchPayload: &api.DispatchPayloadConfig{
  1905  							File: "fileA",
  1906  						},
  1907  					},
  1908  				},
  1909  			},
  1910  		},
  1911  		Status:            helper.StringToPtr("status"),
  1912  		StatusDescription: helper.StringToPtr("status_desc"),
  1913  		Version:           helper.Uint64ToPtr(10),
  1914  		CreateIndex:       helper.Uint64ToPtr(1),
  1915  		ModifyIndex:       helper.Uint64ToPtr(3),
  1916  		JobModifyIndex:    helper.Uint64ToPtr(5),
  1917  	}
  1918  
  1919  	expectedSystemJob := &structs.Job{
  1920  		Stop:        true,
  1921  		Region:      "global",
  1922  		Namespace:   "foo",
  1923  		ID:          "foo",
  1924  		ParentID:    "lol",
  1925  		Name:        "name",
  1926  		Type:        "system",
  1927  		Priority:    50,
  1928  		AllAtOnce:   true,
  1929  		Datacenters: []string{"dc1", "dc2"},
  1930  		Constraints: []*structs.Constraint{
  1931  			{
  1932  				LTarget: "a",
  1933  				RTarget: "b",
  1934  				Operand: "c",
  1935  			},
  1936  		},
  1937  		TaskGroups: []*structs.TaskGroup{
  1938  			{
  1939  				Name:  "group1",
  1940  				Count: 5,
  1941  				Constraints: []*structs.Constraint{
  1942  					{
  1943  						LTarget: "x",
  1944  						RTarget: "y",
  1945  						Operand: "z",
  1946  					},
  1947  				},
  1948  				RestartPolicy: &structs.RestartPolicy{
  1949  					Interval: 1 * time.Second,
  1950  					Attempts: 5,
  1951  					Delay:    10 * time.Second,
  1952  					Mode:     "delay",
  1953  				},
  1954  				EphemeralDisk: &structs.EphemeralDisk{
  1955  					SizeMB:  100,
  1956  					Sticky:  true,
  1957  					Migrate: true,
  1958  				},
  1959  				Meta: map[string]string{
  1960  					"key": "value",
  1961  				},
  1962  				Tasks: []*structs.Task{
  1963  					{
  1964  						Name:   "task1",
  1965  						Driver: "docker",
  1966  						Leader: true,
  1967  						User:   "mary",
  1968  						Config: map[string]interface{}{
  1969  							"lol": "code",
  1970  						},
  1971  						Constraints: []*structs.Constraint{
  1972  							{
  1973  								LTarget: "x",
  1974  								RTarget: "y",
  1975  								Operand: "z",
  1976  							},
  1977  						},
  1978  						Env: map[string]string{
  1979  							"hello": "world",
  1980  						},
  1981  						Resources: &structs.Resources{
  1982  							CPU:      100,
  1983  							MemoryMB: 10,
  1984  							Networks: []*structs.NetworkResource{
  1985  								{
  1986  									IP:    "10.10.11.1",
  1987  									MBits: 10,
  1988  									ReservedPorts: []structs.Port{
  1989  										{
  1990  											Label: "http",
  1991  											Value: 80,
  1992  										},
  1993  									},
  1994  									DynamicPorts: []structs.Port{
  1995  										{
  1996  											Label: "ssh",
  1997  											Value: 2000,
  1998  										},
  1999  									},
  2000  								},
  2001  							},
  2002  						},
  2003  						Meta: map[string]string{
  2004  							"lol": "code",
  2005  						},
  2006  						KillTimeout: 10 * time.Second,
  2007  						KillSignal:  "SIGQUIT",
  2008  						LogConfig: &structs.LogConfig{
  2009  							MaxFiles:      10,
  2010  							MaxFileSizeMB: 100,
  2011  						},
  2012  						Artifacts: []*structs.TaskArtifact{
  2013  							{
  2014  								GetterSource: "source",
  2015  								GetterOptions: map[string]string{
  2016  									"a": "b",
  2017  								},
  2018  								GetterMode:   "dir",
  2019  								RelativeDest: "dest",
  2020  							},
  2021  						},
  2022  						DispatchPayload: &structs.DispatchPayloadConfig{
  2023  							File: "fileA",
  2024  						},
  2025  					},
  2026  				},
  2027  			},
  2028  		},
  2029  	}
  2030  
  2031  	systemStructsJob := ApiJobToStructJob(systemAPIJob)
  2032  
  2033  	if diff := pretty.Diff(expectedSystemJob, systemStructsJob); len(diff) > 0 {
  2034  		t.Fatalf("bad:\n%s", strings.Join(diff, "\n"))
  2035  	}
  2036  }