github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/nomad/structs/csi.go (about)

     1  package structs
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"strings"
     7  	"time"
     8  
     9  	multierror "github.com/hashicorp/go-multierror"
    10  	"github.com/hashicorp/nomad/helper"
    11  	"golang.org/x/exp/maps"
    12  	"golang.org/x/exp/slices"
    13  )
    14  
    15  // CSISocketName is the filename that Nomad expects plugins to create inside the
    16  // PluginMountDir.
    17  const CSISocketName = "csi.sock"
    18  
    19  // CSIIntermediaryDirname is the name of the directory inside the PluginMountDir
    20  // where Nomad will expect plugins to create intermediary mounts for volumes.
    21  const CSIIntermediaryDirname = "volumes"
    22  
    23  // VolumeTypeCSI is the type in the volume stanza of a TaskGroup
    24  const VolumeTypeCSI = "csi"
    25  
    26  // CSIPluginType is an enum string that encapsulates the valid options for a
    27  // CSIPlugin stanza's Type. These modes will allow the plugin to be used in
    28  // different ways by the client.
    29  type CSIPluginType string
    30  
    31  const (
    32  	// CSIPluginTypeNode indicates that Nomad should only use the plugin for
    33  	// performing Node RPCs against the provided plugin.
    34  	CSIPluginTypeNode CSIPluginType = "node"
    35  
    36  	// CSIPluginTypeController indicates that Nomad should only use the plugin for
    37  	// performing Controller RPCs against the provided plugin.
    38  	CSIPluginTypeController CSIPluginType = "controller"
    39  
    40  	// CSIPluginTypeMonolith indicates that Nomad can use the provided plugin for
    41  	// both controller and node rpcs.
    42  	CSIPluginTypeMonolith CSIPluginType = "monolith"
    43  )
    44  
    45  // CSIPluginTypeIsValid validates the given CSIPluginType string and returns
    46  // true only when a correct plugin type is specified.
    47  func CSIPluginTypeIsValid(pt CSIPluginType) bool {
    48  	switch pt {
    49  	case CSIPluginTypeNode, CSIPluginTypeController, CSIPluginTypeMonolith:
    50  		return true
    51  	default:
    52  		return false
    53  	}
    54  }
    55  
    56  // TaskCSIPluginConfig contains the data that is required to setup a task as a
    57  // CSI plugin. This will be used by the csi_plugin_supervisor_hook to configure
    58  // mounts for the plugin and initiate the connection to the plugin catalog.
    59  type TaskCSIPluginConfig struct {
    60  	// ID is the identifier of the plugin.
    61  	// Ideally this should be the FQDN of the plugin.
    62  	ID string
    63  
    64  	// Type instructs Nomad on how to handle processing a plugin
    65  	Type CSIPluginType
    66  
    67  	// MountDir is the directory (within its container) in which the plugin creates a
    68  	// socket (called CSISocketName) for communication with Nomad. Default is /csi.
    69  	MountDir string
    70  
    71  	// StagePublishBaseDir is the base directory (within its container) in which the plugin
    72  	// mounts volumes being staged and bind mount volumes being published.
    73  	// e.g. staging_target_path = {StagePublishBaseDir}/staging/{volume-id}/{usage-mode}
    74  	// e.g. target_path = {StagePublishBaseDir}/per-alloc/{alloc-id}/{volume-id}/{usage-mode}
    75  	// Default is /local/csi.
    76  	StagePublishBaseDir string
    77  
    78  	// HealthTimeout is the time after which the CSI plugin tasks will be killed
    79  	// if the CSI Plugin is not healthy.
    80  	HealthTimeout time.Duration `mapstructure:"health_timeout" hcl:"health_timeout,optional"`
    81  }
    82  
    83  func (t *TaskCSIPluginConfig) Copy() *TaskCSIPluginConfig {
    84  	if t == nil {
    85  		return nil
    86  	}
    87  
    88  	nt := new(TaskCSIPluginConfig)
    89  	*nt = *t
    90  
    91  	return nt
    92  }
    93  
    94  // CSIVolumeCapability is the requested attachment and access mode for a
    95  // volume
    96  type CSIVolumeCapability struct {
    97  	AttachmentMode CSIVolumeAttachmentMode
    98  	AccessMode     CSIVolumeAccessMode
    99  }
   100  
   101  // CSIVolumeAttachmentMode chooses the type of storage api that will be used to
   102  // interact with the device.
   103  type CSIVolumeAttachmentMode string
   104  
   105  const (
   106  	CSIVolumeAttachmentModeUnknown     CSIVolumeAttachmentMode = ""
   107  	CSIVolumeAttachmentModeBlockDevice CSIVolumeAttachmentMode = "block-device"
   108  	CSIVolumeAttachmentModeFilesystem  CSIVolumeAttachmentMode = "file-system"
   109  )
   110  
   111  func ValidCSIVolumeAttachmentMode(attachmentMode CSIVolumeAttachmentMode) bool {
   112  	switch attachmentMode {
   113  	case CSIVolumeAttachmentModeBlockDevice, CSIVolumeAttachmentModeFilesystem:
   114  		return true
   115  	default:
   116  		return false
   117  	}
   118  }
   119  
   120  // CSIVolumeAccessMode indicates how a volume should be used in a storage topology
   121  // e.g whether the provider should make the volume available concurrently.
   122  type CSIVolumeAccessMode string
   123  
   124  const (
   125  	CSIVolumeAccessModeUnknown CSIVolumeAccessMode = ""
   126  
   127  	CSIVolumeAccessModeSingleNodeReader CSIVolumeAccessMode = "single-node-reader-only"
   128  	CSIVolumeAccessModeSingleNodeWriter CSIVolumeAccessMode = "single-node-writer"
   129  
   130  	CSIVolumeAccessModeMultiNodeReader       CSIVolumeAccessMode = "multi-node-reader-only"
   131  	CSIVolumeAccessModeMultiNodeSingleWriter CSIVolumeAccessMode = "multi-node-single-writer"
   132  	CSIVolumeAccessModeMultiNodeMultiWriter  CSIVolumeAccessMode = "multi-node-multi-writer"
   133  )
   134  
   135  // ValidCSIVolumeAccessMode checks to see that the provided access mode is a valid,
   136  // non-empty access mode.
   137  func ValidCSIVolumeAccessMode(accessMode CSIVolumeAccessMode) bool {
   138  	switch accessMode {
   139  	case CSIVolumeAccessModeSingleNodeReader, CSIVolumeAccessModeSingleNodeWriter,
   140  		CSIVolumeAccessModeMultiNodeReader, CSIVolumeAccessModeMultiNodeSingleWriter,
   141  		CSIVolumeAccessModeMultiNodeMultiWriter:
   142  		return true
   143  	default:
   144  		return false
   145  	}
   146  }
   147  
   148  // ValidCSIVolumeWriteAccessMode checks for a writable access mode.
   149  func ValidCSIVolumeWriteAccessMode(accessMode CSIVolumeAccessMode) bool {
   150  	switch accessMode {
   151  	case CSIVolumeAccessModeSingleNodeWriter,
   152  		CSIVolumeAccessModeMultiNodeSingleWriter,
   153  		CSIVolumeAccessModeMultiNodeMultiWriter:
   154  		return true
   155  	default:
   156  		return false
   157  	}
   158  }
   159  
   160  // CSIMountOptions contain optional additional configuration that can be used
   161  // when specifying that a Volume should be used with VolumeAccessTypeMount.
   162  type CSIMountOptions struct {
   163  	// FSType is an optional field that allows an operator to specify the type
   164  	// of the filesystem.
   165  	FSType string
   166  
   167  	// MountFlags contains additional options that may be used when mounting the
   168  	// volume by the plugin. This may contain sensitive data and should not be
   169  	// leaked.
   170  	MountFlags []string
   171  }
   172  
   173  func (o *CSIMountOptions) Copy() *CSIMountOptions {
   174  	if o == nil {
   175  		return nil
   176  	}
   177  
   178  	no := *o
   179  	no.MountFlags = slices.Clone(o.MountFlags)
   180  	return &no
   181  }
   182  
   183  func (o *CSIMountOptions) Merge(p *CSIMountOptions) {
   184  	if p == nil {
   185  		return
   186  	}
   187  	if p.FSType != "" {
   188  		o.FSType = p.FSType
   189  	}
   190  	if p.MountFlags != nil {
   191  		o.MountFlags = p.MountFlags
   192  	}
   193  }
   194  
   195  func (o *CSIMountOptions) Equal(p *CSIMountOptions) bool {
   196  	if o == nil && p == nil {
   197  		return true
   198  	}
   199  	if o == nil || p == nil {
   200  		return false
   201  	}
   202  	if o.FSType != p.FSType {
   203  		return false
   204  	}
   205  	return helper.SliceSetEq(o.MountFlags, p.MountFlags)
   206  }
   207  
   208  // CSIMountOptions implements the Stringer and GoStringer interfaces to prevent
   209  // accidental leakage of sensitive mount flags via logs.
   210  var _ fmt.Stringer = &CSIMountOptions{}
   211  var _ fmt.GoStringer = &CSIMountOptions{}
   212  
   213  func (o *CSIMountOptions) String() string {
   214  	mountFlagsString := "nil"
   215  	if len(o.MountFlags) != 0 {
   216  		mountFlagsString = "[REDACTED]"
   217  	}
   218  
   219  	return fmt.Sprintf("csi.CSIOptions(FSType: %s, MountFlags: %s)", o.FSType, mountFlagsString)
   220  }
   221  
   222  func (o *CSIMountOptions) GoString() string {
   223  	return o.String()
   224  }
   225  
   226  // CSISecrets contain optional additional configuration that can be used
   227  // when specifying that a Volume should be used with VolumeAccessTypeMount.
   228  type CSISecrets map[string]string
   229  
   230  // CSISecrets implements the Stringer and GoStringer interfaces to prevent
   231  // accidental leakage of secrets via logs.
   232  var _ fmt.Stringer = &CSISecrets{}
   233  var _ fmt.GoStringer = &CSISecrets{}
   234  
   235  func (s *CSISecrets) String() string {
   236  	redacted := map[string]string{}
   237  	for k := range *s {
   238  		redacted[k] = "[REDACTED]"
   239  	}
   240  	return fmt.Sprintf("csi.CSISecrets(%v)", redacted)
   241  }
   242  
   243  func (s *CSISecrets) GoString() string {
   244  	return s.String()
   245  }
   246  
   247  type CSIVolumeClaim struct {
   248  	AllocationID   string
   249  	NodeID         string
   250  	ExternalNodeID string
   251  	Mode           CSIVolumeClaimMode
   252  	AccessMode     CSIVolumeAccessMode
   253  	AttachmentMode CSIVolumeAttachmentMode
   254  	State          CSIVolumeClaimState
   255  }
   256  
   257  type CSIVolumeClaimState int
   258  
   259  const (
   260  	CSIVolumeClaimStateTaken CSIVolumeClaimState = iota
   261  	CSIVolumeClaimStateNodeDetached
   262  	CSIVolumeClaimStateControllerDetached
   263  	CSIVolumeClaimStateReadyToFree
   264  	CSIVolumeClaimStateUnpublishing
   265  )
   266  
   267  // CSIVolume is the full representation of a CSI Volume
   268  type CSIVolume struct {
   269  	// ID is a namespace unique URL safe identifier for the volume
   270  	ID string
   271  	// Name is a display name for the volume, not required to be unique
   272  	Name string
   273  	// ExternalID identifies the volume for the CSI interface, may be URL unsafe
   274  	ExternalID string
   275  	Namespace  string
   276  
   277  	// RequestedTopologies are the topologies submitted as options to
   278  	// the storage provider at the time the volume was created. After
   279  	// volumes are created, this field is ignored.
   280  	RequestedTopologies *CSITopologyRequest
   281  
   282  	// Topologies are the topologies returned by the storage provider,
   283  	// based on the RequestedTopologies and what the storage provider
   284  	// could support. This value cannot be set by the user.
   285  	Topologies []*CSITopology
   286  
   287  	AccessMode     CSIVolumeAccessMode     // *current* access mode
   288  	AttachmentMode CSIVolumeAttachmentMode // *current* attachment mode
   289  	MountOptions   *CSIMountOptions
   290  
   291  	Secrets    CSISecrets
   292  	Parameters map[string]string
   293  	Context    map[string]string
   294  	Capacity   int64 // bytes
   295  
   296  	// These values are used only on volume creation but we record them
   297  	// so that we can diff the volume later
   298  	RequestedCapacityMin  int64 // bytes
   299  	RequestedCapacityMax  int64 // bytes
   300  	RequestedCapabilities []*CSIVolumeCapability
   301  	CloneID               string
   302  	SnapshotID            string
   303  
   304  	// Allocations, tracking claim status
   305  	ReadAllocs  map[string]*Allocation // AllocID -> Allocation
   306  	WriteAllocs map[string]*Allocation // AllocID -> Allocation
   307  
   308  	ReadClaims  map[string]*CSIVolumeClaim `json:"-"` // AllocID -> claim
   309  	WriteClaims map[string]*CSIVolumeClaim `json:"-"` // AllocID -> claim
   310  	PastClaims  map[string]*CSIVolumeClaim `json:"-"` // AllocID -> claim
   311  
   312  	// Schedulable is true if all the denormalized plugin health fields are true, and the
   313  	// volume has not been marked for garbage collection
   314  	Schedulable         bool
   315  	PluginID            string
   316  	Provider            string
   317  	ProviderVersion     string
   318  	ControllerRequired  bool
   319  	ControllersHealthy  int
   320  	ControllersExpected int
   321  	NodesHealthy        int
   322  	NodesExpected       int
   323  	ResourceExhausted   time.Time
   324  
   325  	CreateIndex uint64
   326  	ModifyIndex uint64
   327  }
   328  
   329  // GetID implements the IDGetter interface, required for pagination.
   330  func (v *CSIVolume) GetID() string {
   331  	if v == nil {
   332  		return ""
   333  	}
   334  	return v.ID
   335  }
   336  
   337  // GetNamespace implements the NamespaceGetter interface, required for
   338  // pagination.
   339  func (v *CSIVolume) GetNamespace() string {
   340  	if v == nil {
   341  		return ""
   342  	}
   343  	return v.Namespace
   344  }
   345  
   346  // GetCreateIndex implements the CreateIndexGetter interface, required for
   347  // pagination.
   348  func (v *CSIVolume) GetCreateIndex() uint64 {
   349  	if v == nil {
   350  		return 0
   351  	}
   352  	return v.CreateIndex
   353  }
   354  
   355  // CSIVolListStub is partial representation of a CSI Volume for inclusion in lists
   356  type CSIVolListStub struct {
   357  	ID                  string
   358  	Namespace           string
   359  	Name                string
   360  	ExternalID          string
   361  	Topologies          []*CSITopology
   362  	AccessMode          CSIVolumeAccessMode
   363  	AttachmentMode      CSIVolumeAttachmentMode
   364  	CurrentReaders      int
   365  	CurrentWriters      int
   366  	Schedulable         bool
   367  	PluginID            string
   368  	Provider            string
   369  	ControllerRequired  bool
   370  	ControllersHealthy  int
   371  	ControllersExpected int
   372  	NodesHealthy        int
   373  	NodesExpected       int
   374  	ResourceExhausted   time.Time
   375  
   376  	CreateIndex uint64
   377  	ModifyIndex uint64
   378  }
   379  
   380  // NewCSIVolume creates the volume struct. No side-effects
   381  func NewCSIVolume(volumeID string, index uint64) *CSIVolume {
   382  	out := &CSIVolume{
   383  		ID:          volumeID,
   384  		CreateIndex: index,
   385  		ModifyIndex: index,
   386  	}
   387  
   388  	out.newStructs()
   389  	return out
   390  }
   391  
   392  func (v *CSIVolume) newStructs() {
   393  	v.Topologies = []*CSITopology{}
   394  	v.MountOptions = new(CSIMountOptions)
   395  	v.Secrets = CSISecrets{}
   396  	v.Parameters = map[string]string{}
   397  	v.Context = map[string]string{}
   398  
   399  	v.ReadAllocs = map[string]*Allocation{}
   400  	v.WriteAllocs = map[string]*Allocation{}
   401  	v.ReadClaims = map[string]*CSIVolumeClaim{}
   402  	v.WriteClaims = map[string]*CSIVolumeClaim{}
   403  	v.PastClaims = map[string]*CSIVolumeClaim{}
   404  }
   405  
   406  func (v *CSIVolume) RemoteID() string {
   407  	if v.ExternalID != "" {
   408  		return v.ExternalID
   409  	}
   410  	return v.ID
   411  }
   412  
   413  func (v *CSIVolume) Stub() *CSIVolListStub {
   414  	return &CSIVolListStub{
   415  		ID:                  v.ID,
   416  		Namespace:           v.Namespace,
   417  		Name:                v.Name,
   418  		ExternalID:          v.ExternalID,
   419  		Topologies:          v.Topologies,
   420  		AccessMode:          v.AccessMode,
   421  		AttachmentMode:      v.AttachmentMode,
   422  		CurrentReaders:      len(v.ReadAllocs),
   423  		CurrentWriters:      len(v.WriteAllocs),
   424  		Schedulable:         v.Schedulable,
   425  		PluginID:            v.PluginID,
   426  		Provider:            v.Provider,
   427  		ControllerRequired:  v.ControllerRequired,
   428  		ControllersHealthy:  v.ControllersHealthy,
   429  		ControllersExpected: v.ControllersExpected,
   430  		NodesHealthy:        v.NodesHealthy,
   431  		NodesExpected:       v.NodesExpected,
   432  		ResourceExhausted:   v.ResourceExhausted,
   433  		CreateIndex:         v.CreateIndex,
   434  		ModifyIndex:         v.ModifyIndex,
   435  	}
   436  }
   437  
   438  // ReadSchedulable determines if the volume is potentially schedulable
   439  // for reads, considering only the volume capabilities and plugin
   440  // health
   441  func (v *CSIVolume) ReadSchedulable() bool {
   442  	if !v.Schedulable {
   443  		return false
   444  	}
   445  
   446  	return v.ResourceExhausted == time.Time{}
   447  }
   448  
   449  // WriteSchedulable determines if the volume is potentially
   450  // schedulable for writes, considering only volume capabilities and
   451  // plugin health
   452  func (v *CSIVolume) WriteSchedulable() bool {
   453  	if !v.Schedulable {
   454  		return false
   455  	}
   456  
   457  	switch v.AccessMode {
   458  	case CSIVolumeAccessModeSingleNodeWriter,
   459  		CSIVolumeAccessModeMultiNodeSingleWriter,
   460  		CSIVolumeAccessModeMultiNodeMultiWriter:
   461  		return v.ResourceExhausted == time.Time{}
   462  
   463  	case CSIVolumeAccessModeUnknown:
   464  		// this volume was created but not currently claimed, so we check what
   465  		// it's capable of, not what it's been previously assigned
   466  		for _, cap := range v.RequestedCapabilities {
   467  			switch cap.AccessMode {
   468  			case CSIVolumeAccessModeSingleNodeWriter,
   469  				CSIVolumeAccessModeMultiNodeSingleWriter,
   470  				CSIVolumeAccessModeMultiNodeMultiWriter:
   471  				return v.ResourceExhausted == time.Time{}
   472  			}
   473  		}
   474  	}
   475  	return false
   476  }
   477  
   478  // HasFreeReadClaims determines if there are any free read claims available
   479  func (v *CSIVolume) HasFreeReadClaims() bool {
   480  	switch v.AccessMode {
   481  	case CSIVolumeAccessModeSingleNodeReader:
   482  		return len(v.ReadClaims) == 0
   483  	case CSIVolumeAccessModeSingleNodeWriter:
   484  		return len(v.ReadClaims) == 0 && len(v.WriteClaims) == 0
   485  	case CSIVolumeAccessModeUnknown:
   486  		// This volume was created but not yet claimed, so its
   487  		// capabilities have been checked in ReadSchedulable
   488  		return true
   489  	default:
   490  		// For multi-node AccessModes, the CSI spec doesn't allow for
   491  		// setting a max number of readers we track node resource
   492  		// exhaustion through v.ResourceExhausted which is checked in
   493  		// ReadSchedulable
   494  		return true
   495  	}
   496  }
   497  
   498  // HasFreeWriteClaims determines if there are any free write claims available
   499  func (v *CSIVolume) HasFreeWriteClaims() bool {
   500  	switch v.AccessMode {
   501  	case CSIVolumeAccessModeSingleNodeWriter, CSIVolumeAccessModeMultiNodeSingleWriter:
   502  		return len(v.WriteClaims) == 0
   503  	case CSIVolumeAccessModeMultiNodeMultiWriter:
   504  		// the CSI spec doesn't allow for setting a max number of writers.
   505  		// we track node resource exhaustion through v.ResourceExhausted
   506  		// which is checked in WriteSchedulable
   507  		return true
   508  	case CSIVolumeAccessModeUnknown:
   509  		// This volume was created but not yet claimed, so its
   510  		// capabilities have been checked in WriteSchedulable
   511  		return true
   512  	default:
   513  		// Reader modes never have free write claims
   514  		return false
   515  	}
   516  }
   517  
   518  // InUse tests whether any allocations are actively using the volume
   519  func (v *CSIVolume) InUse() bool {
   520  	return len(v.ReadAllocs) != 0 ||
   521  		len(v.WriteAllocs) != 0
   522  }
   523  
   524  // Copy returns a copy of the volume, which shares only the Topologies slice
   525  func (v *CSIVolume) Copy() *CSIVolume {
   526  	out := new(CSIVolume)
   527  	*out = *v
   528  	out.newStructs() // zero-out the non-primitive structs
   529  
   530  	for _, t := range v.Topologies {
   531  		out.Topologies = append(out.Topologies, t.Copy())
   532  	}
   533  	if v.MountOptions != nil {
   534  		*out.MountOptions = *v.MountOptions
   535  	}
   536  	for k, v := range v.Secrets {
   537  		out.Secrets[k] = v
   538  	}
   539  	for k, v := range v.Parameters {
   540  		out.Parameters[k] = v
   541  	}
   542  	for k, v := range v.Context {
   543  		out.Context[k] = v
   544  	}
   545  
   546  	for k, alloc := range v.ReadAllocs {
   547  		out.ReadAllocs[k] = alloc.Copy()
   548  	}
   549  	for k, alloc := range v.WriteAllocs {
   550  		out.WriteAllocs[k] = alloc.Copy()
   551  	}
   552  
   553  	for k, v := range v.ReadClaims {
   554  		claim := *v
   555  		out.ReadClaims[k] = &claim
   556  	}
   557  	for k, v := range v.WriteClaims {
   558  		claim := *v
   559  		out.WriteClaims[k] = &claim
   560  	}
   561  	for k, v := range v.PastClaims {
   562  		claim := *v
   563  		out.PastClaims[k] = &claim
   564  	}
   565  
   566  	return out
   567  }
   568  
   569  // Claim updates the allocations and changes the volume state
   570  func (v *CSIVolume) Claim(claim *CSIVolumeClaim, alloc *Allocation) error {
   571  	// COMPAT: volumes registered prior to 1.1.0 will be missing caps for the
   572  	// volume on any claim. Correct this when we make the first change to a
   573  	// claim by setting its currently claimed capability as the only requested
   574  	// capability
   575  	if len(v.RequestedCapabilities) == 0 && v.AccessMode != "" && v.AttachmentMode != "" {
   576  		v.RequestedCapabilities = []*CSIVolumeCapability{
   577  			{
   578  				AccessMode:     v.AccessMode,
   579  				AttachmentMode: v.AttachmentMode,
   580  			},
   581  		}
   582  	}
   583  	if v.AttachmentMode != CSIVolumeAttachmentModeUnknown &&
   584  		claim.AttachmentMode != CSIVolumeAttachmentModeUnknown &&
   585  		v.AttachmentMode != claim.AttachmentMode {
   586  		return fmt.Errorf("cannot change attachment mode of claimed volume")
   587  	}
   588  
   589  	if claim.State == CSIVolumeClaimStateTaken {
   590  		switch claim.Mode {
   591  		case CSIVolumeClaimRead:
   592  			return v.claimRead(claim, alloc)
   593  		case CSIVolumeClaimWrite:
   594  			return v.claimWrite(claim, alloc)
   595  		}
   596  	}
   597  	// either GC or a Unpublish checkpoint
   598  	return v.claimRelease(claim)
   599  }
   600  
   601  // claimRead marks an allocation as using a volume read-only
   602  func (v *CSIVolume) claimRead(claim *CSIVolumeClaim, alloc *Allocation) error {
   603  	if _, ok := v.ReadAllocs[claim.AllocationID]; ok {
   604  		return nil
   605  	}
   606  	if alloc == nil {
   607  		return fmt.Errorf("allocation missing: %s", claim.AllocationID)
   608  	}
   609  
   610  	if !v.ReadSchedulable() {
   611  		return ErrCSIVolumeUnschedulable
   612  	}
   613  
   614  	if !v.HasFreeReadClaims() {
   615  		return ErrCSIVolumeMaxClaims
   616  	}
   617  
   618  	// Allocations are copy on write, so we want to keep the id but don't need the
   619  	// pointer. We'll get it from the db in denormalize.
   620  	v.ReadAllocs[claim.AllocationID] = nil
   621  	delete(v.WriteAllocs, claim.AllocationID)
   622  
   623  	v.ReadClaims[claim.AllocationID] = claim
   624  	delete(v.WriteClaims, claim.AllocationID)
   625  	delete(v.PastClaims, claim.AllocationID)
   626  
   627  	v.setModesFromClaim(claim)
   628  	return nil
   629  }
   630  
   631  // claimWrite marks an allocation as using a volume as a writer
   632  func (v *CSIVolume) claimWrite(claim *CSIVolumeClaim, alloc *Allocation) error {
   633  	if _, ok := v.WriteAllocs[claim.AllocationID]; ok {
   634  		return nil
   635  	}
   636  	if alloc == nil {
   637  		return fmt.Errorf("allocation missing: %s", claim.AllocationID)
   638  	}
   639  
   640  	if !v.WriteSchedulable() {
   641  		return ErrCSIVolumeUnschedulable
   642  	}
   643  
   644  	if !v.HasFreeWriteClaims() {
   645  		return ErrCSIVolumeMaxClaims
   646  	}
   647  
   648  	// Allocations are copy on write, so we want to keep the id but don't need the
   649  	// pointer. We'll get it from the db in denormalize.
   650  	v.WriteAllocs[alloc.ID] = nil
   651  	delete(v.ReadAllocs, alloc.ID)
   652  
   653  	v.WriteClaims[alloc.ID] = claim
   654  	delete(v.ReadClaims, alloc.ID)
   655  	delete(v.PastClaims, alloc.ID)
   656  
   657  	v.setModesFromClaim(claim)
   658  	return nil
   659  }
   660  
   661  // setModesFromClaim sets the volume AttachmentMode and AccessMode based on
   662  // the first claim we make.  Originally the volume AccessMode and
   663  // AttachmentMode were set during registration, but this is incorrect once we
   664  // started creating volumes ourselves. But we still want these values for CLI
   665  // and UI status.
   666  func (v *CSIVolume) setModesFromClaim(claim *CSIVolumeClaim) {
   667  	if v.AttachmentMode == CSIVolumeAttachmentModeUnknown {
   668  		v.AttachmentMode = claim.AttachmentMode
   669  	}
   670  	if v.AccessMode == CSIVolumeAccessModeUnknown {
   671  		v.AccessMode = claim.AccessMode
   672  	}
   673  }
   674  
   675  // claimRelease is called when the allocation has terminated and
   676  // already stopped using the volume
   677  func (v *CSIVolume) claimRelease(claim *CSIVolumeClaim) error {
   678  	if claim.State == CSIVolumeClaimStateReadyToFree {
   679  		delete(v.ReadAllocs, claim.AllocationID)
   680  		delete(v.WriteAllocs, claim.AllocationID)
   681  		delete(v.ReadClaims, claim.AllocationID)
   682  		delete(v.WriteClaims, claim.AllocationID)
   683  		delete(v.PastClaims, claim.AllocationID)
   684  
   685  		// remove AccessMode/AttachmentMode if this is the last claim
   686  		if len(v.ReadClaims) == 0 && len(v.WriteClaims) == 0 && len(v.PastClaims) == 0 {
   687  			v.AccessMode = CSIVolumeAccessModeUnknown
   688  			v.AttachmentMode = CSIVolumeAttachmentModeUnknown
   689  		}
   690  	} else {
   691  		v.PastClaims[claim.AllocationID] = claim
   692  	}
   693  	return nil
   694  }
   695  
   696  // Equal checks equality by value.
   697  func (v *CSIVolume) Equal(o *CSIVolume) bool {
   698  	if v == nil || o == nil {
   699  		return v == o
   700  	}
   701  
   702  	// Omit the plugin health fields, their values are controlled by plugin jobs
   703  	if v.ID == o.ID &&
   704  		v.Namespace == o.Namespace &&
   705  		v.AccessMode == o.AccessMode &&
   706  		v.AttachmentMode == o.AttachmentMode &&
   707  		v.PluginID == o.PluginID {
   708  		// Setwise equality of topologies
   709  		var ok bool
   710  		for _, t := range v.Topologies {
   711  			ok = false
   712  			for _, u := range o.Topologies {
   713  				if t.Equal(u) {
   714  					ok = true
   715  					break
   716  				}
   717  			}
   718  			if !ok {
   719  				return false
   720  			}
   721  		}
   722  		return true
   723  	}
   724  	return false
   725  }
   726  
   727  // Validate validates the volume struct, returning all validation errors at once
   728  func (v *CSIVolume) Validate() error {
   729  	errs := []string{}
   730  
   731  	if v.ID == "" {
   732  		errs = append(errs, "missing volume id")
   733  	}
   734  	if v.PluginID == "" {
   735  		errs = append(errs, "missing plugin id")
   736  	}
   737  	if v.Namespace == "" {
   738  		errs = append(errs, "missing namespace")
   739  	}
   740  	if v.SnapshotID != "" && v.CloneID != "" {
   741  		errs = append(errs, "only one of snapshot_id and clone_id is allowed")
   742  	}
   743  	if len(v.RequestedCapabilities) == 0 {
   744  		errs = append(errs, "must include at least one capability block")
   745  	}
   746  	if v.RequestedTopologies != nil {
   747  		for _, t := range v.RequestedTopologies.Required {
   748  			if t != nil && len(t.Segments) == 0 {
   749  				errs = append(errs, "required topology is missing segments field")
   750  			}
   751  		}
   752  		for _, t := range v.RequestedTopologies.Preferred {
   753  			if t != nil && len(t.Segments) == 0 {
   754  				errs = append(errs, "preferred topology is missing segments field")
   755  			}
   756  		}
   757  	}
   758  	if len(errs) > 0 {
   759  		return fmt.Errorf("validation: %s", strings.Join(errs, ", "))
   760  	}
   761  	return nil
   762  }
   763  
   764  // Merge updates the mutable fields of a volume with those from
   765  // another volume. CSIVolume has many user-defined fields which are
   766  // immutable once set, and many fields that are not
   767  // user-settable. Merge will return an error if we try to mutate the
   768  // user-defined immutable fields after they're set, but silently
   769  // ignore fields that are controlled by Nomad.
   770  func (v *CSIVolume) Merge(other *CSIVolume) error {
   771  	if other == nil {
   772  		return nil
   773  	}
   774  
   775  	var errs *multierror.Error
   776  
   777  	if v.Name != other.Name && other.Name != "" {
   778  		errs = multierror.Append(errs, errors.New("volume name cannot be updated"))
   779  	}
   780  	if v.ExternalID != other.ExternalID && other.ExternalID != "" {
   781  		errs = multierror.Append(errs, errors.New(
   782  			"volume external ID cannot be updated"))
   783  	}
   784  	if v.PluginID != other.PluginID {
   785  		errs = multierror.Append(errs, errors.New(
   786  			"volume plugin ID cannot be updated"))
   787  	}
   788  	if v.CloneID != other.CloneID && other.CloneID != "" {
   789  		errs = multierror.Append(errs, errors.New(
   790  			"volume clone ID cannot be updated"))
   791  	}
   792  	if v.SnapshotID != other.SnapshotID && other.SnapshotID != "" {
   793  		errs = multierror.Append(errs, errors.New(
   794  			"volume snapshot ID cannot be updated"))
   795  	}
   796  
   797  	// must be compatible with capacity range
   798  	// TODO: when ExpandVolume is implemented we'll need to update
   799  	// this logic https://github.com/hashicorp/nomad/issues/10324
   800  	if v.Capacity != 0 {
   801  		if other.RequestedCapacityMax < v.Capacity ||
   802  			other.RequestedCapacityMin > v.Capacity {
   803  			errs = multierror.Append(errs, errors.New(
   804  				"volume requested capacity update was not compatible with existing capacity"))
   805  		} else {
   806  			v.RequestedCapacityMin = other.RequestedCapacityMin
   807  			v.RequestedCapacityMax = other.RequestedCapacityMax
   808  		}
   809  	}
   810  
   811  	// must be compatible with volume_capabilities
   812  	if v.AccessMode != CSIVolumeAccessModeUnknown ||
   813  		v.AttachmentMode != CSIVolumeAttachmentModeUnknown {
   814  		var ok bool
   815  		for _, cap := range other.RequestedCapabilities {
   816  			if cap.AccessMode == v.AccessMode &&
   817  				cap.AttachmentMode == v.AttachmentMode {
   818  				ok = true
   819  				break
   820  			}
   821  		}
   822  		if ok {
   823  			v.RequestedCapabilities = other.RequestedCapabilities
   824  		} else {
   825  			errs = multierror.Append(errs, errors.New(
   826  				"volume requested capabilities update was not compatible with existing capability in use"))
   827  		}
   828  	} else {
   829  		v.RequestedCapabilities = other.RequestedCapabilities
   830  	}
   831  
   832  	// topologies are immutable, so topology request changes must be
   833  	// compatible with the existing topology, if any
   834  	if len(v.Topologies) > 0 {
   835  		if !v.RequestedTopologies.Equal(other.RequestedTopologies) {
   836  			errs = multierror.Append(errs, errors.New(
   837  				"volume topology request update was not compatible with existing topology"))
   838  		}
   839  	}
   840  
   841  	// MountOptions can be updated so long as the volume isn't in use,
   842  	// but the caller will reject updating an in-use volume
   843  	v.MountOptions = other.MountOptions
   844  
   845  	// Secrets can be updated freely
   846  	v.Secrets = other.Secrets
   847  
   848  	// must be compatible with parameters set by from CreateVolumeResponse
   849  
   850  	if len(other.Parameters) != 0 && !maps.Equal(v.Parameters, other.Parameters) {
   851  		errs = multierror.Append(errs, errors.New(
   852  			"volume parameters cannot be updated"))
   853  	}
   854  
   855  	// Context is mutable and will be used during controller
   856  	// validation
   857  	v.Context = other.Context
   858  	return errs.ErrorOrNil()
   859  }
   860  
   861  // Request and response wrappers
   862  type CSIVolumeRegisterRequest struct {
   863  	Volumes []*CSIVolume
   864  	WriteRequest
   865  }
   866  
   867  type CSIVolumeRegisterResponse struct {
   868  	QueryMeta
   869  }
   870  
   871  type CSIVolumeDeregisterRequest struct {
   872  	VolumeIDs []string
   873  	Force     bool
   874  	WriteRequest
   875  }
   876  
   877  type CSIVolumeDeregisterResponse struct {
   878  	QueryMeta
   879  }
   880  
   881  type CSIVolumeCreateRequest struct {
   882  	Volumes []*CSIVolume
   883  	WriteRequest
   884  }
   885  
   886  type CSIVolumeCreateResponse struct {
   887  	Volumes []*CSIVolume
   888  	QueryMeta
   889  }
   890  
   891  type CSIVolumeDeleteRequest struct {
   892  	VolumeIDs []string
   893  	Secrets   CSISecrets
   894  	WriteRequest
   895  }
   896  
   897  type CSIVolumeDeleteResponse struct {
   898  	QueryMeta
   899  }
   900  
   901  type CSIVolumeClaimMode int
   902  
   903  const (
   904  	CSIVolumeClaimRead CSIVolumeClaimMode = iota
   905  	CSIVolumeClaimWrite
   906  
   907  	// for GC we don't have a specific claim to set the state on, so instead we
   908  	// create a new claim for GC in order to bump the ModifyIndex and trigger
   909  	// volumewatcher
   910  	CSIVolumeClaimGC
   911  )
   912  
   913  type CSIVolumeClaimBatchRequest struct {
   914  	Claims []CSIVolumeClaimRequest
   915  }
   916  
   917  type CSIVolumeClaimRequest struct {
   918  	VolumeID       string
   919  	AllocationID   string
   920  	NodeID         string
   921  	ExternalNodeID string
   922  	Claim          CSIVolumeClaimMode
   923  	AccessMode     CSIVolumeAccessMode
   924  	AttachmentMode CSIVolumeAttachmentMode
   925  	State          CSIVolumeClaimState
   926  	WriteRequest
   927  }
   928  
   929  func (req *CSIVolumeClaimRequest) ToClaim() *CSIVolumeClaim {
   930  	return &CSIVolumeClaim{
   931  		AllocationID:   req.AllocationID,
   932  		NodeID:         req.NodeID,
   933  		ExternalNodeID: req.ExternalNodeID,
   934  		Mode:           req.Claim,
   935  		AccessMode:     req.AccessMode,
   936  		AttachmentMode: req.AttachmentMode,
   937  		State:          req.State,
   938  	}
   939  }
   940  
   941  type CSIVolumeClaimResponse struct {
   942  	// Opaque static publish properties of the volume. SP MAY use this
   943  	// field to ensure subsequent `NodeStageVolume` or `NodePublishVolume`
   944  	// calls calls have contextual information.
   945  	// The contents of this field SHALL be opaque to nomad.
   946  	// The contents of this field SHALL NOT be mutable.
   947  	// The contents of this field SHALL be safe for the nomad to cache.
   948  	// The contents of this field SHOULD NOT contain sensitive
   949  	// information.
   950  	// The contents of this field SHOULD NOT be used for uniquely
   951  	// identifying a volume. The `volume_id` alone SHOULD be sufficient to
   952  	// identify the volume.
   953  	// This field is OPTIONAL and when present MUST be passed to
   954  	// `NodeStageVolume` or `NodePublishVolume` calls on the client
   955  	PublishContext map[string]string
   956  
   957  	// Volume contains the expanded CSIVolume for use on the client after a Claim
   958  	// has completed.
   959  	Volume *CSIVolume
   960  
   961  	QueryMeta
   962  }
   963  
   964  type CSIVolumeListRequest struct {
   965  	PluginID string
   966  	NodeID   string
   967  	QueryOptions
   968  }
   969  
   970  type CSIVolumeListResponse struct {
   971  	Volumes []*CSIVolListStub
   972  	QueryMeta
   973  }
   974  
   975  // CSIVolumeExternalListRequest is a request to a controller plugin to list
   976  // all the volumes known to the the storage provider. This request is
   977  // paginated by the plugin and accepts the QueryOptions.PerPage and
   978  // QueryOptions.NextToken fields
   979  type CSIVolumeExternalListRequest struct {
   980  	PluginID string
   981  	QueryOptions
   982  }
   983  
   984  type CSIVolumeExternalListResponse struct {
   985  	Volumes   []*CSIVolumeExternalStub
   986  	NextToken string
   987  	QueryMeta
   988  }
   989  
   990  // CSIVolumeExternalStub is the storage provider's view of a volume, as
   991  // returned from the controller plugin; all IDs are for external resources
   992  type CSIVolumeExternalStub struct {
   993  	ExternalID    string
   994  	CapacityBytes int64
   995  	VolumeContext map[string]string
   996  	CloneID       string
   997  	SnapshotID    string
   998  
   999  	PublishedExternalNodeIDs []string
  1000  	IsAbnormal               bool
  1001  	Status                   string
  1002  }
  1003  
  1004  type CSIVolumeGetRequest struct {
  1005  	ID string
  1006  	QueryOptions
  1007  }
  1008  
  1009  type CSIVolumeGetResponse struct {
  1010  	Volume *CSIVolume
  1011  	QueryMeta
  1012  }
  1013  
  1014  type CSIVolumeUnpublishRequest struct {
  1015  	VolumeID string
  1016  	Claim    *CSIVolumeClaim
  1017  	WriteRequest
  1018  }
  1019  
  1020  type CSIVolumeUnpublishResponse struct {
  1021  	QueryMeta
  1022  }
  1023  
  1024  // CSISnapshot is the storage provider's view of a volume snapshot
  1025  type CSISnapshot struct {
  1026  	// These fields map to those returned by the storage provider plugin
  1027  	ID                     string // storage provider's ID
  1028  	ExternalSourceVolumeID string // storage provider's ID for volume
  1029  	SizeBytes              int64
  1030  	CreateTime             int64
  1031  	IsReady                bool
  1032  
  1033  	// These fields are controlled by Nomad
  1034  	SourceVolumeID string
  1035  	PluginID       string
  1036  
  1037  	// These field are only used during snapshot creation and will not be
  1038  	// populated when the snapshot is returned
  1039  	Name       string
  1040  	Secrets    CSISecrets
  1041  	Parameters map[string]string
  1042  }
  1043  
  1044  type CSISnapshotCreateRequest struct {
  1045  	Snapshots []*CSISnapshot
  1046  	WriteRequest
  1047  }
  1048  
  1049  type CSISnapshotCreateResponse struct {
  1050  	Snapshots []*CSISnapshot
  1051  	QueryMeta
  1052  }
  1053  
  1054  type CSISnapshotDeleteRequest struct {
  1055  	Snapshots []*CSISnapshot
  1056  	WriteRequest
  1057  }
  1058  
  1059  type CSISnapshotDeleteResponse struct {
  1060  	QueryMeta
  1061  }
  1062  
  1063  // CSISnapshotListRequest is a request to a controller plugin to list all the
  1064  // snapshot known to the the storage provider. This request is paginated by
  1065  // the plugin and accepts the QueryOptions.PerPage and QueryOptions.NextToken
  1066  // fields
  1067  type CSISnapshotListRequest struct {
  1068  	PluginID string
  1069  	Secrets  CSISecrets
  1070  	QueryOptions
  1071  }
  1072  
  1073  type CSISnapshotListResponse struct {
  1074  	Snapshots []*CSISnapshot
  1075  	NextToken string
  1076  	QueryMeta
  1077  }
  1078  
  1079  // CSIPlugin collects fingerprint info context for the plugin for clients
  1080  type CSIPlugin struct {
  1081  	ID                 string
  1082  	Provider           string // the vendor name from CSI GetPluginInfoResponse
  1083  	Version            string // the vendor verson from  CSI GetPluginInfoResponse
  1084  	ControllerRequired bool
  1085  
  1086  	// Map Node.IDs to fingerprint results, split by type. Monolith type plugins have
  1087  	// both sets of fingerprinting results.
  1088  	Controllers map[string]*CSIInfo
  1089  	Nodes       map[string]*CSIInfo
  1090  
  1091  	// Allocations are populated by denormalize to show running allocations
  1092  	Allocations []*AllocListStub
  1093  
  1094  	// Jobs are populated to by job update to support expected counts and the UI
  1095  	ControllerJobs JobDescriptions
  1096  	NodeJobs       JobDescriptions
  1097  
  1098  	// Cache the count of healthy plugins
  1099  	ControllersHealthy  int
  1100  	ControllersExpected int
  1101  	NodesHealthy        int
  1102  	NodesExpected       int
  1103  
  1104  	CreateIndex uint64
  1105  	ModifyIndex uint64
  1106  }
  1107  
  1108  // NewCSIPlugin creates the plugin struct. No side-effects
  1109  func NewCSIPlugin(id string, index uint64) *CSIPlugin {
  1110  	out := &CSIPlugin{
  1111  		ID:          id,
  1112  		CreateIndex: index,
  1113  		ModifyIndex: index,
  1114  	}
  1115  
  1116  	out.newStructs()
  1117  	return out
  1118  }
  1119  
  1120  func (p *CSIPlugin) newStructs() {
  1121  	p.Controllers = map[string]*CSIInfo{}
  1122  	p.Nodes = map[string]*CSIInfo{}
  1123  	p.ControllerJobs = make(JobDescriptions)
  1124  	p.NodeJobs = make(JobDescriptions)
  1125  }
  1126  
  1127  func (p *CSIPlugin) Copy() *CSIPlugin {
  1128  	copy := *p
  1129  	out := &copy
  1130  	out.newStructs()
  1131  
  1132  	for k, v := range p.Controllers {
  1133  		out.Controllers[k] = v.Copy()
  1134  	}
  1135  
  1136  	for k, v := range p.Nodes {
  1137  		out.Nodes[k] = v.Copy()
  1138  	}
  1139  
  1140  	for k, v := range p.ControllerJobs {
  1141  		out.ControllerJobs[k] = v.Copy()
  1142  	}
  1143  
  1144  	for k, v := range p.NodeJobs {
  1145  		out.NodeJobs[k] = v.Copy()
  1146  	}
  1147  
  1148  	return out
  1149  }
  1150  
  1151  type CSIControllerCapability byte
  1152  
  1153  const (
  1154  	// CSIControllerSupportsCreateDelete indicates plugin support for
  1155  	// CREATE_DELETE_VOLUME
  1156  	CSIControllerSupportsCreateDelete CSIControllerCapability = 0
  1157  
  1158  	// CSIControllerSupportsAttachDetach is true when the controller
  1159  	// implements the methods required to attach and detach volumes. If this
  1160  	// is false Nomad should skip the controller attachment flow.
  1161  	CSIControllerSupportsAttachDetach CSIControllerCapability = 1
  1162  
  1163  	// CSIControllerSupportsListVolumes is true when the controller implements
  1164  	// the ListVolumes RPC. NOTE: This does not guarantee that attached nodes
  1165  	// will be returned unless SupportsListVolumesAttachedNodes is also true.
  1166  	CSIControllerSupportsListVolumes CSIControllerCapability = 2
  1167  
  1168  	// CSIControllerSupportsGetCapacity indicates plugin support for
  1169  	// GET_CAPACITY
  1170  	CSIControllerSupportsGetCapacity CSIControllerCapability = 3
  1171  
  1172  	// CSIControllerSupportsCreateDeleteSnapshot indicates plugin support for
  1173  	// CREATE_DELETE_SNAPSHOT
  1174  	CSIControllerSupportsCreateDeleteSnapshot CSIControllerCapability = 4
  1175  
  1176  	// CSIControllerSupportsListSnapshots indicates plugin support for
  1177  	// LIST_SNAPSHOTS
  1178  	CSIControllerSupportsListSnapshots CSIControllerCapability = 5
  1179  
  1180  	// CSIControllerSupportsClone indicates plugin support for CLONE_VOLUME
  1181  	CSIControllerSupportsClone CSIControllerCapability = 6
  1182  
  1183  	// CSIControllerSupportsReadOnlyAttach is set to true when the controller
  1184  	// returns the ATTACH_READONLY capability.
  1185  	CSIControllerSupportsReadOnlyAttach CSIControllerCapability = 7
  1186  
  1187  	// CSIControllerSupportsExpand indicates plugin support for EXPAND_VOLUME
  1188  	CSIControllerSupportsExpand CSIControllerCapability = 8
  1189  
  1190  	// CSIControllerSupportsListVolumesAttachedNodes indicates whether the
  1191  	// plugin will return attached nodes data when making ListVolume RPCs
  1192  	// (plugin support for LIST_VOLUMES_PUBLISHED_NODES)
  1193  	CSIControllerSupportsListVolumesAttachedNodes CSIControllerCapability = 9
  1194  
  1195  	// CSIControllerSupportsCondition indicates plugin support for
  1196  	// VOLUME_CONDITION
  1197  	CSIControllerSupportsCondition CSIControllerCapability = 10
  1198  
  1199  	// CSIControllerSupportsGet indicates plugin support for GET_VOLUME
  1200  	CSIControllerSupportsGet CSIControllerCapability = 11
  1201  )
  1202  
  1203  type CSINodeCapability byte
  1204  
  1205  const (
  1206  
  1207  	// CSINodeSupportsStageVolume indicates whether the client should
  1208  	// Stage/Unstage volumes on this node.
  1209  	CSINodeSupportsStageVolume CSINodeCapability = 0
  1210  
  1211  	// CSINodeSupportsStats indicates plugin support for GET_VOLUME_STATS
  1212  	CSINodeSupportsStats CSINodeCapability = 1
  1213  
  1214  	// CSINodeSupportsExpand indicates plugin support for EXPAND_VOLUME
  1215  	CSINodeSupportsExpand CSINodeCapability = 2
  1216  
  1217  	// CSINodeSupportsCondition indicates plugin support for VOLUME_CONDITION
  1218  	CSINodeSupportsCondition CSINodeCapability = 3
  1219  )
  1220  
  1221  func (p *CSIPlugin) HasControllerCapability(cap CSIControllerCapability) bool {
  1222  	if len(p.Controllers) < 1 {
  1223  		return false
  1224  	}
  1225  	// we're picking the first controller because they should be uniform
  1226  	// across the same version of the plugin
  1227  	for _, c := range p.Controllers {
  1228  		switch cap {
  1229  		case CSIControllerSupportsCreateDelete:
  1230  			return c.ControllerInfo.SupportsCreateDelete
  1231  		case CSIControllerSupportsAttachDetach:
  1232  			return c.ControllerInfo.SupportsAttachDetach
  1233  		case CSIControllerSupportsListVolumes:
  1234  			return c.ControllerInfo.SupportsListVolumes
  1235  		case CSIControllerSupportsGetCapacity:
  1236  			return c.ControllerInfo.SupportsGetCapacity
  1237  		case CSIControllerSupportsCreateDeleteSnapshot:
  1238  			return c.ControllerInfo.SupportsCreateDeleteSnapshot
  1239  		case CSIControllerSupportsListSnapshots:
  1240  			return c.ControllerInfo.SupportsListSnapshots
  1241  		case CSIControllerSupportsClone:
  1242  			return c.ControllerInfo.SupportsClone
  1243  		case CSIControllerSupportsReadOnlyAttach:
  1244  			return c.ControllerInfo.SupportsReadOnlyAttach
  1245  		case CSIControllerSupportsExpand:
  1246  			return c.ControllerInfo.SupportsExpand
  1247  		case CSIControllerSupportsListVolumesAttachedNodes:
  1248  			return c.ControllerInfo.SupportsListVolumesAttachedNodes
  1249  		case CSIControllerSupportsCondition:
  1250  			return c.ControllerInfo.SupportsCondition
  1251  		case CSIControllerSupportsGet:
  1252  			return c.ControllerInfo.SupportsGet
  1253  		default:
  1254  			return false
  1255  		}
  1256  	}
  1257  	return false
  1258  }
  1259  
  1260  func (p *CSIPlugin) HasNodeCapability(cap CSINodeCapability) bool {
  1261  	if len(p.Nodes) < 1 {
  1262  		return false
  1263  	}
  1264  	// we're picking the first node because they should be uniform
  1265  	// across the same version of the plugin
  1266  	for _, c := range p.Nodes {
  1267  		switch cap {
  1268  		case CSINodeSupportsStageVolume:
  1269  			return c.NodeInfo.RequiresNodeStageVolume
  1270  		case CSINodeSupportsStats:
  1271  			return c.NodeInfo.SupportsStats
  1272  		case CSINodeSupportsExpand:
  1273  			return c.NodeInfo.SupportsExpand
  1274  		case CSINodeSupportsCondition:
  1275  			return c.NodeInfo.SupportsCondition
  1276  		default:
  1277  			return false
  1278  		}
  1279  	}
  1280  	return false
  1281  }
  1282  
  1283  // AddPlugin adds a single plugin running on the node. Called from state.NodeUpdate in a
  1284  // transaction
  1285  func (p *CSIPlugin) AddPlugin(nodeID string, info *CSIInfo) error {
  1286  	if info.ControllerInfo != nil {
  1287  		p.ControllerRequired = info.RequiresControllerPlugin
  1288  		prev, ok := p.Controllers[nodeID]
  1289  		if ok {
  1290  			if prev == nil {
  1291  				return fmt.Errorf("plugin missing controller: %s", nodeID)
  1292  			}
  1293  			if prev.Healthy {
  1294  				p.ControllersHealthy -= 1
  1295  			}
  1296  		}
  1297  
  1298  		// note: for this to work as expected, only a single
  1299  		// controller for a given plugin can be on a given Nomad
  1300  		// client, they also conflict on the client so this should be
  1301  		// ok
  1302  		if prev != nil || info.Healthy {
  1303  			p.Controllers[nodeID] = info
  1304  		}
  1305  		if info.Healthy {
  1306  			p.ControllersHealthy += 1
  1307  		}
  1308  	}
  1309  
  1310  	if info.NodeInfo != nil {
  1311  		prev, ok := p.Nodes[nodeID]
  1312  		if ok {
  1313  			if prev == nil {
  1314  				return fmt.Errorf("plugin missing node: %s", nodeID)
  1315  			}
  1316  			if prev.Healthy {
  1317  				p.NodesHealthy -= 1
  1318  			}
  1319  		}
  1320  		if prev != nil || info.Healthy {
  1321  			p.Nodes[nodeID] = info
  1322  		}
  1323  		if info.Healthy {
  1324  			p.NodesHealthy += 1
  1325  		}
  1326  	}
  1327  
  1328  	return nil
  1329  }
  1330  
  1331  // DeleteNode removes all plugins from the node. Called from state.DeleteNode in a
  1332  // transaction
  1333  func (p *CSIPlugin) DeleteNode(nodeID string) error {
  1334  	return p.DeleteNodeForType(nodeID, CSIPluginTypeMonolith)
  1335  }
  1336  
  1337  // DeleteNodeForType deletes a client node from the list of controllers or node instance of
  1338  // a plugin. Called from deleteJobFromPlugin during job deregistration, in a transaction
  1339  func (p *CSIPlugin) DeleteNodeForType(nodeID string, pluginType CSIPluginType) error {
  1340  	switch pluginType {
  1341  	case CSIPluginTypeController:
  1342  		if prev, ok := p.Controllers[nodeID]; ok {
  1343  			if prev == nil {
  1344  				return fmt.Errorf("plugin missing controller: %s", nodeID)
  1345  			}
  1346  			if prev.Healthy {
  1347  				p.ControllersHealthy--
  1348  			}
  1349  			delete(p.Controllers, nodeID)
  1350  		}
  1351  
  1352  	case CSIPluginTypeNode:
  1353  		if prev, ok := p.Nodes[nodeID]; ok {
  1354  			if prev == nil {
  1355  				return fmt.Errorf("plugin missing node: %s", nodeID)
  1356  			}
  1357  			if prev.Healthy {
  1358  				p.NodesHealthy--
  1359  			}
  1360  			delete(p.Nodes, nodeID)
  1361  		}
  1362  
  1363  	case CSIPluginTypeMonolith:
  1364  		var result error
  1365  
  1366  		err := p.DeleteNodeForType(nodeID, CSIPluginTypeController)
  1367  		if err != nil {
  1368  			result = multierror.Append(result, err)
  1369  		}
  1370  
  1371  		err = p.DeleteNodeForType(nodeID, CSIPluginTypeNode)
  1372  		if err != nil {
  1373  			result = multierror.Append(result, err)
  1374  		}
  1375  
  1376  		return result
  1377  	}
  1378  
  1379  	return nil
  1380  }
  1381  
  1382  // DeleteAlloc removes the fingerprint info for the allocation
  1383  func (p *CSIPlugin) DeleteAlloc(allocID, nodeID string) error {
  1384  	prev, ok := p.Controllers[nodeID]
  1385  	if ok {
  1386  		if prev == nil {
  1387  			return fmt.Errorf("plugin missing controller: %s", nodeID)
  1388  		}
  1389  		if prev.AllocID == allocID {
  1390  			if prev.Healthy {
  1391  				p.ControllersHealthy -= 1
  1392  			}
  1393  			delete(p.Controllers, nodeID)
  1394  		}
  1395  	}
  1396  
  1397  	prev, ok = p.Nodes[nodeID]
  1398  	if ok {
  1399  		if prev == nil {
  1400  			return fmt.Errorf("plugin missing node: %s", nodeID)
  1401  		}
  1402  		if prev.AllocID == allocID {
  1403  			if prev.Healthy {
  1404  				p.NodesHealthy -= 1
  1405  			}
  1406  			delete(p.Nodes, nodeID)
  1407  		}
  1408  	}
  1409  
  1410  	return nil
  1411  }
  1412  
  1413  // AddJob adds a job to the plugin and increments expected
  1414  func (p *CSIPlugin) AddJob(job *Job, summary *JobSummary) {
  1415  	p.UpdateExpectedWithJob(job, summary, false)
  1416  }
  1417  
  1418  // DeleteJob removes the job from the plugin and decrements expected
  1419  func (p *CSIPlugin) DeleteJob(job *Job, summary *JobSummary) {
  1420  	p.UpdateExpectedWithJob(job, summary, true)
  1421  }
  1422  
  1423  // UpdateExpectedWithJob maintains the expected instance count
  1424  // we use the summary to add non-allocation expected counts
  1425  func (p *CSIPlugin) UpdateExpectedWithJob(job *Job, summary *JobSummary, terminal bool) {
  1426  	var count int
  1427  
  1428  	for _, tg := range job.TaskGroups {
  1429  		if job.Type == JobTypeSystem {
  1430  			if summary == nil {
  1431  				continue
  1432  			}
  1433  
  1434  			s, ok := summary.Summary[tg.Name]
  1435  			if !ok {
  1436  				continue
  1437  			}
  1438  
  1439  			count = s.Running + s.Queued + s.Starting
  1440  		} else {
  1441  			count = tg.Count
  1442  		}
  1443  
  1444  		for _, t := range tg.Tasks {
  1445  			if t.CSIPluginConfig == nil ||
  1446  				t.CSIPluginConfig.ID != p.ID {
  1447  				continue
  1448  			}
  1449  
  1450  			// Change the correct plugin expected, monolith should change both
  1451  			if t.CSIPluginConfig.Type == CSIPluginTypeController ||
  1452  				t.CSIPluginConfig.Type == CSIPluginTypeMonolith {
  1453  				if terminal {
  1454  					p.ControllerJobs.Delete(job)
  1455  				} else {
  1456  					p.ControllerJobs.Add(job, count)
  1457  				}
  1458  			}
  1459  
  1460  			if t.CSIPluginConfig.Type == CSIPluginTypeNode ||
  1461  				t.CSIPluginConfig.Type == CSIPluginTypeMonolith {
  1462  				if terminal {
  1463  					p.NodeJobs.Delete(job)
  1464  				} else {
  1465  					p.NodeJobs.Add(job, count)
  1466  				}
  1467  			}
  1468  		}
  1469  	}
  1470  
  1471  	p.ControllersExpected = p.ControllerJobs.Count()
  1472  	p.NodesExpected = p.NodeJobs.Count()
  1473  }
  1474  
  1475  // JobDescription records Job identification and the count of expected plugin instances
  1476  type JobDescription struct {
  1477  	Namespace string
  1478  	ID        string
  1479  	Expected  int
  1480  }
  1481  
  1482  // JobNamespacedDescriptions maps Job.ID to JobDescription
  1483  type JobNamespacedDescriptions map[string]JobDescription
  1484  
  1485  func (j JobNamespacedDescriptions) Copy() JobNamespacedDescriptions {
  1486  	copy := JobNamespacedDescriptions{}
  1487  	for k, v := range j {
  1488  		copy[k] = v
  1489  	}
  1490  	return copy
  1491  }
  1492  
  1493  // JobDescriptions maps Namespace to a mapping of Job.ID to JobDescription
  1494  type JobDescriptions map[string]JobNamespacedDescriptions
  1495  
  1496  // Add the Job to the JobDescriptions, creating maps as necessary
  1497  func (j JobDescriptions) Add(job *Job, expected int) {
  1498  	if j == nil {
  1499  		j = make(JobDescriptions)
  1500  	}
  1501  	if j[job.Namespace] == nil {
  1502  		j[job.Namespace] = make(JobNamespacedDescriptions)
  1503  	}
  1504  	j[job.Namespace][job.ID] = JobDescription{
  1505  		Namespace: job.Namespace,
  1506  		ID:        job.ID,
  1507  		Expected:  expected,
  1508  	}
  1509  }
  1510  
  1511  // Count the Expected instances for all JobDescriptions
  1512  func (j JobDescriptions) Count() int {
  1513  	if j == nil {
  1514  		return 0
  1515  	}
  1516  	count := 0
  1517  	for _, jnd := range j {
  1518  		for _, jd := range jnd {
  1519  			count += jd.Expected
  1520  		}
  1521  	}
  1522  	return count
  1523  }
  1524  
  1525  // Delete the Job from the JobDescriptions
  1526  func (j JobDescriptions) Delete(job *Job) {
  1527  	if j != nil &&
  1528  		j[job.Namespace] != nil {
  1529  		delete(j[job.Namespace], job.ID)
  1530  	}
  1531  }
  1532  
  1533  type CSIPluginListStub struct {
  1534  	ID                  string
  1535  	Provider            string
  1536  	ControllerRequired  bool
  1537  	ControllersHealthy  int
  1538  	ControllersExpected int
  1539  	NodesHealthy        int
  1540  	NodesExpected       int
  1541  	CreateIndex         uint64
  1542  	ModifyIndex         uint64
  1543  }
  1544  
  1545  func (p *CSIPlugin) Stub() *CSIPluginListStub {
  1546  	return &CSIPluginListStub{
  1547  		ID:                  p.ID,
  1548  		Provider:            p.Provider,
  1549  		ControllerRequired:  p.ControllerRequired,
  1550  		ControllersHealthy:  p.ControllersHealthy,
  1551  		ControllersExpected: p.ControllersExpected,
  1552  		NodesHealthy:        p.NodesHealthy,
  1553  		NodesExpected:       p.NodesExpected,
  1554  		CreateIndex:         p.CreateIndex,
  1555  		ModifyIndex:         p.ModifyIndex,
  1556  	}
  1557  }
  1558  
  1559  func (p *CSIPlugin) IsEmpty() bool {
  1560  	return p == nil ||
  1561  		len(p.Controllers) == 0 &&
  1562  			len(p.Nodes) == 0 &&
  1563  			p.ControllerJobs.Count() == 0 &&
  1564  			p.NodeJobs.Count() == 0
  1565  }
  1566  
  1567  type CSIPluginListRequest struct {
  1568  	QueryOptions
  1569  }
  1570  
  1571  type CSIPluginListResponse struct {
  1572  	Plugins []*CSIPluginListStub
  1573  	QueryMeta
  1574  }
  1575  
  1576  type CSIPluginGetRequest struct {
  1577  	ID string
  1578  	QueryOptions
  1579  }
  1580  
  1581  type CSIPluginGetResponse struct {
  1582  	Plugin *CSIPlugin
  1583  	QueryMeta
  1584  }
  1585  
  1586  type CSIPluginDeleteRequest struct {
  1587  	ID string
  1588  	QueryOptions
  1589  }
  1590  
  1591  type CSIPluginDeleteResponse struct {
  1592  	QueryMeta
  1593  }