mirror of
https://github.com/kemko/nomad.git
synced 2026-01-01 16:05:42 +03:00
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
156 lines
4.0 KiB
Go
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
|
|
}
|