Files
nomad/command/agent/host_volume_endpoint.go
Tim Gross 3f59860254 host volumes: add configuration to GC on node GC (#25903)
When a node is garbage collected, any dynamic host volumes on the node are
orphaned in the state store. We generally don't want to automatically collect
these volumes and risk data loss, and have provided a CLI flag to `-force`
remove them in #25902. But for clusters running on ephemeral cloud
instances (ex. AWS EC2 in an autoscaling group), deleting host volumes may add
excessive friction. Add a configuration knob to the client configuration to
remove host volumes from the state store on node GC.

Ref: https://github.com/hashicorp/nomad/pull/25902
Ref: https://github.com/hashicorp/nomad/issues/25762
Ref: https://hashicorp.atlassian.net/browse/NMD-705
2025-05-27 10:22:08 -04:00

156 lines
4.0 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package agent
import (
"net/http"
"strconv"
"strings"
"github.com/hashicorp/nomad/nomad/structs"
)
func (s *HTTPServer) HostVolumesListRequest(resp http.ResponseWriter, req *http.Request) (any, error) {
args := structs.HostVolumeListRequest{}
if s.parse(resp, req, &args.Region, &args.QueryOptions) {
return nil, nil
}
query := req.URL.Query()
args.Prefix = query.Get("prefix")
args.NodePool = query.Get("node_pool")
args.NodeID = query.Get("node_id")
var out structs.HostVolumeListResponse
if err := s.agent.RPC("HostVolume.List", &args, &out); err != nil {
return nil, err
}
setMeta(resp, &out.QueryMeta)
return out.Volumes, nil
}
// HostVolumeSpecificRequest dispatches GET and PUT
func (s *HTTPServer) HostVolumeSpecificRequest(resp http.ResponseWriter, req *http.Request) (any, error) {
// Tokenize the suffix of the path to get the volume id, tolerating a
// present or missing trailing slash
reqSuffix := strings.TrimPrefix(req.URL.Path, "/v1/volume/host/")
tokens := strings.FieldsFunc(reqSuffix, func(c rune) bool { return c == '/' })
if len(tokens) == 0 {
return nil, CodedError(404, resourceNotFoundErr)
}
switch req.Method {
// PUT /v1/volume/host/create
// POST /v1/volume/host/create
// PUT /v1/volume/host/register
// POST /v1/volume/host/register
case http.MethodPut, http.MethodPost:
switch tokens[0] {
case "create", "":
return s.hostVolumeCreate(resp, req)
case "register":
return s.hostVolumeRegister(resp, req)
default:
return nil, CodedError(404, resourceNotFoundErr)
}
// DELETE /v1/volume/host/:id
case http.MethodDelete:
return s.hostVolumeDelete(tokens[0], resp, req)
// GET /v1/volume/host/:id
case http.MethodGet:
return s.hostVolumeGet(tokens[0], resp, req)
}
return nil, CodedError(404, resourceNotFoundErr)
}
func (s *HTTPServer) hostVolumeGet(id string, resp http.ResponseWriter, req *http.Request) (any, error) {
args := structs.HostVolumeGetRequest{
ID: id,
}
if s.parse(resp, req, &args.Region, &args.QueryOptions) {
return nil, nil
}
var out structs.HostVolumeGetResponse
if err := s.agent.RPC("HostVolume.Get", &args, &out); err != nil {
return nil, err
}
setMeta(resp, &out.QueryMeta)
if out.Volume == nil {
return nil, CodedError(404, "volume not found")
}
return out.Volume, nil
}
func (s *HTTPServer) hostVolumeRegister(resp http.ResponseWriter, req *http.Request) (any, error) {
args := structs.HostVolumeRegisterRequest{}
if err := decodeBody(req, &args); err != nil {
return err, CodedError(400, err.Error())
}
s.parseWriteRequest(req, &args.WriteRequest)
var out structs.HostVolumeRegisterResponse
if err := s.agent.RPC("HostVolume.Register", &args, &out); err != nil {
return nil, err
}
setIndex(resp, out.Index)
return &out, nil
}
func (s *HTTPServer) hostVolumeCreate(resp http.ResponseWriter, req *http.Request) (any, error) {
args := structs.HostVolumeCreateRequest{}
if err := decodeBody(req, &args); err != nil {
return err, CodedError(400, err.Error())
}
s.parseWriteRequest(req, &args.WriteRequest)
var out structs.HostVolumeCreateResponse
if err := s.agent.RPC("HostVolume.Create", &args, &out); err != nil {
return nil, err
}
setIndex(resp, out.Index)
return &out, nil
}
func (s *HTTPServer) hostVolumeDelete(id string, resp http.ResponseWriter, req *http.Request) (any, error) {
// HTTP API only supports deleting a single ID because of compatibility with
// the existing HTTP routes for CSI
args := structs.HostVolumeDeleteRequest{VolumeID: id}
s.parseWriteRequest(req, &args.WriteRequest)
raw := req.URL.Query().Get("force")
var force bool
if raw != "" {
var err error
force, err = strconv.ParseBool(raw)
if err != nil {
return nil, CodedError(400, "invalid force value")
}
}
args.Force = force
var out structs.HostVolumeDeleteResponse
if err := s.agent.RPC("HostVolume.Delete", &args, &out); err != nil {
return nil, err
}
setIndex(resp, out.Index)
return out, nil
}