github.com/hooklift/nomad@v0.5.7-0.20170407200202-db11e7dd7b55/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
   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 gone
   411  		getReq := structs.JobSpecificRequest{
   412  			JobID:        job.ID,
   413  			QueryOptions: structs.QueryOptions{Region: "global"},
   414  		}
   415  		var getResp structs.SingleJobResponse
   416  		if err := s.Agent.RPC("Job.GetJob", &getReq, &getResp); err != nil {
   417  			t.Fatalf("err: %v", err)
   418  		}
   419  		if getResp.Job != nil {
   420  			t.Fatalf("job still exists")
   421  		}
   422  	})
   423  }
   424  
   425  func TestHTTP_JobForceEvaluate(t *testing.T) {
   426  	httpTest(t, nil, func(s *TestServer) {
   427  		// Create the job
   428  		job := mock.Job()
   429  		args := structs.JobRegisterRequest{
   430  			Job:          job,
   431  			WriteRequest: structs.WriteRequest{Region: "global"},
   432  		}
   433  		var resp structs.JobRegisterResponse
   434  		if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil {
   435  			t.Fatalf("err: %v", err)
   436  		}
   437  
   438  		// Make the HTTP request
   439  		req, err := http.NewRequest("POST", "/v1/job/"+job.ID+"/evaluate", nil)
   440  		if err != nil {
   441  			t.Fatalf("err: %v", err)
   442  		}
   443  		respW := httptest.NewRecorder()
   444  
   445  		// Make the request
   446  		obj, err := s.Server.JobSpecificRequest(respW, req)
   447  		if err != nil {
   448  			t.Fatalf("err: %v", err)
   449  		}
   450  
   451  		// Check the response
   452  		reg := obj.(structs.JobRegisterResponse)
   453  		if reg.EvalID == "" {
   454  			t.Fatalf("bad: %v", reg)
   455  		}
   456  
   457  		// Check for the index
   458  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
   459  			t.Fatalf("missing index")
   460  		}
   461  	})
   462  }
   463  
   464  func TestHTTP_JobEvaluations(t *testing.T) {
   465  	httpTest(t, nil, func(s *TestServer) {
   466  		// Create the job
   467  		job := mock.Job()
   468  		args := structs.JobRegisterRequest{
   469  			Job:          job,
   470  			WriteRequest: structs.WriteRequest{Region: "global"},
   471  		}
   472  		var resp structs.JobRegisterResponse
   473  		if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil {
   474  			t.Fatalf("err: %v", err)
   475  		}
   476  
   477  		// Make the HTTP request
   478  		req, err := http.NewRequest("GET", "/v1/job/"+job.ID+"/evaluations", nil)
   479  		if err != nil {
   480  			t.Fatalf("err: %v", err)
   481  		}
   482  		respW := httptest.NewRecorder()
   483  
   484  		// Make the request
   485  		obj, err := s.Server.JobSpecificRequest(respW, req)
   486  		if err != nil {
   487  			t.Fatalf("err: %v", err)
   488  		}
   489  
   490  		// Check the response
   491  		evals := obj.([]*structs.Evaluation)
   492  		// Can be multiple evals, use the last one, since they are in order
   493  		idx := len(evals) - 1
   494  		if len(evals) < 0 || evals[idx].ID != resp.EvalID {
   495  			t.Fatalf("bad: %v", evals)
   496  		}
   497  
   498  		// Check for the index
   499  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
   500  			t.Fatalf("missing index")
   501  		}
   502  		if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" {
   503  			t.Fatalf("missing known leader")
   504  		}
   505  		if respW.HeaderMap.Get("X-Nomad-LastContact") == "" {
   506  			t.Fatalf("missing last contact")
   507  		}
   508  	})
   509  }
   510  
   511  func TestHTTP_JobAllocations(t *testing.T) {
   512  	httpTest(t, nil, func(s *TestServer) {
   513  		// Create the job
   514  		alloc1 := mock.Alloc()
   515  		args := structs.JobRegisterRequest{
   516  			Job:          alloc1.Job,
   517  			WriteRequest: structs.WriteRequest{Region: "global"},
   518  		}
   519  		var resp structs.JobRegisterResponse
   520  		if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil {
   521  			t.Fatalf("err: %v", err)
   522  		}
   523  
   524  		// Directly manipulate the state
   525  		state := s.Agent.server.State()
   526  		err := state.UpsertAllocs(1000, []*structs.Allocation{alloc1})
   527  		if err != nil {
   528  			t.Fatalf("err: %v", err)
   529  		}
   530  
   531  		// Make the HTTP request
   532  		req, err := http.NewRequest("GET", "/v1/job/"+alloc1.Job.ID+"/allocations?all=true", nil)
   533  		if err != nil {
   534  			t.Fatalf("err: %v", err)
   535  		}
   536  		respW := httptest.NewRecorder()
   537  
   538  		// Make the request
   539  		obj, err := s.Server.JobSpecificRequest(respW, req)
   540  		if err != nil {
   541  			t.Fatalf("err: %v", err)
   542  		}
   543  
   544  		// Check the response
   545  		allocs := obj.([]*structs.AllocListStub)
   546  		if len(allocs) != 1 && allocs[0].ID != alloc1.ID {
   547  			t.Fatalf("bad: %v", allocs)
   548  		}
   549  
   550  		// Check for the index
   551  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
   552  			t.Fatalf("missing index")
   553  		}
   554  		if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" {
   555  			t.Fatalf("missing known leader")
   556  		}
   557  		if respW.HeaderMap.Get("X-Nomad-LastContact") == "" {
   558  			t.Fatalf("missing last contact")
   559  		}
   560  	})
   561  }
   562  
   563  func TestHTTP_PeriodicForce(t *testing.T) {
   564  	httpTest(t, nil, func(s *TestServer) {
   565  		// Create and register a periodic job.
   566  		job := mock.PeriodicJob()
   567  		args := structs.JobRegisterRequest{
   568  			Job:          job,
   569  			WriteRequest: structs.WriteRequest{Region: "global"},
   570  		}
   571  		var resp structs.JobRegisterResponse
   572  		if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil {
   573  			t.Fatalf("err: %v", err)
   574  		}
   575  
   576  		// Make the HTTP request
   577  		req, err := http.NewRequest("POST", "/v1/job/"+job.ID+"/periodic/force", nil)
   578  		if err != nil {
   579  			t.Fatalf("err: %v", err)
   580  		}
   581  		respW := httptest.NewRecorder()
   582  
   583  		// Make the request
   584  		obj, err := s.Server.JobSpecificRequest(respW, req)
   585  		if err != nil {
   586  			t.Fatalf("err: %v", err)
   587  		}
   588  
   589  		// Check for the index
   590  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
   591  			t.Fatalf("missing index")
   592  		}
   593  
   594  		// Check the response
   595  		r := obj.(structs.PeriodicForceResponse)
   596  		if r.EvalID == "" {
   597  			t.Fatalf("bad: %#v", r)
   598  		}
   599  	})
   600  }
   601  
   602  func TestHTTP_JobPlan(t *testing.T) {
   603  	httpTest(t, nil, func(s *TestServer) {
   604  		// Create the job
   605  		job := mock.Job()
   606  		args := structs.JobPlanRequest{
   607  			Job:          job,
   608  			Diff:         true,
   609  			WriteRequest: structs.WriteRequest{Region: "global"},
   610  		}
   611  		buf := encodeReq(args)
   612  
   613  		// Make the HTTP request
   614  		req, err := http.NewRequest("PUT", "/v1/job/"+job.ID+"/plan", buf)
   615  		if err != nil {
   616  			t.Fatalf("err: %v", err)
   617  		}
   618  		respW := httptest.NewRecorder()
   619  
   620  		// Make the request
   621  		obj, err := s.Server.JobSpecificRequest(respW, req)
   622  		if err != nil {
   623  			t.Fatalf("err: %v", err)
   624  		}
   625  
   626  		// Check the response
   627  		plan := obj.(structs.JobPlanResponse)
   628  		if plan.Annotations == nil {
   629  			t.Fatalf("bad: %v", plan)
   630  		}
   631  
   632  		if plan.Diff == nil {
   633  			t.Fatalf("bad: %v", plan)
   634  		}
   635  	})
   636  }
   637  
   638  func TestHTTP_JobDispatch(t *testing.T) {
   639  	httpTest(t, nil, func(s *TestServer) {
   640  		// Create the parameterized job
   641  		job := mock.Job()
   642  		job.Type = "batch"
   643  		job.ParameterizedJob = &structs.ParameterizedJobConfig{}
   644  
   645  		args := structs.JobRegisterRequest{
   646  			Job:          job,
   647  			WriteRequest: structs.WriteRequest{Region: "global"},
   648  		}
   649  		var resp structs.JobRegisterResponse
   650  		if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil {
   651  			t.Fatalf("err: %v", err)
   652  		}
   653  
   654  		// Make the request
   655  		respW := httptest.NewRecorder()
   656  		args2 := structs.JobDispatchRequest{
   657  			WriteRequest: structs.WriteRequest{Region: "global"},
   658  		}
   659  		buf := encodeReq(args2)
   660  
   661  		// Make the HTTP request
   662  		req2, err := http.NewRequest("PUT", "/v1/job/"+job.ID+"/dispatch", buf)
   663  		if err != nil {
   664  			t.Fatalf("err: %v", err)
   665  		}
   666  		respW.Flush()
   667  
   668  		// Make the request
   669  		obj, err := s.Server.JobSpecificRequest(respW, req2)
   670  		if err != nil {
   671  			t.Fatalf("err: %v", err)
   672  		}
   673  
   674  		// Check the response
   675  		dispatch := obj.(structs.JobDispatchResponse)
   676  		if dispatch.EvalID == "" {
   677  			t.Fatalf("bad: %v", dispatch)
   678  		}
   679  
   680  		if dispatch.DispatchedJobID == "" {
   681  			t.Fatalf("bad: %v", dispatch)
   682  		}
   683  	})
   684  }
   685  
   686  func TestJobs_ApiJobToStructsJob(t *testing.T) {
   687  	apiJob := &api.Job{
   688  		Region:      helper.StringToPtr("global"),
   689  		ID:          helper.StringToPtr("foo"),
   690  		ParentID:    helper.StringToPtr("lol"),
   691  		Name:        helper.StringToPtr("name"),
   692  		Type:        helper.StringToPtr("service"),
   693  		Priority:    helper.IntToPtr(50),
   694  		AllAtOnce:   helper.BoolToPtr(true),
   695  		Datacenters: []string{"dc1", "dc2"},
   696  		Constraints: []*api.Constraint{
   697  			{
   698  				LTarget: "a",
   699  				RTarget: "b",
   700  				Operand: "c",
   701  			},
   702  		},
   703  		Update: &api.UpdateStrategy{
   704  			Stagger:     1 * time.Second,
   705  			MaxParallel: 5,
   706  		},
   707  		Periodic: &api.PeriodicConfig{
   708  			Enabled:         helper.BoolToPtr(true),
   709  			Spec:            helper.StringToPtr("spec"),
   710  			SpecType:        helper.StringToPtr("cron"),
   711  			ProhibitOverlap: helper.BoolToPtr(true),
   712  			TimeZone:        helper.StringToPtr("test zone"),
   713  		},
   714  		ParameterizedJob: &api.ParameterizedJobConfig{
   715  			Payload:      "payload",
   716  			MetaRequired: []string{"a", "b"},
   717  			MetaOptional: []string{"c", "d"},
   718  		},
   719  		Payload: []byte("payload"),
   720  		Meta: map[string]string{
   721  			"foo": "bar",
   722  		},
   723  		TaskGroups: []*api.TaskGroup{
   724  			{
   725  				Name:  helper.StringToPtr("group1"),
   726  				Count: helper.IntToPtr(5),
   727  				Constraints: []*api.Constraint{
   728  					{
   729  						LTarget: "x",
   730  						RTarget: "y",
   731  						Operand: "z",
   732  					},
   733  				},
   734  				RestartPolicy: &api.RestartPolicy{
   735  					Interval: helper.TimeToPtr(1 * time.Second),
   736  					Attempts: helper.IntToPtr(5),
   737  					Delay:    helper.TimeToPtr(10 * time.Second),
   738  					Mode:     helper.StringToPtr("delay"),
   739  				},
   740  				EphemeralDisk: &api.EphemeralDisk{
   741  					SizeMB:  helper.IntToPtr(100),
   742  					Sticky:  helper.BoolToPtr(true),
   743  					Migrate: helper.BoolToPtr(true),
   744  				},
   745  				Meta: map[string]string{
   746  					"key": "value",
   747  				},
   748  				Tasks: []*api.Task{
   749  					{
   750  						Name:   "task1",
   751  						Leader: true,
   752  						Driver: "docker",
   753  						User:   "mary",
   754  						Config: map[string]interface{}{
   755  							"lol": "code",
   756  						},
   757  						Env: map[string]string{
   758  							"hello": "world",
   759  						},
   760  						Constraints: []*api.Constraint{
   761  							{
   762  								LTarget: "x",
   763  								RTarget: "y",
   764  								Operand: "z",
   765  							},
   766  						},
   767  
   768  						Services: []*api.Service{
   769  							{
   770  								Id:        "id",
   771  								Name:      "serviceA",
   772  								Tags:      []string{"1", "2"},
   773  								PortLabel: "foo",
   774  								Checks: []api.ServiceCheck{
   775  									{
   776  										Id:            "hello",
   777  										Name:          "bar",
   778  										Type:          "http",
   779  										Command:       "foo",
   780  										Args:          []string{"a", "b"},
   781  										Path:          "/check",
   782  										Protocol:      "http",
   783  										PortLabel:     "foo",
   784  										Interval:      4 * time.Second,
   785  										Timeout:       2 * time.Second,
   786  										InitialStatus: "ok",
   787  									},
   788  								},
   789  							},
   790  						},
   791  						Resources: &api.Resources{
   792  							CPU:      helper.IntToPtr(100),
   793  							MemoryMB: helper.IntToPtr(10),
   794  							Networks: []*api.NetworkResource{
   795  								{
   796  									IP:    "10.10.11.1",
   797  									MBits: helper.IntToPtr(10),
   798  									ReservedPorts: []api.Port{
   799  										{
   800  											Label: "http",
   801  											Value: 80,
   802  										},
   803  									},
   804  									DynamicPorts: []api.Port{
   805  										{
   806  											Label: "ssh",
   807  											Value: 2000,
   808  										},
   809  									},
   810  								},
   811  							},
   812  						},
   813  						Meta: map[string]string{
   814  							"lol": "code",
   815  						},
   816  						KillTimeout: helper.TimeToPtr(10 * time.Second),
   817  						LogConfig: &api.LogConfig{
   818  							MaxFiles:      helper.IntToPtr(10),
   819  							MaxFileSizeMB: helper.IntToPtr(100),
   820  						},
   821  						Artifacts: []*api.TaskArtifact{
   822  							{
   823  								GetterSource: helper.StringToPtr("source"),
   824  								GetterOptions: map[string]string{
   825  									"a": "b",
   826  								},
   827  								RelativeDest: helper.StringToPtr("dest"),
   828  							},
   829  						},
   830  						Vault: &api.Vault{
   831  							Policies:     []string{"a", "b", "c"},
   832  							Env:          helper.BoolToPtr(true),
   833  							ChangeMode:   helper.StringToPtr("c"),
   834  							ChangeSignal: helper.StringToPtr("sighup"),
   835  						},
   836  						Templates: []*api.Template{
   837  							{
   838  								SourcePath:   helper.StringToPtr("source"),
   839  								DestPath:     helper.StringToPtr("dest"),
   840  								EmbeddedTmpl: helper.StringToPtr("embedded"),
   841  								ChangeMode:   helper.StringToPtr("change"),
   842  								ChangeSignal: helper.StringToPtr("signal"),
   843  								Splay:        helper.TimeToPtr(1 * time.Minute),
   844  								Perms:        helper.StringToPtr("666"),
   845  								LeftDelim:    helper.StringToPtr("abc"),
   846  								RightDelim:   helper.StringToPtr("def"),
   847  							},
   848  						},
   849  						DispatchPayload: &api.DispatchPayloadConfig{
   850  							File: "fileA",
   851  						},
   852  					},
   853  				},
   854  			},
   855  		},
   856  		VaultToken:        helper.StringToPtr("token"),
   857  		Status:            helper.StringToPtr("status"),
   858  		StatusDescription: helper.StringToPtr("status_desc"),
   859  		CreateIndex:       helper.Uint64ToPtr(1),
   860  		ModifyIndex:       helper.Uint64ToPtr(3),
   861  		JobModifyIndex:    helper.Uint64ToPtr(5),
   862  	}
   863  
   864  	expected := &structs.Job{
   865  		Region:      "global",
   866  		ID:          "foo",
   867  		ParentID:    "lol",
   868  		Name:        "name",
   869  		Type:        "service",
   870  		Priority:    50,
   871  		AllAtOnce:   true,
   872  		Datacenters: []string{"dc1", "dc2"},
   873  		Constraints: []*structs.Constraint{
   874  			{
   875  				LTarget: "a",
   876  				RTarget: "b",
   877  				Operand: "c",
   878  			},
   879  		},
   880  		Update: structs.UpdateStrategy{
   881  			Stagger:     1 * time.Second,
   882  			MaxParallel: 5,
   883  		},
   884  		Periodic: &structs.PeriodicConfig{
   885  			Enabled:         true,
   886  			Spec:            "spec",
   887  			SpecType:        "cron",
   888  			ProhibitOverlap: true,
   889  			TimeZone:        "test zone",
   890  		},
   891  		ParameterizedJob: &structs.ParameterizedJobConfig{
   892  			Payload:      "payload",
   893  			MetaRequired: []string{"a", "b"},
   894  			MetaOptional: []string{"c", "d"},
   895  		},
   896  		Payload: []byte("payload"),
   897  		Meta: map[string]string{
   898  			"foo": "bar",
   899  		},
   900  		TaskGroups: []*structs.TaskGroup{
   901  			{
   902  				Name:  "group1",
   903  				Count: 5,
   904  				Constraints: []*structs.Constraint{
   905  					{
   906  						LTarget: "x",
   907  						RTarget: "y",
   908  						Operand: "z",
   909  					},
   910  				},
   911  				RestartPolicy: &structs.RestartPolicy{
   912  					Interval: 1 * time.Second,
   913  					Attempts: 5,
   914  					Delay:    10 * time.Second,
   915  					Mode:     "delay",
   916  				},
   917  				EphemeralDisk: &structs.EphemeralDisk{
   918  					SizeMB:  100,
   919  					Sticky:  true,
   920  					Migrate: true,
   921  				},
   922  				Meta: map[string]string{
   923  					"key": "value",
   924  				},
   925  				Tasks: []*structs.Task{
   926  					{
   927  						Name:   "task1",
   928  						Driver: "docker",
   929  						Leader: true,
   930  						User:   "mary",
   931  						Config: map[string]interface{}{
   932  							"lol": "code",
   933  						},
   934  						Constraints: []*structs.Constraint{
   935  							{
   936  								LTarget: "x",
   937  								RTarget: "y",
   938  								Operand: "z",
   939  							},
   940  						},
   941  						Env: map[string]string{
   942  							"hello": "world",
   943  						},
   944  						Services: []*structs.Service{
   945  							&structs.Service{
   946  								Name:      "serviceA",
   947  								Tags:      []string{"1", "2"},
   948  								PortLabel: "foo",
   949  								Checks: []*structs.ServiceCheck{
   950  									&structs.ServiceCheck{
   951  										Name:          "bar",
   952  										Type:          "http",
   953  										Command:       "foo",
   954  										Args:          []string{"a", "b"},
   955  										Path:          "/check",
   956  										Protocol:      "http",
   957  										PortLabel:     "foo",
   958  										Interval:      4 * time.Second,
   959  										Timeout:       2 * time.Second,
   960  										InitialStatus: "ok",
   961  									},
   962  								},
   963  							},
   964  						},
   965  						Resources: &structs.Resources{
   966  							CPU:      100,
   967  							MemoryMB: 10,
   968  							Networks: []*structs.NetworkResource{
   969  								{
   970  									IP:    "10.10.11.1",
   971  									MBits: 10,
   972  									ReservedPorts: []structs.Port{
   973  										{
   974  											Label: "http",
   975  											Value: 80,
   976  										},
   977  									},
   978  									DynamicPorts: []structs.Port{
   979  										{
   980  											Label: "ssh",
   981  											Value: 2000,
   982  										},
   983  									},
   984  								},
   985  							},
   986  						},
   987  						Meta: map[string]string{
   988  							"lol": "code",
   989  						},
   990  						KillTimeout: 10 * time.Second,
   991  						LogConfig: &structs.LogConfig{
   992  							MaxFiles:      10,
   993  							MaxFileSizeMB: 100,
   994  						},
   995  						Artifacts: []*structs.TaskArtifact{
   996  							{
   997  								GetterSource: "source",
   998  								GetterOptions: map[string]string{
   999  									"a": "b",
  1000  								},
  1001  								RelativeDest: "dest",
  1002  							},
  1003  						},
  1004  						Vault: &structs.Vault{
  1005  							Policies:     []string{"a", "b", "c"},
  1006  							Env:          true,
  1007  							ChangeMode:   "c",
  1008  							ChangeSignal: "sighup",
  1009  						},
  1010  						Templates: []*structs.Template{
  1011  							{
  1012  								SourcePath:   "source",
  1013  								DestPath:     "dest",
  1014  								EmbeddedTmpl: "embedded",
  1015  								ChangeMode:   "change",
  1016  								ChangeSignal: "SIGNAL",
  1017  								Splay:        1 * time.Minute,
  1018  								Perms:        "666",
  1019  								LeftDelim:    "abc",
  1020  								RightDelim:   "def",
  1021  							},
  1022  						},
  1023  						DispatchPayload: &structs.DispatchPayloadConfig{
  1024  							File: "fileA",
  1025  						},
  1026  					},
  1027  				},
  1028  			},
  1029  		},
  1030  
  1031  		VaultToken:        "token",
  1032  		Status:            "status",
  1033  		StatusDescription: "status_desc",
  1034  		CreateIndex:       1,
  1035  		ModifyIndex:       3,
  1036  		JobModifyIndex:    5,
  1037  	}
  1038  
  1039  	structsJob := ApiJobToStructJob(apiJob)
  1040  
  1041  	if !reflect.DeepEqual(expected, structsJob) {
  1042  		t.Fatalf("bad %#v", structsJob)
  1043  	}
  1044  }