github.com/hernad/nomad@v1.6.112/nomad/structs/csi.go (about)

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