diff --git a/nomad/job_endpoint.go b/nomad/job_endpoint.go index 379c759d4c4..01cc4343759 100644 --- a/nomad/job_endpoint.go +++ b/nomad/job_endpoint.go @@ -1039,6 +1039,18 @@ func (j *Job) Scale(args *structs.JobScaleRequest, reply *structs.JobRegisterRes prevCount := found.Count if args.Count != nil { + // if there is a scaling policy, check that the new count is within bounds + if found.Scaling != nil { + if *args.Count < found.Scaling.Min { + return structs.NewErrRPCCoded(400, + fmt.Sprintf("new scaling count cannot be less than the scaling policy minimum")) + } + if found.Scaling.Max < *args.Count { + return structs.NewErrRPCCoded(400, + fmt.Sprintf("new scaling count cannot be greater than the scaling policy maximum")) + } + } + // Lookup the latest deployment, to see whether this scaling event should be blocked d, err := snap.LatestDeploymentByJobID(ws, namespace, args.JobID) if err != nil { diff --git a/nomad/job_endpoint_test.go b/nomad/job_endpoint_test.go index 684557060f6..c255f8cfb95 100644 --- a/nomad/job_endpoint_test.go +++ b/nomad/job_endpoint_test.go @@ -6705,6 +6705,50 @@ func TestJobEndpoint_Scale_Invalid(t *testing.T) { require.Contains(err.Error(), "should not contain count if error is true") } +func TestJobEndpoint_Scale_OutOfBounds(t *testing.T) { + t.Parallel() + require := require.New(t) + + s1, cleanupS1 := TestServer(t, nil) + defer cleanupS1() + codec := rpcClient(t, s1) + testutil.WaitForLeader(t, s1.RPC) + state := s1.fsm.State() + + job, pol := mock.JobWithScalingPolicy() + pol.Min = 3 + pol.Max = 10 + job.TaskGroups[0].Count = 5 + + // register the job + err := state.UpsertJob(structs.MsgTypeTestSetup, 1000, job) + require.Nil(err) + + var resp structs.JobRegisterResponse + scale := &structs.JobScaleRequest{ + JobID: job.ID, + Target: map[string]string{ + structs.ScalingTargetGroup: job.TaskGroups[0].Name, + }, + Count: helper.Int64ToPtr(pol.Max + 1), + Message: "too high", + PolicyOverride: false, + WriteRequest: structs.WriteRequest{ + Region: "global", + Namespace: job.Namespace, + }, + } + err = msgpackrpc.CallWithCodec(codec, "Job.Scale", scale, &resp) + require.Error(err) + require.Contains(err.Error(), "cannot be greater than") + + scale.Count = helper.Int64ToPtr(pol.Min - 1) + scale.Message = "too low" + err = msgpackrpc.CallWithCodec(codec, "Job.Scale", scale, &resp) + require.Error(err) + require.Contains(err.Error(), "cannot be less than") +} + func TestJobEndpoint_Scale_NoEval(t *testing.T) { t.Parallel() require := require.New(t)