github.com/diptanu/nomad@v0.5.7-0.20170516172507-d72e86cbe3d9/command/agent/job_endpoint_test.go (about)

     1  package agent
     2  
     3  import (
     4  	"net/http"
     5  	"net/http/httptest"
     6  	"reflect"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/golang/snappy"
    11  	"github.com/hashicorp/nomad/api"
    12  	"github.com/hashicorp/nomad/helper"
    13  	"github.com/hashicorp/nomad/nomad/mock"
    14  	"github.com/hashicorp/nomad/nomad/structs"
    15  )
    16  
    17  func TestHTTP_JobsList(t *testing.T) {
    18  	httpTest(t, nil, func(s *TestServer) {
    19  		for i := 0; i < 3; i++ {
    20  			// Create the job
    21  			job := mock.Job()
    22  			args := structs.JobRegisterRequest{
    23  				Job:          job,
    24  				WriteRequest: structs.WriteRequest{Region: "global"},
    25  			}
    26  			var resp structs.JobRegisterResponse
    27  			if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil {
    28  				t.Fatalf("err: %v", err)
    29  			}
    30  		}
    31  
    32  		// Make the HTTP request
    33  		req, err := http.NewRequest("GET", "/v1/jobs", nil)
    34  		if err != nil {
    35  			t.Fatalf("err: %v", err)
    36  		}
    37  		respW := httptest.NewRecorder()
    38  
    39  		// Make the request
    40  		obj, err := s.Server.JobsRequest(respW, req)
    41  		if err != nil {
    42  			t.Fatalf("err: %v", err)
    43  		}
    44  
    45  		// Check for the index
    46  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
    47  			t.Fatalf("missing index")
    48  		}
    49  		if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" {
    50  			t.Fatalf("missing known leader")
    51  		}
    52  		if respW.HeaderMap.Get("X-Nomad-LastContact") == "" {
    53  			t.Fatalf("missing last contact")
    54  		}
    55  
    56  		// Check the job
    57  		j := obj.([]*structs.JobListStub)
    58  		if len(j) != 3 {
    59  			t.Fatalf("bad: %#v", j)
    60  		}
    61  	})
    62  }
    63  
    64  func TestHTTP_PrefixJobsList(t *testing.T) {
    65  	ids := []string{
    66  		"aaaaaaaa-e8f7-fd38-c855-ab94ceb89706",
    67  		"aabbbbbb-e8f7-fd38-c855-ab94ceb89706",
    68  		"aabbcccc-e8f7-fd38-c855-ab94ceb89706",
    69  	}
    70  	httpTest(t, nil, func(s *TestServer) {
    71  		for i := 0; i < 3; i++ {
    72  			// Create the job
    73  			job := mock.Job()
    74  			job.ID = ids[i]
    75  			job.TaskGroups[0].Count = 1
    76  			args := structs.JobRegisterRequest{
    77  				Job:          job,
    78  				WriteRequest: structs.WriteRequest{Region: "global"},
    79  			}
    80  			var resp structs.JobRegisterResponse
    81  			if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil {
    82  				t.Fatalf("err: %v", err)
    83  			}
    84  		}
    85  
    86  		// Make the HTTP request
    87  		req, err := http.NewRequest("GET", "/v1/jobs?prefix=aabb", nil)
    88  		if err != nil {
    89  			t.Fatalf("err: %v", err)
    90  		}
    91  		respW := httptest.NewRecorder()
    92  
    93  		// Make the request
    94  		obj, err := s.Server.JobsRequest(respW, req)
    95  		if err != nil {
    96  			t.Fatalf("err: %v", err)
    97  		}
    98  
    99  		// Check for the index
   100  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
   101  			t.Fatalf("missing index")
   102  		}
   103  		if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" {
   104  			t.Fatalf("missing known leader")
   105  		}
   106  		if respW.HeaderMap.Get("X-Nomad-LastContact") == "" {
   107  			t.Fatalf("missing last contact")
   108  		}
   109  
   110  		// Check the job
   111  		j := obj.([]*structs.JobListStub)
   112  		if len(j) != 2 {
   113  			t.Fatalf("bad: %#v", j)
   114  		}
   115  	})
   116  }
   117  
   118  func TestHTTP_JobsRegister(t *testing.T) {
   119  	httpTest(t, nil, func(s *TestServer) {
   120  		// Create the job
   121  		job := api.MockJob()
   122  		args := api.JobRegisterRequest{
   123  			Job:          job,
   124  			WriteRequest: api.WriteRequest{Region: "global"},
   125  		}
   126  		buf := encodeReq(args)
   127  
   128  		// Make the HTTP request
   129  		req, err := http.NewRequest("PUT", "/v1/jobs", buf)
   130  		if err != nil {
   131  			t.Fatalf("err: %v", err)
   132  		}
   133  		respW := httptest.NewRecorder()
   134  
   135  		// Make the request
   136  		obj, err := s.Server.JobsRequest(respW, req)
   137  		if err != nil {
   138  			t.Fatalf("err: %v", err)
   139  		}
   140  
   141  		// Check the response
   142  		dereg := obj.(structs.JobRegisterResponse)
   143  		if dereg.EvalID == "" {
   144  			t.Fatalf("bad: %v", dereg)
   145  		}
   146  
   147  		// Check for the index
   148  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
   149  			t.Fatalf("missing index")
   150  		}
   151  
   152  		// Check the job is registered
   153  		getReq := structs.JobSpecificRequest{
   154  			JobID:        *job.ID,
   155  			QueryOptions: structs.QueryOptions{Region: "global"},
   156  		}
   157  		var getResp structs.SingleJobResponse
   158  		if err := s.Agent.RPC("Job.GetJob", &getReq, &getResp); err != nil {
   159  			t.Fatalf("err: %v", err)
   160  		}
   161  
   162  		if getResp.Job == nil {
   163  			t.Fatalf("job does not exist")
   164  		}
   165  	})
   166  }
   167  
   168  func TestHTTP_JobsRegister_Defaulting(t *testing.T) {
   169  	httpTest(t, nil, func(s *TestServer) {
   170  		// Create the job
   171  		job := api.MockJob()
   172  
   173  		// Do not set its priority
   174  		job.Priority = nil
   175  
   176  		args := api.JobRegisterRequest{
   177  			Job:          job,
   178  			WriteRequest: api.WriteRequest{Region: "global"},
   179  		}
   180  		buf := encodeReq(args)
   181  
   182  		// Make the HTTP request
   183  		req, err := http.NewRequest("PUT", "/v1/jobs", buf)
   184  		if err != nil {
   185  			t.Fatalf("err: %v", err)
   186  		}
   187  		respW := httptest.NewRecorder()
   188  
   189  		// Make the request
   190  		obj, err := s.Server.JobsRequest(respW, req)
   191  		if err != nil {
   192  			t.Fatalf("err: %v", err)
   193  		}
   194  
   195  		// Check the response
   196  		dereg := obj.(structs.JobRegisterResponse)
   197  		if dereg.EvalID == "" {
   198  			t.Fatalf("bad: %v", dereg)
   199  		}
   200  
   201  		// Check for the index
   202  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
   203  			t.Fatalf("missing index")
   204  		}
   205  
   206  		// Check the job is registered
   207  		getReq := structs.JobSpecificRequest{
   208  			JobID:        *job.ID,
   209  			QueryOptions: structs.QueryOptions{Region: "global"},
   210  		}
   211  		var getResp structs.SingleJobResponse
   212  		if err := s.Agent.RPC("Job.GetJob", &getReq, &getResp); err != nil {
   213  			t.Fatalf("err: %v", err)
   214  		}
   215  
   216  		if getResp.Job == nil {
   217  			t.Fatalf("job does not exist")
   218  		}
   219  		if getResp.Job.Priority != 50 {
   220  			t.Fatalf("job didn't get defaulted")
   221  		}
   222  	})
   223  }
   224  
   225  func TestHTTP_JobQuery(t *testing.T) {
   226  	httpTest(t, nil, func(s *TestServer) {
   227  		// Create the job
   228  		job := mock.Job()
   229  		args := structs.JobRegisterRequest{
   230  			Job:          job,
   231  			WriteRequest: structs.WriteRequest{Region: "global"},
   232  		}
   233  		var resp structs.JobRegisterResponse
   234  		if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil {
   235  			t.Fatalf("err: %v", err)
   236  		}
   237  
   238  		// Make the HTTP request
   239  		req, err := http.NewRequest("GET", "/v1/job/"+job.ID, nil)
   240  		if err != nil {
   241  			t.Fatalf("err: %v", err)
   242  		}
   243  		respW := httptest.NewRecorder()
   244  
   245  		// Make the request
   246  		obj, err := s.Server.JobSpecificRequest(respW, req)
   247  		if err != nil {
   248  			t.Fatalf("err: %v", err)
   249  		}
   250  
   251  		// Check for the index
   252  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
   253  			t.Fatalf("missing index")
   254  		}
   255  		if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" {
   256  			t.Fatalf("missing known leader")
   257  		}
   258  		if respW.HeaderMap.Get("X-Nomad-LastContact") == "" {
   259  			t.Fatalf("missing last contact")
   260  		}
   261  
   262  		// Check the job
   263  		j := obj.(*structs.Job)
   264  		if j.ID != job.ID {
   265  			t.Fatalf("bad: %#v", j)
   266  		}
   267  	})
   268  }
   269  
   270  func TestHTTP_JobQuery_Payload(t *testing.T) {
   271  	httpTest(t, nil, func(s *TestServer) {
   272  		// Create the job
   273  		job := mock.Job()
   274  
   275  		// Insert Payload compressed
   276  		expected := []byte("hello world")
   277  		compressed := snappy.Encode(nil, expected)
   278  		job.Payload = compressed
   279  
   280  		// Directly manipulate the state
   281  		state := s.Agent.server.State()
   282  		if err := state.UpsertJob(1000, job); err != nil {
   283  			t.Fatalf("Failed to upsert job: %v", err)
   284  		}
   285  
   286  		// Make the HTTP request
   287  		req, err := http.NewRequest("GET", "/v1/job/"+job.ID, nil)
   288  		if err != nil {
   289  			t.Fatalf("err: %v", err)
   290  		}
   291  		respW := httptest.NewRecorder()
   292  
   293  		// Make the request
   294  		obj, err := s.Server.JobSpecificRequest(respW, req)
   295  		if err != nil {
   296  			t.Fatalf("err: %v", err)
   297  		}
   298  
   299  		// Check for the index
   300  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
   301  			t.Fatalf("missing index")
   302  		}
   303  		if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" {
   304  			t.Fatalf("missing known leader")
   305  		}
   306  		if respW.HeaderMap.Get("X-Nomad-LastContact") == "" {
   307  			t.Fatalf("missing last contact")
   308  		}
   309  
   310  		// Check the job
   311  		j := obj.(*structs.Job)
   312  		if j.ID != job.ID {
   313  			t.Fatalf("bad: %#v", j)
   314  		}
   315  
   316  		// Check the payload is decompressed
   317  		if !reflect.DeepEqual(j.Payload, expected) {
   318  			t.Fatalf("Payload not decompressed properly; got %#v; want %#v", j.Payload, expected)
   319  		}
   320  	})
   321  }
   322  
   323  func TestHTTP_JobUpdate(t *testing.T) {
   324  	httpTest(t, nil, func(s *TestServer) {
   325  		// Create the job
   326  		job := api.MockJob()
   327  		args := api.JobRegisterRequest{
   328  			Job:          job,
   329  			WriteRequest: api.WriteRequest{Region: "global"},
   330  		}
   331  		buf := encodeReq(args)
   332  
   333  		// Make the HTTP request
   334  		req, err := http.NewRequest("PUT", "/v1/job/"+*job.ID, buf)
   335  		if err != nil {
   336  			t.Fatalf("err: %v", err)
   337  		}
   338  		respW := httptest.NewRecorder()
   339  
   340  		// Make the request
   341  		obj, err := s.Server.JobSpecificRequest(respW, req)
   342  		if err != nil {
   343  			t.Fatalf("err: %v", err)
   344  		}
   345  
   346  		// Check the response
   347  		dereg := obj.(structs.JobRegisterResponse)
   348  		if dereg.EvalID == "" {
   349  			t.Fatalf("bad: %v", dereg)
   350  		}
   351  
   352  		// Check for the index
   353  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
   354  			t.Fatalf("missing index")
   355  		}
   356  
   357  		// Check the job is registered
   358  		getReq := structs.JobSpecificRequest{
   359  			JobID:        *job.ID,
   360  			QueryOptions: structs.QueryOptions{Region: "global"},
   361  		}
   362  		var getResp structs.SingleJobResponse
   363  		if err := s.Agent.RPC("Job.GetJob", &getReq, &getResp); err != nil {
   364  			t.Fatalf("err: %v", err)
   365  		}
   366  
   367  		if getResp.Job == nil {
   368  			t.Fatalf("job does not exist")
   369  		}
   370  	})
   371  }
   372  
   373  func TestHTTP_JobDelete(t *testing.T) {
   374  	httpTest(t, nil, func(s *TestServer) {
   375  		// Create the job
   376  		job := mock.Job()
   377  		args := structs.JobRegisterRequest{
   378  			Job:          job,
   379  			WriteRequest: structs.WriteRequest{Region: "global"},
   380  		}
   381  		var resp structs.JobRegisterResponse
   382  		if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil {
   383  			t.Fatalf("err: %v", err)
   384  		}
   385  
   386  		// Make the HTTP request to do a soft delete
   387  		req, err := http.NewRequest("DELETE", "/v1/job/"+job.ID, nil)
   388  		if err != nil {
   389  			t.Fatalf("err: %v", err)
   390  		}
   391  		respW := httptest.NewRecorder()
   392  
   393  		// Make the request
   394  		obj, err := s.Server.JobSpecificRequest(respW, req)
   395  		if err != nil {
   396  			t.Fatalf("err: %v", err)
   397  		}
   398  
   399  		// Check the response
   400  		dereg := obj.(structs.JobDeregisterResponse)
   401  		if dereg.EvalID == "" {
   402  			t.Fatalf("bad: %v", dereg)
   403  		}
   404  
   405  		// Check for the index
   406  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
   407  			t.Fatalf("missing index")
   408  		}
   409  
   410  		// Check the job is still queryable
   411  		getReq1 := structs.JobSpecificRequest{
   412  			JobID:        job.ID,
   413  			QueryOptions: structs.QueryOptions{Region: "global"},
   414  		}
   415  		var getResp1 structs.SingleJobResponse
   416  		if err := s.Agent.RPC("Job.GetJob", &getReq1, &getResp1); err != nil {
   417  			t.Fatalf("err: %v", err)
   418  		}
   419  		if getResp1.Job == nil {
   420  			t.Fatalf("job doesn't exists")
   421  		}
   422  		if !getResp1.Job.Stop {
   423  			t.Fatalf("job should be marked as stop")
   424  		}
   425  
   426  		// Make the HTTP request to do a purge delete
   427  		req2, err := http.NewRequest("DELETE", "/v1/job/"+job.ID+"?purge=true", nil)
   428  		if err != nil {
   429  			t.Fatalf("err: %v", err)
   430  		}
   431  		respW.Flush()
   432  
   433  		// Make the request
   434  		obj, err = s.Server.JobSpecificRequest(respW, req2)
   435  		if err != nil {
   436  			t.Fatalf("err: %v", err)
   437  		}
   438  
   439  		// Check the response
   440  		dereg = obj.(structs.JobDeregisterResponse)
   441  		if dereg.EvalID == "" {
   442  			t.Fatalf("bad: %v", dereg)
   443  		}
   444  
   445  		// Check for the index
   446  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
   447  			t.Fatalf("missing index")
   448  		}
   449  
   450  		// Check the job is gone
   451  		getReq2 := structs.JobSpecificRequest{
   452  			JobID:        job.ID,
   453  			QueryOptions: structs.QueryOptions{Region: "global"},
   454  		}
   455  		var getResp2 structs.SingleJobResponse
   456  		if err := s.Agent.RPC("Job.GetJob", &getReq2, &getResp2); err != nil {
   457  			t.Fatalf("err: %v", err)
   458  		}
   459  		if getResp2.Job != nil {
   460  			t.Fatalf("job still exists")
   461  		}
   462  	})
   463  }
   464  
   465  func TestHTTP_JobForceEvaluate(t *testing.T) {
   466  	httpTest(t, nil, func(s *TestServer) {
   467  		// Create the job
   468  		job := mock.Job()
   469  		args := structs.JobRegisterRequest{
   470  			Job:          job,
   471  			WriteRequest: structs.WriteRequest{Region: "global"},
   472  		}
   473  		var resp structs.JobRegisterResponse
   474  		if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil {
   475  			t.Fatalf("err: %v", err)
   476  		}
   477  
   478  		// Make the HTTP request
   479  		req, err := http.NewRequest("POST", "/v1/job/"+job.ID+"/evaluate", nil)
   480  		if err != nil {
   481  			t.Fatalf("err: %v", err)
   482  		}
   483  		respW := httptest.NewRecorder()
   484  
   485  		// Make the request
   486  		obj, err := s.Server.JobSpecificRequest(respW, req)
   487  		if err != nil {
   488  			t.Fatalf("err: %v", err)
   489  		}
   490  
   491  		// Check the response
   492  		reg := obj.(structs.JobRegisterResponse)
   493  		if reg.EvalID == "" {
   494  			t.Fatalf("bad: %v", reg)
   495  		}
   496  
   497  		// Check for the index
   498  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
   499  			t.Fatalf("missing index")
   500  		}
   501  	})
   502  }
   503  
   504  func TestHTTP_JobEvaluations(t *testing.T) {
   505  	httpTest(t, nil, func(s *TestServer) {
   506  		// Create the job
   507  		job := mock.Job()
   508  		args := structs.JobRegisterRequest{
   509  			Job:          job,
   510  			WriteRequest: structs.WriteRequest{Region: "global"},
   511  		}
   512  		var resp structs.JobRegisterResponse
   513  		if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil {
   514  			t.Fatalf("err: %v", err)
   515  		}
   516  
   517  		// Make the HTTP request
   518  		req, err := http.NewRequest("GET", "/v1/job/"+job.ID+"/evaluations", nil)
   519  		if err != nil {
   520  			t.Fatalf("err: %v", err)
   521  		}
   522  		respW := httptest.NewRecorder()
   523  
   524  		// Make the request
   525  		obj, err := s.Server.JobSpecificRequest(respW, req)
   526  		if err != nil {
   527  			t.Fatalf("err: %v", err)
   528  		}
   529  
   530  		// Check the response
   531  		evals := obj.([]*structs.Evaluation)
   532  		// Can be multiple evals, use the last one, since they are in order
   533  		idx := len(evals) - 1
   534  		if len(evals) < 0 || evals[idx].ID != resp.EvalID {
   535  			t.Fatalf("bad: %v", evals)
   536  		}
   537  
   538  		// Check for the index
   539  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
   540  			t.Fatalf("missing index")
   541  		}
   542  		if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" {
   543  			t.Fatalf("missing known leader")
   544  		}
   545  		if respW.HeaderMap.Get("X-Nomad-LastContact") == "" {
   546  			t.Fatalf("missing last contact")
   547  		}
   548  	})
   549  }
   550  
   551  func TestHTTP_JobAllocations(t *testing.T) {
   552  	httpTest(t, nil, func(s *TestServer) {
   553  		// Create the job
   554  		alloc1 := mock.Alloc()
   555  		args := structs.JobRegisterRequest{
   556  			Job:          alloc1.Job,
   557  			WriteRequest: structs.WriteRequest{Region: "global"},
   558  		}
   559  		var resp structs.JobRegisterResponse
   560  		if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil {
   561  			t.Fatalf("err: %v", err)
   562  		}
   563  
   564  		// Directly manipulate the state
   565  		state := s.Agent.server.State()
   566  		err := state.UpsertAllocs(1000, []*structs.Allocation{alloc1})
   567  		if err != nil {
   568  			t.Fatalf("err: %v", err)
   569  		}
   570  
   571  		// Make the HTTP request
   572  		req, err := http.NewRequest("GET", "/v1/job/"+alloc1.Job.ID+"/allocations?all=true", nil)
   573  		if err != nil {
   574  			t.Fatalf("err: %v", err)
   575  		}
   576  		respW := httptest.NewRecorder()
   577  
   578  		// Make the request
   579  		obj, err := s.Server.JobSpecificRequest(respW, req)
   580  		if err != nil {
   581  			t.Fatalf("err: %v", err)
   582  		}
   583  
   584  		// Check the response
   585  		allocs := obj.([]*structs.AllocListStub)
   586  		if len(allocs) != 1 && allocs[0].ID != alloc1.ID {
   587  			t.Fatalf("bad: %v", allocs)
   588  		}
   589  
   590  		// Check for the index
   591  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
   592  			t.Fatalf("missing index")
   593  		}
   594  		if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" {
   595  			t.Fatalf("missing known leader")
   596  		}
   597  		if respW.HeaderMap.Get("X-Nomad-LastContact") == "" {
   598  			t.Fatalf("missing last contact")
   599  		}
   600  	})
   601  }
   602  
   603  func TestHTTP_JobVersions(t *testing.T) {
   604  	httpTest(t, nil, func(s *TestServer) {
   605  		// Create the job
   606  		job := mock.Job()
   607  		args := structs.JobRegisterRequest{
   608  			Job:          job,
   609  			WriteRequest: structs.WriteRequest{Region: "global"},
   610  		}
   611  		var resp structs.JobRegisterResponse
   612  		if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil {
   613  			t.Fatalf("err: %v", err)
   614  		}
   615  
   616  		job2 := mock.Job()
   617  		job2.ID = job.ID
   618  		job2.Priority = 100
   619  
   620  		args2 := structs.JobRegisterRequest{
   621  			Job:          job2,
   622  			WriteRequest: structs.WriteRequest{Region: "global"},
   623  		}
   624  		var resp2 structs.JobRegisterResponse
   625  		if err := s.Agent.RPC("Job.Register", &args2, &resp2); 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+"/versions", 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  		versions := obj.([]*structs.Job)
   644  		if len(versions) != 2 {
   645  			t.Fatalf("got %d versions; want 2", len(versions))
   646  		}
   647  
   648  		if v := versions[0]; v.Version != 1 || v.Priority != 100 {
   649  			t.Fatalf("bad %v", v)
   650  		}
   651  
   652  		if v := versions[1]; v.Version != 0 {
   653  			t.Fatalf("bad %v", v)
   654  		}
   655  
   656  		// Check for the index
   657  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
   658  			t.Fatalf("missing index")
   659  		}
   660  		if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" {
   661  			t.Fatalf("missing known leader")
   662  		}
   663  		if respW.HeaderMap.Get("X-Nomad-LastContact") == "" {
   664  			t.Fatalf("missing last contact")
   665  		}
   666  	})
   667  }
   668  
   669  func TestHTTP_PeriodicForce(t *testing.T) {
   670  	httpTest(t, nil, func(s *TestServer) {
   671  		// Create and register a periodic job.
   672  		job := mock.PeriodicJob()
   673  		args := structs.JobRegisterRequest{
   674  			Job:          job,
   675  			WriteRequest: structs.WriteRequest{Region: "global"},
   676  		}
   677  		var resp structs.JobRegisterResponse
   678  		if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil {
   679  			t.Fatalf("err: %v", err)
   680  		}
   681  
   682  		// Make the HTTP request
   683  		req, err := http.NewRequest("POST", "/v1/job/"+job.ID+"/periodic/force", nil)
   684  		if err != nil {
   685  			t.Fatalf("err: %v", err)
   686  		}
   687  		respW := httptest.NewRecorder()
   688  
   689  		// Make the request
   690  		obj, err := s.Server.JobSpecificRequest(respW, req)
   691  		if err != nil {
   692  			t.Fatalf("err: %v", err)
   693  		}
   694  
   695  		// Check for the index
   696  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
   697  			t.Fatalf("missing index")
   698  		}
   699  
   700  		// Check the response
   701  		r := obj.(structs.PeriodicForceResponse)
   702  		if r.EvalID == "" {
   703  			t.Fatalf("bad: %#v", r)
   704  		}
   705  	})
   706  }
   707  
   708  func TestHTTP_JobPlan(t *testing.T) {
   709  	httpTest(t, nil, func(s *TestServer) {
   710  		// Create the job
   711  		job := mock.Job()
   712  		args := structs.JobPlanRequest{
   713  			Job:          job,
   714  			Diff:         true,
   715  			WriteRequest: structs.WriteRequest{Region: "global"},
   716  		}
   717  		buf := encodeReq(args)
   718  
   719  		// Make the HTTP request
   720  		req, err := http.NewRequest("PUT", "/v1/job/"+job.ID+"/plan", buf)
   721  		if err != nil {
   722  			t.Fatalf("err: %v", err)
   723  		}
   724  		respW := httptest.NewRecorder()
   725  
   726  		// Make the request
   727  		obj, err := s.Server.JobSpecificRequest(respW, req)
   728  		if err != nil {
   729  			t.Fatalf("err: %v", err)
   730  		}
   731  
   732  		// Check the response
   733  		plan := obj.(structs.JobPlanResponse)
   734  		if plan.Annotations == nil {
   735  			t.Fatalf("bad: %v", plan)
   736  		}
   737  
   738  		if plan.Diff == nil {
   739  			t.Fatalf("bad: %v", plan)
   740  		}
   741  	})
   742  }
   743  
   744  func TestHTTP_JobDispatch(t *testing.T) {
   745  	httpTest(t, nil, func(s *TestServer) {
   746  		// Create the parameterized job
   747  		job := mock.Job()
   748  		job.Type = "batch"
   749  		job.ParameterizedJob = &structs.ParameterizedJobConfig{}
   750  
   751  		args := structs.JobRegisterRequest{
   752  			Job:          job,
   753  			WriteRequest: structs.WriteRequest{Region: "global"},
   754  		}
   755  		var resp structs.JobRegisterResponse
   756  		if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil {
   757  			t.Fatalf("err: %v", err)
   758  		}
   759  
   760  		// Make the request
   761  		respW := httptest.NewRecorder()
   762  		args2 := structs.JobDispatchRequest{
   763  			WriteRequest: structs.WriteRequest{Region: "global"},
   764  		}
   765  		buf := encodeReq(args2)
   766  
   767  		// Make the HTTP request
   768  		req2, err := http.NewRequest("PUT", "/v1/job/"+job.ID+"/dispatch", buf)
   769  		if err != nil {
   770  			t.Fatalf("err: %v", err)
   771  		}
   772  		respW.Flush()
   773  
   774  		// Make the request
   775  		obj, err := s.Server.JobSpecificRequest(respW, req2)
   776  		if err != nil {
   777  			t.Fatalf("err: %v", err)
   778  		}
   779  
   780  		// Check the response
   781  		dispatch := obj.(structs.JobDispatchResponse)
   782  		if dispatch.EvalID == "" {
   783  			t.Fatalf("bad: %v", dispatch)
   784  		}
   785  
   786  		if dispatch.DispatchedJobID == "" {
   787  			t.Fatalf("bad: %v", dispatch)
   788  		}
   789  	})
   790  }
   791  
   792  func TestHTTP_JobRevert(t *testing.T) {
   793  	httpTest(t, nil, func(s *TestServer) {
   794  		// Create the job and register it twice
   795  		job := mock.Job()
   796  		regReq := structs.JobRegisterRequest{
   797  			Job:          job,
   798  			WriteRequest: structs.WriteRequest{Region: "global"},
   799  		}
   800  		var regResp structs.JobRegisterResponse
   801  		if err := s.Agent.RPC("Job.Register", &regReq, &regResp); err != nil {
   802  			t.Fatalf("err: %v", err)
   803  		}
   804  
   805  		if err := s.Agent.RPC("Job.Register", &regReq, &regResp); err != nil {
   806  			t.Fatalf("err: %v", err)
   807  		}
   808  
   809  		args := structs.JobRevertRequest{
   810  			JobID:        job.ID,
   811  			JobVersion:   0,
   812  			WriteRequest: structs.WriteRequest{Region: "global"},
   813  		}
   814  		buf := encodeReq(args)
   815  
   816  		// Make the HTTP request
   817  		req, err := http.NewRequest("PUT", "/v1/job/"+job.ID+"/revert", buf)
   818  		if err != nil {
   819  			t.Fatalf("err: %v", err)
   820  		}
   821  		respW := httptest.NewRecorder()
   822  
   823  		// Make the request
   824  		obj, err := s.Server.JobSpecificRequest(respW, req)
   825  		if err != nil {
   826  			t.Fatalf("err: %v", err)
   827  		}
   828  
   829  		// Check the response
   830  		revertResp := obj.(structs.JobRegisterResponse)
   831  		if revertResp.EvalID == "" {
   832  			t.Fatalf("bad: %v", revertResp)
   833  		}
   834  
   835  		// Check for the index
   836  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
   837  			t.Fatalf("missing index")
   838  		}
   839  	})
   840  }
   841  
   842  func TestJobs_ApiJobToStructsJob(t *testing.T) {
   843  	apiJob := &api.Job{
   844  		Stop:        helper.BoolToPtr(true),
   845  		Region:      helper.StringToPtr("global"),
   846  		ID:          helper.StringToPtr("foo"),
   847  		ParentID:    helper.StringToPtr("lol"),
   848  		Name:        helper.StringToPtr("name"),
   849  		Type:        helper.StringToPtr("service"),
   850  		Priority:    helper.IntToPtr(50),
   851  		AllAtOnce:   helper.BoolToPtr(true),
   852  		Datacenters: []string{"dc1", "dc2"},
   853  		Constraints: []*api.Constraint{
   854  			{
   855  				LTarget: "a",
   856  				RTarget: "b",
   857  				Operand: "c",
   858  			},
   859  		},
   860  		Update: &api.UpdateStrategy{
   861  			Stagger:     1 * time.Second,
   862  			MaxParallel: 5,
   863  		},
   864  		Periodic: &api.PeriodicConfig{
   865  			Enabled:         helper.BoolToPtr(true),
   866  			Spec:            helper.StringToPtr("spec"),
   867  			SpecType:        helper.StringToPtr("cron"),
   868  			ProhibitOverlap: helper.BoolToPtr(true),
   869  			TimeZone:        helper.StringToPtr("test zone"),
   870  		},
   871  		ParameterizedJob: &api.ParameterizedJobConfig{
   872  			Payload:      "payload",
   873  			MetaRequired: []string{"a", "b"},
   874  			MetaOptional: []string{"c", "d"},
   875  		},
   876  		Payload: []byte("payload"),
   877  		Meta: map[string]string{
   878  			"foo": "bar",
   879  		},
   880  		TaskGroups: []*api.TaskGroup{
   881  			{
   882  				Name:  helper.StringToPtr("group1"),
   883  				Count: helper.IntToPtr(5),
   884  				Constraints: []*api.Constraint{
   885  					{
   886  						LTarget: "x",
   887  						RTarget: "y",
   888  						Operand: "z",
   889  					},
   890  				},
   891  				RestartPolicy: &api.RestartPolicy{
   892  					Interval: helper.TimeToPtr(1 * time.Second),
   893  					Attempts: helper.IntToPtr(5),
   894  					Delay:    helper.TimeToPtr(10 * time.Second),
   895  					Mode:     helper.StringToPtr("delay"),
   896  				},
   897  				EphemeralDisk: &api.EphemeralDisk{
   898  					SizeMB:  helper.IntToPtr(100),
   899  					Sticky:  helper.BoolToPtr(true),
   900  					Migrate: helper.BoolToPtr(true),
   901  				},
   902  				Meta: map[string]string{
   903  					"key": "value",
   904  				},
   905  				Tasks: []*api.Task{
   906  					{
   907  						Name:   "task1",
   908  						Leader: true,
   909  						Driver: "docker",
   910  						User:   "mary",
   911  						Config: map[string]interface{}{
   912  							"lol": "code",
   913  						},
   914  						Env: map[string]string{
   915  							"hello": "world",
   916  						},
   917  						Constraints: []*api.Constraint{
   918  							{
   919  								LTarget: "x",
   920  								RTarget: "y",
   921  								Operand: "z",
   922  							},
   923  						},
   924  
   925  						Services: []*api.Service{
   926  							{
   927  								Id:        "id",
   928  								Name:      "serviceA",
   929  								Tags:      []string{"1", "2"},
   930  								PortLabel: "foo",
   931  								Checks: []api.ServiceCheck{
   932  									{
   933  										Id:            "hello",
   934  										Name:          "bar",
   935  										Type:          "http",
   936  										Command:       "foo",
   937  										Args:          []string{"a", "b"},
   938  										Path:          "/check",
   939  										Protocol:      "http",
   940  										PortLabel:     "foo",
   941  										Interval:      4 * time.Second,
   942  										Timeout:       2 * time.Second,
   943  										InitialStatus: "ok",
   944  									},
   945  								},
   946  							},
   947  						},
   948  						Resources: &api.Resources{
   949  							CPU:      helper.IntToPtr(100),
   950  							MemoryMB: helper.IntToPtr(10),
   951  							Networks: []*api.NetworkResource{
   952  								{
   953  									IP:    "10.10.11.1",
   954  									MBits: helper.IntToPtr(10),
   955  									ReservedPorts: []api.Port{
   956  										{
   957  											Label: "http",
   958  											Value: 80,
   959  										},
   960  									},
   961  									DynamicPorts: []api.Port{
   962  										{
   963  											Label: "ssh",
   964  											Value: 2000,
   965  										},
   966  									},
   967  								},
   968  							},
   969  						},
   970  						Meta: map[string]string{
   971  							"lol": "code",
   972  						},
   973  						KillTimeout: helper.TimeToPtr(10 * time.Second),
   974  						LogConfig: &api.LogConfig{
   975  							MaxFiles:      helper.IntToPtr(10),
   976  							MaxFileSizeMB: helper.IntToPtr(100),
   977  						},
   978  						Artifacts: []*api.TaskArtifact{
   979  							{
   980  								GetterSource: helper.StringToPtr("source"),
   981  								GetterOptions: map[string]string{
   982  									"a": "b",
   983  								},
   984  								RelativeDest: helper.StringToPtr("dest"),
   985  							},
   986  						},
   987  						Vault: &api.Vault{
   988  							Policies:     []string{"a", "b", "c"},
   989  							Env:          helper.BoolToPtr(true),
   990  							ChangeMode:   helper.StringToPtr("c"),
   991  							ChangeSignal: helper.StringToPtr("sighup"),
   992  						},
   993  						Templates: []*api.Template{
   994  							{
   995  								SourcePath:   helper.StringToPtr("source"),
   996  								DestPath:     helper.StringToPtr("dest"),
   997  								EmbeddedTmpl: helper.StringToPtr("embedded"),
   998  								ChangeMode:   helper.StringToPtr("change"),
   999  								ChangeSignal: helper.StringToPtr("signal"),
  1000  								Splay:        helper.TimeToPtr(1 * time.Minute),
  1001  								Perms:        helper.StringToPtr("666"),
  1002  								LeftDelim:    helper.StringToPtr("abc"),
  1003  								RightDelim:   helper.StringToPtr("def"),
  1004  							},
  1005  						},
  1006  						DispatchPayload: &api.DispatchPayloadConfig{
  1007  							File: "fileA",
  1008  						},
  1009  					},
  1010  				},
  1011  			},
  1012  		},
  1013  		VaultToken:        helper.StringToPtr("token"),
  1014  		Status:            helper.StringToPtr("status"),
  1015  		StatusDescription: helper.StringToPtr("status_desc"),
  1016  		Version:           helper.Uint64ToPtr(10),
  1017  		CreateIndex:       helper.Uint64ToPtr(1),
  1018  		ModifyIndex:       helper.Uint64ToPtr(3),
  1019  		JobModifyIndex:    helper.Uint64ToPtr(5),
  1020  	}
  1021  
  1022  	expected := &structs.Job{
  1023  		Stop:        true,
  1024  		Region:      "global",
  1025  		ID:          "foo",
  1026  		ParentID:    "lol",
  1027  		Name:        "name",
  1028  		Type:        "service",
  1029  		Priority:    50,
  1030  		AllAtOnce:   true,
  1031  		Datacenters: []string{"dc1", "dc2"},
  1032  		Constraints: []*structs.Constraint{
  1033  			{
  1034  				LTarget: "a",
  1035  				RTarget: "b",
  1036  				Operand: "c",
  1037  			},
  1038  		},
  1039  		Update: structs.UpdateStrategy{
  1040  			Stagger:     1 * time.Second,
  1041  			MaxParallel: 5,
  1042  		},
  1043  		Periodic: &structs.PeriodicConfig{
  1044  			Enabled:         true,
  1045  			Spec:            "spec",
  1046  			SpecType:        "cron",
  1047  			ProhibitOverlap: true,
  1048  			TimeZone:        "test zone",
  1049  		},
  1050  		ParameterizedJob: &structs.ParameterizedJobConfig{
  1051  			Payload:      "payload",
  1052  			MetaRequired: []string{"a", "b"},
  1053  			MetaOptional: []string{"c", "d"},
  1054  		},
  1055  		Payload: []byte("payload"),
  1056  		Meta: map[string]string{
  1057  			"foo": "bar",
  1058  		},
  1059  		TaskGroups: []*structs.TaskGroup{
  1060  			{
  1061  				Name:  "group1",
  1062  				Count: 5,
  1063  				Constraints: []*structs.Constraint{
  1064  					{
  1065  						LTarget: "x",
  1066  						RTarget: "y",
  1067  						Operand: "z",
  1068  					},
  1069  				},
  1070  				RestartPolicy: &structs.RestartPolicy{
  1071  					Interval: 1 * time.Second,
  1072  					Attempts: 5,
  1073  					Delay:    10 * time.Second,
  1074  					Mode:     "delay",
  1075  				},
  1076  				EphemeralDisk: &structs.EphemeralDisk{
  1077  					SizeMB:  100,
  1078  					Sticky:  true,
  1079  					Migrate: true,
  1080  				},
  1081  				Meta: map[string]string{
  1082  					"key": "value",
  1083  				},
  1084  				Tasks: []*structs.Task{
  1085  					{
  1086  						Name:   "task1",
  1087  						Driver: "docker",
  1088  						Leader: true,
  1089  						User:   "mary",
  1090  						Config: map[string]interface{}{
  1091  							"lol": "code",
  1092  						},
  1093  						Constraints: []*structs.Constraint{
  1094  							{
  1095  								LTarget: "x",
  1096  								RTarget: "y",
  1097  								Operand: "z",
  1098  							},
  1099  						},
  1100  						Env: map[string]string{
  1101  							"hello": "world",
  1102  						},
  1103  						Services: []*structs.Service{
  1104  							&structs.Service{
  1105  								Name:      "serviceA",
  1106  								Tags:      []string{"1", "2"},
  1107  								PortLabel: "foo",
  1108  								Checks: []*structs.ServiceCheck{
  1109  									&structs.ServiceCheck{
  1110  										Name:          "bar",
  1111  										Type:          "http",
  1112  										Command:       "foo",
  1113  										Args:          []string{"a", "b"},
  1114  										Path:          "/check",
  1115  										Protocol:      "http",
  1116  										PortLabel:     "foo",
  1117  										Interval:      4 * time.Second,
  1118  										Timeout:       2 * time.Second,
  1119  										InitialStatus: "ok",
  1120  									},
  1121  								},
  1122  							},
  1123  						},
  1124  						Resources: &structs.Resources{
  1125  							CPU:      100,
  1126  							MemoryMB: 10,
  1127  							Networks: []*structs.NetworkResource{
  1128  								{
  1129  									IP:    "10.10.11.1",
  1130  									MBits: 10,
  1131  									ReservedPorts: []structs.Port{
  1132  										{
  1133  											Label: "http",
  1134  											Value: 80,
  1135  										},
  1136  									},
  1137  									DynamicPorts: []structs.Port{
  1138  										{
  1139  											Label: "ssh",
  1140  											Value: 2000,
  1141  										},
  1142  									},
  1143  								},
  1144  							},
  1145  						},
  1146  						Meta: map[string]string{
  1147  							"lol": "code",
  1148  						},
  1149  						KillTimeout: 10 * time.Second,
  1150  						LogConfig: &structs.LogConfig{
  1151  							MaxFiles:      10,
  1152  							MaxFileSizeMB: 100,
  1153  						},
  1154  						Artifacts: []*structs.TaskArtifact{
  1155  							{
  1156  								GetterSource: "source",
  1157  								GetterOptions: map[string]string{
  1158  									"a": "b",
  1159  								},
  1160  								RelativeDest: "dest",
  1161  							},
  1162  						},
  1163  						Vault: &structs.Vault{
  1164  							Policies:     []string{"a", "b", "c"},
  1165  							Env:          true,
  1166  							ChangeMode:   "c",
  1167  							ChangeSignal: "sighup",
  1168  						},
  1169  						Templates: []*structs.Template{
  1170  							{
  1171  								SourcePath:   "source",
  1172  								DestPath:     "dest",
  1173  								EmbeddedTmpl: "embedded",
  1174  								ChangeMode:   "change",
  1175  								ChangeSignal: "SIGNAL",
  1176  								Splay:        1 * time.Minute,
  1177  								Perms:        "666",
  1178  								LeftDelim:    "abc",
  1179  								RightDelim:   "def",
  1180  							},
  1181  						},
  1182  						DispatchPayload: &structs.DispatchPayloadConfig{
  1183  							File: "fileA",
  1184  						},
  1185  					},
  1186  				},
  1187  			},
  1188  		},
  1189  
  1190  		VaultToken: "token",
  1191  	}
  1192  
  1193  	structsJob := ApiJobToStructJob(apiJob)
  1194  
  1195  	if !reflect.DeepEqual(expected, structsJob) {
  1196  		t.Fatalf("bad %#v", structsJob)
  1197  	}
  1198  }