github.com/smintz/nomad@v0.8.3/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_JobEvaluations(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  
   629  		// Make the HTTP request
   630  		req, err := http.NewRequest("GET", "/v1/job/"+job.ID+"/evaluations", nil)
   631  		if err != nil {
   632  			t.Fatalf("err: %v", err)
   633  		}
   634  		respW := httptest.NewRecorder()
   635  
   636  		// Make the request
   637  		obj, err := s.Server.JobSpecificRequest(respW, req)
   638  		if err != nil {
   639  			t.Fatalf("err: %v", err)
   640  		}
   641  
   642  		// Check the response
   643  		evals := obj.([]*structs.Evaluation)
   644  		// Can be multiple evals, use the last one, since they are in order
   645  		idx := len(evals) - 1
   646  		if len(evals) < 0 || evals[idx].ID != resp.EvalID {
   647  			t.Fatalf("bad: %v", evals)
   648  		}
   649  
   650  		// Check for the index
   651  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
   652  			t.Fatalf("missing index")
   653  		}
   654  		if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" {
   655  			t.Fatalf("missing known leader")
   656  		}
   657  		if respW.HeaderMap.Get("X-Nomad-LastContact") == "" {
   658  			t.Fatalf("missing last contact")
   659  		}
   660  	})
   661  }
   662  
   663  func TestHTTP_JobAllocations(t *testing.T) {
   664  	t.Parallel()
   665  	httpTest(t, nil, func(s *TestAgent) {
   666  		// Create the job
   667  		alloc1 := mock.Alloc()
   668  		args := structs.JobRegisterRequest{
   669  			Job: alloc1.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  		// Directly manipulate the state
   681  		expectedDisplayMsg := "test message"
   682  		testEvent := structs.NewTaskEvent("test event").SetMessage(expectedDisplayMsg)
   683  		var events []*structs.TaskEvent
   684  		events = append(events, testEvent)
   685  		taskState := &structs.TaskState{Events: events}
   686  		alloc1.TaskStates = make(map[string]*structs.TaskState)
   687  		alloc1.TaskStates["test"] = taskState
   688  		state := s.Agent.server.State()
   689  		err := state.UpsertAllocs(1000, []*structs.Allocation{alloc1})
   690  		if err != nil {
   691  			t.Fatalf("err: %v", err)
   692  		}
   693  
   694  		// Make the HTTP request
   695  		req, err := http.NewRequest("GET", "/v1/job/"+alloc1.Job.ID+"/allocations?all=true", nil)
   696  		if err != nil {
   697  			t.Fatalf("err: %v", err)
   698  		}
   699  		respW := httptest.NewRecorder()
   700  
   701  		// Make the request
   702  		obj, err := s.Server.JobSpecificRequest(respW, req)
   703  		if err != nil {
   704  			t.Fatalf("err: %v", err)
   705  		}
   706  
   707  		// Check the response
   708  		allocs := obj.([]*structs.AllocListStub)
   709  		if len(allocs) != 1 && allocs[0].ID != alloc1.ID {
   710  			t.Fatalf("bad: %v", allocs)
   711  		}
   712  		displayMsg := allocs[0].TaskStates["test"].Events[0].DisplayMessage
   713  		assert.Equal(t, expectedDisplayMsg, displayMsg)
   714  
   715  		// Check for the index
   716  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
   717  			t.Fatalf("missing index")
   718  		}
   719  		if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" {
   720  			t.Fatalf("missing known leader")
   721  		}
   722  		if respW.HeaderMap.Get("X-Nomad-LastContact") == "" {
   723  			t.Fatalf("missing last contact")
   724  		}
   725  	})
   726  }
   727  
   728  func TestHTTP_JobDeployments(t *testing.T) {
   729  	assert := assert.New(t)
   730  	t.Parallel()
   731  	httpTest(t, nil, func(s *TestAgent) {
   732  		// Create the job
   733  		j := mock.Job()
   734  		args := structs.JobRegisterRequest{
   735  			Job: j,
   736  			WriteRequest: structs.WriteRequest{
   737  				Region:    "global",
   738  				Namespace: structs.DefaultNamespace,
   739  			},
   740  		}
   741  		var resp structs.JobRegisterResponse
   742  		assert.Nil(s.Agent.RPC("Job.Register", &args, &resp), "JobRegister")
   743  
   744  		// Directly manipulate the state
   745  		state := s.Agent.server.State()
   746  		d := mock.Deployment()
   747  		d.JobID = j.ID
   748  		assert.Nil(state.UpsertDeployment(1000, d), "UpsertDeployment")
   749  
   750  		// Make the HTTP request
   751  		req, err := http.NewRequest("GET", "/v1/job/"+j.ID+"/deployments", nil)
   752  		assert.Nil(err, "HTTP")
   753  		respW := httptest.NewRecorder()
   754  
   755  		// Make the request
   756  		obj, err := s.Server.JobSpecificRequest(respW, req)
   757  		assert.Nil(err, "JobSpecificRequest")
   758  
   759  		// Check the response
   760  		deploys := obj.([]*structs.Deployment)
   761  		assert.Len(deploys, 1, "deployments")
   762  		assert.Equal(d.ID, deploys[0].ID, "deployment id")
   763  
   764  		assert.NotZero(respW.HeaderMap.Get("X-Nomad-Index"), "missing index")
   765  		assert.Equal("true", respW.HeaderMap.Get("X-Nomad-KnownLeader"), "missing known leader")
   766  		assert.NotZero(respW.HeaderMap.Get("X-Nomad-LastContact"), "missing last contact")
   767  	})
   768  }
   769  
   770  func TestHTTP_JobDeployment(t *testing.T) {
   771  	assert := assert.New(t)
   772  	t.Parallel()
   773  	httpTest(t, nil, func(s *TestAgent) {
   774  		// Create the job
   775  		j := mock.Job()
   776  		args := structs.JobRegisterRequest{
   777  			Job: j,
   778  			WriteRequest: structs.WriteRequest{
   779  				Region:    "global",
   780  				Namespace: structs.DefaultNamespace,
   781  			},
   782  		}
   783  		var resp structs.JobRegisterResponse
   784  		assert.Nil(s.Agent.RPC("Job.Register", &args, &resp), "JobRegister")
   785  
   786  		// Directly manipulate the state
   787  		state := s.Agent.server.State()
   788  		d := mock.Deployment()
   789  		d.JobID = j.ID
   790  		assert.Nil(state.UpsertDeployment(1000, d), "UpsertDeployment")
   791  
   792  		// Make the HTTP request
   793  		req, err := http.NewRequest("GET", "/v1/job/"+j.ID+"/deployment", nil)
   794  		assert.Nil(err, "HTTP")
   795  		respW := httptest.NewRecorder()
   796  
   797  		// Make the request
   798  		obj, err := s.Server.JobSpecificRequest(respW, req)
   799  		assert.Nil(err, "JobSpecificRequest")
   800  
   801  		// Check the response
   802  		out := obj.(*structs.Deployment)
   803  		assert.NotNil(out, "deployment")
   804  		assert.Equal(d.ID, out.ID, "deployment id")
   805  
   806  		assert.NotZero(respW.HeaderMap.Get("X-Nomad-Index"), "missing index")
   807  		assert.Equal("true", respW.HeaderMap.Get("X-Nomad-KnownLeader"), "missing known leader")
   808  		assert.NotZero(respW.HeaderMap.Get("X-Nomad-LastContact"), "missing last contact")
   809  	})
   810  }
   811  
   812  func TestHTTP_JobVersions(t *testing.T) {
   813  	t.Parallel()
   814  	httpTest(t, nil, func(s *TestAgent) {
   815  		// Create the job
   816  		job := mock.Job()
   817  		args := structs.JobRegisterRequest{
   818  			Job: job,
   819  			WriteRequest: structs.WriteRequest{
   820  				Region:    "global",
   821  				Namespace: structs.DefaultNamespace,
   822  			},
   823  		}
   824  		var resp structs.JobRegisterResponse
   825  		if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil {
   826  			t.Fatalf("err: %v", err)
   827  		}
   828  
   829  		job2 := mock.Job()
   830  		job2.ID = job.ID
   831  		job2.Priority = 100
   832  
   833  		args2 := structs.JobRegisterRequest{
   834  			Job: job2,
   835  			WriteRequest: structs.WriteRequest{
   836  				Region:    "global",
   837  				Namespace: structs.DefaultNamespace,
   838  			},
   839  		}
   840  		var resp2 structs.JobRegisterResponse
   841  		if err := s.Agent.RPC("Job.Register", &args2, &resp2); err != nil {
   842  			t.Fatalf("err: %v", err)
   843  		}
   844  
   845  		// Make the HTTP request
   846  		req, err := http.NewRequest("GET", "/v1/job/"+job.ID+"/versions?diffs=true", nil)
   847  		if err != nil {
   848  			t.Fatalf("err: %v", err)
   849  		}
   850  		respW := httptest.NewRecorder()
   851  
   852  		// Make the request
   853  		obj, err := s.Server.JobSpecificRequest(respW, req)
   854  		if err != nil {
   855  			t.Fatalf("err: %v", err)
   856  		}
   857  
   858  		// Check the response
   859  		vResp := obj.(structs.JobVersionsResponse)
   860  		versions := vResp.Versions
   861  		if len(versions) != 2 {
   862  			t.Fatalf("got %d versions; want 2", len(versions))
   863  		}
   864  
   865  		if v := versions[0]; v.Version != 1 || v.Priority != 100 {
   866  			t.Fatalf("bad %v", v)
   867  		}
   868  
   869  		if v := versions[1]; v.Version != 0 {
   870  			t.Fatalf("bad %v", v)
   871  		}
   872  
   873  		if len(vResp.Diffs) != 1 {
   874  			t.Fatalf("bad %v", vResp)
   875  		}
   876  
   877  		// Check for the index
   878  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
   879  			t.Fatalf("missing index")
   880  		}
   881  		if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" {
   882  			t.Fatalf("missing known leader")
   883  		}
   884  		if respW.HeaderMap.Get("X-Nomad-LastContact") == "" {
   885  			t.Fatalf("missing last contact")
   886  		}
   887  	})
   888  }
   889  
   890  func TestHTTP_PeriodicForce(t *testing.T) {
   891  	t.Parallel()
   892  	httpTest(t, nil, func(s *TestAgent) {
   893  		// Create and register a periodic job.
   894  		job := mock.PeriodicJob()
   895  		args := structs.JobRegisterRequest{
   896  			Job: job,
   897  			WriteRequest: structs.WriteRequest{
   898  				Region:    "global",
   899  				Namespace: structs.DefaultNamespace,
   900  			},
   901  		}
   902  		var resp structs.JobRegisterResponse
   903  		if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil {
   904  			t.Fatalf("err: %v", err)
   905  		}
   906  
   907  		// Make the HTTP request
   908  		req, err := http.NewRequest("POST", "/v1/job/"+job.ID+"/periodic/force", nil)
   909  		if err != nil {
   910  			t.Fatalf("err: %v", err)
   911  		}
   912  		respW := httptest.NewRecorder()
   913  
   914  		// Make the request
   915  		obj, err := s.Server.JobSpecificRequest(respW, req)
   916  		if err != nil {
   917  			t.Fatalf("err: %v", err)
   918  		}
   919  
   920  		// Check for the index
   921  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
   922  			t.Fatalf("missing index")
   923  		}
   924  
   925  		// Check the response
   926  		r := obj.(structs.PeriodicForceResponse)
   927  		if r.EvalID == "" {
   928  			t.Fatalf("bad: %#v", r)
   929  		}
   930  	})
   931  }
   932  
   933  func TestHTTP_JobPlan(t *testing.T) {
   934  	t.Parallel()
   935  	httpTest(t, nil, func(s *TestAgent) {
   936  		// Create the job
   937  		job := api.MockJob()
   938  		args := api.JobPlanRequest{
   939  			Job:  job,
   940  			Diff: true,
   941  			WriteRequest: api.WriteRequest{
   942  				Region:    "global",
   943  				Namespace: api.DefaultNamespace,
   944  			},
   945  		}
   946  		buf := encodeReq(args)
   947  
   948  		// Make the HTTP request
   949  		req, err := http.NewRequest("PUT", "/v1/job/"+*job.ID+"/plan", buf)
   950  		if err != nil {
   951  			t.Fatalf("err: %v", err)
   952  		}
   953  		respW := httptest.NewRecorder()
   954  
   955  		// Make the request
   956  		obj, err := s.Server.JobSpecificRequest(respW, req)
   957  		if err != nil {
   958  			t.Fatalf("err: %v", err)
   959  		}
   960  
   961  		// Check the response
   962  		plan := obj.(structs.JobPlanResponse)
   963  		if plan.Annotations == nil {
   964  			t.Fatalf("bad: %v", plan)
   965  		}
   966  
   967  		if plan.Diff == nil {
   968  			t.Fatalf("bad: %v", plan)
   969  		}
   970  	})
   971  }
   972  
   973  func TestHTTP_JobDispatch(t *testing.T) {
   974  	t.Parallel()
   975  	httpTest(t, nil, func(s *TestAgent) {
   976  		// Create the parameterized job
   977  		job := mock.BatchJob()
   978  		job.ParameterizedJob = &structs.ParameterizedJobConfig{}
   979  
   980  		args := structs.JobRegisterRequest{
   981  			Job: job,
   982  			WriteRequest: structs.WriteRequest{
   983  				Region:    "global",
   984  				Namespace: structs.DefaultNamespace,
   985  			},
   986  		}
   987  		var resp structs.JobRegisterResponse
   988  		if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil {
   989  			t.Fatalf("err: %v", err)
   990  		}
   991  
   992  		// Make the request
   993  		respW := httptest.NewRecorder()
   994  		args2 := structs.JobDispatchRequest{
   995  			WriteRequest: structs.WriteRequest{
   996  				Region:    "global",
   997  				Namespace: structs.DefaultNamespace,
   998  			},
   999  		}
  1000  		buf := encodeReq(args2)
  1001  
  1002  		// Make the HTTP request
  1003  		req2, err := http.NewRequest("PUT", "/v1/job/"+job.ID+"/dispatch", buf)
  1004  		if err != nil {
  1005  			t.Fatalf("err: %v", err)
  1006  		}
  1007  		respW.Flush()
  1008  
  1009  		// Make the request
  1010  		obj, err := s.Server.JobSpecificRequest(respW, req2)
  1011  		if err != nil {
  1012  			t.Fatalf("err: %v", err)
  1013  		}
  1014  
  1015  		// Check the response
  1016  		dispatch := obj.(structs.JobDispatchResponse)
  1017  		if dispatch.EvalID == "" {
  1018  			t.Fatalf("bad: %v", dispatch)
  1019  		}
  1020  
  1021  		if dispatch.DispatchedJobID == "" {
  1022  			t.Fatalf("bad: %v", dispatch)
  1023  		}
  1024  	})
  1025  }
  1026  
  1027  func TestHTTP_JobRevert(t *testing.T) {
  1028  	t.Parallel()
  1029  	httpTest(t, nil, func(s *TestAgent) {
  1030  		// Create the job and register it twice
  1031  		job := mock.Job()
  1032  		regReq := structs.JobRegisterRequest{
  1033  			Job: job,
  1034  			WriteRequest: structs.WriteRequest{
  1035  				Region:    "global",
  1036  				Namespace: structs.DefaultNamespace,
  1037  			},
  1038  		}
  1039  		var regResp structs.JobRegisterResponse
  1040  		if err := s.Agent.RPC("Job.Register", &regReq, &regResp); err != nil {
  1041  			t.Fatalf("err: %v", err)
  1042  		}
  1043  
  1044  		// Change the job to get a new version
  1045  		job.Datacenters = append(job.Datacenters, "foo")
  1046  		if err := s.Agent.RPC("Job.Register", &regReq, &regResp); err != nil {
  1047  			t.Fatalf("err: %v", err)
  1048  		}
  1049  
  1050  		args := structs.JobRevertRequest{
  1051  			JobID:      job.ID,
  1052  			JobVersion: 0,
  1053  			WriteRequest: structs.WriteRequest{
  1054  				Region:    "global",
  1055  				Namespace: structs.DefaultNamespace,
  1056  			},
  1057  		}
  1058  		buf := encodeReq(args)
  1059  
  1060  		// Make the HTTP request
  1061  		req, err := http.NewRequest("PUT", "/v1/job/"+job.ID+"/revert", buf)
  1062  		if err != nil {
  1063  			t.Fatalf("err: %v", err)
  1064  		}
  1065  		respW := httptest.NewRecorder()
  1066  
  1067  		// Make the request
  1068  		obj, err := s.Server.JobSpecificRequest(respW, req)
  1069  		if err != nil {
  1070  			t.Fatalf("err: %v", err)
  1071  		}
  1072  
  1073  		// Check the response
  1074  		revertResp := obj.(structs.JobRegisterResponse)
  1075  		if revertResp.EvalID == "" {
  1076  			t.Fatalf("bad: %v", revertResp)
  1077  		}
  1078  
  1079  		// Check for the index
  1080  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
  1081  			t.Fatalf("missing index")
  1082  		}
  1083  	})
  1084  }
  1085  
  1086  func TestHTTP_JobStable(t *testing.T) {
  1087  	t.Parallel()
  1088  	httpTest(t, nil, func(s *TestAgent) {
  1089  		// Create the job and register it twice
  1090  		job := mock.Job()
  1091  		regReq := structs.JobRegisterRequest{
  1092  			Job: job,
  1093  			WriteRequest: structs.WriteRequest{
  1094  				Region:    "global",
  1095  				Namespace: structs.DefaultNamespace,
  1096  			},
  1097  		}
  1098  		var regResp structs.JobRegisterResponse
  1099  		if err := s.Agent.RPC("Job.Register", &regReq, &regResp); err != nil {
  1100  			t.Fatalf("err: %v", err)
  1101  		}
  1102  
  1103  		if err := s.Agent.RPC("Job.Register", &regReq, &regResp); err != nil {
  1104  			t.Fatalf("err: %v", err)
  1105  		}
  1106  
  1107  		args := structs.JobStabilityRequest{
  1108  			JobID:      job.ID,
  1109  			JobVersion: 0,
  1110  			Stable:     true,
  1111  			WriteRequest: structs.WriteRequest{
  1112  				Region:    "global",
  1113  				Namespace: structs.DefaultNamespace,
  1114  			},
  1115  		}
  1116  		buf := encodeReq(args)
  1117  
  1118  		// Make the HTTP request
  1119  		req, err := http.NewRequest("PUT", "/v1/job/"+job.ID+"/stable", buf)
  1120  		if err != nil {
  1121  			t.Fatalf("err: %v", err)
  1122  		}
  1123  		respW := httptest.NewRecorder()
  1124  
  1125  		// Make the request
  1126  		obj, err := s.Server.JobSpecificRequest(respW, req)
  1127  		if err != nil {
  1128  			t.Fatalf("err: %v", err)
  1129  		}
  1130  
  1131  		// Check the response
  1132  		stableResp := obj.(structs.JobStabilityResponse)
  1133  		if stableResp.Index == 0 {
  1134  			t.Fatalf("bad: %v", stableResp)
  1135  		}
  1136  
  1137  		// Check for the index
  1138  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
  1139  			t.Fatalf("missing index")
  1140  		}
  1141  	})
  1142  }
  1143  
  1144  func TestJobs_ApiJobToStructsJob(t *testing.T) {
  1145  	apiJob := &api.Job{
  1146  		Stop:        helper.BoolToPtr(true),
  1147  		Region:      helper.StringToPtr("global"),
  1148  		Namespace:   helper.StringToPtr("foo"),
  1149  		ID:          helper.StringToPtr("foo"),
  1150  		ParentID:    helper.StringToPtr("lol"),
  1151  		Name:        helper.StringToPtr("name"),
  1152  		Type:        helper.StringToPtr("service"),
  1153  		Priority:    helper.IntToPtr(50),
  1154  		AllAtOnce:   helper.BoolToPtr(true),
  1155  		Datacenters: []string{"dc1", "dc2"},
  1156  		Constraints: []*api.Constraint{
  1157  			{
  1158  				LTarget: "a",
  1159  				RTarget: "b",
  1160  				Operand: "c",
  1161  			},
  1162  		},
  1163  		Update: &api.UpdateStrategy{
  1164  			Stagger:         helper.TimeToPtr(1 * time.Second),
  1165  			MaxParallel:     helper.IntToPtr(5),
  1166  			HealthCheck:     helper.StringToPtr(structs.UpdateStrategyHealthCheck_Manual),
  1167  			MinHealthyTime:  helper.TimeToPtr(1 * time.Minute),
  1168  			HealthyDeadline: helper.TimeToPtr(3 * time.Minute),
  1169  			AutoRevert:      helper.BoolToPtr(false),
  1170  			Canary:          helper.IntToPtr(1),
  1171  		},
  1172  		Periodic: &api.PeriodicConfig{
  1173  			Enabled:         helper.BoolToPtr(true),
  1174  			Spec:            helper.StringToPtr("spec"),
  1175  			SpecType:        helper.StringToPtr("cron"),
  1176  			ProhibitOverlap: helper.BoolToPtr(true),
  1177  			TimeZone:        helper.StringToPtr("test zone"),
  1178  		},
  1179  		ParameterizedJob: &api.ParameterizedJobConfig{
  1180  			Payload:      "payload",
  1181  			MetaRequired: []string{"a", "b"},
  1182  			MetaOptional: []string{"c", "d"},
  1183  		},
  1184  		Payload: []byte("payload"),
  1185  		Meta: map[string]string{
  1186  			"foo": "bar",
  1187  		},
  1188  		TaskGroups: []*api.TaskGroup{
  1189  			{
  1190  				Name:  helper.StringToPtr("group1"),
  1191  				Count: helper.IntToPtr(5),
  1192  				Constraints: []*api.Constraint{
  1193  					{
  1194  						LTarget: "x",
  1195  						RTarget: "y",
  1196  						Operand: "z",
  1197  					},
  1198  				},
  1199  				RestartPolicy: &api.RestartPolicy{
  1200  					Interval: helper.TimeToPtr(1 * time.Second),
  1201  					Attempts: helper.IntToPtr(5),
  1202  					Delay:    helper.TimeToPtr(10 * time.Second),
  1203  					Mode:     helper.StringToPtr("delay"),
  1204  				},
  1205  				ReschedulePolicy: &api.ReschedulePolicy{
  1206  					Interval:      helper.TimeToPtr(12 * time.Hour),
  1207  					Attempts:      helper.IntToPtr(5),
  1208  					DelayFunction: helper.StringToPtr("constant"),
  1209  					Delay:         helper.TimeToPtr(30 * time.Second),
  1210  					Unlimited:     helper.BoolToPtr(true),
  1211  					MaxDelay:      helper.TimeToPtr(20 * time.Minute),
  1212  				},
  1213  				Migrate: &api.MigrateStrategy{
  1214  					MaxParallel:     helper.IntToPtr(12),
  1215  					HealthCheck:     helper.StringToPtr("task_events"),
  1216  					MinHealthyTime:  helper.TimeToPtr(12 * time.Hour),
  1217  					HealthyDeadline: helper.TimeToPtr(12 * time.Hour),
  1218  				},
  1219  				EphemeralDisk: &api.EphemeralDisk{
  1220  					SizeMB:  helper.IntToPtr(100),
  1221  					Sticky:  helper.BoolToPtr(true),
  1222  					Migrate: helper.BoolToPtr(true),
  1223  				},
  1224  				Update: &api.UpdateStrategy{
  1225  					HealthCheck:     helper.StringToPtr(structs.UpdateStrategyHealthCheck_Checks),
  1226  					MinHealthyTime:  helper.TimeToPtr(2 * time.Minute),
  1227  					HealthyDeadline: helper.TimeToPtr(5 * time.Minute),
  1228  					AutoRevert:      helper.BoolToPtr(true),
  1229  				},
  1230  
  1231  				Meta: map[string]string{
  1232  					"key": "value",
  1233  				},
  1234  				Tasks: []*api.Task{
  1235  					{
  1236  						Name:   "task1",
  1237  						Leader: true,
  1238  						Driver: "docker",
  1239  						User:   "mary",
  1240  						Config: map[string]interface{}{
  1241  							"lol": "code",
  1242  						},
  1243  						Env: map[string]string{
  1244  							"hello": "world",
  1245  						},
  1246  						Constraints: []*api.Constraint{
  1247  							{
  1248  								LTarget: "x",
  1249  								RTarget: "y",
  1250  								Operand: "z",
  1251  							},
  1252  						},
  1253  
  1254  						Services: []*api.Service{
  1255  							{
  1256  								Id:        "id",
  1257  								Name:      "serviceA",
  1258  								Tags:      []string{"1", "2"},
  1259  								PortLabel: "foo",
  1260  								CheckRestart: &api.CheckRestart{
  1261  									Limit: 4,
  1262  									Grace: helper.TimeToPtr(11 * time.Second),
  1263  								},
  1264  								Checks: []api.ServiceCheck{
  1265  									{
  1266  										Id:            "hello",
  1267  										Name:          "bar",
  1268  										Type:          "http",
  1269  										Command:       "foo",
  1270  										Args:          []string{"a", "b"},
  1271  										Path:          "/check",
  1272  										Protocol:      "http",
  1273  										PortLabel:     "foo",
  1274  										AddressMode:   "driver",
  1275  										Interval:      4 * time.Second,
  1276  										Timeout:       2 * time.Second,
  1277  										InitialStatus: "ok",
  1278  										CheckRestart: &api.CheckRestart{
  1279  											Limit:          3,
  1280  											IgnoreWarnings: true,
  1281  										},
  1282  									},
  1283  									{
  1284  										Id:        "check2id",
  1285  										Name:      "check2",
  1286  										Type:      "tcp",
  1287  										PortLabel: "foo",
  1288  										Interval:  4 * time.Second,
  1289  										Timeout:   2 * time.Second,
  1290  									},
  1291  								},
  1292  							},
  1293  						},
  1294  						Resources: &api.Resources{
  1295  							CPU:      helper.IntToPtr(100),
  1296  							MemoryMB: helper.IntToPtr(10),
  1297  							Networks: []*api.NetworkResource{
  1298  								{
  1299  									IP:    "10.10.11.1",
  1300  									MBits: helper.IntToPtr(10),
  1301  									ReservedPorts: []api.Port{
  1302  										{
  1303  											Label: "http",
  1304  											Value: 80,
  1305  										},
  1306  									},
  1307  									DynamicPorts: []api.Port{
  1308  										{
  1309  											Label: "ssh",
  1310  											Value: 2000,
  1311  										},
  1312  									},
  1313  								},
  1314  							},
  1315  						},
  1316  						Meta: map[string]string{
  1317  							"lol": "code",
  1318  						},
  1319  						KillTimeout: helper.TimeToPtr(10 * time.Second),
  1320  						KillSignal:  "SIGQUIT",
  1321  						LogConfig: &api.LogConfig{
  1322  							MaxFiles:      helper.IntToPtr(10),
  1323  							MaxFileSizeMB: helper.IntToPtr(100),
  1324  						},
  1325  						Artifacts: []*api.TaskArtifact{
  1326  							{
  1327  								GetterSource: helper.StringToPtr("source"),
  1328  								GetterOptions: map[string]string{
  1329  									"a": "b",
  1330  								},
  1331  								GetterMode:   helper.StringToPtr("dir"),
  1332  								RelativeDest: helper.StringToPtr("dest"),
  1333  							},
  1334  						},
  1335  						Vault: &api.Vault{
  1336  							Policies:     []string{"a", "b", "c"},
  1337  							Env:          helper.BoolToPtr(true),
  1338  							ChangeMode:   helper.StringToPtr("c"),
  1339  							ChangeSignal: helper.StringToPtr("sighup"),
  1340  						},
  1341  						Templates: []*api.Template{
  1342  							{
  1343  								SourcePath:   helper.StringToPtr("source"),
  1344  								DestPath:     helper.StringToPtr("dest"),
  1345  								EmbeddedTmpl: helper.StringToPtr("embedded"),
  1346  								ChangeMode:   helper.StringToPtr("change"),
  1347  								ChangeSignal: helper.StringToPtr("signal"),
  1348  								Splay:        helper.TimeToPtr(1 * time.Minute),
  1349  								Perms:        helper.StringToPtr("666"),
  1350  								LeftDelim:    helper.StringToPtr("abc"),
  1351  								RightDelim:   helper.StringToPtr("def"),
  1352  								Envvars:      helper.BoolToPtr(true),
  1353  								VaultGrace:   helper.TimeToPtr(3 * time.Second),
  1354  							},
  1355  						},
  1356  						DispatchPayload: &api.DispatchPayloadConfig{
  1357  							File: "fileA",
  1358  						},
  1359  					},
  1360  				},
  1361  			},
  1362  		},
  1363  		VaultToken:        helper.StringToPtr("token"),
  1364  		Status:            helper.StringToPtr("status"),
  1365  		StatusDescription: helper.StringToPtr("status_desc"),
  1366  		Version:           helper.Uint64ToPtr(10),
  1367  		CreateIndex:       helper.Uint64ToPtr(1),
  1368  		ModifyIndex:       helper.Uint64ToPtr(3),
  1369  		JobModifyIndex:    helper.Uint64ToPtr(5),
  1370  	}
  1371  
  1372  	expected := &structs.Job{
  1373  		Stop:        true,
  1374  		Region:      "global",
  1375  		Namespace:   "foo",
  1376  		ID:          "foo",
  1377  		ParentID:    "lol",
  1378  		Name:        "name",
  1379  		Type:        "service",
  1380  		Priority:    50,
  1381  		AllAtOnce:   true,
  1382  		Datacenters: []string{"dc1", "dc2"},
  1383  		Constraints: []*structs.Constraint{
  1384  			{
  1385  				LTarget: "a",
  1386  				RTarget: "b",
  1387  				Operand: "c",
  1388  			},
  1389  		},
  1390  		Update: structs.UpdateStrategy{
  1391  			Stagger:     1 * time.Second,
  1392  			MaxParallel: 5,
  1393  		},
  1394  		Periodic: &structs.PeriodicConfig{
  1395  			Enabled:         true,
  1396  			Spec:            "spec",
  1397  			SpecType:        "cron",
  1398  			ProhibitOverlap: true,
  1399  			TimeZone:        "test zone",
  1400  		},
  1401  		ParameterizedJob: &structs.ParameterizedJobConfig{
  1402  			Payload:      "payload",
  1403  			MetaRequired: []string{"a", "b"},
  1404  			MetaOptional: []string{"c", "d"},
  1405  		},
  1406  		Payload: []byte("payload"),
  1407  		Meta: map[string]string{
  1408  			"foo": "bar",
  1409  		},
  1410  		TaskGroups: []*structs.TaskGroup{
  1411  			{
  1412  				Name:  "group1",
  1413  				Count: 5,
  1414  				Constraints: []*structs.Constraint{
  1415  					{
  1416  						LTarget: "x",
  1417  						RTarget: "y",
  1418  						Operand: "z",
  1419  					},
  1420  				},
  1421  				RestartPolicy: &structs.RestartPolicy{
  1422  					Interval: 1 * time.Second,
  1423  					Attempts: 5,
  1424  					Delay:    10 * time.Second,
  1425  					Mode:     "delay",
  1426  				},
  1427  				ReschedulePolicy: &structs.ReschedulePolicy{
  1428  					Interval:      12 * time.Hour,
  1429  					Attempts:      5,
  1430  					DelayFunction: "constant",
  1431  					Delay:         30 * time.Second,
  1432  					Unlimited:     true,
  1433  					MaxDelay:      20 * time.Minute,
  1434  				},
  1435  				Migrate: &structs.MigrateStrategy{
  1436  					MaxParallel:     12,
  1437  					HealthCheck:     "task_events",
  1438  					MinHealthyTime:  12 * time.Hour,
  1439  					HealthyDeadline: 12 * time.Hour,
  1440  				},
  1441  				EphemeralDisk: &structs.EphemeralDisk{
  1442  					SizeMB:  100,
  1443  					Sticky:  true,
  1444  					Migrate: true,
  1445  				},
  1446  				Update: &structs.UpdateStrategy{
  1447  					Stagger:         1 * time.Second,
  1448  					MaxParallel:     5,
  1449  					HealthCheck:     structs.UpdateStrategyHealthCheck_Checks,
  1450  					MinHealthyTime:  2 * time.Minute,
  1451  					HealthyDeadline: 5 * time.Minute,
  1452  					AutoRevert:      true,
  1453  					Canary:          1,
  1454  				},
  1455  				Meta: map[string]string{
  1456  					"key": "value",
  1457  				},
  1458  				Tasks: []*structs.Task{
  1459  					{
  1460  						Name:   "task1",
  1461  						Driver: "docker",
  1462  						Leader: true,
  1463  						User:   "mary",
  1464  						Config: map[string]interface{}{
  1465  							"lol": "code",
  1466  						},
  1467  						Constraints: []*structs.Constraint{
  1468  							{
  1469  								LTarget: "x",
  1470  								RTarget: "y",
  1471  								Operand: "z",
  1472  							},
  1473  						},
  1474  						Env: map[string]string{
  1475  							"hello": "world",
  1476  						},
  1477  						Services: []*structs.Service{
  1478  							{
  1479  								Name:        "serviceA",
  1480  								Tags:        []string{"1", "2"},
  1481  								PortLabel:   "foo",
  1482  								AddressMode: "auto",
  1483  								Checks: []*structs.ServiceCheck{
  1484  									{
  1485  										Name:          "bar",
  1486  										Type:          "http",
  1487  										Command:       "foo",
  1488  										Args:          []string{"a", "b"},
  1489  										Path:          "/check",
  1490  										Protocol:      "http",
  1491  										PortLabel:     "foo",
  1492  										AddressMode:   "driver",
  1493  										Interval:      4 * time.Second,
  1494  										Timeout:       2 * time.Second,
  1495  										InitialStatus: "ok",
  1496  										CheckRestart: &structs.CheckRestart{
  1497  											Limit:          3,
  1498  											Grace:          11 * time.Second,
  1499  											IgnoreWarnings: true,
  1500  										},
  1501  									},
  1502  									{
  1503  										Name:      "check2",
  1504  										Type:      "tcp",
  1505  										PortLabel: "foo",
  1506  										Interval:  4 * time.Second,
  1507  										Timeout:   2 * time.Second,
  1508  										CheckRestart: &structs.CheckRestart{
  1509  											Limit: 4,
  1510  											Grace: 11 * time.Second,
  1511  										},
  1512  									},
  1513  								},
  1514  							},
  1515  						},
  1516  						Resources: &structs.Resources{
  1517  							CPU:      100,
  1518  							MemoryMB: 10,
  1519  							Networks: []*structs.NetworkResource{
  1520  								{
  1521  									IP:    "10.10.11.1",
  1522  									MBits: 10,
  1523  									ReservedPorts: []structs.Port{
  1524  										{
  1525  											Label: "http",
  1526  											Value: 80,
  1527  										},
  1528  									},
  1529  									DynamicPorts: []structs.Port{
  1530  										{
  1531  											Label: "ssh",
  1532  											Value: 2000,
  1533  										},
  1534  									},
  1535  								},
  1536  							},
  1537  						},
  1538  						Meta: map[string]string{
  1539  							"lol": "code",
  1540  						},
  1541  						KillTimeout: 10 * time.Second,
  1542  						KillSignal:  "SIGQUIT",
  1543  						LogConfig: &structs.LogConfig{
  1544  							MaxFiles:      10,
  1545  							MaxFileSizeMB: 100,
  1546  						},
  1547  						Artifacts: []*structs.TaskArtifact{
  1548  							{
  1549  								GetterSource: "source",
  1550  								GetterOptions: map[string]string{
  1551  									"a": "b",
  1552  								},
  1553  								GetterMode:   "dir",
  1554  								RelativeDest: "dest",
  1555  							},
  1556  						},
  1557  						Vault: &structs.Vault{
  1558  							Policies:     []string{"a", "b", "c"},
  1559  							Env:          true,
  1560  							ChangeMode:   "c",
  1561  							ChangeSignal: "sighup",
  1562  						},
  1563  						Templates: []*structs.Template{
  1564  							{
  1565  								SourcePath:   "source",
  1566  								DestPath:     "dest",
  1567  								EmbeddedTmpl: "embedded",
  1568  								ChangeMode:   "change",
  1569  								ChangeSignal: "SIGNAL",
  1570  								Splay:        1 * time.Minute,
  1571  								Perms:        "666",
  1572  								LeftDelim:    "abc",
  1573  								RightDelim:   "def",
  1574  								Envvars:      true,
  1575  								VaultGrace:   3 * time.Second,
  1576  							},
  1577  						},
  1578  						DispatchPayload: &structs.DispatchPayloadConfig{
  1579  							File: "fileA",
  1580  						},
  1581  					},
  1582  				},
  1583  			},
  1584  		},
  1585  
  1586  		VaultToken: "token",
  1587  	}
  1588  
  1589  	structsJob := ApiJobToStructJob(apiJob)
  1590  
  1591  	if diff := pretty.Diff(expected, structsJob); len(diff) > 0 {
  1592  		t.Fatalf("bad:\n%s", strings.Join(diff, "\n"))
  1593  	}
  1594  
  1595  	systemAPIJob := &api.Job{
  1596  		Stop:        helper.BoolToPtr(true),
  1597  		Region:      helper.StringToPtr("global"),
  1598  		Namespace:   helper.StringToPtr("foo"),
  1599  		ID:          helper.StringToPtr("foo"),
  1600  		ParentID:    helper.StringToPtr("lol"),
  1601  		Name:        helper.StringToPtr("name"),
  1602  		Type:        helper.StringToPtr("system"),
  1603  		Priority:    helper.IntToPtr(50),
  1604  		AllAtOnce:   helper.BoolToPtr(true),
  1605  		Datacenters: []string{"dc1", "dc2"},
  1606  		Constraints: []*api.Constraint{
  1607  			{
  1608  				LTarget: "a",
  1609  				RTarget: "b",
  1610  				Operand: "c",
  1611  			},
  1612  		},
  1613  		TaskGroups: []*api.TaskGroup{
  1614  			{
  1615  				Name:  helper.StringToPtr("group1"),
  1616  				Count: helper.IntToPtr(5),
  1617  				Constraints: []*api.Constraint{
  1618  					{
  1619  						LTarget: "x",
  1620  						RTarget: "y",
  1621  						Operand: "z",
  1622  					},
  1623  				},
  1624  				RestartPolicy: &api.RestartPolicy{
  1625  					Interval: helper.TimeToPtr(1 * time.Second),
  1626  					Attempts: helper.IntToPtr(5),
  1627  					Delay:    helper.TimeToPtr(10 * time.Second),
  1628  					Mode:     helper.StringToPtr("delay"),
  1629  				},
  1630  				EphemeralDisk: &api.EphemeralDisk{
  1631  					SizeMB:  helper.IntToPtr(100),
  1632  					Sticky:  helper.BoolToPtr(true),
  1633  					Migrate: helper.BoolToPtr(true),
  1634  				},
  1635  				Meta: map[string]string{
  1636  					"key": "value",
  1637  				},
  1638  				Tasks: []*api.Task{
  1639  					{
  1640  						Name:   "task1",
  1641  						Leader: true,
  1642  						Driver: "docker",
  1643  						User:   "mary",
  1644  						Config: map[string]interface{}{
  1645  							"lol": "code",
  1646  						},
  1647  						Env: map[string]string{
  1648  							"hello": "world",
  1649  						},
  1650  						Constraints: []*api.Constraint{
  1651  							{
  1652  								LTarget: "x",
  1653  								RTarget: "y",
  1654  								Operand: "z",
  1655  							},
  1656  						},
  1657  						Resources: &api.Resources{
  1658  							CPU:      helper.IntToPtr(100),
  1659  							MemoryMB: helper.IntToPtr(10),
  1660  							Networks: []*api.NetworkResource{
  1661  								{
  1662  									IP:    "10.10.11.1",
  1663  									MBits: helper.IntToPtr(10),
  1664  									ReservedPorts: []api.Port{
  1665  										{
  1666  											Label: "http",
  1667  											Value: 80,
  1668  										},
  1669  									},
  1670  									DynamicPorts: []api.Port{
  1671  										{
  1672  											Label: "ssh",
  1673  											Value: 2000,
  1674  										},
  1675  									},
  1676  								},
  1677  							},
  1678  						},
  1679  						Meta: map[string]string{
  1680  							"lol": "code",
  1681  						},
  1682  						KillTimeout: helper.TimeToPtr(10 * time.Second),
  1683  						KillSignal:  "SIGQUIT",
  1684  						LogConfig: &api.LogConfig{
  1685  							MaxFiles:      helper.IntToPtr(10),
  1686  							MaxFileSizeMB: helper.IntToPtr(100),
  1687  						},
  1688  						Artifacts: []*api.TaskArtifact{
  1689  							{
  1690  								GetterSource: helper.StringToPtr("source"),
  1691  								GetterOptions: map[string]string{
  1692  									"a": "b",
  1693  								},
  1694  								GetterMode:   helper.StringToPtr("dir"),
  1695  								RelativeDest: helper.StringToPtr("dest"),
  1696  							},
  1697  						},
  1698  						DispatchPayload: &api.DispatchPayloadConfig{
  1699  							File: "fileA",
  1700  						},
  1701  					},
  1702  				},
  1703  			},
  1704  		},
  1705  		Status:            helper.StringToPtr("status"),
  1706  		StatusDescription: helper.StringToPtr("status_desc"),
  1707  		Version:           helper.Uint64ToPtr(10),
  1708  		CreateIndex:       helper.Uint64ToPtr(1),
  1709  		ModifyIndex:       helper.Uint64ToPtr(3),
  1710  		JobModifyIndex:    helper.Uint64ToPtr(5),
  1711  	}
  1712  
  1713  	expectedSystemJob := &structs.Job{
  1714  		Stop:        true,
  1715  		Region:      "global",
  1716  		Namespace:   "foo",
  1717  		ID:          "foo",
  1718  		ParentID:    "lol",
  1719  		Name:        "name",
  1720  		Type:        "system",
  1721  		Priority:    50,
  1722  		AllAtOnce:   true,
  1723  		Datacenters: []string{"dc1", "dc2"},
  1724  		Constraints: []*structs.Constraint{
  1725  			{
  1726  				LTarget: "a",
  1727  				RTarget: "b",
  1728  				Operand: "c",
  1729  			},
  1730  		},
  1731  		TaskGroups: []*structs.TaskGroup{
  1732  			{
  1733  				Name:  "group1",
  1734  				Count: 5,
  1735  				Constraints: []*structs.Constraint{
  1736  					{
  1737  						LTarget: "x",
  1738  						RTarget: "y",
  1739  						Operand: "z",
  1740  					},
  1741  				},
  1742  				RestartPolicy: &structs.RestartPolicy{
  1743  					Interval: 1 * time.Second,
  1744  					Attempts: 5,
  1745  					Delay:    10 * time.Second,
  1746  					Mode:     "delay",
  1747  				},
  1748  				EphemeralDisk: &structs.EphemeralDisk{
  1749  					SizeMB:  100,
  1750  					Sticky:  true,
  1751  					Migrate: true,
  1752  				},
  1753  				Meta: map[string]string{
  1754  					"key": "value",
  1755  				},
  1756  				Tasks: []*structs.Task{
  1757  					{
  1758  						Name:   "task1",
  1759  						Driver: "docker",
  1760  						Leader: true,
  1761  						User:   "mary",
  1762  						Config: map[string]interface{}{
  1763  							"lol": "code",
  1764  						},
  1765  						Constraints: []*structs.Constraint{
  1766  							{
  1767  								LTarget: "x",
  1768  								RTarget: "y",
  1769  								Operand: "z",
  1770  							},
  1771  						},
  1772  						Env: map[string]string{
  1773  							"hello": "world",
  1774  						},
  1775  						Resources: &structs.Resources{
  1776  							CPU:      100,
  1777  							MemoryMB: 10,
  1778  							Networks: []*structs.NetworkResource{
  1779  								{
  1780  									IP:    "10.10.11.1",
  1781  									MBits: 10,
  1782  									ReservedPorts: []structs.Port{
  1783  										{
  1784  											Label: "http",
  1785  											Value: 80,
  1786  										},
  1787  									},
  1788  									DynamicPorts: []structs.Port{
  1789  										{
  1790  											Label: "ssh",
  1791  											Value: 2000,
  1792  										},
  1793  									},
  1794  								},
  1795  							},
  1796  						},
  1797  						Meta: map[string]string{
  1798  							"lol": "code",
  1799  						},
  1800  						KillTimeout: 10 * time.Second,
  1801  						KillSignal:  "SIGQUIT",
  1802  						LogConfig: &structs.LogConfig{
  1803  							MaxFiles:      10,
  1804  							MaxFileSizeMB: 100,
  1805  						},
  1806  						Artifacts: []*structs.TaskArtifact{
  1807  							{
  1808  								GetterSource: "source",
  1809  								GetterOptions: map[string]string{
  1810  									"a": "b",
  1811  								},
  1812  								GetterMode:   "dir",
  1813  								RelativeDest: "dest",
  1814  							},
  1815  						},
  1816  						DispatchPayload: &structs.DispatchPayloadConfig{
  1817  							File: "fileA",
  1818  						},
  1819  					},
  1820  				},
  1821  			},
  1822  		},
  1823  	}
  1824  
  1825  	systemStructsJob := ApiJobToStructJob(systemAPIJob)
  1826  
  1827  	if diff := pretty.Diff(expectedSystemJob, systemStructsJob); len(diff) > 0 {
  1828  		t.Fatalf("bad:\n%s", strings.Join(diff, "\n"))
  1829  	}
  1830  }