494 lines
16 KiB
Go
494 lines
16 KiB
Go
package api
|
|
|
|
import (
|
|
"fmt"
|
|
"net/url"
|
|
"sort"
|
|
"time"
|
|
)
|
|
|
|
// CSIVolumes is used to access Container Storage Interface (CSI) endpoints.
|
|
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
|
|
}
|
|
|
|
// ListExternal returns all CSI volumes, as understood by the external storage
|
|
// provider. These volumes may or may not be currently registered with Nomad.
|
|
// The response is paginated by the plugin and accepts the
|
|
// QueryOptions.PerPage and QueryOptions.NextToken fields.
|
|
func (v *CSIVolumes) ListExternal(pluginID string, q *QueryOptions) (*CSIVolumeListExternalResponse, *QueryMeta, error) {
|
|
var resp *CSIVolumeListExternalResponse
|
|
|
|
qp := url.Values{}
|
|
qp.Set("plugin_id", pluginID)
|
|
if q.NextToken != "" {
|
|
qp.Set("next_token", q.NextToken)
|
|
}
|
|
if q.PerPage != 0 {
|
|
qp.Set("per_page", fmt.Sprint(q.PerPage))
|
|
}
|
|
|
|
qm, err := v.client.query("/v1/volumes/external?"+qp.Encode(), &resp, q)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
sort.Sort(CSIVolumeExternalStubSort(resp.Volumes))
|
|
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
|
|
}
|
|
|
|
return &resp, qm, nil
|
|
}
|
|
|
|
// Register registers a single CSIVolume with Nomad. The volume must already
|
|
// exist in the external storage provider.
|
|
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
|
|
}
|
|
|
|
// Deregister deregisters a single CSIVolume from Nomad. The volume will not be deleted from the external storage provider.
|
|
func (v *CSIVolumes) Deregister(id string, force bool, w *WriteOptions) error {
|
|
_, err := v.client.delete(fmt.Sprintf("/v1/volume/csi/%v?force=%t", url.PathEscape(id), force), nil, w)
|
|
return err
|
|
}
|
|
|
|
// Create creates a single CSIVolume in an external storage provider and
|
|
// registers it with Nomad. You do not need to call Register if this call is
|
|
// successful.
|
|
func (v *CSIVolumes) Create(vol *CSIVolume, w *WriteOptions) ([]*CSIVolume, *WriteMeta, error) {
|
|
req := CSIVolumeCreateRequest{
|
|
Volumes: []*CSIVolume{vol},
|
|
}
|
|
|
|
resp := &CSIVolumeCreateResponse{}
|
|
meta, err := v.client.write(fmt.Sprintf("/v1/volume/csi/%v/create", vol.ID), req, resp, w)
|
|
return resp.Volumes, meta, err
|
|
}
|
|
|
|
// Delete deletes a CSI volume from an external storage provider. The ID
|
|
// passed as an argument here is for the storage provider's ID, so a volume
|
|
// that's already been deregistered can be deleted.
|
|
func (v *CSIVolumes) Delete(externalVolID string, w *WriteOptions) error {
|
|
_, err := v.client.delete(fmt.Sprintf("/v1/volume/csi/%v/delete", url.PathEscape(externalVolID)), nil, w)
|
|
return err
|
|
}
|
|
|
|
// Detach causes Nomad to attempt to detach a CSI volume from a client
|
|
// node. This is used in the case that the node is temporarily lost and the
|
|
// allocations are unable to drop their claims automatically.
|
|
func (v *CSIVolumes) Detach(volID, nodeID string, w *WriteOptions) error {
|
|
_, err := v.client.delete(fmt.Sprintf("/v1/volume/csi/%v/detach?node=%v", url.PathEscape(volID), nodeID), nil, w)
|
|
return err
|
|
}
|
|
|
|
// CreateSnapshot snapshots an external storage volume.
|
|
func (v *CSIVolumes) CreateSnapshot(snap *CSISnapshot, w *WriteOptions) (*CSISnapshotCreateResponse, *WriteMeta, error) {
|
|
req := &CSISnapshotCreateRequest{
|
|
Snapshots: []*CSISnapshot{snap},
|
|
}
|
|
resp := &CSISnapshotCreateResponse{}
|
|
meta, err := v.client.write("/v1/volumes/snapshot", req, resp, w)
|
|
return resp, meta, err
|
|
}
|
|
|
|
// DeleteSnapshot deletes an external storage volume snapshot.
|
|
func (v *CSIVolumes) DeleteSnapshot(snap *CSISnapshot, w *WriteOptions) error {
|
|
qp := url.Values{}
|
|
qp.Set("snapshot_id", snap.ID)
|
|
qp.Set("plugin_id", snap.PluginID)
|
|
for k, v := range snap.Secrets {
|
|
qp.Set("secret", fmt.Sprintf("%v=%v", k, v))
|
|
}
|
|
_, err := v.client.delete("/v1/volumes/snapshot?"+qp.Encode(), nil, w)
|
|
return err
|
|
}
|
|
|
|
// ListSnapshots lists external storage volume snapshots.
|
|
func (v *CSIVolumes) ListSnapshots(pluginID string, q *QueryOptions) (*CSISnapshotListResponse, *QueryMeta, error) {
|
|
var resp *CSISnapshotListResponse
|
|
|
|
qp := url.Values{}
|
|
if pluginID != "" {
|
|
qp.Set("plugin_id", pluginID)
|
|
}
|
|
if q.NextToken != "" {
|
|
qp.Set("next_token", q.NextToken)
|
|
}
|
|
if q.PerPage != 0 {
|
|
qp.Set("per_page", fmt.Sprint(q.PerPage))
|
|
}
|
|
|
|
qm, err := v.client.query("/v1/volumes/snapshot?"+qp.Encode(), &resp, q)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
sort.Sort(CSISnapshotSort(resp.Snapshots))
|
|
return resp, qm, nil
|
|
}
|
|
|
|
// CSIVolumeAttachmentMode chooses the type of storage api that will be used to
|
|
// interact with the device. (Duplicated in nomad/structs/csi.go)
|
|
type CSIVolumeAttachmentMode string
|
|
|
|
const (
|
|
CSIVolumeAttachmentModeUnknown CSIVolumeAttachmentMode = ""
|
|
CSIVolumeAttachmentModeBlockDevice CSIVolumeAttachmentMode = "block-device"
|
|
CSIVolumeAttachmentModeFilesystem CSIVolumeAttachmentMode = "file-system"
|
|
)
|
|
|
|
// CSIVolumeAccessMode indicates how a volume should be used in a storage topology
|
|
// e.g whether the provider should make the volume available concurrently. (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"
|
|
)
|
|
|
|
// CSIMountOptions contain optional additional configuration that can be used
|
|
// when specifying that a Volume should be used with VolumeAccessTypeMount.
|
|
type CSIMountOptions struct {
|
|
// FSType is an optional field that allows an operator to specify the type
|
|
// of the filesystem.
|
|
FSType string `hcl:"fs_type,optional"`
|
|
|
|
// MountFlags contains additional options that may be used when mounting the
|
|
// volume by the plugin. This may contain sensitive data and should not be
|
|
// leaked.
|
|
MountFlags []string `hcl:"mount_flags,optional"`
|
|
|
|
ExtraKeysHCL []string `hcl1:",unusedKeys" json:"-"` // report unexpected keys
|
|
}
|
|
|
|
// CSISecrets contain optional additional credentials that may be needed by
|
|
// the storage provider. These values will be redacted when reported in the
|
|
// API or in Nomad's logs.
|
|
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 `mapstructure:"external_id" hcl:"external_id"`
|
|
Namespace string
|
|
Topologies []*CSITopology
|
|
AccessMode CSIVolumeAccessMode `hcl:"access_mode"`
|
|
AttachmentMode CSIVolumeAttachmentMode `hcl:"attachment_mode"`
|
|
MountOptions *CSIMountOptions `hcl:"mount_options"`
|
|
Secrets CSISecrets `mapstructure:"secrets" hcl:"secrets"`
|
|
Parameters map[string]string `mapstructure:"parameters" hcl:"parameters"`
|
|
Context map[string]string `mapstructure:"context" hcl:"context"`
|
|
Capacity int64 `hcl:"-"`
|
|
|
|
// These fields are used as part of the volume creation request
|
|
RequestedCapacityMin int64 `hcl:"capacity_min"`
|
|
RequestedCapacityMax int64 `hcl:"capacity_max"`
|
|
RequestedCapabilities []*CSIVolumeCapability `hcl:"capability"`
|
|
CloneID string `mapstructure:"clone_id" hcl:"clone_id"`
|
|
SnapshotID string `mapstructure:"snapshot_id" hcl:"snapshot_id"`
|
|
|
|
// ReadAllocs is a map of allocation IDs for tracking reader claim status.
|
|
// The Allocation value will always be nil; clients can populate this data
|
|
// by iterating over the Allocations field.
|
|
ReadAllocs map[string]*Allocation
|
|
|
|
// WriteAllocs is a map of allocation IDs for tracking writer claim
|
|
// status. The Allocation value will always be nil; clients can populate
|
|
// this data by iterating over the Allocations field.
|
|
WriteAllocs map[string]*Allocation
|
|
|
|
// Allocations is a combined list of readers and writers
|
|
Allocations []*AllocationListStub
|
|
|
|
// Schedulable is true if all the denormalized plugin health fields are true
|
|
Schedulable bool
|
|
PluginID string `mapstructure:"plugin_id" 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 `hcl1:",unusedKeys" json:"-"`
|
|
}
|
|
|
|
// CSIVolumeCapability is a requested attachment and access mode for a
|
|
// volume
|
|
type CSIVolumeCapability struct {
|
|
AccessMode CSIVolumeAccessMode `mapstructure:"access_mode" hcl:"access_mode"`
|
|
AttachmentMode CSIVolumeAttachmentMode `mapstructure:"attachment_mode" hcl:"attachment_mode"`
|
|
}
|
|
|
|
// CSIVolumeIndexSort is a helper used for sorting volume stubs by creation
|
|
// time.
|
|
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 CSIVolumeListExternalResponse struct {
|
|
Volumes []*CSIVolumeExternalStub
|
|
NextToken string
|
|
}
|
|
|
|
// CSIVolumeExternalStub is the storage provider's view of a volume, as
|
|
// returned from the controller plugin; all IDs are for external resources
|
|
type CSIVolumeExternalStub struct {
|
|
ExternalID string
|
|
CapacityBytes int64
|
|
VolumeContext map[string]string
|
|
CloneID string
|
|
SnapshotID string
|
|
PublishedExternalNodeIDs []string
|
|
IsAbnormal bool
|
|
Status string
|
|
}
|
|
|
|
// CSIVolumeExternalStubSort is a sorting helper for external volumes. We
|
|
// can't sort these by creation time because we don't get that data back from
|
|
// the storage provider. Sort by External ID within this page.
|
|
type CSIVolumeExternalStubSort []*CSIVolumeExternalStub
|
|
|
|
func (v CSIVolumeExternalStubSort) Len() int {
|
|
return len(v)
|
|
}
|
|
|
|
func (v CSIVolumeExternalStubSort) Less(i, j int) bool {
|
|
return v[i].ExternalID > v[j].ExternalID
|
|
}
|
|
|
|
func (v CSIVolumeExternalStubSort) Swap(i, j int) {
|
|
v[i], v[j] = v[j], v[i]
|
|
}
|
|
|
|
type CSIVolumeCreateRequest struct {
|
|
Volumes []*CSIVolume
|
|
WriteRequest
|
|
}
|
|
|
|
type CSIVolumeCreateResponse struct {
|
|
Volumes []*CSIVolume
|
|
QueryMeta
|
|
}
|
|
|
|
type CSIVolumeRegisterRequest struct {
|
|
Volumes []*CSIVolume
|
|
WriteRequest
|
|
}
|
|
|
|
type CSIVolumeDeregisterRequest struct {
|
|
VolumeIDs []string
|
|
WriteRequest
|
|
}
|
|
|
|
// CSISnapshot is the storage provider's view of a volume snapshot
|
|
type CSISnapshot struct {
|
|
ID string // storage provider's ID
|
|
ExternalSourceVolumeID string // storage provider's ID for volume
|
|
SizeBytes int64 // value from storage provider
|
|
CreateTime int64 // value from storage provider
|
|
IsReady bool // value from storage provider
|
|
SourceVolumeID string // Nomad volume ID
|
|
PluginID string // CSI plugin ID
|
|
|
|
// These field are only used during snapshot creation and will not be
|
|
// populated when the snapshot is returned
|
|
Name string // suggested name of the snapshot, used for creation
|
|
Secrets CSISecrets // secrets needed to create snapshot
|
|
Parameters map[string]string // secrets needed to create snapshot
|
|
}
|
|
|
|
// CSISnapshotSort is a helper used for sorting snapshots by creation time.
|
|
type CSISnapshotSort []*CSISnapshot
|
|
|
|
func (v CSISnapshotSort) Len() int {
|
|
return len(v)
|
|
}
|
|
|
|
func (v CSISnapshotSort) Less(i, j int) bool {
|
|
return v[i].CreateTime > v[j].CreateTime
|
|
}
|
|
|
|
func (v CSISnapshotSort) Swap(i, j int) {
|
|
v[i], v[j] = v[j], v[i]
|
|
}
|
|
|
|
type CSISnapshotCreateRequest struct {
|
|
Snapshots []*CSISnapshot
|
|
WriteRequest
|
|
}
|
|
|
|
type CSISnapshotCreateResponse struct {
|
|
Snapshots []*CSISnapshot
|
|
QueryMeta
|
|
}
|
|
|
|
// CSISnapshotListRequest is a request to a controller plugin to list all the
|
|
// snapshot known to the the storage provider. This request is paginated by
|
|
// the plugin and accepts the QueryOptions.PerPage and QueryOptions.NextToken
|
|
// fields
|
|
type CSISnapshotListRequest struct {
|
|
PluginID string
|
|
QueryOptions
|
|
}
|
|
|
|
type CSISnapshotListResponse struct {
|
|
Snapshots []*CSISnapshot
|
|
NextToken string
|
|
QueryMeta
|
|
}
|
|
|
|
// CSI Plugins are jobs with plugin specific data
|
|
type CSIPlugins struct {
|
|
client *Client
|
|
}
|
|
|
|
// CSIPlugin is used for serialization, see also nomad/structs/csi.go
|
|
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
|
|
}
|
|
|
|
// CSIPluginIndexSort is a helper used for sorting plugin stubs by creation
|
|
// time.
|
|
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
|
|
}
|