Files
nomad/command/agent/scaling_endpoint.go
Tim Gross 5e1ad14f1f scaling policy: use request namespace as target if unset in jobspec (#24065)
When jobs are submitted with a scaling policy, the scaling policy's target only
includes the job's namespace if the `namespace` field is set in the jobspec and
not from the request. Normally jobs are canonicalized in the RPC handler before
being written to Raft. But the scaling policy targets are instead written during
the conversion from `api.Job` to `structs.Job`. We populate the `structs.Job`
namespace from the request here as well, but only after the conversion has
occurred. Swap the order of these operations so that the conversion is always
happening with a correct namespace.

Long-term we should not be making mutations during conversion either. But we
can't remove it immediately because API requests may come from any agent across
upgrades. Move the scaling target creation into the `Canonicalize` method and
mark it for future removal in the API conversion code path.

Fixes: https://github.com/hashicorp/nomad/issues/24039
2024-10-01 11:41:40 -04:00

112 lines
2.8 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package agent
import (
"net/http"
"strings"
"github.com/hashicorp/nomad/api"
"github.com/hashicorp/nomad/nomad/structs"
)
func (s *HTTPServer) ScalingPoliciesRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
switch req.Method {
case http.MethodGet:
return s.scalingPoliciesListRequest(resp, req)
default:
return nil, CodedError(405, ErrInvalidMethod)
}
}
func (s *HTTPServer) scalingPoliciesListRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
args := structs.ScalingPolicyListRequest{}
if s.parse(resp, req, &args.Region, &args.QueryOptions) {
return nil, nil
}
if job := req.URL.Query().Get("job"); job != "" {
args.Job = job
}
if tpe := req.URL.Query().Get("type"); tpe != "" {
args.Type = tpe
}
var out structs.ScalingPolicyListResponse
if err := s.agent.RPC("Scaling.ListPolicies", &args, &out); err != nil {
return nil, err
}
setMeta(resp, &out.QueryMeta)
if out.Policies == nil {
out.Policies = make([]*structs.ScalingPolicyListStub, 0)
}
return out.Policies, nil
}
func (s *HTTPServer) ScalingPolicySpecificRequest(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
path := strings.TrimPrefix(req.URL.Path, "/v1/scaling/policy/")
return s.scalingPolicyCRUD(resp, req, path)
}
func (s *HTTPServer) scalingPolicyCRUD(resp http.ResponseWriter, req *http.Request,
policyID string) (interface{}, error) {
switch req.Method {
case http.MethodGet:
return s.scalingPolicyQuery(resp, req, policyID)
default:
return nil, CodedError(405, ErrInvalidMethod)
}
}
func (s *HTTPServer) scalingPolicyQuery(resp http.ResponseWriter, req *http.Request,
policyID string) (interface{}, error) {
args := structs.ScalingPolicySpecificRequest{
ID: policyID,
}
if s.parse(resp, req, &args.Region, &args.QueryOptions) {
return nil, nil
}
var out structs.SingleScalingPolicyResponse
if err := s.agent.RPC("Scaling.GetPolicy", &args, &out); err != nil {
return nil, err
}
setMeta(resp, &out.QueryMeta)
if out.Policy == nil {
return nil, CodedError(404, "policy not found")
}
return out.Policy, nil
}
func ApiScalingPolicyToStructs(job *structs.Job, tg *structs.TaskGroup, task *structs.Task, count int, ap *api.ScalingPolicy) *structs.ScalingPolicy {
p := structs.ScalingPolicy{
Type: ap.Type,
Policy: ap.Policy,
Target: map[string]string{},
}
if ap.Enabled != nil {
p.Enabled = *ap.Enabled
} else {
p.Enabled = true
}
if ap.Max != nil {
p.Max = *ap.Max
} else {
// catch this in Validate
p.Max = -1
}
if ap.Min != nil {
p.Min = *ap.Min
} else {
p.Min = int64(count)
}
// COMPAT(1.12.0) - canonicalization is done in Job.Register as of 1.9,
// remove this canonicalization in 1.12.0 LTS
p.Canonicalize(job, tg, task)
return &p
}