github.com/adityamillind98/nomad@v0.11.8/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  	"github.com/stretchr/testify/require"
    19  )
    20  
    21  func TestHTTP_JobsList(t *testing.T) {
    22  	t.Parallel()
    23  	httpTest(t, nil, func(s *TestAgent) {
    24  		for i := 0; i < 3; i++ {
    25  			// Create the job
    26  			job := mock.Job()
    27  			args := structs.JobRegisterRequest{
    28  				Job: job,
    29  				WriteRequest: structs.WriteRequest{
    30  					Region:    "global",
    31  					Namespace: structs.DefaultNamespace,
    32  				},
    33  			}
    34  			var resp structs.JobRegisterResponse
    35  			if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil {
    36  				t.Fatalf("err: %v", err)
    37  			}
    38  		}
    39  
    40  		// Make the HTTP request
    41  		req, err := http.NewRequest("GET", "/v1/jobs", nil)
    42  		if err != nil {
    43  			t.Fatalf("err: %v", err)
    44  		}
    45  		respW := httptest.NewRecorder()
    46  
    47  		// Make the request
    48  		obj, err := s.Server.JobsRequest(respW, req)
    49  		if err != nil {
    50  			t.Fatalf("err: %v", err)
    51  		}
    52  
    53  		// Check for the index
    54  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
    55  			t.Fatalf("missing index")
    56  		}
    57  		if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" {
    58  			t.Fatalf("missing known leader")
    59  		}
    60  		if respW.HeaderMap.Get("X-Nomad-LastContact") == "" {
    61  			t.Fatalf("missing last contact")
    62  		}
    63  
    64  		// Check the job
    65  		j := obj.([]*structs.JobListStub)
    66  		if len(j) != 3 {
    67  			t.Fatalf("bad: %#v", j)
    68  		}
    69  	})
    70  }
    71  
    72  func TestHTTP_PrefixJobsList(t *testing.T) {
    73  	ids := []string{
    74  		"aaaaaaaa-e8f7-fd38-c855-ab94ceb89706",
    75  		"aabbbbbb-e8f7-fd38-c855-ab94ceb89706",
    76  		"aabbcccc-e8f7-fd38-c855-ab94ceb89706",
    77  	}
    78  	t.Parallel()
    79  	httpTest(t, nil, func(s *TestAgent) {
    80  		for i := 0; i < 3; i++ {
    81  			// Create the job
    82  			job := mock.Job()
    83  			job.ID = ids[i]
    84  			job.TaskGroups[0].Count = 1
    85  			args := structs.JobRegisterRequest{
    86  				Job: job,
    87  				WriteRequest: structs.WriteRequest{
    88  					Region:    "global",
    89  					Namespace: structs.DefaultNamespace,
    90  				},
    91  			}
    92  			var resp structs.JobRegisterResponse
    93  			if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil {
    94  				t.Fatalf("err: %v", err)
    95  			}
    96  		}
    97  
    98  		// Make the HTTP request
    99  		req, err := http.NewRequest("GET", "/v1/jobs?prefix=aabb", nil)
   100  		if err != nil {
   101  			t.Fatalf("err: %v", err)
   102  		}
   103  		respW := httptest.NewRecorder()
   104  
   105  		// Make the request
   106  		obj, err := s.Server.JobsRequest(respW, req)
   107  		if err != nil {
   108  			t.Fatalf("err: %v", err)
   109  		}
   110  
   111  		// Check for the index
   112  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
   113  			t.Fatalf("missing index")
   114  		}
   115  		if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" {
   116  			t.Fatalf("missing known leader")
   117  		}
   118  		if respW.HeaderMap.Get("X-Nomad-LastContact") == "" {
   119  			t.Fatalf("missing last contact")
   120  		}
   121  
   122  		// Check the job
   123  		j := obj.([]*structs.JobListStub)
   124  		if len(j) != 2 {
   125  			t.Fatalf("bad: %#v", j)
   126  		}
   127  	})
   128  }
   129  
   130  func TestHTTP_JobsRegister(t *testing.T) {
   131  	t.Parallel()
   132  	httpTest(t, nil, func(s *TestAgent) {
   133  		// Create the job
   134  		job := MockJob()
   135  		args := api.JobRegisterRequest{
   136  			Job:          job,
   137  			WriteRequest: api.WriteRequest{Region: "global"},
   138  		}
   139  		buf := encodeReq(args)
   140  
   141  		// Make the HTTP request
   142  		req, err := http.NewRequest("PUT", "/v1/jobs", buf)
   143  		if err != nil {
   144  			t.Fatalf("err: %v", err)
   145  		}
   146  		respW := httptest.NewRecorder()
   147  
   148  		// Make the request
   149  		obj, err := s.Server.JobsRequest(respW, req)
   150  		if err != nil {
   151  			t.Fatalf("err: %v", err)
   152  		}
   153  
   154  		// Check the response
   155  		dereg := obj.(structs.JobRegisterResponse)
   156  		if dereg.EvalID == "" {
   157  			t.Fatalf("bad: %v", dereg)
   158  		}
   159  
   160  		// Check for the index
   161  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
   162  			t.Fatalf("missing index")
   163  		}
   164  
   165  		// Check the job is registered
   166  		getReq := structs.JobSpecificRequest{
   167  			JobID: *job.ID,
   168  			QueryOptions: structs.QueryOptions{
   169  				Region:    "global",
   170  				Namespace: structs.DefaultNamespace,
   171  			},
   172  		}
   173  		var getResp structs.SingleJobResponse
   174  		if err := s.Agent.RPC("Job.GetJob", &getReq, &getResp); err != nil {
   175  			t.Fatalf("err: %v", err)
   176  		}
   177  
   178  		if getResp.Job == nil {
   179  			t.Fatalf("job does not exist")
   180  		}
   181  	})
   182  }
   183  
   184  // Test that ACL token is properly threaded through to the RPC endpoint
   185  func TestHTTP_JobsRegister_ACL(t *testing.T) {
   186  	t.Parallel()
   187  	httpACLTest(t, nil, func(s *TestAgent) {
   188  		// Create the job
   189  		job := MockJob()
   190  		args := api.JobRegisterRequest{
   191  			Job: job,
   192  			WriteRequest: api.WriteRequest{
   193  				Region: "global",
   194  			},
   195  		}
   196  		buf := encodeReq(args)
   197  
   198  		// Make the HTTP request
   199  		req, err := http.NewRequest("PUT", "/v1/jobs", buf)
   200  		if err != nil {
   201  			t.Fatalf("err: %v", err)
   202  		}
   203  		respW := httptest.NewRecorder()
   204  		setToken(req, s.RootToken)
   205  
   206  		// Make the request
   207  		obj, err := s.Server.JobsRequest(respW, req)
   208  		if err != nil {
   209  			t.Fatalf("err: %v", err)
   210  		}
   211  		assert.NotNil(t, obj)
   212  	})
   213  }
   214  
   215  func TestHTTP_JobsRegister_Defaulting(t *testing.T) {
   216  	t.Parallel()
   217  	httpTest(t, nil, func(s *TestAgent) {
   218  		// Create the job
   219  		job := MockJob()
   220  
   221  		// Do not set its priority
   222  		job.Priority = nil
   223  
   224  		args := api.JobRegisterRequest{
   225  			Job:          job,
   226  			WriteRequest: api.WriteRequest{Region: "global"},
   227  		}
   228  		buf := encodeReq(args)
   229  
   230  		// Make the HTTP request
   231  		req, err := http.NewRequest("PUT", "/v1/jobs", buf)
   232  		if err != nil {
   233  			t.Fatalf("err: %v", err)
   234  		}
   235  		respW := httptest.NewRecorder()
   236  
   237  		// Make the request
   238  		obj, err := s.Server.JobsRequest(respW, req)
   239  		if err != nil {
   240  			t.Fatalf("err: %v", err)
   241  		}
   242  
   243  		// Check the response
   244  		dereg := obj.(structs.JobRegisterResponse)
   245  		if dereg.EvalID == "" {
   246  			t.Fatalf("bad: %v", dereg)
   247  		}
   248  
   249  		// Check for the index
   250  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
   251  			t.Fatalf("missing index")
   252  		}
   253  
   254  		// Check the job is registered
   255  		getReq := structs.JobSpecificRequest{
   256  			JobID: *job.ID,
   257  			QueryOptions: structs.QueryOptions{
   258  				Region:    "global",
   259  				Namespace: structs.DefaultNamespace,
   260  			},
   261  		}
   262  		var getResp structs.SingleJobResponse
   263  		if err := s.Agent.RPC("Job.GetJob", &getReq, &getResp); err != nil {
   264  			t.Fatalf("err: %v", err)
   265  		}
   266  
   267  		if getResp.Job == nil {
   268  			t.Fatalf("job does not exist")
   269  		}
   270  		if getResp.Job.Priority != 50 {
   271  			t.Fatalf("job didn't get defaulted")
   272  		}
   273  	})
   274  }
   275  
   276  func TestHTTP_JobsParse(t *testing.T) {
   277  	t.Parallel()
   278  	httpTest(t, nil, func(s *TestAgent) {
   279  		buf := encodeReq(api.JobsParseRequest{JobHCL: mock.HCL()})
   280  		req, err := http.NewRequest("POST", "/v1/jobs/parse", buf)
   281  		if err != nil {
   282  			t.Fatalf("err: %v", err)
   283  		}
   284  
   285  		respW := httptest.NewRecorder()
   286  
   287  		obj, err := s.Server.JobsParseRequest(respW, req)
   288  		if err != nil {
   289  			t.Fatalf("err: %v", err)
   290  		}
   291  		if obj == nil {
   292  			t.Fatal("response should not be nil")
   293  		}
   294  
   295  		job := obj.(*api.Job)
   296  		expected := mock.Job()
   297  		if job.Name == nil || *job.Name != expected.Name {
   298  			t.Fatalf("job name is '%s', expected '%s'", *job.Name, expected.Name)
   299  		}
   300  
   301  		if job.Datacenters == nil ||
   302  			job.Datacenters[0] != expected.Datacenters[0] {
   303  			t.Fatalf("job datacenters is '%s', expected '%s'",
   304  				job.Datacenters[0], expected.Datacenters[0])
   305  		}
   306  	})
   307  }
   308  func TestHTTP_JobQuery(t *testing.T) {
   309  	t.Parallel()
   310  	httpTest(t, nil, func(s *TestAgent) {
   311  		// Create the job
   312  		job := mock.Job()
   313  		args := structs.JobRegisterRequest{
   314  			Job: job,
   315  			WriteRequest: structs.WriteRequest{
   316  				Region:    "global",
   317  				Namespace: structs.DefaultNamespace,
   318  			},
   319  		}
   320  		var resp structs.JobRegisterResponse
   321  		if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil {
   322  			t.Fatalf("err: %v", err)
   323  		}
   324  
   325  		// Make the HTTP request
   326  		req, err := http.NewRequest("GET", "/v1/job/"+job.ID, nil)
   327  		if err != nil {
   328  			t.Fatalf("err: %v", err)
   329  		}
   330  		respW := httptest.NewRecorder()
   331  
   332  		// Make the request
   333  		obj, err := s.Server.JobSpecificRequest(respW, req)
   334  		if err != nil {
   335  			t.Fatalf("err: %v", err)
   336  		}
   337  
   338  		// Check for the index
   339  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
   340  			t.Fatalf("missing index")
   341  		}
   342  		if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" {
   343  			t.Fatalf("missing known leader")
   344  		}
   345  		if respW.HeaderMap.Get("X-Nomad-LastContact") == "" {
   346  			t.Fatalf("missing last contact")
   347  		}
   348  
   349  		// Check the job
   350  		j := obj.(*structs.Job)
   351  		if j.ID != job.ID {
   352  			t.Fatalf("bad: %#v", j)
   353  		}
   354  	})
   355  }
   356  
   357  func TestHTTP_JobQuery_Payload(t *testing.T) {
   358  	t.Parallel()
   359  	httpTest(t, nil, func(s *TestAgent) {
   360  		// Create the job
   361  		job := mock.Job()
   362  
   363  		// Insert Payload compressed
   364  		expected := []byte("hello world")
   365  		compressed := snappy.Encode(nil, expected)
   366  		job.Payload = compressed
   367  
   368  		// Directly manipulate the state
   369  		state := s.Agent.server.State()
   370  		if err := state.UpsertJob(1000, job); err != nil {
   371  			t.Fatalf("Failed to upsert job: %v", err)
   372  		}
   373  
   374  		// Make the HTTP request
   375  		req, err := http.NewRequest("GET", "/v1/job/"+job.ID, nil)
   376  		if err != nil {
   377  			t.Fatalf("err: %v", err)
   378  		}
   379  		respW := httptest.NewRecorder()
   380  
   381  		// Make the request
   382  		obj, err := s.Server.JobSpecificRequest(respW, req)
   383  		if err != nil {
   384  			t.Fatalf("err: %v", err)
   385  		}
   386  
   387  		// Check for the index
   388  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
   389  			t.Fatalf("missing index")
   390  		}
   391  		if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" {
   392  			t.Fatalf("missing known leader")
   393  		}
   394  		if respW.HeaderMap.Get("X-Nomad-LastContact") == "" {
   395  			t.Fatalf("missing last contact")
   396  		}
   397  
   398  		// Check the job
   399  		j := obj.(*structs.Job)
   400  		if j.ID != job.ID {
   401  			t.Fatalf("bad: %#v", j)
   402  		}
   403  
   404  		// Check the payload is decompressed
   405  		if !reflect.DeepEqual(j.Payload, expected) {
   406  			t.Fatalf("Payload not decompressed properly; got %#v; want %#v", j.Payload, expected)
   407  		}
   408  	})
   409  }
   410  
   411  func TestHTTP_JobUpdate(t *testing.T) {
   412  	t.Parallel()
   413  	httpTest(t, nil, func(s *TestAgent) {
   414  		// Create the job
   415  		job := MockJob()
   416  		args := api.JobRegisterRequest{
   417  			Job: job,
   418  			WriteRequest: api.WriteRequest{
   419  				Region:    "global",
   420  				Namespace: api.DefaultNamespace,
   421  			},
   422  		}
   423  		buf := encodeReq(args)
   424  
   425  		// Make the HTTP request
   426  		req, err := http.NewRequest("PUT", "/v1/job/"+*job.ID, buf)
   427  		if err != nil {
   428  			t.Fatalf("err: %v", err)
   429  		}
   430  		respW := httptest.NewRecorder()
   431  
   432  		// Make the request
   433  		obj, err := s.Server.JobSpecificRequest(respW, req)
   434  		if err != nil {
   435  			t.Fatalf("err: %v", err)
   436  		}
   437  
   438  		// Check the response
   439  		dereg := obj.(structs.JobRegisterResponse)
   440  		if dereg.EvalID == "" {
   441  			t.Fatalf("bad: %v", dereg)
   442  		}
   443  
   444  		// Check for the index
   445  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
   446  			t.Fatalf("missing index")
   447  		}
   448  
   449  		// Check the job is registered
   450  		getReq := structs.JobSpecificRequest{
   451  			JobID: *job.ID,
   452  			QueryOptions: structs.QueryOptions{
   453  				Region:    "global",
   454  				Namespace: structs.DefaultNamespace,
   455  			},
   456  		}
   457  		var getResp structs.SingleJobResponse
   458  		if err := s.Agent.RPC("Job.GetJob", &getReq, &getResp); err != nil {
   459  			t.Fatalf("err: %v", err)
   460  		}
   461  
   462  		if getResp.Job == nil {
   463  			t.Fatalf("job does not exist")
   464  		}
   465  	})
   466  }
   467  
   468  func TestHTTP_JobUpdateRegion(t *testing.T) {
   469  	t.Parallel()
   470  
   471  	cases := []struct {
   472  		Name           string
   473  		ConfigRegion   string
   474  		APIRegion      string
   475  		ExpectedRegion string
   476  	}{
   477  		{
   478  			Name:           "api region takes precedence",
   479  			ConfigRegion:   "not-global",
   480  			APIRegion:      "north-america",
   481  			ExpectedRegion: "north-america",
   482  		},
   483  		{
   484  			Name:           "config region is set",
   485  			ConfigRegion:   "north-america",
   486  			APIRegion:      "",
   487  			ExpectedRegion: "north-america",
   488  		},
   489  		{
   490  			Name:           "api region is set",
   491  			ConfigRegion:   "",
   492  			APIRegion:      "north-america",
   493  			ExpectedRegion: "north-america",
   494  		},
   495  		{
   496  			Name:           "defaults to node region global if no region is provided",
   497  			ConfigRegion:   "",
   498  			APIRegion:      "",
   499  			ExpectedRegion: "global",
   500  		},
   501  		{
   502  			Name:           "defaults to node region not-global if no region is provided",
   503  			ConfigRegion:   "",
   504  			APIRegion:      "",
   505  			ExpectedRegion: "not-global",
   506  		},
   507  	}
   508  
   509  	for _, tc := range cases {
   510  		t.Run(tc.Name, func(t *testing.T) {
   511  			httpTest(t, func(c *Config) { c.Region = tc.ExpectedRegion }, func(s *TestAgent) {
   512  				// Create the job
   513  				job := MockRegionalJob()
   514  
   515  				if tc.ConfigRegion == "" {
   516  					job.Region = nil
   517  				} else {
   518  					job.Region = &tc.ConfigRegion
   519  				}
   520  
   521  				args := api.JobRegisterRequest{
   522  					Job: job,
   523  					WriteRequest: api.WriteRequest{
   524  						Namespace: api.DefaultNamespace,
   525  						Region:    tc.APIRegion,
   526  					},
   527  				}
   528  
   529  				buf := encodeReq(args)
   530  
   531  				// Make the HTTP request
   532  				url := "/v1/job/" + *job.ID
   533  
   534  				req, err := http.NewRequest("PUT", url, buf)
   535  				require.NoError(t, err)
   536  				respW := httptest.NewRecorder()
   537  
   538  				// Make the request
   539  				obj, err := s.Server.JobSpecificRequest(respW, req)
   540  				require.NoError(t, err)
   541  
   542  				// Check the response
   543  				dereg := obj.(structs.JobRegisterResponse)
   544  				require.NotEmpty(t, dereg.EvalID)
   545  
   546  				// Check for the index
   547  				require.NotEmpty(t, respW.HeaderMap.Get("X-Nomad-Index"), "missing index")
   548  
   549  				// Check the job is registered
   550  				getReq := structs.JobSpecificRequest{
   551  					JobID: *job.ID,
   552  					QueryOptions: structs.QueryOptions{
   553  						Region:    tc.ExpectedRegion,
   554  						Namespace: structs.DefaultNamespace,
   555  					},
   556  				}
   557  				var getResp structs.SingleJobResponse
   558  				err = s.Agent.RPC("Job.GetJob", &getReq, &getResp)
   559  				require.NoError(t, err)
   560  				require.NotNil(t, getResp.Job, "job does not exist")
   561  				require.Equal(t, tc.ExpectedRegion, getResp.Job.Region)
   562  			})
   563  		})
   564  	}
   565  }
   566  
   567  func TestHTTP_JobDelete(t *testing.T) {
   568  	t.Parallel()
   569  	httpTest(t, nil, func(s *TestAgent) {
   570  		// Create the job
   571  		job := mock.Job()
   572  		args := structs.JobRegisterRequest{
   573  			Job: job,
   574  			WriteRequest: structs.WriteRequest{
   575  				Region:    "global",
   576  				Namespace: structs.DefaultNamespace,
   577  			},
   578  		}
   579  		var resp structs.JobRegisterResponse
   580  		if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil {
   581  			t.Fatalf("err: %v", err)
   582  		}
   583  
   584  		// Make the HTTP request to do a soft delete
   585  		req, err := http.NewRequest("DELETE", "/v1/job/"+job.ID, nil)
   586  		if err != nil {
   587  			t.Fatalf("err: %v", err)
   588  		}
   589  		respW := httptest.NewRecorder()
   590  
   591  		// Make the request
   592  		obj, err := s.Server.JobSpecificRequest(respW, req)
   593  		if err != nil {
   594  			t.Fatalf("err: %v", err)
   595  		}
   596  
   597  		// Check the response
   598  		dereg := obj.(structs.JobDeregisterResponse)
   599  		if dereg.EvalID == "" {
   600  			t.Fatalf("bad: %v", dereg)
   601  		}
   602  
   603  		// Check for the index
   604  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
   605  			t.Fatalf("missing index")
   606  		}
   607  
   608  		// Check the job is still queryable
   609  		getReq1 := structs.JobSpecificRequest{
   610  			JobID: job.ID,
   611  			QueryOptions: structs.QueryOptions{
   612  				Region:    "global",
   613  				Namespace: structs.DefaultNamespace,
   614  			},
   615  		}
   616  		var getResp1 structs.SingleJobResponse
   617  		if err := s.Agent.RPC("Job.GetJob", &getReq1, &getResp1); err != nil {
   618  			t.Fatalf("err: %v", err)
   619  		}
   620  		if getResp1.Job == nil {
   621  			t.Fatalf("job doesn't exists")
   622  		}
   623  		if !getResp1.Job.Stop {
   624  			t.Fatalf("job should be marked as stop")
   625  		}
   626  
   627  		// Make the HTTP request to do a purge delete
   628  		req2, err := http.NewRequest("DELETE", "/v1/job/"+job.ID+"?purge=true", nil)
   629  		if err != nil {
   630  			t.Fatalf("err: %v", err)
   631  		}
   632  		respW.Flush()
   633  
   634  		// Make the request
   635  		obj, err = s.Server.JobSpecificRequest(respW, req2)
   636  		if err != nil {
   637  			t.Fatalf("err: %v", err)
   638  		}
   639  
   640  		// Check the response
   641  		dereg = obj.(structs.JobDeregisterResponse)
   642  		if dereg.EvalID == "" {
   643  			t.Fatalf("bad: %v", dereg)
   644  		}
   645  
   646  		// Check for the index
   647  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
   648  			t.Fatalf("missing index")
   649  		}
   650  
   651  		// Check the job is gone
   652  		getReq2 := structs.JobSpecificRequest{
   653  			JobID: job.ID,
   654  			QueryOptions: structs.QueryOptions{
   655  				Region:    "global",
   656  				Namespace: structs.DefaultNamespace,
   657  			},
   658  		}
   659  		var getResp2 structs.SingleJobResponse
   660  		if err := s.Agent.RPC("Job.GetJob", &getReq2, &getResp2); err != nil {
   661  			t.Fatalf("err: %v", err)
   662  		}
   663  		if getResp2.Job != nil {
   664  			t.Fatalf("job still exists")
   665  		}
   666  	})
   667  }
   668  
   669  func TestHTTP_Job_ScaleTaskGroup(t *testing.T) {
   670  	t.Parallel()
   671  
   672  	require := require.New(t)
   673  
   674  	httpTest(t, nil, func(s *TestAgent) {
   675  		// Create the job
   676  		job := mock.Job()
   677  		args := structs.JobRegisterRequest{
   678  			Job: job,
   679  			WriteRequest: structs.WriteRequest{
   680  				Region:    "global",
   681  				Namespace: structs.DefaultNamespace,
   682  			},
   683  		}
   684  		var resp structs.JobRegisterResponse
   685  		require.NoError(s.Agent.RPC("Job.Register", &args, &resp))
   686  
   687  		newCount := job.TaskGroups[0].Count + 1
   688  		scaleReq := &api.ScalingRequest{
   689  			Count:   helper.Int64ToPtr(int64(newCount)),
   690  			Message: "testing",
   691  			Target: map[string]string{
   692  				"Job":   job.ID,
   693  				"Group": job.TaskGroups[0].Name,
   694  			},
   695  		}
   696  		buf := encodeReq(scaleReq)
   697  
   698  		// Make the HTTP request to scale the job group
   699  		req, err := http.NewRequest("POST", "/v1/job/"+job.ID+"/scale", buf)
   700  		require.NoError(err)
   701  		respW := httptest.NewRecorder()
   702  
   703  		// Make the request
   704  		obj, err := s.Server.JobSpecificRequest(respW, req)
   705  		require.NoError(err)
   706  
   707  		// Check the response
   708  		resp = obj.(structs.JobRegisterResponse)
   709  		require.NotEmpty(resp.EvalID)
   710  
   711  		// Check for the index
   712  		require.NotEmpty(respW.Header().Get("X-Nomad-Index"))
   713  
   714  		// Check that the group count was changed
   715  		getReq := structs.JobSpecificRequest{
   716  			JobID: job.ID,
   717  			QueryOptions: structs.QueryOptions{
   718  				Region:    "global",
   719  				Namespace: structs.DefaultNamespace,
   720  			},
   721  		}
   722  		var getResp structs.SingleJobResponse
   723  		err = s.Agent.RPC("Job.GetJob", &getReq, &getResp)
   724  		require.NoError(err)
   725  		require.NotNil(getResp.Job)
   726  		require.Equal(newCount, getResp.Job.TaskGroups[0].Count)
   727  	})
   728  }
   729  
   730  func TestHTTP_Job_ScaleStatus(t *testing.T) {
   731  	t.Parallel()
   732  
   733  	require := require.New(t)
   734  
   735  	httpTest(t, nil, func(s *TestAgent) {
   736  		// Create the job
   737  		job := mock.Job()
   738  		args := structs.JobRegisterRequest{
   739  			Job: job,
   740  			WriteRequest: structs.WriteRequest{
   741  				Region:    "global",
   742  				Namespace: structs.DefaultNamespace,
   743  			},
   744  		}
   745  		var resp structs.JobRegisterResponse
   746  		if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil {
   747  			t.Fatalf("err: %v", err)
   748  		}
   749  
   750  		// Make the HTTP request to scale the job group
   751  		req, err := http.NewRequest("GET", "/v1/job/"+job.ID+"/scale", nil)
   752  		require.NoError(err)
   753  		respW := httptest.NewRecorder()
   754  
   755  		// Make the request
   756  		obj, err := s.Server.JobSpecificRequest(respW, req)
   757  		require.NoError(err)
   758  
   759  		// Check the response
   760  		status := obj.(*structs.JobScaleStatus)
   761  		require.NotEmpty(resp.EvalID)
   762  		require.Equal(job.TaskGroups[0].Count, status.TaskGroups[job.TaskGroups[0].Name].Desired)
   763  
   764  		// Check for the index
   765  		require.NotEmpty(respW.Header().Get("X-Nomad-Index"))
   766  	})
   767  }
   768  
   769  func TestHTTP_JobForceEvaluate(t *testing.T) {
   770  	t.Parallel()
   771  	httpTest(t, nil, func(s *TestAgent) {
   772  		// Create the job
   773  		job := mock.Job()
   774  		args := structs.JobRegisterRequest{
   775  			Job: job,
   776  			WriteRequest: structs.WriteRequest{
   777  				Region:    "global",
   778  				Namespace: structs.DefaultNamespace,
   779  			},
   780  		}
   781  		var resp structs.JobRegisterResponse
   782  		if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil {
   783  			t.Fatalf("err: %v", err)
   784  		}
   785  
   786  		// Make the HTTP request
   787  		req, err := http.NewRequest("POST", "/v1/job/"+job.ID+"/evaluate", nil)
   788  		if err != nil {
   789  			t.Fatalf("err: %v", err)
   790  		}
   791  		respW := httptest.NewRecorder()
   792  
   793  		// Make the request
   794  		obj, err := s.Server.JobSpecificRequest(respW, req)
   795  		if err != nil {
   796  			t.Fatalf("err: %v", err)
   797  		}
   798  
   799  		// Check the response
   800  		reg := obj.(structs.JobRegisterResponse)
   801  		if reg.EvalID == "" {
   802  			t.Fatalf("bad: %v", reg)
   803  		}
   804  
   805  		// Check for the index
   806  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
   807  			t.Fatalf("missing index")
   808  		}
   809  	})
   810  }
   811  
   812  func TestHTTP_JobEvaluate_ForceReschedule(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  		jobEvalReq := api.JobEvaluateRequest{
   829  			JobID: job.ID,
   830  			EvalOptions: api.EvalOptions{
   831  				ForceReschedule: true,
   832  			},
   833  		}
   834  
   835  		buf := encodeReq(jobEvalReq)
   836  
   837  		// Make the HTTP request
   838  		req, err := http.NewRequest("POST", "/v1/job/"+job.ID+"/evaluate", buf)
   839  		if err != nil {
   840  			t.Fatalf("err: %v", err)
   841  		}
   842  		respW := httptest.NewRecorder()
   843  
   844  		// Make the request
   845  		obj, err := s.Server.JobSpecificRequest(respW, req)
   846  		if err != nil {
   847  			t.Fatalf("err: %v", err)
   848  		}
   849  
   850  		// Check the response
   851  		reg := obj.(structs.JobRegisterResponse)
   852  		if reg.EvalID == "" {
   853  			t.Fatalf("bad: %v", reg)
   854  		}
   855  
   856  		// Check for the index
   857  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
   858  			t.Fatalf("missing index")
   859  		}
   860  	})
   861  }
   862  
   863  func TestHTTP_JobEvaluations(t *testing.T) {
   864  	t.Parallel()
   865  	httpTest(t, nil, func(s *TestAgent) {
   866  		// Create the job
   867  		job := mock.Job()
   868  		args := structs.JobRegisterRequest{
   869  			Job: job,
   870  			WriteRequest: structs.WriteRequest{
   871  				Region:    "global",
   872  				Namespace: structs.DefaultNamespace,
   873  			},
   874  		}
   875  		var resp structs.JobRegisterResponse
   876  		if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil {
   877  			t.Fatalf("err: %v", err)
   878  		}
   879  
   880  		// Make the HTTP request
   881  		req, err := http.NewRequest("GET", "/v1/job/"+job.ID+"/evaluations", nil)
   882  		if err != nil {
   883  			t.Fatalf("err: %v", err)
   884  		}
   885  		respW := httptest.NewRecorder()
   886  
   887  		// Make the request
   888  		obj, err := s.Server.JobSpecificRequest(respW, req)
   889  		if err != nil {
   890  			t.Fatalf("err: %v", err)
   891  		}
   892  
   893  		// Check the response
   894  		evals := obj.([]*structs.Evaluation)
   895  		// Can be multiple evals, use the last one, since they are in order
   896  		idx := len(evals) - 1
   897  		if len(evals) < 0 || evals[idx].ID != resp.EvalID {
   898  			t.Fatalf("bad: %v", evals)
   899  		}
   900  
   901  		// Check for the index
   902  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
   903  			t.Fatalf("missing index")
   904  		}
   905  		if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" {
   906  			t.Fatalf("missing known leader")
   907  		}
   908  		if respW.HeaderMap.Get("X-Nomad-LastContact") == "" {
   909  			t.Fatalf("missing last contact")
   910  		}
   911  	})
   912  }
   913  
   914  func TestHTTP_JobAllocations(t *testing.T) {
   915  	t.Parallel()
   916  	httpTest(t, nil, func(s *TestAgent) {
   917  		// Create the job
   918  		alloc1 := mock.Alloc()
   919  		args := structs.JobRegisterRequest{
   920  			Job: alloc1.Job,
   921  			WriteRequest: structs.WriteRequest{
   922  				Region:    "global",
   923  				Namespace: structs.DefaultNamespace,
   924  			},
   925  		}
   926  		var resp structs.JobRegisterResponse
   927  		if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil {
   928  			t.Fatalf("err: %v", err)
   929  		}
   930  
   931  		// Directly manipulate the state
   932  		expectedDisplayMsg := "test message"
   933  		testEvent := structs.NewTaskEvent("test event").SetMessage(expectedDisplayMsg)
   934  		var events []*structs.TaskEvent
   935  		events = append(events, testEvent)
   936  		taskState := &structs.TaskState{Events: events}
   937  		alloc1.TaskStates = make(map[string]*structs.TaskState)
   938  		alloc1.TaskStates["test"] = taskState
   939  		state := s.Agent.server.State()
   940  		err := state.UpsertAllocs(1000, []*structs.Allocation{alloc1})
   941  		if err != nil {
   942  			t.Fatalf("err: %v", err)
   943  		}
   944  
   945  		// Make the HTTP request
   946  		req, err := http.NewRequest("GET", "/v1/job/"+alloc1.Job.ID+"/allocations?all=true", nil)
   947  		if err != nil {
   948  			t.Fatalf("err: %v", err)
   949  		}
   950  		respW := httptest.NewRecorder()
   951  
   952  		// Make the request
   953  		obj, err := s.Server.JobSpecificRequest(respW, req)
   954  		if err != nil {
   955  			t.Fatalf("err: %v", err)
   956  		}
   957  
   958  		// Check the response
   959  		allocs := obj.([]*structs.AllocListStub)
   960  		if len(allocs) != 1 && allocs[0].ID != alloc1.ID {
   961  			t.Fatalf("bad: %v", allocs)
   962  		}
   963  		displayMsg := allocs[0].TaskStates["test"].Events[0].DisplayMessage
   964  		assert.Equal(t, expectedDisplayMsg, displayMsg)
   965  
   966  		// Check for the index
   967  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
   968  			t.Fatalf("missing index")
   969  		}
   970  		if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" {
   971  			t.Fatalf("missing known leader")
   972  		}
   973  		if respW.HeaderMap.Get("X-Nomad-LastContact") == "" {
   974  			t.Fatalf("missing last contact")
   975  		}
   976  	})
   977  }
   978  
   979  func TestHTTP_JobDeployments(t *testing.T) {
   980  	assert := assert.New(t)
   981  	t.Parallel()
   982  	httpTest(t, nil, func(s *TestAgent) {
   983  		// Create the job
   984  		j := mock.Job()
   985  		args := structs.JobRegisterRequest{
   986  			Job: j,
   987  			WriteRequest: structs.WriteRequest{
   988  				Region:    "global",
   989  				Namespace: structs.DefaultNamespace,
   990  			},
   991  		}
   992  		var resp structs.JobRegisterResponse
   993  		assert.Nil(s.Agent.RPC("Job.Register", &args, &resp), "JobRegister")
   994  
   995  		// Directly manipulate the state
   996  		state := s.Agent.server.State()
   997  		d := mock.Deployment()
   998  		d.JobID = j.ID
   999  		d.JobCreateIndex = resp.JobModifyIndex
  1000  
  1001  		assert.Nil(state.UpsertDeployment(1000, d), "UpsertDeployment")
  1002  
  1003  		// Make the HTTP request
  1004  		req, err := http.NewRequest("GET", "/v1/job/"+j.ID+"/deployments", nil)
  1005  		assert.Nil(err, "HTTP")
  1006  		respW := httptest.NewRecorder()
  1007  
  1008  		// Make the request
  1009  		obj, err := s.Server.JobSpecificRequest(respW, req)
  1010  		assert.Nil(err, "JobSpecificRequest")
  1011  
  1012  		// Check the response
  1013  		deploys := obj.([]*structs.Deployment)
  1014  		assert.Len(deploys, 1, "deployments")
  1015  		assert.Equal(d.ID, deploys[0].ID, "deployment id")
  1016  
  1017  		assert.NotZero(respW.HeaderMap.Get("X-Nomad-Index"), "missing index")
  1018  		assert.Equal("true", respW.HeaderMap.Get("X-Nomad-KnownLeader"), "missing known leader")
  1019  		assert.NotZero(respW.HeaderMap.Get("X-Nomad-LastContact"), "missing last contact")
  1020  	})
  1021  }
  1022  
  1023  func TestHTTP_JobDeployment(t *testing.T) {
  1024  	assert := assert.New(t)
  1025  	t.Parallel()
  1026  	httpTest(t, nil, func(s *TestAgent) {
  1027  		// Create the job
  1028  		j := mock.Job()
  1029  		args := structs.JobRegisterRequest{
  1030  			Job: j,
  1031  			WriteRequest: structs.WriteRequest{
  1032  				Region:    "global",
  1033  				Namespace: structs.DefaultNamespace,
  1034  			},
  1035  		}
  1036  		var resp structs.JobRegisterResponse
  1037  		assert.Nil(s.Agent.RPC("Job.Register", &args, &resp), "JobRegister")
  1038  
  1039  		// Directly manipulate the state
  1040  		state := s.Agent.server.State()
  1041  		d := mock.Deployment()
  1042  		d.JobID = j.ID
  1043  		d.JobCreateIndex = resp.JobModifyIndex
  1044  		assert.Nil(state.UpsertDeployment(1000, d), "UpsertDeployment")
  1045  
  1046  		// Make the HTTP request
  1047  		req, err := http.NewRequest("GET", "/v1/job/"+j.ID+"/deployment", nil)
  1048  		assert.Nil(err, "HTTP")
  1049  		respW := httptest.NewRecorder()
  1050  
  1051  		// Make the request
  1052  		obj, err := s.Server.JobSpecificRequest(respW, req)
  1053  		assert.Nil(err, "JobSpecificRequest")
  1054  
  1055  		// Check the response
  1056  		out := obj.(*structs.Deployment)
  1057  		assert.NotNil(out, "deployment")
  1058  		assert.Equal(d.ID, out.ID, "deployment id")
  1059  
  1060  		assert.NotZero(respW.HeaderMap.Get("X-Nomad-Index"), "missing index")
  1061  		assert.Equal("true", respW.HeaderMap.Get("X-Nomad-KnownLeader"), "missing known leader")
  1062  		assert.NotZero(respW.HeaderMap.Get("X-Nomad-LastContact"), "missing last contact")
  1063  	})
  1064  }
  1065  
  1066  func TestHTTP_JobVersions(t *testing.T) {
  1067  	t.Parallel()
  1068  	httpTest(t, nil, func(s *TestAgent) {
  1069  		// Create the job
  1070  		job := mock.Job()
  1071  		args := structs.JobRegisterRequest{
  1072  			Job: job,
  1073  			WriteRequest: structs.WriteRequest{
  1074  				Region:    "global",
  1075  				Namespace: structs.DefaultNamespace,
  1076  			},
  1077  		}
  1078  		var resp structs.JobRegisterResponse
  1079  		if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil {
  1080  			t.Fatalf("err: %v", err)
  1081  		}
  1082  
  1083  		job2 := mock.Job()
  1084  		job2.ID = job.ID
  1085  		job2.Priority = 100
  1086  
  1087  		args2 := structs.JobRegisterRequest{
  1088  			Job: job2,
  1089  			WriteRequest: structs.WriteRequest{
  1090  				Region:    "global",
  1091  				Namespace: structs.DefaultNamespace,
  1092  			},
  1093  		}
  1094  		var resp2 structs.JobRegisterResponse
  1095  		if err := s.Agent.RPC("Job.Register", &args2, &resp2); err != nil {
  1096  			t.Fatalf("err: %v", err)
  1097  		}
  1098  
  1099  		// Make the HTTP request
  1100  		req, err := http.NewRequest("GET", "/v1/job/"+job.ID+"/versions?diffs=true", nil)
  1101  		if err != nil {
  1102  			t.Fatalf("err: %v", err)
  1103  		}
  1104  		respW := httptest.NewRecorder()
  1105  
  1106  		// Make the request
  1107  		obj, err := s.Server.JobSpecificRequest(respW, req)
  1108  		if err != nil {
  1109  			t.Fatalf("err: %v", err)
  1110  		}
  1111  
  1112  		// Check the response
  1113  		vResp := obj.(structs.JobVersionsResponse)
  1114  		versions := vResp.Versions
  1115  		if len(versions) != 2 {
  1116  			t.Fatalf("got %d versions; want 2", len(versions))
  1117  		}
  1118  
  1119  		if v := versions[0]; v.Version != 1 || v.Priority != 100 {
  1120  			t.Fatalf("bad %v", v)
  1121  		}
  1122  
  1123  		if v := versions[1]; v.Version != 0 {
  1124  			t.Fatalf("bad %v", v)
  1125  		}
  1126  
  1127  		if len(vResp.Diffs) != 1 {
  1128  			t.Fatalf("bad %v", vResp)
  1129  		}
  1130  
  1131  		// Check for the index
  1132  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
  1133  			t.Fatalf("missing index")
  1134  		}
  1135  		if respW.HeaderMap.Get("X-Nomad-KnownLeader") != "true" {
  1136  			t.Fatalf("missing known leader")
  1137  		}
  1138  		if respW.HeaderMap.Get("X-Nomad-LastContact") == "" {
  1139  			t.Fatalf("missing last contact")
  1140  		}
  1141  	})
  1142  }
  1143  
  1144  func TestHTTP_PeriodicForce(t *testing.T) {
  1145  	t.Parallel()
  1146  	httpTest(t, nil, func(s *TestAgent) {
  1147  		// Create and register a periodic job.
  1148  		job := mock.PeriodicJob()
  1149  		args := structs.JobRegisterRequest{
  1150  			Job: job,
  1151  			WriteRequest: structs.WriteRequest{
  1152  				Region:    "global",
  1153  				Namespace: structs.DefaultNamespace,
  1154  			},
  1155  		}
  1156  		var resp structs.JobRegisterResponse
  1157  		if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil {
  1158  			t.Fatalf("err: %v", err)
  1159  		}
  1160  
  1161  		// Make the HTTP request
  1162  		req, err := http.NewRequest("POST", "/v1/job/"+job.ID+"/periodic/force", nil)
  1163  		if err != nil {
  1164  			t.Fatalf("err: %v", err)
  1165  		}
  1166  		respW := httptest.NewRecorder()
  1167  
  1168  		// Make the request
  1169  		obj, err := s.Server.JobSpecificRequest(respW, req)
  1170  		if err != nil {
  1171  			t.Fatalf("err: %v", err)
  1172  		}
  1173  
  1174  		// Check for the index
  1175  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
  1176  			t.Fatalf("missing index")
  1177  		}
  1178  
  1179  		// Check the response
  1180  		r := obj.(structs.PeriodicForceResponse)
  1181  		if r.EvalID == "" {
  1182  			t.Fatalf("bad: %#v", r)
  1183  		}
  1184  	})
  1185  }
  1186  
  1187  func TestHTTP_JobPlan(t *testing.T) {
  1188  	t.Parallel()
  1189  	httpTest(t, nil, func(s *TestAgent) {
  1190  		// Create the job
  1191  		job := MockJob()
  1192  		args := api.JobPlanRequest{
  1193  			Job:  job,
  1194  			Diff: true,
  1195  			WriteRequest: api.WriteRequest{
  1196  				Region:    "global",
  1197  				Namespace: api.DefaultNamespace,
  1198  			},
  1199  		}
  1200  		buf := encodeReq(args)
  1201  
  1202  		// Make the HTTP request
  1203  		req, err := http.NewRequest("PUT", "/v1/job/"+*job.ID+"/plan", buf)
  1204  		if err != nil {
  1205  			t.Fatalf("err: %v", err)
  1206  		}
  1207  		respW := httptest.NewRecorder()
  1208  
  1209  		// Make the request
  1210  		obj, err := s.Server.JobSpecificRequest(respW, req)
  1211  		if err != nil {
  1212  			t.Fatalf("err: %v", err)
  1213  		}
  1214  
  1215  		// Check the response
  1216  		plan := obj.(structs.JobPlanResponse)
  1217  		if plan.Annotations == nil {
  1218  			t.Fatalf("bad: %v", plan)
  1219  		}
  1220  
  1221  		if plan.Diff == nil {
  1222  			t.Fatalf("bad: %v", plan)
  1223  		}
  1224  	})
  1225  }
  1226  
  1227  func TestHTTP_JobPlanRegion(t *testing.T) {
  1228  	t.Parallel()
  1229  
  1230  	cases := []struct {
  1231  		Name           string
  1232  		ConfigRegion   string
  1233  		APIRegion      string
  1234  		ExpectedRegion string
  1235  	}{
  1236  		{
  1237  			Name:           "api region takes precedence",
  1238  			ConfigRegion:   "not-global",
  1239  			APIRegion:      "north-america",
  1240  			ExpectedRegion: "north-america",
  1241  		},
  1242  		{
  1243  			Name:           "config region is set",
  1244  			ConfigRegion:   "north-america",
  1245  			APIRegion:      "",
  1246  			ExpectedRegion: "north-america",
  1247  		},
  1248  		{
  1249  			Name:           "api region is set",
  1250  			ConfigRegion:   "",
  1251  			APIRegion:      "north-america",
  1252  			ExpectedRegion: "north-america",
  1253  		},
  1254  		{
  1255  			Name:           "falls back to default if no region is provided",
  1256  			ConfigRegion:   "",
  1257  			APIRegion:      "",
  1258  			ExpectedRegion: "global",
  1259  		},
  1260  	}
  1261  
  1262  	for _, tc := range cases {
  1263  		t.Run(tc.Name, func(t *testing.T) {
  1264  			httpTest(t, func(c *Config) { c.Region = tc.ExpectedRegion }, func(s *TestAgent) {
  1265  				// Create the job
  1266  				job := MockRegionalJob()
  1267  
  1268  				if tc.ConfigRegion == "" {
  1269  					job.Region = nil
  1270  				} else {
  1271  					job.Region = &tc.ConfigRegion
  1272  				}
  1273  
  1274  				args := api.JobPlanRequest{
  1275  					Job:  job,
  1276  					Diff: true,
  1277  					WriteRequest: api.WriteRequest{
  1278  						Region:    tc.APIRegion,
  1279  						Namespace: api.DefaultNamespace,
  1280  					},
  1281  				}
  1282  				buf := encodeReq(args)
  1283  
  1284  				// Make the HTTP request
  1285  				req, err := http.NewRequest("PUT", "/v1/job/"+*job.ID+"/plan", buf)
  1286  				require.NoError(t, err)
  1287  				respW := httptest.NewRecorder()
  1288  
  1289  				// Make the request
  1290  				obj, err := s.Server.JobSpecificRequest(respW, req)
  1291  				require.NoError(t, err)
  1292  
  1293  				// Check the response
  1294  				plan := obj.(structs.JobPlanResponse)
  1295  				require.NotNil(t, plan.Annotations)
  1296  				require.NotNil(t, plan.Diff)
  1297  			})
  1298  		})
  1299  	}
  1300  }
  1301  
  1302  func TestHTTP_JobDispatch(t *testing.T) {
  1303  	t.Parallel()
  1304  	httpTest(t, nil, func(s *TestAgent) {
  1305  		// Create the parameterized job
  1306  		job := mock.BatchJob()
  1307  		job.ParameterizedJob = &structs.ParameterizedJobConfig{}
  1308  
  1309  		args := structs.JobRegisterRequest{
  1310  			Job: job,
  1311  			WriteRequest: structs.WriteRequest{
  1312  				Region:    "global",
  1313  				Namespace: structs.DefaultNamespace,
  1314  			},
  1315  		}
  1316  		var resp structs.JobRegisterResponse
  1317  		if err := s.Agent.RPC("Job.Register", &args, &resp); err != nil {
  1318  			t.Fatalf("err: %v", err)
  1319  		}
  1320  
  1321  		// Make the request
  1322  		respW := httptest.NewRecorder()
  1323  		args2 := structs.JobDispatchRequest{
  1324  			WriteRequest: structs.WriteRequest{
  1325  				Region:    "global",
  1326  				Namespace: structs.DefaultNamespace,
  1327  			},
  1328  		}
  1329  		buf := encodeReq(args2)
  1330  
  1331  		// Make the HTTP request
  1332  		req2, err := http.NewRequest("PUT", "/v1/job/"+job.ID+"/dispatch", buf)
  1333  		if err != nil {
  1334  			t.Fatalf("err: %v", err)
  1335  		}
  1336  		respW.Flush()
  1337  
  1338  		// Make the request
  1339  		obj, err := s.Server.JobSpecificRequest(respW, req2)
  1340  		if err != nil {
  1341  			t.Fatalf("err: %v", err)
  1342  		}
  1343  
  1344  		// Check the response
  1345  		dispatch := obj.(structs.JobDispatchResponse)
  1346  		if dispatch.EvalID == "" {
  1347  			t.Fatalf("bad: %v", dispatch)
  1348  		}
  1349  
  1350  		if dispatch.DispatchedJobID == "" {
  1351  			t.Fatalf("bad: %v", dispatch)
  1352  		}
  1353  	})
  1354  }
  1355  
  1356  func TestHTTP_JobRevert(t *testing.T) {
  1357  	t.Parallel()
  1358  	httpTest(t, nil, func(s *TestAgent) {
  1359  		// Create the job and register it twice
  1360  		job := mock.Job()
  1361  		regReq := structs.JobRegisterRequest{
  1362  			Job: job,
  1363  			WriteRequest: structs.WriteRequest{
  1364  				Region:    "global",
  1365  				Namespace: structs.DefaultNamespace,
  1366  			},
  1367  		}
  1368  		var regResp structs.JobRegisterResponse
  1369  		if err := s.Agent.RPC("Job.Register", &regReq, &regResp); err != nil {
  1370  			t.Fatalf("err: %v", err)
  1371  		}
  1372  
  1373  		// Change the job to get a new version
  1374  		job.Datacenters = append(job.Datacenters, "foo")
  1375  		if err := s.Agent.RPC("Job.Register", &regReq, &regResp); err != nil {
  1376  			t.Fatalf("err: %v", err)
  1377  		}
  1378  
  1379  		args := structs.JobRevertRequest{
  1380  			JobID:      job.ID,
  1381  			JobVersion: 0,
  1382  			WriteRequest: structs.WriteRequest{
  1383  				Region:    "global",
  1384  				Namespace: structs.DefaultNamespace,
  1385  			},
  1386  		}
  1387  		buf := encodeReq(args)
  1388  
  1389  		// Make the HTTP request
  1390  		req, err := http.NewRequest("PUT", "/v1/job/"+job.ID+"/revert", buf)
  1391  		if err != nil {
  1392  			t.Fatalf("err: %v", err)
  1393  		}
  1394  		respW := httptest.NewRecorder()
  1395  
  1396  		// Make the request
  1397  		obj, err := s.Server.JobSpecificRequest(respW, req)
  1398  		if err != nil {
  1399  			t.Fatalf("err: %v", err)
  1400  		}
  1401  
  1402  		// Check the response
  1403  		revertResp := obj.(structs.JobRegisterResponse)
  1404  		if revertResp.EvalID == "" {
  1405  			t.Fatalf("bad: %v", revertResp)
  1406  		}
  1407  
  1408  		// Check for the index
  1409  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
  1410  			t.Fatalf("missing index")
  1411  		}
  1412  	})
  1413  }
  1414  
  1415  func TestHTTP_JobStable(t *testing.T) {
  1416  	t.Parallel()
  1417  	httpTest(t, nil, func(s *TestAgent) {
  1418  		// Create the job and register it twice
  1419  		job := mock.Job()
  1420  		regReq := structs.JobRegisterRequest{
  1421  			Job: job,
  1422  			WriteRequest: structs.WriteRequest{
  1423  				Region:    "global",
  1424  				Namespace: structs.DefaultNamespace,
  1425  			},
  1426  		}
  1427  		var regResp structs.JobRegisterResponse
  1428  		if err := s.Agent.RPC("Job.Register", &regReq, &regResp); err != nil {
  1429  			t.Fatalf("err: %v", err)
  1430  		}
  1431  
  1432  		if err := s.Agent.RPC("Job.Register", &regReq, &regResp); err != nil {
  1433  			t.Fatalf("err: %v", err)
  1434  		}
  1435  
  1436  		args := structs.JobStabilityRequest{
  1437  			JobID:      job.ID,
  1438  			JobVersion: 0,
  1439  			Stable:     true,
  1440  			WriteRequest: structs.WriteRequest{
  1441  				Region:    "global",
  1442  				Namespace: structs.DefaultNamespace,
  1443  			},
  1444  		}
  1445  		buf := encodeReq(args)
  1446  
  1447  		// Make the HTTP request
  1448  		req, err := http.NewRequest("PUT", "/v1/job/"+job.ID+"/stable", buf)
  1449  		if err != nil {
  1450  			t.Fatalf("err: %v", err)
  1451  		}
  1452  		respW := httptest.NewRecorder()
  1453  
  1454  		// Make the request
  1455  		obj, err := s.Server.JobSpecificRequest(respW, req)
  1456  		if err != nil {
  1457  			t.Fatalf("err: %v", err)
  1458  		}
  1459  
  1460  		// Check the response
  1461  		stableResp := obj.(structs.JobStabilityResponse)
  1462  		if stableResp.Index == 0 {
  1463  			t.Fatalf("bad: %v", stableResp)
  1464  		}
  1465  
  1466  		// Check for the index
  1467  		if respW.HeaderMap.Get("X-Nomad-Index") == "" {
  1468  			t.Fatalf("missing index")
  1469  		}
  1470  	})
  1471  }
  1472  
  1473  func TestJobs_ApiJobToStructsJob(t *testing.T) {
  1474  	apiJob := &api.Job{
  1475  		Stop:        helper.BoolToPtr(true),
  1476  		Region:      helper.StringToPtr("global"),
  1477  		Namespace:   helper.StringToPtr("foo"),
  1478  		ID:          helper.StringToPtr("foo"),
  1479  		ParentID:    helper.StringToPtr("lol"),
  1480  		Name:        helper.StringToPtr("name"),
  1481  		Type:        helper.StringToPtr("service"),
  1482  		Priority:    helper.IntToPtr(50),
  1483  		AllAtOnce:   helper.BoolToPtr(true),
  1484  		Datacenters: []string{"dc1", "dc2"},
  1485  		Constraints: []*api.Constraint{
  1486  			{
  1487  				LTarget: "a",
  1488  				RTarget: "b",
  1489  				Operand: "c",
  1490  			},
  1491  		},
  1492  		Affinities: []*api.Affinity{
  1493  			{
  1494  				LTarget: "a",
  1495  				RTarget: "b",
  1496  				Operand: "c",
  1497  				Weight:  helper.Int8ToPtr(50),
  1498  			},
  1499  		},
  1500  		Update: &api.UpdateStrategy{
  1501  			Stagger:          helper.TimeToPtr(1 * time.Second),
  1502  			MaxParallel:      helper.IntToPtr(5),
  1503  			HealthCheck:      helper.StringToPtr(structs.UpdateStrategyHealthCheck_Manual),
  1504  			MinHealthyTime:   helper.TimeToPtr(1 * time.Minute),
  1505  			HealthyDeadline:  helper.TimeToPtr(3 * time.Minute),
  1506  			ProgressDeadline: helper.TimeToPtr(3 * time.Minute),
  1507  			AutoRevert:       helper.BoolToPtr(false),
  1508  			Canary:           helper.IntToPtr(1),
  1509  		},
  1510  		Spreads: []*api.Spread{
  1511  			{
  1512  				Attribute: "${meta.rack}",
  1513  				Weight:    helper.Int8ToPtr(100),
  1514  				SpreadTarget: []*api.SpreadTarget{
  1515  					{
  1516  						Value:   "r1",
  1517  						Percent: 50,
  1518  					},
  1519  				},
  1520  			},
  1521  		},
  1522  		Periodic: &api.PeriodicConfig{
  1523  			Enabled:         helper.BoolToPtr(true),
  1524  			Spec:            helper.StringToPtr("spec"),
  1525  			SpecType:        helper.StringToPtr("cron"),
  1526  			ProhibitOverlap: helper.BoolToPtr(true),
  1527  			TimeZone:        helper.StringToPtr("test zone"),
  1528  		},
  1529  		ParameterizedJob: &api.ParameterizedJobConfig{
  1530  			Payload:      "payload",
  1531  			MetaRequired: []string{"a", "b"},
  1532  			MetaOptional: []string{"c", "d"},
  1533  		},
  1534  		Payload: []byte("payload"),
  1535  		Meta: map[string]string{
  1536  			"foo": "bar",
  1537  		},
  1538  		TaskGroups: []*api.TaskGroup{
  1539  			{
  1540  				Name:  helper.StringToPtr("group1"),
  1541  				Count: helper.IntToPtr(5),
  1542  				Constraints: []*api.Constraint{
  1543  					{
  1544  						LTarget: "x",
  1545  						RTarget: "y",
  1546  						Operand: "z",
  1547  					},
  1548  				},
  1549  				Affinities: []*api.Affinity{
  1550  					{
  1551  						LTarget: "x",
  1552  						RTarget: "y",
  1553  						Operand: "z",
  1554  						Weight:  helper.Int8ToPtr(100),
  1555  					},
  1556  				},
  1557  				RestartPolicy: &api.RestartPolicy{
  1558  					Interval: helper.TimeToPtr(1 * time.Second),
  1559  					Attempts: helper.IntToPtr(5),
  1560  					Delay:    helper.TimeToPtr(10 * time.Second),
  1561  					Mode:     helper.StringToPtr("delay"),
  1562  				},
  1563  				ReschedulePolicy: &api.ReschedulePolicy{
  1564  					Interval:      helper.TimeToPtr(12 * time.Hour),
  1565  					Attempts:      helper.IntToPtr(5),
  1566  					DelayFunction: helper.StringToPtr("constant"),
  1567  					Delay:         helper.TimeToPtr(30 * time.Second),
  1568  					Unlimited:     helper.BoolToPtr(true),
  1569  					MaxDelay:      helper.TimeToPtr(20 * time.Minute),
  1570  				},
  1571  				Migrate: &api.MigrateStrategy{
  1572  					MaxParallel:     helper.IntToPtr(12),
  1573  					HealthCheck:     helper.StringToPtr("task_events"),
  1574  					MinHealthyTime:  helper.TimeToPtr(12 * time.Hour),
  1575  					HealthyDeadline: helper.TimeToPtr(12 * time.Hour),
  1576  				},
  1577  				Spreads: []*api.Spread{
  1578  					{
  1579  						Attribute: "${node.datacenter}",
  1580  						Weight:    helper.Int8ToPtr(100),
  1581  						SpreadTarget: []*api.SpreadTarget{
  1582  							{
  1583  								Value:   "dc1",
  1584  								Percent: 100,
  1585  							},
  1586  						},
  1587  					},
  1588  				},
  1589  				EphemeralDisk: &api.EphemeralDisk{
  1590  					SizeMB:  helper.IntToPtr(100),
  1591  					Sticky:  helper.BoolToPtr(true),
  1592  					Migrate: helper.BoolToPtr(true),
  1593  				},
  1594  				Update: &api.UpdateStrategy{
  1595  					HealthCheck:      helper.StringToPtr(structs.UpdateStrategyHealthCheck_Checks),
  1596  					MinHealthyTime:   helper.TimeToPtr(2 * time.Minute),
  1597  					HealthyDeadline:  helper.TimeToPtr(5 * time.Minute),
  1598  					ProgressDeadline: helper.TimeToPtr(5 * time.Minute),
  1599  					AutoRevert:       helper.BoolToPtr(true),
  1600  				},
  1601  				Meta: map[string]string{
  1602  					"key": "value",
  1603  				},
  1604  				Services: []*api.Service{
  1605  					{
  1606  						Name:              "groupserviceA",
  1607  						Tags:              []string{"a", "b"},
  1608  						CanaryTags:        []string{"d", "e"},
  1609  						EnableTagOverride: true,
  1610  						PortLabel:         "1234",
  1611  						Meta: map[string]string{
  1612  							"servicemeta": "foobar",
  1613  						},
  1614  						CheckRestart: &api.CheckRestart{
  1615  							Limit: 4,
  1616  							Grace: helper.TimeToPtr(11 * time.Second),
  1617  						},
  1618  						Checks: []api.ServiceCheck{
  1619  							{
  1620  								Id:            "hello",
  1621  								Name:          "bar",
  1622  								Type:          "http",
  1623  								Command:       "foo",
  1624  								Args:          []string{"a", "b"},
  1625  								Path:          "/check",
  1626  								Protocol:      "http",
  1627  								PortLabel:     "foo",
  1628  								AddressMode:   "driver",
  1629  								GRPCService:   "foo.Bar",
  1630  								GRPCUseTLS:    true,
  1631  								Interval:      4 * time.Second,
  1632  								Timeout:       2 * time.Second,
  1633  								InitialStatus: "ok",
  1634  								CheckRestart: &api.CheckRestart{
  1635  									Limit:          3,
  1636  									IgnoreWarnings: true,
  1637  								},
  1638  								TaskName: "task1",
  1639  							},
  1640  						},
  1641  						Connect: &api.ConsulConnect{
  1642  							Native: false,
  1643  							SidecarService: &api.ConsulSidecarService{
  1644  								Tags: []string{"f", "g"},
  1645  								Port: "9000",
  1646  							},
  1647  						},
  1648  					},
  1649  				},
  1650  				Tasks: []*api.Task{
  1651  					{
  1652  						Name:   "task1",
  1653  						Leader: true,
  1654  						Driver: "docker",
  1655  						User:   "mary",
  1656  						Config: map[string]interface{}{
  1657  							"lol": "code",
  1658  						},
  1659  						Env: map[string]string{
  1660  							"hello": "world",
  1661  						},
  1662  						Constraints: []*api.Constraint{
  1663  							{
  1664  								LTarget: "x",
  1665  								RTarget: "y",
  1666  								Operand: "z",
  1667  							},
  1668  						},
  1669  						Affinities: []*api.Affinity{
  1670  							{
  1671  								LTarget: "a",
  1672  								RTarget: "b",
  1673  								Operand: "c",
  1674  								Weight:  helper.Int8ToPtr(50),
  1675  							},
  1676  						},
  1677  						RestartPolicy: &api.RestartPolicy{
  1678  							Interval: helper.TimeToPtr(2 * time.Second),
  1679  							Attempts: helper.IntToPtr(10),
  1680  							Delay:    helper.TimeToPtr(20 * time.Second),
  1681  							Mode:     helper.StringToPtr("delay"),
  1682  						},
  1683  						Services: []*api.Service{
  1684  							{
  1685  								Id:                "id",
  1686  								Name:              "serviceA",
  1687  								Tags:              []string{"1", "2"},
  1688  								CanaryTags:        []string{"3", "4"},
  1689  								EnableTagOverride: true,
  1690  								PortLabel:         "foo",
  1691  								Meta: map[string]string{
  1692  									"servicemeta": "foobar",
  1693  								},
  1694  								CheckRestart: &api.CheckRestart{
  1695  									Limit: 4,
  1696  									Grace: helper.TimeToPtr(11 * time.Second),
  1697  								},
  1698  								Checks: []api.ServiceCheck{
  1699  									{
  1700  										Id:            "hello",
  1701  										Name:          "bar",
  1702  										Type:          "http",
  1703  										Command:       "foo",
  1704  										Args:          []string{"a", "b"},
  1705  										Path:          "/check",
  1706  										Protocol:      "http",
  1707  										PortLabel:     "foo",
  1708  										AddressMode:   "driver",
  1709  										GRPCService:   "foo.Bar",
  1710  										GRPCUseTLS:    true,
  1711  										Interval:      4 * time.Second,
  1712  										Timeout:       2 * time.Second,
  1713  										InitialStatus: "ok",
  1714  										CheckRestart: &api.CheckRestart{
  1715  											Limit:          3,
  1716  											IgnoreWarnings: true,
  1717  										},
  1718  									},
  1719  									{
  1720  										Id:        "check2id",
  1721  										Name:      "check2",
  1722  										Type:      "tcp",
  1723  										PortLabel: "foo",
  1724  										Interval:  4 * time.Second,
  1725  										Timeout:   2 * time.Second,
  1726  									},
  1727  								},
  1728  							},
  1729  						},
  1730  						Resources: &api.Resources{
  1731  							CPU:      helper.IntToPtr(100),
  1732  							MemoryMB: helper.IntToPtr(10),
  1733  							Networks: []*api.NetworkResource{
  1734  								{
  1735  									IP:    "10.10.11.1",
  1736  									MBits: helper.IntToPtr(10),
  1737  									ReservedPorts: []api.Port{
  1738  										{
  1739  											Label: "http",
  1740  											Value: 80,
  1741  										},
  1742  									},
  1743  									DynamicPorts: []api.Port{
  1744  										{
  1745  											Label: "ssh",
  1746  											Value: 2000,
  1747  										},
  1748  									},
  1749  								},
  1750  							},
  1751  							Devices: []*api.RequestedDevice{
  1752  								{
  1753  									Name:  "nvidia/gpu",
  1754  									Count: helper.Uint64ToPtr(4),
  1755  									Constraints: []*api.Constraint{
  1756  										{
  1757  											LTarget: "x",
  1758  											RTarget: "y",
  1759  											Operand: "z",
  1760  										},
  1761  									},
  1762  									Affinities: []*api.Affinity{
  1763  										{
  1764  											LTarget: "a",
  1765  											RTarget: "b",
  1766  											Operand: "c",
  1767  											Weight:  helper.Int8ToPtr(50),
  1768  										},
  1769  									},
  1770  								},
  1771  								{
  1772  									Name:  "gpu",
  1773  									Count: nil,
  1774  								},
  1775  							},
  1776  						},
  1777  						Meta: map[string]string{
  1778  							"lol": "code",
  1779  						},
  1780  						KillTimeout: helper.TimeToPtr(10 * time.Second),
  1781  						KillSignal:  "SIGQUIT",
  1782  						LogConfig: &api.LogConfig{
  1783  							MaxFiles:      helper.IntToPtr(10),
  1784  							MaxFileSizeMB: helper.IntToPtr(100),
  1785  						},
  1786  						Artifacts: []*api.TaskArtifact{
  1787  							{
  1788  								GetterSource: helper.StringToPtr("source"),
  1789  								GetterOptions: map[string]string{
  1790  									"a": "b",
  1791  								},
  1792  								GetterMode:   helper.StringToPtr("dir"),
  1793  								RelativeDest: helper.StringToPtr("dest"),
  1794  							},
  1795  						},
  1796  						Vault: &api.Vault{
  1797  							Policies:     []string{"a", "b", "c"},
  1798  							Env:          helper.BoolToPtr(true),
  1799  							ChangeMode:   helper.StringToPtr("c"),
  1800  							ChangeSignal: helper.StringToPtr("sighup"),
  1801  						},
  1802  						Templates: []*api.Template{
  1803  							{
  1804  								SourcePath:   helper.StringToPtr("source"),
  1805  								DestPath:     helper.StringToPtr("dest"),
  1806  								EmbeddedTmpl: helper.StringToPtr("embedded"),
  1807  								ChangeMode:   helper.StringToPtr("change"),
  1808  								ChangeSignal: helper.StringToPtr("signal"),
  1809  								Splay:        helper.TimeToPtr(1 * time.Minute),
  1810  								Perms:        helper.StringToPtr("666"),
  1811  								LeftDelim:    helper.StringToPtr("abc"),
  1812  								RightDelim:   helper.StringToPtr("def"),
  1813  								Envvars:      helper.BoolToPtr(true),
  1814  							},
  1815  						},
  1816  						DispatchPayload: &api.DispatchPayloadConfig{
  1817  							File: "fileA",
  1818  						},
  1819  					},
  1820  				},
  1821  			},
  1822  		},
  1823  		ConsulToken:       helper.StringToPtr("abc123"),
  1824  		VaultToken:        helper.StringToPtr("def456"),
  1825  		Status:            helper.StringToPtr("status"),
  1826  		StatusDescription: helper.StringToPtr("status_desc"),
  1827  		Version:           helper.Uint64ToPtr(10),
  1828  		CreateIndex:       helper.Uint64ToPtr(1),
  1829  		ModifyIndex:       helper.Uint64ToPtr(3),
  1830  		JobModifyIndex:    helper.Uint64ToPtr(5),
  1831  	}
  1832  
  1833  	expected := &structs.Job{
  1834  		Stop:        true,
  1835  		Region:      "global",
  1836  		Namespace:   "foo",
  1837  		ID:          "foo",
  1838  		ParentID:    "lol",
  1839  		Name:        "name",
  1840  		Type:        "service",
  1841  		Priority:    50,
  1842  		AllAtOnce:   true,
  1843  		Datacenters: []string{"dc1", "dc2"},
  1844  		Constraints: []*structs.Constraint{
  1845  			{
  1846  				LTarget: "a",
  1847  				RTarget: "b",
  1848  				Operand: "c",
  1849  			},
  1850  		},
  1851  		Affinities: []*structs.Affinity{
  1852  			{
  1853  				LTarget: "a",
  1854  				RTarget: "b",
  1855  				Operand: "c",
  1856  				Weight:  50,
  1857  			},
  1858  		},
  1859  		Spreads: []*structs.Spread{
  1860  			{
  1861  				Attribute: "${meta.rack}",
  1862  				Weight:    100,
  1863  				SpreadTarget: []*structs.SpreadTarget{
  1864  					{
  1865  						Value:   "r1",
  1866  						Percent: 50,
  1867  					},
  1868  				},
  1869  			},
  1870  		},
  1871  		Update: structs.UpdateStrategy{
  1872  			Stagger:     1 * time.Second,
  1873  			MaxParallel: 5,
  1874  		},
  1875  		Periodic: &structs.PeriodicConfig{
  1876  			Enabled:         true,
  1877  			Spec:            "spec",
  1878  			SpecType:        "cron",
  1879  			ProhibitOverlap: true,
  1880  			TimeZone:        "test zone",
  1881  		},
  1882  		ParameterizedJob: &structs.ParameterizedJobConfig{
  1883  			Payload:      "payload",
  1884  			MetaRequired: []string{"a", "b"},
  1885  			MetaOptional: []string{"c", "d"},
  1886  		},
  1887  		Payload: []byte("payload"),
  1888  		Meta: map[string]string{
  1889  			"foo": "bar",
  1890  		},
  1891  		TaskGroups: []*structs.TaskGroup{
  1892  			{
  1893  				Name:  "group1",
  1894  				Count: 5,
  1895  				Constraints: []*structs.Constraint{
  1896  					{
  1897  						LTarget: "x",
  1898  						RTarget: "y",
  1899  						Operand: "z",
  1900  					},
  1901  				},
  1902  				Affinities: []*structs.Affinity{
  1903  					{
  1904  						LTarget: "x",
  1905  						RTarget: "y",
  1906  						Operand: "z",
  1907  						Weight:  100,
  1908  					},
  1909  				},
  1910  				RestartPolicy: &structs.RestartPolicy{
  1911  					Interval: 1 * time.Second,
  1912  					Attempts: 5,
  1913  					Delay:    10 * time.Second,
  1914  					Mode:     "delay",
  1915  				},
  1916  				Spreads: []*structs.Spread{
  1917  					{
  1918  						Attribute: "${node.datacenter}",
  1919  						Weight:    100,
  1920  						SpreadTarget: []*structs.SpreadTarget{
  1921  							{
  1922  								Value:   "dc1",
  1923  								Percent: 100,
  1924  							},
  1925  						},
  1926  					},
  1927  				},
  1928  				ReschedulePolicy: &structs.ReschedulePolicy{
  1929  					Interval:      12 * time.Hour,
  1930  					Attempts:      5,
  1931  					DelayFunction: "constant",
  1932  					Delay:         30 * time.Second,
  1933  					Unlimited:     true,
  1934  					MaxDelay:      20 * time.Minute,
  1935  				},
  1936  				Migrate: &structs.MigrateStrategy{
  1937  					MaxParallel:     12,
  1938  					HealthCheck:     "task_events",
  1939  					MinHealthyTime:  12 * time.Hour,
  1940  					HealthyDeadline: 12 * time.Hour,
  1941  				},
  1942  				EphemeralDisk: &structs.EphemeralDisk{
  1943  					SizeMB:  100,
  1944  					Sticky:  true,
  1945  					Migrate: true,
  1946  				},
  1947  				Update: &structs.UpdateStrategy{
  1948  					Stagger:          1 * time.Second,
  1949  					MaxParallel:      5,
  1950  					HealthCheck:      structs.UpdateStrategyHealthCheck_Checks,
  1951  					MinHealthyTime:   2 * time.Minute,
  1952  					HealthyDeadline:  5 * time.Minute,
  1953  					ProgressDeadline: 5 * time.Minute,
  1954  					AutoRevert:       true,
  1955  					AutoPromote:      false,
  1956  					Canary:           1,
  1957  				},
  1958  				Meta: map[string]string{
  1959  					"key": "value",
  1960  				},
  1961  				Services: []*structs.Service{
  1962  					{
  1963  						Name:              "groupserviceA",
  1964  						Tags:              []string{"a", "b"},
  1965  						CanaryTags:        []string{"d", "e"},
  1966  						EnableTagOverride: true,
  1967  						PortLabel:         "1234",
  1968  						AddressMode:       "auto",
  1969  						Meta: map[string]string{
  1970  							"servicemeta": "foobar",
  1971  						},
  1972  						Checks: []*structs.ServiceCheck{
  1973  							{
  1974  								Name:          "bar",
  1975  								Type:          "http",
  1976  								Command:       "foo",
  1977  								Args:          []string{"a", "b"},
  1978  								Path:          "/check",
  1979  								Protocol:      "http",
  1980  								PortLabel:     "foo",
  1981  								AddressMode:   "driver",
  1982  								GRPCService:   "foo.Bar",
  1983  								GRPCUseTLS:    true,
  1984  								Interval:      4 * time.Second,
  1985  								Timeout:       2 * time.Second,
  1986  								InitialStatus: "ok",
  1987  								CheckRestart: &structs.CheckRestart{
  1988  									Grace:          11 * time.Second,
  1989  									Limit:          3,
  1990  									IgnoreWarnings: true,
  1991  								},
  1992  								TaskName: "task1",
  1993  							},
  1994  						},
  1995  						Connect: &structs.ConsulConnect{
  1996  							Native: false,
  1997  							SidecarService: &structs.ConsulSidecarService{
  1998  								Tags: []string{"f", "g"},
  1999  								Port: "9000",
  2000  							},
  2001  						},
  2002  					},
  2003  				},
  2004  				Tasks: []*structs.Task{
  2005  					{
  2006  						Name:   "task1",
  2007  						Driver: "docker",
  2008  						Leader: true,
  2009  						User:   "mary",
  2010  						Config: map[string]interface{}{
  2011  							"lol": "code",
  2012  						},
  2013  						Constraints: []*structs.Constraint{
  2014  							{
  2015  								LTarget: "x",
  2016  								RTarget: "y",
  2017  								Operand: "z",
  2018  							},
  2019  						},
  2020  						Affinities: []*structs.Affinity{
  2021  							{
  2022  								LTarget: "a",
  2023  								RTarget: "b",
  2024  								Operand: "c",
  2025  								Weight:  50,
  2026  							},
  2027  						},
  2028  						Env: map[string]string{
  2029  							"hello": "world",
  2030  						},
  2031  						RestartPolicy: &structs.RestartPolicy{
  2032  							Interval: 2 * time.Second,
  2033  							Attempts: 10,
  2034  							Delay:    20 * time.Second,
  2035  							Mode:     "delay",
  2036  						},
  2037  						Services: []*structs.Service{
  2038  							{
  2039  								Name:              "serviceA",
  2040  								Tags:              []string{"1", "2"},
  2041  								CanaryTags:        []string{"3", "4"},
  2042  								EnableTagOverride: true,
  2043  								PortLabel:         "foo",
  2044  								AddressMode:       "auto",
  2045  								Meta: map[string]string{
  2046  									"servicemeta": "foobar",
  2047  								},
  2048  								Checks: []*structs.ServiceCheck{
  2049  									{
  2050  										Name:          "bar",
  2051  										Type:          "http",
  2052  										Command:       "foo",
  2053  										Args:          []string{"a", "b"},
  2054  										Path:          "/check",
  2055  										Protocol:      "http",
  2056  										PortLabel:     "foo",
  2057  										AddressMode:   "driver",
  2058  										Interval:      4 * time.Second,
  2059  										Timeout:       2 * time.Second,
  2060  										InitialStatus: "ok",
  2061  										GRPCService:   "foo.Bar",
  2062  										GRPCUseTLS:    true,
  2063  										CheckRestart: &structs.CheckRestart{
  2064  											Limit:          3,
  2065  											Grace:          11 * time.Second,
  2066  											IgnoreWarnings: true,
  2067  										},
  2068  									},
  2069  									{
  2070  										Name:      "check2",
  2071  										Type:      "tcp",
  2072  										PortLabel: "foo",
  2073  										Interval:  4 * time.Second,
  2074  										Timeout:   2 * time.Second,
  2075  										CheckRestart: &structs.CheckRestart{
  2076  											Limit: 4,
  2077  											Grace: 11 * time.Second,
  2078  										},
  2079  									},
  2080  								},
  2081  							},
  2082  						},
  2083  						Resources: &structs.Resources{
  2084  							CPU:      100,
  2085  							MemoryMB: 10,
  2086  							Networks: []*structs.NetworkResource{
  2087  								{
  2088  									IP:    "10.10.11.1",
  2089  									MBits: 10,
  2090  									ReservedPorts: []structs.Port{
  2091  										{
  2092  											Label: "http",
  2093  											Value: 80,
  2094  										},
  2095  									},
  2096  									DynamicPorts: []structs.Port{
  2097  										{
  2098  											Label: "ssh",
  2099  											Value: 2000,
  2100  										},
  2101  									},
  2102  								},
  2103  							},
  2104  							Devices: []*structs.RequestedDevice{
  2105  								{
  2106  									Name:  "nvidia/gpu",
  2107  									Count: 4,
  2108  									Constraints: []*structs.Constraint{
  2109  										{
  2110  											LTarget: "x",
  2111  											RTarget: "y",
  2112  											Operand: "z",
  2113  										},
  2114  									},
  2115  									Affinities: []*structs.Affinity{
  2116  										{
  2117  											LTarget: "a",
  2118  											RTarget: "b",
  2119  											Operand: "c",
  2120  											Weight:  50,
  2121  										},
  2122  									},
  2123  								},
  2124  								{
  2125  									Name:  "gpu",
  2126  									Count: 1,
  2127  								},
  2128  							},
  2129  						},
  2130  						Meta: map[string]string{
  2131  							"lol": "code",
  2132  						},
  2133  						KillTimeout: 10 * time.Second,
  2134  						KillSignal:  "SIGQUIT",
  2135  						LogConfig: &structs.LogConfig{
  2136  							MaxFiles:      10,
  2137  							MaxFileSizeMB: 100,
  2138  						},
  2139  						Artifacts: []*structs.TaskArtifact{
  2140  							{
  2141  								GetterSource: "source",
  2142  								GetterOptions: map[string]string{
  2143  									"a": "b",
  2144  								},
  2145  								GetterMode:   "dir",
  2146  								RelativeDest: "dest",
  2147  							},
  2148  						},
  2149  						Vault: &structs.Vault{
  2150  							Policies:     []string{"a", "b", "c"},
  2151  							Env:          true,
  2152  							ChangeMode:   "c",
  2153  							ChangeSignal: "sighup",
  2154  						},
  2155  						Templates: []*structs.Template{
  2156  							{
  2157  								SourcePath:   "source",
  2158  								DestPath:     "dest",
  2159  								EmbeddedTmpl: "embedded",
  2160  								ChangeMode:   "change",
  2161  								ChangeSignal: "SIGNAL",
  2162  								Splay:        1 * time.Minute,
  2163  								Perms:        "666",
  2164  								LeftDelim:    "abc",
  2165  								RightDelim:   "def",
  2166  								Envvars:      true,
  2167  							},
  2168  						},
  2169  						DispatchPayload: &structs.DispatchPayloadConfig{
  2170  							File: "fileA",
  2171  						},
  2172  					},
  2173  				},
  2174  			},
  2175  		},
  2176  
  2177  		ConsulToken: "abc123",
  2178  		VaultToken:  "def456",
  2179  	}
  2180  
  2181  	structsJob := ApiJobToStructJob(apiJob)
  2182  
  2183  	if diff := pretty.Diff(expected, structsJob); len(diff) > 0 {
  2184  		t.Fatalf("bad:\n%s", strings.Join(diff, "\n"))
  2185  	}
  2186  
  2187  	systemAPIJob := &api.Job{
  2188  		Stop:        helper.BoolToPtr(true),
  2189  		Region:      helper.StringToPtr("global"),
  2190  		Namespace:   helper.StringToPtr("foo"),
  2191  		ID:          helper.StringToPtr("foo"),
  2192  		ParentID:    helper.StringToPtr("lol"),
  2193  		Name:        helper.StringToPtr("name"),
  2194  		Type:        helper.StringToPtr("system"),
  2195  		Priority:    helper.IntToPtr(50),
  2196  		AllAtOnce:   helper.BoolToPtr(true),
  2197  		Datacenters: []string{"dc1", "dc2"},
  2198  		Constraints: []*api.Constraint{
  2199  			{
  2200  				LTarget: "a",
  2201  				RTarget: "b",
  2202  				Operand: "c",
  2203  			},
  2204  		},
  2205  		TaskGroups: []*api.TaskGroup{
  2206  			{
  2207  				Name:  helper.StringToPtr("group1"),
  2208  				Count: helper.IntToPtr(5),
  2209  				Constraints: []*api.Constraint{
  2210  					{
  2211  						LTarget: "x",
  2212  						RTarget: "y",
  2213  						Operand: "z",
  2214  					},
  2215  				},
  2216  				RestartPolicy: &api.RestartPolicy{
  2217  					Interval: helper.TimeToPtr(1 * time.Second),
  2218  					Attempts: helper.IntToPtr(5),
  2219  					Delay:    helper.TimeToPtr(10 * time.Second),
  2220  					Mode:     helper.StringToPtr("delay"),
  2221  				},
  2222  				EphemeralDisk: &api.EphemeralDisk{
  2223  					SizeMB:  helper.IntToPtr(100),
  2224  					Sticky:  helper.BoolToPtr(true),
  2225  					Migrate: helper.BoolToPtr(true),
  2226  				},
  2227  				Meta: map[string]string{
  2228  					"key": "value",
  2229  				},
  2230  				Tasks: []*api.Task{
  2231  					{
  2232  						Name:   "task1",
  2233  						Leader: true,
  2234  						Driver: "docker",
  2235  						User:   "mary",
  2236  						Config: map[string]interface{}{
  2237  							"lol": "code",
  2238  						},
  2239  						Env: map[string]string{
  2240  							"hello": "world",
  2241  						},
  2242  						Constraints: []*api.Constraint{
  2243  							{
  2244  								LTarget: "x",
  2245  								RTarget: "y",
  2246  								Operand: "z",
  2247  							},
  2248  						},
  2249  						Resources: &api.Resources{
  2250  							CPU:      helper.IntToPtr(100),
  2251  							MemoryMB: helper.IntToPtr(10),
  2252  							Networks: []*api.NetworkResource{
  2253  								{
  2254  									IP:    "10.10.11.1",
  2255  									MBits: helper.IntToPtr(10),
  2256  									ReservedPorts: []api.Port{
  2257  										{
  2258  											Label: "http",
  2259  											Value: 80,
  2260  										},
  2261  									},
  2262  									DynamicPorts: []api.Port{
  2263  										{
  2264  											Label: "ssh",
  2265  											Value: 2000,
  2266  										},
  2267  									},
  2268  								},
  2269  							},
  2270  						},
  2271  						Meta: map[string]string{
  2272  							"lol": "code",
  2273  						},
  2274  						KillTimeout: helper.TimeToPtr(10 * time.Second),
  2275  						KillSignal:  "SIGQUIT",
  2276  						LogConfig: &api.LogConfig{
  2277  							MaxFiles:      helper.IntToPtr(10),
  2278  							MaxFileSizeMB: helper.IntToPtr(100),
  2279  						},
  2280  						Artifacts: []*api.TaskArtifact{
  2281  							{
  2282  								GetterSource: helper.StringToPtr("source"),
  2283  								GetterOptions: map[string]string{
  2284  									"a": "b",
  2285  								},
  2286  								GetterMode:   helper.StringToPtr("dir"),
  2287  								RelativeDest: helper.StringToPtr("dest"),
  2288  							},
  2289  						},
  2290  						DispatchPayload: &api.DispatchPayloadConfig{
  2291  							File: "fileA",
  2292  						},
  2293  					},
  2294  				},
  2295  			},
  2296  		},
  2297  		Status:            helper.StringToPtr("status"),
  2298  		StatusDescription: helper.StringToPtr("status_desc"),
  2299  		Version:           helper.Uint64ToPtr(10),
  2300  		CreateIndex:       helper.Uint64ToPtr(1),
  2301  		ModifyIndex:       helper.Uint64ToPtr(3),
  2302  		JobModifyIndex:    helper.Uint64ToPtr(5),
  2303  	}
  2304  
  2305  	expectedSystemJob := &structs.Job{
  2306  		Stop:        true,
  2307  		Region:      "global",
  2308  		Namespace:   "foo",
  2309  		ID:          "foo",
  2310  		ParentID:    "lol",
  2311  		Name:        "name",
  2312  		Type:        "system",
  2313  		Priority:    50,
  2314  		AllAtOnce:   true,
  2315  		Datacenters: []string{"dc1", "dc2"},
  2316  		Constraints: []*structs.Constraint{
  2317  			{
  2318  				LTarget: "a",
  2319  				RTarget: "b",
  2320  				Operand: "c",
  2321  			},
  2322  		},
  2323  		TaskGroups: []*structs.TaskGroup{
  2324  			{
  2325  				Name:  "group1",
  2326  				Count: 5,
  2327  				Constraints: []*structs.Constraint{
  2328  					{
  2329  						LTarget: "x",
  2330  						RTarget: "y",
  2331  						Operand: "z",
  2332  					},
  2333  				},
  2334  				RestartPolicy: &structs.RestartPolicy{
  2335  					Interval: 1 * time.Second,
  2336  					Attempts: 5,
  2337  					Delay:    10 * time.Second,
  2338  					Mode:     "delay",
  2339  				},
  2340  				EphemeralDisk: &structs.EphemeralDisk{
  2341  					SizeMB:  100,
  2342  					Sticky:  true,
  2343  					Migrate: true,
  2344  				},
  2345  				Meta: map[string]string{
  2346  					"key": "value",
  2347  				},
  2348  				Tasks: []*structs.Task{
  2349  					{
  2350  						Name:   "task1",
  2351  						Driver: "docker",
  2352  						Leader: true,
  2353  						User:   "mary",
  2354  						Config: map[string]interface{}{
  2355  							"lol": "code",
  2356  						},
  2357  						Constraints: []*structs.Constraint{
  2358  							{
  2359  								LTarget: "x",
  2360  								RTarget: "y",
  2361  								Operand: "z",
  2362  							},
  2363  						},
  2364  						Env: map[string]string{
  2365  							"hello": "world",
  2366  						},
  2367  						Resources: &structs.Resources{
  2368  							CPU:      100,
  2369  							MemoryMB: 10,
  2370  							Networks: []*structs.NetworkResource{
  2371  								{
  2372  									IP:    "10.10.11.1",
  2373  									MBits: 10,
  2374  									ReservedPorts: []structs.Port{
  2375  										{
  2376  											Label: "http",
  2377  											Value: 80,
  2378  										},
  2379  									},
  2380  									DynamicPorts: []structs.Port{
  2381  										{
  2382  											Label: "ssh",
  2383  											Value: 2000,
  2384  										},
  2385  									},
  2386  								},
  2387  							},
  2388  						},
  2389  						RestartPolicy: &structs.RestartPolicy{
  2390  							Interval: 1 * time.Second,
  2391  							Attempts: 5,
  2392  							Delay:    10 * time.Second,
  2393  							Mode:     "delay",
  2394  						},
  2395  						Meta: map[string]string{
  2396  							"lol": "code",
  2397  						},
  2398  						KillTimeout: 10 * time.Second,
  2399  						KillSignal:  "SIGQUIT",
  2400  						LogConfig: &structs.LogConfig{
  2401  							MaxFiles:      10,
  2402  							MaxFileSizeMB: 100,
  2403  						},
  2404  						Artifacts: []*structs.TaskArtifact{
  2405  							{
  2406  								GetterSource: "source",
  2407  								GetterOptions: map[string]string{
  2408  									"a": "b",
  2409  								},
  2410  								GetterMode:   "dir",
  2411  								RelativeDest: "dest",
  2412  							},
  2413  						},
  2414  						DispatchPayload: &structs.DispatchPayloadConfig{
  2415  							File: "fileA",
  2416  						},
  2417  					},
  2418  				},
  2419  			},
  2420  		},
  2421  	}
  2422  
  2423  	systemStructsJob := ApiJobToStructJob(systemAPIJob)
  2424  
  2425  	if diff := pretty.Diff(expectedSystemJob, systemStructsJob); len(diff) > 0 {
  2426  		t.Fatalf("bad:\n%s", strings.Join(diff, "\n"))
  2427  	}
  2428  }
  2429  
  2430  func TestJobs_ApiJobToStructsJobUpdate(t *testing.T) {
  2431  	apiJob := &api.Job{
  2432  		Update: &api.UpdateStrategy{
  2433  			Stagger:          helper.TimeToPtr(1 * time.Second),
  2434  			MaxParallel:      helper.IntToPtr(5),
  2435  			HealthCheck:      helper.StringToPtr(structs.UpdateStrategyHealthCheck_Manual),
  2436  			MinHealthyTime:   helper.TimeToPtr(1 * time.Minute),
  2437  			HealthyDeadline:  helper.TimeToPtr(3 * time.Minute),
  2438  			ProgressDeadline: helper.TimeToPtr(3 * time.Minute),
  2439  			AutoRevert:       helper.BoolToPtr(false),
  2440  			AutoPromote:      nil,
  2441  			Canary:           helper.IntToPtr(1),
  2442  		},
  2443  		TaskGroups: []*api.TaskGroup{
  2444  			{
  2445  				Update: &api.UpdateStrategy{
  2446  					Canary:     helper.IntToPtr(2),
  2447  					AutoRevert: helper.BoolToPtr(true),
  2448  				},
  2449  			}, {
  2450  				Update: &api.UpdateStrategy{
  2451  					Canary:      helper.IntToPtr(3),
  2452  					AutoPromote: helper.BoolToPtr(true),
  2453  				},
  2454  			},
  2455  		},
  2456  	}
  2457  
  2458  	structsJob := ApiJobToStructJob(apiJob)
  2459  
  2460  	// Update has been moved from job down to the groups
  2461  	jobUpdate := structs.UpdateStrategy{
  2462  		Stagger:          1000000000,
  2463  		MaxParallel:      5,
  2464  		HealthCheck:      "",
  2465  		MinHealthyTime:   0,
  2466  		HealthyDeadline:  0,
  2467  		ProgressDeadline: 0,
  2468  		AutoRevert:       false,
  2469  		AutoPromote:      false,
  2470  		Canary:           0,
  2471  	}
  2472  
  2473  	// But the groups inherit settings from the job update
  2474  	group1 := structs.UpdateStrategy{
  2475  		Stagger:          1000000000,
  2476  		MaxParallel:      5,
  2477  		HealthCheck:      "manual",
  2478  		MinHealthyTime:   60000000000,
  2479  		HealthyDeadline:  180000000000,
  2480  		ProgressDeadline: 180000000000,
  2481  		AutoRevert:       true,
  2482  		AutoPromote:      false,
  2483  		Canary:           2,
  2484  	}
  2485  
  2486  	group2 := structs.UpdateStrategy{
  2487  		Stagger:          1000000000,
  2488  		MaxParallel:      5,
  2489  		HealthCheck:      "manual",
  2490  		MinHealthyTime:   60000000000,
  2491  		HealthyDeadline:  180000000000,
  2492  		ProgressDeadline: 180000000000,
  2493  		AutoRevert:       false,
  2494  		AutoPromote:      true,
  2495  		Canary:           3,
  2496  	}
  2497  
  2498  	require.Equal(t, jobUpdate, structsJob.Update)
  2499  	require.Equal(t, group1, *structsJob.TaskGroups[0].Update)
  2500  	require.Equal(t, group2, *structsJob.TaskGroups[1].Update)
  2501  }
  2502  
  2503  // TestHTTP_JobValidate_SystemMigrate asserts that a system job with a migrate
  2504  // stanza fails to validate but does not panic (see #5477).
  2505  func TestHTTP_JobValidate_SystemMigrate(t *testing.T) {
  2506  	t.Parallel()
  2507  	httpTest(t, nil, func(s *TestAgent) {
  2508  		// Create the job
  2509  		job := &api.Job{
  2510  			Region:      helper.StringToPtr("global"),
  2511  			Datacenters: []string{"dc1"},
  2512  			ID:          helper.StringToPtr("systemmigrate"),
  2513  			Name:        helper.StringToPtr("systemmigrate"),
  2514  			TaskGroups: []*api.TaskGroup{
  2515  				{Name: helper.StringToPtr("web")},
  2516  			},
  2517  
  2518  			// System job...
  2519  			Type: helper.StringToPtr("system"),
  2520  
  2521  			// ...with an empty migrate stanza
  2522  			Migrate: &api.MigrateStrategy{},
  2523  		}
  2524  
  2525  		args := api.JobValidateRequest{
  2526  			Job:          job,
  2527  			WriteRequest: api.WriteRequest{Region: "global"},
  2528  		}
  2529  		buf := encodeReq(args)
  2530  
  2531  		// Make the HTTP request
  2532  		req, err := http.NewRequest("PUT", "/v1/validate/job", buf)
  2533  		require.NoError(t, err)
  2534  		respW := httptest.NewRecorder()
  2535  
  2536  		// Make the request
  2537  		obj, err := s.Server.ValidateJobRequest(respW, req)
  2538  		require.NoError(t, err)
  2539  
  2540  		// Check the response
  2541  		resp := obj.(structs.JobValidateResponse)
  2542  		require.Contains(t, resp.Error, `Job type "system" does not allow migrate block`)
  2543  	})
  2544  }
  2545  
  2546  func TestConversion_dereferenceInt(t *testing.T) {
  2547  	t.Parallel()
  2548  	require.Equal(t, 0, dereferenceInt(nil))
  2549  	require.Equal(t, 42, dereferenceInt(helper.IntToPtr(42)))
  2550  }
  2551  
  2552  func TestConversion_apiLogConfigToStructs(t *testing.T) {
  2553  	t.Parallel()
  2554  	require.Nil(t, apiLogConfigToStructs(nil))
  2555  	require.Equal(t, &structs.LogConfig{
  2556  		MaxFiles:      2,
  2557  		MaxFileSizeMB: 8,
  2558  	}, apiLogConfigToStructs(&api.LogConfig{
  2559  		MaxFiles:      helper.IntToPtr(2),
  2560  		MaxFileSizeMB: helper.IntToPtr(8),
  2561  	}))
  2562  }
  2563  
  2564  func TestConversion_apiConnectSidecarTaskToStructs(t *testing.T) {
  2565  	t.Parallel()
  2566  	require.Nil(t, apiConnectSidecarTaskToStructs(nil))
  2567  	delay := time.Duration(200)
  2568  	timeout := time.Duration(1000)
  2569  	config := make(map[string]interface{})
  2570  	env := make(map[string]string)
  2571  	meta := make(map[string]string)
  2572  	require.Equal(t, &structs.SidecarTask{
  2573  		Name:   "name",
  2574  		Driver: "driver",
  2575  		User:   "user",
  2576  		Config: config,
  2577  		Env:    env,
  2578  		Resources: &structs.Resources{
  2579  			CPU:      1,
  2580  			MemoryMB: 128,
  2581  		},
  2582  		Meta:        meta,
  2583  		KillTimeout: &timeout,
  2584  		LogConfig: &structs.LogConfig{
  2585  			MaxFiles:      2,
  2586  			MaxFileSizeMB: 8,
  2587  		},
  2588  		ShutdownDelay: &delay,
  2589  		KillSignal:    "SIGTERM",
  2590  	}, apiConnectSidecarTaskToStructs(&api.SidecarTask{
  2591  		Name:   "name",
  2592  		Driver: "driver",
  2593  		User:   "user",
  2594  		Config: config,
  2595  		Env:    env,
  2596  		Resources: &api.Resources{
  2597  			CPU:      helper.IntToPtr(1),
  2598  			MemoryMB: helper.IntToPtr(128),
  2599  		},
  2600  		Meta:        meta,
  2601  		KillTimeout: &timeout,
  2602  		LogConfig: &api.LogConfig{
  2603  			MaxFiles:      helper.IntToPtr(2),
  2604  			MaxFileSizeMB: helper.IntToPtr(8),
  2605  		},
  2606  		ShutdownDelay: &delay,
  2607  		KillSignal:    "SIGTERM",
  2608  	}))
  2609  }
  2610  
  2611  func TestConversion_apiConsulExposePathsToStructs(t *testing.T) {
  2612  	t.Parallel()
  2613  	require.Nil(t, apiConsulExposePathsToStructs(nil))
  2614  	require.Nil(t, apiConsulExposePathsToStructs(make([]*api.ConsulExposePath, 0)))
  2615  	require.Equal(t, []structs.ConsulExposePath{{
  2616  		Path:          "/health",
  2617  		Protocol:      "http",
  2618  		LocalPathPort: 8080,
  2619  		ListenerPort:  "hcPort",
  2620  	}}, apiConsulExposePathsToStructs([]*api.ConsulExposePath{{
  2621  		Path:          "/health",
  2622  		Protocol:      "http",
  2623  		LocalPathPort: 8080,
  2624  		ListenerPort:  "hcPort",
  2625  	}}))
  2626  }
  2627  
  2628  func TestConversion_apiConsulExposeConfigToStructs(t *testing.T) {
  2629  	t.Parallel()
  2630  	require.Nil(t, apiConsulExposeConfigToStructs(nil))
  2631  	require.Equal(t, &structs.ConsulExposeConfig{
  2632  		Paths: []structs.ConsulExposePath{{Path: "/health"}},
  2633  	}, apiConsulExposeConfigToStructs(&api.ConsulExposeConfig{
  2634  		Path: []*api.ConsulExposePath{{Path: "/health"}},
  2635  	}))
  2636  }
  2637  
  2638  func TestConversion_apiUpstreamsToStructs(t *testing.T) {
  2639  	t.Parallel()
  2640  	require.Nil(t, apiUpstreamsToStructs(nil))
  2641  	require.Nil(t, apiUpstreamsToStructs(make([]*api.ConsulUpstream, 0)))
  2642  	require.Equal(t, []structs.ConsulUpstream{{
  2643  		DestinationName: "upstream",
  2644  		LocalBindPort:   8000,
  2645  	}}, apiUpstreamsToStructs([]*api.ConsulUpstream{{
  2646  		DestinationName: "upstream",
  2647  		LocalBindPort:   8000,
  2648  	}}))
  2649  }
  2650  
  2651  func TestConversion_apiConnectSidecarServiceProxyToStructs(t *testing.T) {
  2652  	t.Parallel()
  2653  	require.Nil(t, apiConnectSidecarServiceProxyToStructs(nil))
  2654  	config := make(map[string]interface{})
  2655  	require.Equal(t, &structs.ConsulProxy{
  2656  		LocalServiceAddress: "192.168.30.1",
  2657  		LocalServicePort:    9000,
  2658  		Config:              config,
  2659  		Upstreams: []structs.ConsulUpstream{{
  2660  			DestinationName: "upstream",
  2661  		}},
  2662  		Expose: &structs.ConsulExposeConfig{
  2663  			Paths: []structs.ConsulExposePath{{Path: "/health"}},
  2664  		},
  2665  	}, apiConnectSidecarServiceProxyToStructs(&api.ConsulProxy{
  2666  		LocalServiceAddress: "192.168.30.1",
  2667  		LocalServicePort:    9000,
  2668  		Config:              config,
  2669  		Upstreams: []*api.ConsulUpstream{{
  2670  			DestinationName: "upstream",
  2671  		}},
  2672  		ExposeConfig: &api.ConsulExposeConfig{
  2673  			Path: []*api.ConsulExposePath{{
  2674  				Path: "/health",
  2675  			}},
  2676  		},
  2677  	}))
  2678  }
  2679  
  2680  func TestConversion_apiConnectSidecarServiceToStructs(t *testing.T) {
  2681  	t.Parallel()
  2682  	require.Nil(t, apiConnectSidecarTaskToStructs(nil))
  2683  	require.Equal(t, &structs.ConsulSidecarService{
  2684  		Tags: []string{"foo"},
  2685  		Port: "myPort",
  2686  		Proxy: &structs.ConsulProxy{
  2687  			LocalServiceAddress: "192.168.30.1",
  2688  		},
  2689  	}, apiConnectSidecarServiceToStructs(&api.ConsulSidecarService{
  2690  		Tags: []string{"foo"},
  2691  		Port: "myPort",
  2692  		Proxy: &api.ConsulProxy{
  2693  			LocalServiceAddress: "192.168.30.1",
  2694  		},
  2695  	}))
  2696  }
  2697  
  2698  func TestConversion_ApiConsulConnectToStructs(t *testing.T) {
  2699  	t.Parallel()
  2700  	require.Nil(t, ApiConsulConnectToStructs(nil))
  2701  	require.Equal(t, &structs.ConsulConnect{
  2702  		Native:         false,
  2703  		SidecarService: &structs.ConsulSidecarService{Port: "myPort"},
  2704  		SidecarTask:    &structs.SidecarTask{Name: "task"},
  2705  	}, ApiConsulConnectToStructs(&api.ConsulConnect{
  2706  		Native:         false,
  2707  		SidecarService: &api.ConsulSidecarService{Port: "myPort"},
  2708  		SidecarTask:    &api.SidecarTask{Name: "task"},
  2709  	}))
  2710  }