diff --git a/command/agent/job_endpoint.go b/command/agent/job_endpoint.go index d650ffef6..b7671b4ac 100644 --- a/command/agent/job_endpoint.go +++ b/command/agent/job_endpoint.go @@ -413,6 +413,7 @@ func (s *HTTPServer) jobUpdate(resp http.ResponseWriter, req *http.Request, EnforceIndex: args.EnforceIndex, JobModifyIndex: args.JobModifyIndex, PolicyOverride: args.PolicyOverride, + PreserveCounts: args.PreserveCounts, WriteRequest: structs.WriteRequest{ Region: sJob.Region, AuthToken: args.WriteRequest.SecretID, diff --git a/nomad/job_endpoint_test.go b/nomad/job_endpoint_test.go index d15f8aa67..8db4085c8 100644 --- a/nomad/job_endpoint_test.go +++ b/nomad/job_endpoint_test.go @@ -108,6 +108,67 @@ func TestJobEndpoint_Register(t *testing.T) { } } +func TestJobEndpoint_Register_PreserveCounts(t *testing.T) { + t.Parallel() + require := require.New(t) + + s1, cleanupS1 := TestServer(t, func(c *Config) { + c.NumSchedulers = 0 // Prevent automatic dequeue + }) + defer cleanupS1() + codec := rpcClient(t, s1) + testutil.WaitForLeader(t, s1.RPC) + + // Create the register request + job := mock.Job() + job.TaskGroups[0].Name = "group1" + job.TaskGroups[0].Count = 10 + job.Canonicalize() + + // Register the job + require.NoError(msgpackrpc.CallWithCodec(codec, "Job.Register", &structs.JobRegisterRequest{ + Job: job, + WriteRequest: structs.WriteRequest{ + Region: "global", + Namespace: job.Namespace, + }, + }, &structs.JobRegisterResponse{})) + + // Check the job in the FSM state + state := s1.fsm.State() + out, err := state.JobByID(nil, job.Namespace, job.ID) + require.NoError(err) + require.NotNil(out) + require.Equal(10, out.TaskGroups[0].Count) + + // New version: + // new "group2" with 2 instances + // "group1" goes from 10 -> 0 in the spec + job = job.Copy() + job.TaskGroups[0].Count = 0 // 10 -> 0 in the job spec + job.TaskGroups = append(job.TaskGroups, job.TaskGroups[0].Copy()) + job.TaskGroups[1].Name = "group2" + job.TaskGroups[1].Count = 2 + + // Perform the update + require.NoError(msgpackrpc.CallWithCodec(codec, "Job.Register", &structs.JobRegisterRequest{ + Job: job, + PreserveCounts: true, + WriteRequest: structs.WriteRequest{ + Region: "global", + Namespace: job.Namespace, + }, + }, &structs.JobRegisterResponse{})) + + // Check the job in the FSM state + out, err = state.JobByID(nil, job.Namespace, job.ID) + require.NoError(err) + require.NotNil(out) + require.Equal(10, out.TaskGroups[0].Count) // should not change + require.Equal(2, out.TaskGroups[1].Count) // should be as in job spec +} + + func TestJobEndpoint_Register_Connect(t *testing.T) { t.Parallel() require := require.New(t) diff --git a/nomad/structs/structs.go b/nomad/structs/structs.go index 922c4804d..4949f0fb6 100644 --- a/nomad/structs/structs.go +++ b/nomad/structs/structs.go @@ -549,6 +549,11 @@ type JobRegisterRequest struct { EnforceIndex bool JobModifyIndex uint64 + // PreserveCounts indicates that during job update, existing task group + // counts should be preserved, over those specified in the new job spec + // PreserveCounts is ignored for newly created jobs. + PreserveCounts bool + // PolicyOverride is set when the user is attempting to override any policies PolicyOverride bool diff --git a/vendor/github.com/hashicorp/nomad/api/api.go b/vendor/github.com/hashicorp/nomad/api/api.go index 83ccf6bd2..9633efb0e 100644 --- a/vendor/github.com/hashicorp/nomad/api/api.go +++ b/vendor/github.com/hashicorp/nomad/api/api.go @@ -943,7 +943,10 @@ func decodeBody(resp *http.Response, out interface{}) error { } } -// encodeBody is used to encode a request body +// encodeBody prepares the reader to serve as the request body. +// +// Returns the `obj` input if it is a raw io.Reader object; otherwise +// returns a reader of the json format of the passed argument. func encodeBody(obj interface{}) (io.Reader, error) { if reader, ok := obj.(io.Reader); ok { return reader, nil diff --git a/vendor/github.com/hashicorp/nomad/api/jobs.go b/vendor/github.com/hashicorp/nomad/api/jobs.go index 578659511..6607717a1 100644 --- a/vendor/github.com/hashicorp/nomad/api/jobs.go +++ b/vendor/github.com/hashicorp/nomad/api/jobs.go @@ -87,6 +87,7 @@ type RegisterOptions struct { EnforceIndex bool ModifyIndex uint64 PolicyOverride bool + PreserveCounts bool } // Register is used to register a new job. It returns the ID @@ -1041,9 +1042,10 @@ type JobRegisterRequest struct { // If EnforceIndex is set then the job will only be registered if the passed // JobModifyIndex matches the current Jobs index. If the index is zero, the // register only occurs if the job is new. - EnforceIndex bool - JobModifyIndex uint64 - PolicyOverride bool + EnforceIndex bool `json:",omitempty"` + JobModifyIndex uint64 `json:",omitempty"` + PolicyOverride bool `json:",omitempty"` + PreserveCounts bool `json:",omitempty"` WriteRequest } diff --git a/vendor/github.com/hashicorp/nomad/api/scaling.go b/vendor/github.com/hashicorp/nomad/api/scaling.go index 1b3c3badf..3a31abd89 100644 --- a/vendor/github.com/hashicorp/nomad/api/scaling.go +++ b/vendor/github.com/hashicorp/nomad/api/scaling.go @@ -92,11 +92,12 @@ type TaskGroupScaleStatus struct { } type ScalingEvent struct { - Count *int64 - Error bool - Message string - Meta map[string]interface{} - EvalID *string - Time uint64 - CreateIndex uint64 + Count *int64 + PreviousCount int64 + Error bool + Message string + Meta map[string]interface{} + EvalID *string + Time uint64 + CreateIndex uint64 }