-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
dynamic host volumes: basic CLI CRUD #24382
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,236 @@ | ||
// 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 | ||
|
||
// 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" | ||
HostVolumeStateDeleted HostVolumeState = "deleted" | ||
Comment on lines
+85
to
+87
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what if a node becomes unavailable, temporary or otherwise? would it go back to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We didn't really cover that feature in the RFC. I think we'll need to figure out whether there's any notion of liveness when we start on the client-side work. |
||
) | ||
|
||
// 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" | ||
|
||
HostVolumeAccessModeMultiNodeReader HostVolumeAccessMode = "multi-node-reader-only" | ||
HostVolumeAccessModeMultiNodeSingleWriter HostVolumeAccessMode = "multi-node-single-writer" | ||
HostVolumeAccessModeMultiNodeMultiWriter HostVolumeAccessMode = "multi-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 { | ||
Volumes []*HostVolume | ||
} | ||
|
||
type HostVolumeRegisterRequest struct { | ||
Volumes []*HostVolume | ||
} | ||
|
||
type HostVolumeListRequest struct { | ||
NodeID string | ||
NodePool string | ||
} | ||
|
||
type HostVolumeDeleteRequest struct { | ||
VolumeIDs []string | ||
} | ||
|
||
// Create forwards to client agents so host volumes can be created on those | ||
// hosts, and registers the volumes with Nomad servers. | ||
func (hv *HostVolumes) Create(req *HostVolumeCreateRequest, opts *WriteOptions) ([]*HostVolume, *WriteMeta, error) { | ||
var out struct { | ||
Volumes []*HostVolume | ||
} | ||
wm, err := hv.client.put("/v1/volume/host/create", req, &out, opts) | ||
if err != nil { | ||
return nil, wm, err | ||
} | ||
return out.Volumes, wm, nil | ||
} | ||
|
||
// Register registers host volumes that were created out-of-band with the Nomad | ||
// servers. | ||
func (hv *HostVolumes) Register(req *HostVolumeRegisterRequest, opts *WriteOptions) ([]*HostVolume, *WriteMeta, error) { | ||
var out struct { | ||
Volumes []*HostVolume | ||
} | ||
wm, err := hv.client.put("/v1/volume/host/register", req, &out, opts) | ||
if err != nil { | ||
return nil, wm, err | ||
} | ||
return out.Volumes, 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(id string, opts *WriteOptions) (*WriteMeta, error) { | ||
path, err := url.JoinPath("/v1/volume/host/", url.PathEscape(id)) | ||
if err != nil { | ||
return nil, err | ||
} | ||
wm, err := hv.client.delete(path, nil, nil, opts) | ||
return wm, err | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
users can't (shouldn't be able to) override this, right? so why hcl tag?
...on second thought, I guess it must be for
Register
, where the volume will have been created out-of-band.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, only for
Register
.