23be116da0
The `nomad volume deregister` command currently returns an error if the volume has any claims, but in cases where the claims can't be dropped because of plugin errors, providing a `-force` flag gives the operator an escape hatch. If the volume has no allocations or if they are all terminal, this flag deletes the volume from the state store, immediately and implicitly dropping all claims without further CSI RPCs. Note that this will not also unmount/detach the volume, which we'll make the responsibility of a separate `nomad volume detach` command.
269 lines
7.2 KiB
Go
269 lines
7.2 KiB
Go
package api
|
|
|
|
import (
|
|
"fmt"
|
|
"net/url"
|
|
"sort"
|
|
"time"
|
|
)
|
|
|
|
// CSIVolumes is used to query the top level csi volumes
|
|
type CSIVolumes struct {
|
|
client *Client
|
|
}
|
|
|
|
// CSIVolumes returns a handle on the CSIVolumes endpoint
|
|
func (c *Client) CSIVolumes() *CSIVolumes {
|
|
return &CSIVolumes{client: c}
|
|
}
|
|
|
|
// List returns all CSI volumes
|
|
func (v *CSIVolumes) List(q *QueryOptions) ([]*CSIVolumeListStub, *QueryMeta, error) {
|
|
var resp []*CSIVolumeListStub
|
|
qm, err := v.client.query("/v1/volumes?type=csi", &resp, q)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
sort.Sort(CSIVolumeIndexSort(resp))
|
|
return resp, qm, nil
|
|
}
|
|
|
|
// PluginList returns all CSI volumes for the specified plugin id
|
|
func (v *CSIVolumes) PluginList(pluginID string) ([]*CSIVolumeListStub, *QueryMeta, error) {
|
|
return v.List(&QueryOptions{Prefix: pluginID})
|
|
}
|
|
|
|
// Info is used to retrieve a single CSIVolume
|
|
func (v *CSIVolumes) Info(id string, q *QueryOptions) (*CSIVolume, *QueryMeta, error) {
|
|
var resp CSIVolume
|
|
qm, err := v.client.query("/v1/volume/csi/"+id, &resp, q)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
// Cleanup allocation representation for the ui
|
|
resp.allocs()
|
|
|
|
return &resp, qm, nil
|
|
}
|
|
|
|
func (v *CSIVolumes) Register(vol *CSIVolume, w *WriteOptions) (*WriteMeta, error) {
|
|
req := CSIVolumeRegisterRequest{
|
|
Volumes: []*CSIVolume{vol},
|
|
}
|
|
meta, err := v.client.write("/v1/volume/csi/"+vol.ID, req, nil, w)
|
|
return meta, err
|
|
}
|
|
|
|
func (v *CSIVolumes) Deregister(id string, force bool, w *WriteOptions) error {
|
|
_, err := v.client.delete(fmt.Sprintf("/v1/volume/csi/%v?purge=%t", url.PathEscape(id), force), nil, w)
|
|
return err
|
|
}
|
|
|
|
// CSIVolumeAttachmentMode duplicated in nomad/structs/csi.go
|
|
type CSIVolumeAttachmentMode string
|
|
|
|
const (
|
|
CSIVolumeAttachmentModeUnknown CSIVolumeAttachmentMode = ""
|
|
CSIVolumeAttachmentModeBlockDevice CSIVolumeAttachmentMode = "block-device"
|
|
CSIVolumeAttachmentModeFilesystem CSIVolumeAttachmentMode = "file-system"
|
|
)
|
|
|
|
// CSIVolumeAccessMode duplicated in nomad/structs/csi.go
|
|
type CSIVolumeAccessMode string
|
|
|
|
const (
|
|
CSIVolumeAccessModeUnknown CSIVolumeAccessMode = ""
|
|
|
|
CSIVolumeAccessModeSingleNodeReader CSIVolumeAccessMode = "single-node-reader-only"
|
|
CSIVolumeAccessModeSingleNodeWriter CSIVolumeAccessMode = "single-node-writer"
|
|
|
|
CSIVolumeAccessModeMultiNodeReader CSIVolumeAccessMode = "multi-node-reader-only"
|
|
CSIVolumeAccessModeMultiNodeSingleWriter CSIVolumeAccessMode = "multi-node-single-writer"
|
|
CSIVolumeAccessModeMultiNodeMultiWriter CSIVolumeAccessMode = "multi-node-multi-writer"
|
|
)
|
|
|
|
type CSIMountOptions struct {
|
|
FSType string `hcl:"fs_type"`
|
|
MountFlags []string `hcl:"mount_flags"`
|
|
ExtraKeysHCL []string `hcl:",unusedKeys" json:"-"` // report unexpected keys
|
|
}
|
|
|
|
type CSISecrets map[string]string
|
|
|
|
// CSIVolume is used for serialization, see also nomad/structs/csi.go
|
|
type CSIVolume struct {
|
|
ID string
|
|
Name string
|
|
ExternalID string `hcl:"external_id"`
|
|
Namespace string
|
|
Topologies []*CSITopology
|
|
AccessMode CSIVolumeAccessMode `hcl:"access_mode"`
|
|
AttachmentMode CSIVolumeAttachmentMode `hcl:"attachment_mode"`
|
|
MountOptions *CSIMountOptions `hcl:"mount_options"`
|
|
Secrets CSISecrets `hcl:"secrets"`
|
|
Parameters map[string]string `hcl:"parameters"`
|
|
Context map[string]string `hcl:"context"`
|
|
|
|
// Allocations, tracking claim status
|
|
ReadAllocs map[string]*Allocation
|
|
WriteAllocs map[string]*Allocation
|
|
|
|
// Combine structs.{Read,Write}Allocs
|
|
Allocations []*AllocationListStub
|
|
|
|
// Schedulable is true if all the denormalized plugin health fields are true
|
|
Schedulable bool
|
|
PluginID string `hcl:"plugin_id"`
|
|
Provider string
|
|
ProviderVersion string
|
|
ControllerRequired bool
|
|
ControllersHealthy int
|
|
ControllersExpected int
|
|
NodesHealthy int
|
|
NodesExpected int
|
|
ResourceExhausted time.Time
|
|
|
|
CreateIndex uint64
|
|
ModifyIndex uint64
|
|
|
|
// ExtraKeysHCL is used by the hcl parser to report unexpected keys
|
|
ExtraKeysHCL []string `hcl:",unusedKeys" json:"-"`
|
|
}
|
|
|
|
// allocs is called after we query the volume (creating this CSIVolume struct) to collapse
|
|
// allocations for the UI
|
|
func (v *CSIVolume) allocs() {
|
|
for _, a := range v.WriteAllocs {
|
|
if a != nil {
|
|
v.Allocations = append(v.Allocations, a.Stub())
|
|
}
|
|
}
|
|
for _, a := range v.ReadAllocs {
|
|
if a != nil {
|
|
v.Allocations = append(v.Allocations, a.Stub())
|
|
}
|
|
}
|
|
}
|
|
|
|
type CSIVolumeIndexSort []*CSIVolumeListStub
|
|
|
|
func (v CSIVolumeIndexSort) Len() int {
|
|
return len(v)
|
|
}
|
|
|
|
func (v CSIVolumeIndexSort) Less(i, j int) bool {
|
|
return v[i].CreateIndex > v[j].CreateIndex
|
|
}
|
|
|
|
func (v CSIVolumeIndexSort) Swap(i, j int) {
|
|
v[i], v[j] = v[j], v[i]
|
|
}
|
|
|
|
// CSIVolumeListStub omits allocations. See also nomad/structs/csi.go
|
|
type CSIVolumeListStub struct {
|
|
ID string
|
|
Namespace string
|
|
Name string
|
|
ExternalID string
|
|
Topologies []*CSITopology
|
|
AccessMode CSIVolumeAccessMode
|
|
AttachmentMode CSIVolumeAttachmentMode
|
|
Schedulable bool
|
|
PluginID string
|
|
Provider string
|
|
ControllerRequired bool
|
|
ControllersHealthy int
|
|
ControllersExpected int
|
|
NodesHealthy int
|
|
NodesExpected int
|
|
ResourceExhausted time.Time
|
|
|
|
CreateIndex uint64
|
|
ModifyIndex uint64
|
|
}
|
|
|
|
type CSIVolumeRegisterRequest struct {
|
|
Volumes []*CSIVolume
|
|
WriteRequest
|
|
}
|
|
|
|
type CSIVolumeDeregisterRequest struct {
|
|
VolumeIDs []string
|
|
WriteRequest
|
|
}
|
|
|
|
// CSI Plugins are jobs with plugin specific data
|
|
type CSIPlugins struct {
|
|
client *Client
|
|
}
|
|
|
|
type CSIPlugin struct {
|
|
ID string
|
|
Provider string
|
|
Version string
|
|
ControllerRequired bool
|
|
// Map Node.ID to CSIInfo fingerprint results
|
|
Controllers map[string]*CSIInfo
|
|
Nodes map[string]*CSIInfo
|
|
Allocations []*AllocationListStub
|
|
ControllersHealthy int
|
|
ControllersExpected int
|
|
NodesHealthy int
|
|
NodesExpected int
|
|
CreateIndex uint64
|
|
ModifyIndex uint64
|
|
}
|
|
|
|
type CSIPluginListStub struct {
|
|
ID string
|
|
Provider string
|
|
ControllerRequired bool
|
|
ControllersHealthy int
|
|
ControllersExpected int
|
|
NodesHealthy int
|
|
NodesExpected int
|
|
CreateIndex uint64
|
|
ModifyIndex uint64
|
|
}
|
|
|
|
type CSIPluginIndexSort []*CSIPluginListStub
|
|
|
|
func (v CSIPluginIndexSort) Len() int {
|
|
return len(v)
|
|
}
|
|
|
|
func (v CSIPluginIndexSort) Less(i, j int) bool {
|
|
return v[i].CreateIndex > v[j].CreateIndex
|
|
}
|
|
|
|
func (v CSIPluginIndexSort) Swap(i, j int) {
|
|
v[i], v[j] = v[j], v[i]
|
|
}
|
|
|
|
// CSIPlugins returns a handle on the CSIPlugins endpoint
|
|
func (c *Client) CSIPlugins() *CSIPlugins {
|
|
return &CSIPlugins{client: c}
|
|
}
|
|
|
|
// List returns all CSI plugins
|
|
func (v *CSIPlugins) List(q *QueryOptions) ([]*CSIPluginListStub, *QueryMeta, error) {
|
|
var resp []*CSIPluginListStub
|
|
qm, err := v.client.query("/v1/plugins?type=csi", &resp, q)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
sort.Sort(CSIPluginIndexSort(resp))
|
|
return resp, qm, nil
|
|
}
|
|
|
|
// Info is used to retrieve a single CSI Plugin Job
|
|
func (v *CSIPlugins) Info(id string, q *QueryOptions) (*CSIPlugin, *QueryMeta, error) {
|
|
var resp *CSIPlugin
|
|
qm, err := v.client.query("/v1/plugin/csi/"+id, &resp, q)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
return resp, qm, nil
|
|
}
|