mirror of
https://github.com/kemko/nomad.git
synced 2026-01-01 16:05:42 +03:00
When a node is garbage collected, we leave behind the dynamic host volume in the state store. We don't want to automatically garbage collect the volumes and risk data loss, but we should allow these to be removed via the API. Fixes: https://github.com/hashicorp/nomad/issues/25762 Fixes: https://hashicorp.atlassian.net/browse/NMD-705
254 lines
8.2 KiB
Go
254 lines
8.2 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package api
|
|
|
|
import "net/url"
|
|
|
|
// HostVolume represents a Dynamic Host Volume: a volume associated with a
|
|
// specific Nomad client agent but created via API.
|
|
type HostVolume struct {
|
|
// Namespace is the Nomad namespace for the host volume, which constrains
|
|
// which jobs can mount it.
|
|
Namespace string `mapstructure:"namespace" hcl:"namespace"`
|
|
|
|
// ID is a UUID-like string generated by the server.
|
|
ID string `mapstructure:"id" hcl:"id"`
|
|
|
|
// Name is the name that group.volume will use to identify the volume
|
|
// source. Not expected to be unique.
|
|
Name string `mapstructure:"name" hcl:"name"`
|
|
|
|
// PluginID is the name of the host volume plugin on the client that will be
|
|
// used for creating the volume. If omitted, the client will use its default
|
|
// built-in plugin.
|
|
PluginID string `mapstructure:"plugin_id" hcl:"plugin_id"`
|
|
|
|
// NodePool is the node pool of the node where the volume is placed. If the
|
|
// user doesn't provide a node ID, a node will be selected using the
|
|
// NodePool and Constraints. If the user provides both NodePool and NodeID,
|
|
// NodePool will be used to validate the request. If omitted, the server
|
|
// will populate this value in before writing the volume to Raft.
|
|
NodePool string `mapstructure:"node_pool" hcl:"node_pool"`
|
|
|
|
// NodeID is the node where the volume is placed. If the user doesn't
|
|
// provide a NodeID, one will be selected using the NodePool and
|
|
// Constraints. If omitted, this field will then be populated by the server
|
|
// before writing the volume to Raft.
|
|
NodeID string `mapstructure:"node_id" hcl:"node_id"`
|
|
|
|
// Constraints are optional. If the NodeID is not provided, the NodePool and
|
|
// Constraints are used to select a node. If the NodeID is provided,
|
|
// Constraints are used to validate that the node meets those constraints at
|
|
// the time of volume creation.
|
|
Constraints []*Constraint `json:",omitempty" hcl:"constraint"`
|
|
|
|
// Because storage may allow only specific intervals of size, we accept a
|
|
// min and max and return the actual capacity when the volume is created or
|
|
// updated on the client
|
|
RequestedCapacityMinBytes int64 `mapstructure:"capacity_min" hcl:"capacity_min"`
|
|
RequestedCapacityMaxBytes int64 `mapstructure:"capacity_max" hcl:"capacity_max"`
|
|
CapacityBytes int64 `mapstructure:"capacity" hcl:"capacity"`
|
|
|
|
// RequestedCapabilities defines the options available to group.volume
|
|
// blocks. The scheduler checks against the listed capability blocks and
|
|
// selects a node for placement if *any* capability block works.
|
|
RequestedCapabilities []*HostVolumeCapability `hcl:"capability"`
|
|
|
|
// Parameters are an opaque map of parameters for the host volume plugin.
|
|
Parameters map[string]string `json:",omitempty"`
|
|
|
|
// HostPath is the path on disk where the volume's mount point was
|
|
// created. We record this to make debugging easier.
|
|
HostPath string `mapstructure:"host_path" hcl:"host_path"`
|
|
|
|
// State represents the overall state of the volume. One of pending, ready,
|
|
// deleted.
|
|
State HostVolumeState
|
|
|
|
CreateIndex uint64
|
|
CreateTime int64
|
|
|
|
ModifyIndex uint64
|
|
ModifyTime int64
|
|
|
|
// Allocations is the list of non-client-terminal allocations with claims on
|
|
// this host volume. They are denormalized on read and this field will be
|
|
// never written to Raft
|
|
Allocations []*AllocationListStub `json:",omitempty" mapstructure:"-" hcl:"-"`
|
|
}
|
|
|
|
// HostVolume state reports the current status of the host volume
|
|
type HostVolumeState string
|
|
|
|
const (
|
|
HostVolumeStatePending HostVolumeState = "pending"
|
|
HostVolumeStateReady HostVolumeState = "ready"
|
|
HostVolumeStateUnavailable HostVolumeState = "unavailable"
|
|
)
|
|
|
|
// HostVolumeCapability is the requested attachment and access mode for a volume
|
|
type HostVolumeCapability struct {
|
|
AttachmentMode HostVolumeAttachmentMode `mapstructure:"attachment_mode" hcl:"attachment_mode"`
|
|
AccessMode HostVolumeAccessMode `mapstructure:"access_mode" hcl:"access_mode"`
|
|
}
|
|
|
|
// HostVolumeAttachmentMode chooses the type of storage API that will be used to
|
|
// interact with the device.
|
|
type HostVolumeAttachmentMode string
|
|
|
|
const (
|
|
HostVolumeAttachmentModeUnknown HostVolumeAttachmentMode = ""
|
|
HostVolumeAttachmentModeBlockDevice HostVolumeAttachmentMode = "block-device"
|
|
HostVolumeAttachmentModeFilesystem HostVolumeAttachmentMode = "file-system"
|
|
)
|
|
|
|
// HostVolumeAccessMode indicates how Nomad should make the volume available to
|
|
// concurrent allocations.
|
|
type HostVolumeAccessMode string
|
|
|
|
const (
|
|
HostVolumeAccessModeUnknown HostVolumeAccessMode = ""
|
|
|
|
HostVolumeAccessModeSingleNodeReader HostVolumeAccessMode = "single-node-reader-only"
|
|
HostVolumeAccessModeSingleNodeWriter HostVolumeAccessMode = "single-node-writer"
|
|
HostVolumeAccessModeSingleNodeSingleWriter HostVolumeAccessMode = "single-node-single-writer"
|
|
HostVolumeAccessModeSingleNodeMultiWriter HostVolumeAccessMode = "single-node-multi-writer"
|
|
)
|
|
|
|
// HostVolumeStub is used for responses for the List Volumes endpoint
|
|
type HostVolumeStub struct {
|
|
Namespace string
|
|
ID string
|
|
Name string
|
|
PluginID string
|
|
NodePool string
|
|
NodeID string
|
|
CapacityBytes int64
|
|
State HostVolumeState
|
|
|
|
CreateIndex uint64
|
|
CreateTime int64
|
|
|
|
ModifyIndex uint64
|
|
ModifyTime int64
|
|
}
|
|
|
|
// HostVolumes is used to access the host volumes API.
|
|
type HostVolumes struct {
|
|
client *Client
|
|
}
|
|
|
|
// HostVolumes returns a new handle on the host volumes API.
|
|
func (c *Client) HostVolumes() *HostVolumes {
|
|
return &HostVolumes{client: c}
|
|
}
|
|
|
|
type HostVolumeCreateRequest struct {
|
|
Volume *HostVolume
|
|
|
|
// PolicyOverride overrides Sentinel soft-mandatory policy enforcement
|
|
PolicyOverride bool
|
|
}
|
|
|
|
type HostVolumeRegisterRequest struct {
|
|
Volume *HostVolume
|
|
|
|
// PolicyOverride overrides Sentinel soft-mandatory policy enforcement
|
|
PolicyOverride bool
|
|
}
|
|
|
|
type HostVolumeCreateResponse struct {
|
|
Volume *HostVolume
|
|
Warnings string
|
|
}
|
|
|
|
type HostVolumeRegisterResponse struct {
|
|
Volume *HostVolume
|
|
Warnings string
|
|
}
|
|
|
|
type HostVolumeListRequest struct {
|
|
NodeID string
|
|
NodePool string
|
|
}
|
|
|
|
type HostVolumeDeleteRequest struct {
|
|
ID string
|
|
Force bool
|
|
}
|
|
|
|
type HostVolumeDeleteResponse struct{}
|
|
|
|
// Create forwards to client agents so a host volume can be created on those
|
|
// hosts, and registers the volume with Nomad servers.
|
|
func (hv *HostVolumes) Create(req *HostVolumeCreateRequest, opts *WriteOptions) (*HostVolumeCreateResponse, *WriteMeta, error) {
|
|
var out *HostVolumeCreateResponse
|
|
wm, err := hv.client.put("/v1/volume/host/create", req, &out, opts)
|
|
if err != nil {
|
|
return nil, wm, err
|
|
}
|
|
return out, wm, nil
|
|
}
|
|
|
|
// Register registers a host volume that was created out-of-band with the Nomad
|
|
// servers.
|
|
func (hv *HostVolumes) Register(req *HostVolumeRegisterRequest, opts *WriteOptions) (*HostVolumeRegisterResponse, *WriteMeta, error) {
|
|
var out *HostVolumeRegisterResponse
|
|
wm, err := hv.client.put("/v1/volume/host/register", req, &out, opts)
|
|
if err != nil {
|
|
return nil, wm, err
|
|
}
|
|
return out, wm, nil
|
|
}
|
|
|
|
// Get queries for a single host volume, by ID
|
|
func (hv *HostVolumes) Get(id string, opts *QueryOptions) (*HostVolume, *QueryMeta, error) {
|
|
var out *HostVolume
|
|
path, err := url.JoinPath("/v1/volume/host/", url.PathEscape(id))
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
qm, err := hv.client.query(path, &out, opts)
|
|
if err != nil {
|
|
return nil, qm, err
|
|
}
|
|
return out, qm, nil
|
|
}
|
|
|
|
// List queries for a set of host volumes, by namespace, node, node pool, or
|
|
// name prefix.
|
|
func (hv *HostVolumes) List(req *HostVolumeListRequest, opts *QueryOptions) ([]*HostVolumeStub, *QueryMeta, error) {
|
|
var out []*HostVolumeStub
|
|
qv := url.Values{}
|
|
qv.Set("type", "host")
|
|
if req != nil {
|
|
if req.NodeID != "" {
|
|
qv.Set("node_id", req.NodeID)
|
|
}
|
|
if req.NodePool != "" {
|
|
qv.Set("node_pool", req.NodePool)
|
|
}
|
|
}
|
|
|
|
qm, err := hv.client.query("/v1/volumes?"+qv.Encode(), &out, opts)
|
|
if err != nil {
|
|
return nil, qm, err
|
|
}
|
|
return out, qm, nil
|
|
}
|
|
|
|
// Delete deletes a host volume
|
|
func (hv *HostVolumes) Delete(req *HostVolumeDeleteRequest, opts *WriteOptions) (*HostVolumeDeleteResponse, *WriteMeta, error) {
|
|
var resp *HostVolumeDeleteResponse
|
|
path, err := url.JoinPath("/v1/volume/host/", url.PathEscape(req.ID))
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
if req.Force {
|
|
path = path + "?force=true"
|
|
}
|
|
wm, err := hv.client.delete(path, nil, resp, opts)
|
|
return resp, wm, err
|
|
}
|