github.com/ThomasObenaus/nomad@v0.11.1/nomad/structs/csi.go (about)

     1  package structs
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  	"time"
     7  )
     8  
     9  // CSISocketName is the filename that Nomad expects plugins to create inside the
    10  // PluginMountDir.
    11  const CSISocketName = "csi.sock"
    12  
    13  // CSIIntermediaryDirname is the name of the directory inside the PluginMountDir
    14  // where Nomad will expect plugins to create intermediary mounts for volumes.
    15  const CSIIntermediaryDirname = "volumes"
    16  
    17  // VolumeTypeCSI is the type in the volume stanza of a TaskGroup
    18  const VolumeTypeCSI = "csi"
    19  
    20  // CSIPluginType is an enum string that encapsulates the valid options for a
    21  // CSIPlugin stanza's Type. These modes will allow the plugin to be used in
    22  // different ways by the client.
    23  type CSIPluginType string
    24  
    25  const (
    26  	// CSIPluginTypeNode indicates that Nomad should only use the plugin for
    27  	// performing Node RPCs against the provided plugin.
    28  	CSIPluginTypeNode CSIPluginType = "node"
    29  
    30  	// CSIPluginTypeController indicates that Nomad should only use the plugin for
    31  	// performing Controller RPCs against the provided plugin.
    32  	CSIPluginTypeController CSIPluginType = "controller"
    33  
    34  	// CSIPluginTypeMonolith indicates that Nomad can use the provided plugin for
    35  	// both controller and node rpcs.
    36  	CSIPluginTypeMonolith CSIPluginType = "monolith"
    37  )
    38  
    39  // CSIPluginTypeIsValid validates the given CSIPluginType string and returns
    40  // true only when a correct plugin type is specified.
    41  func CSIPluginTypeIsValid(pt CSIPluginType) bool {
    42  	switch pt {
    43  	case CSIPluginTypeNode, CSIPluginTypeController, CSIPluginTypeMonolith:
    44  		return true
    45  	default:
    46  		return false
    47  	}
    48  }
    49  
    50  // TaskCSIPluginConfig contains the data that is required to setup a task as a
    51  // CSI plugin. This will be used by the csi_plugin_supervisor_hook to configure
    52  // mounts for the plugin and initiate the connection to the plugin catalog.
    53  type TaskCSIPluginConfig struct {
    54  	// ID is the identifier of the plugin.
    55  	// Ideally this should be the FQDN of the plugin.
    56  	ID string
    57  
    58  	// Type instructs Nomad on how to handle processing a plugin
    59  	Type CSIPluginType
    60  
    61  	// MountDir is the destination that nomad should mount in its CSI
    62  	// directory for the plugin. It will then expect a file called CSISocketName
    63  	// to be created by the plugin, and will provide references into
    64  	// "MountDir/CSIIntermediaryDirname/{VolumeName}/{AllocID} for mounts.
    65  	MountDir string
    66  }
    67  
    68  func (t *TaskCSIPluginConfig) Copy() *TaskCSIPluginConfig {
    69  	if t == nil {
    70  		return nil
    71  	}
    72  
    73  	nt := new(TaskCSIPluginConfig)
    74  	*nt = *t
    75  
    76  	return nt
    77  }
    78  
    79  // CSIVolumeAttachmentMode chooses the type of storage api that will be used to
    80  // interact with the device.
    81  type CSIVolumeAttachmentMode string
    82  
    83  const (
    84  	CSIVolumeAttachmentModeUnknown     CSIVolumeAttachmentMode = ""
    85  	CSIVolumeAttachmentModeBlockDevice CSIVolumeAttachmentMode = "block-device"
    86  	CSIVolumeAttachmentModeFilesystem  CSIVolumeAttachmentMode = "file-system"
    87  )
    88  
    89  func ValidCSIVolumeAttachmentMode(attachmentMode CSIVolumeAttachmentMode) bool {
    90  	switch attachmentMode {
    91  	case CSIVolumeAttachmentModeBlockDevice, CSIVolumeAttachmentModeFilesystem:
    92  		return true
    93  	default:
    94  		return false
    95  	}
    96  }
    97  
    98  // CSIVolumeAccessMode indicates how a volume should be used in a storage topology
    99  // e.g whether the provider should make the volume available concurrently.
   100  type CSIVolumeAccessMode string
   101  
   102  const (
   103  	CSIVolumeAccessModeUnknown CSIVolumeAccessMode = ""
   104  
   105  	CSIVolumeAccessModeSingleNodeReader CSIVolumeAccessMode = "single-node-reader-only"
   106  	CSIVolumeAccessModeSingleNodeWriter CSIVolumeAccessMode = "single-node-writer"
   107  
   108  	CSIVolumeAccessModeMultiNodeReader       CSIVolumeAccessMode = "multi-node-reader-only"
   109  	CSIVolumeAccessModeMultiNodeSingleWriter CSIVolumeAccessMode = "multi-node-single-writer"
   110  	CSIVolumeAccessModeMultiNodeMultiWriter  CSIVolumeAccessMode = "multi-node-multi-writer"
   111  )
   112  
   113  // ValidCSIVolumeAccessMode checks to see that the provided access mode is a valid,
   114  // non-empty access mode.
   115  func ValidCSIVolumeAccessMode(accessMode CSIVolumeAccessMode) bool {
   116  	switch accessMode {
   117  	case CSIVolumeAccessModeSingleNodeReader, CSIVolumeAccessModeSingleNodeWriter,
   118  		CSIVolumeAccessModeMultiNodeReader, CSIVolumeAccessModeMultiNodeSingleWriter,
   119  		CSIVolumeAccessModeMultiNodeMultiWriter:
   120  		return true
   121  	default:
   122  		return false
   123  	}
   124  }
   125  
   126  // ValidCSIVolumeAccessMode checks for a writable access mode
   127  func ValidCSIVolumeWriteAccessMode(accessMode CSIVolumeAccessMode) bool {
   128  	switch accessMode {
   129  	case CSIVolumeAccessModeSingleNodeWriter,
   130  		CSIVolumeAccessModeMultiNodeSingleWriter,
   131  		CSIVolumeAccessModeMultiNodeMultiWriter:
   132  		return true
   133  	default:
   134  		return false
   135  	}
   136  }
   137  
   138  // CSIMountOptions contain optional additional configuration that can be used
   139  // when specifying that a Volume should be used with VolumeAccessTypeMount.
   140  type CSIMountOptions struct {
   141  	// FSType is an optional field that allows an operator to specify the type
   142  	// of the filesystem.
   143  	FSType string
   144  
   145  	// MountFlags contains additional options that may be used when mounting the
   146  	// volume by the plugin. This may contain sensitive data and should not be
   147  	// leaked.
   148  	MountFlags []string
   149  }
   150  
   151  func (o *CSIMountOptions) Copy() *CSIMountOptions {
   152  	if o == nil {
   153  		return nil
   154  	}
   155  	return &(*o)
   156  }
   157  
   158  func (o *CSIMountOptions) Merge(p *CSIMountOptions) {
   159  	if p == nil {
   160  		return
   161  	}
   162  	if p.FSType != "" {
   163  		o.FSType = p.FSType
   164  	}
   165  	if p.MountFlags != nil {
   166  		o.MountFlags = p.MountFlags
   167  	}
   168  }
   169  
   170  // VolumeMountOptions implements the Stringer and GoStringer interfaces to prevent
   171  // accidental leakage of sensitive mount flags via logs.
   172  var _ fmt.Stringer = &CSIMountOptions{}
   173  var _ fmt.GoStringer = &CSIMountOptions{}
   174  
   175  func (v *CSIMountOptions) String() string {
   176  	mountFlagsString := "nil"
   177  	if len(v.MountFlags) != 0 {
   178  		mountFlagsString = "[REDACTED]"
   179  	}
   180  
   181  	return fmt.Sprintf("csi.CSIOptions(FSType: %s, MountFlags: %s)", v.FSType, mountFlagsString)
   182  }
   183  
   184  func (v *CSIMountOptions) GoString() string {
   185  	return v.String()
   186  }
   187  
   188  // CSIVolume is the full representation of a CSI Volume
   189  type CSIVolume struct {
   190  	// ID is a namespace unique URL safe identifier for the volume
   191  	ID string
   192  	// Name is a display name for the volume, not required to be unique
   193  	Name string
   194  	// ExternalID identifies the volume for the CSI interface, may be URL unsafe
   195  	ExternalID     string
   196  	Namespace      string
   197  	Topologies     []*CSITopology
   198  	AccessMode     CSIVolumeAccessMode
   199  	AttachmentMode CSIVolumeAttachmentMode
   200  	MountOptions   *CSIMountOptions
   201  
   202  	// Allocations, tracking claim status
   203  	ReadAllocs  map[string]*Allocation
   204  	WriteAllocs map[string]*Allocation
   205  
   206  	// Schedulable is true if all the denormalized plugin health fields are true, and the
   207  	// volume has not been marked for garbage collection
   208  	Schedulable         bool
   209  	PluginID            string
   210  	Provider            string
   211  	ProviderVersion     string
   212  	ControllerRequired  bool
   213  	ControllersHealthy  int
   214  	ControllersExpected int
   215  	NodesHealthy        int
   216  	NodesExpected       int
   217  	ResourceExhausted   time.Time
   218  
   219  	CreateIndex uint64
   220  	ModifyIndex uint64
   221  }
   222  
   223  // CSIVolListStub is partial representation of a CSI Volume for inclusion in lists
   224  type CSIVolListStub struct {
   225  	ID                  string
   226  	Namespace           string
   227  	Name                string
   228  	ExternalID          string
   229  	Topologies          []*CSITopology
   230  	AccessMode          CSIVolumeAccessMode
   231  	AttachmentMode      CSIVolumeAttachmentMode
   232  	MountOptions        *CSIMountOptions
   233  	CurrentReaders      int
   234  	CurrentWriters      int
   235  	Schedulable         bool
   236  	PluginID            string
   237  	Provider            string
   238  	ControllersHealthy  int
   239  	ControllersExpected int
   240  	NodesHealthy        int
   241  	NodesExpected       int
   242  	CreateIndex         uint64
   243  	ModifyIndex         uint64
   244  }
   245  
   246  // NewCSIVolume creates the volume struct. No side-effects
   247  func NewCSIVolume(volumeID string, index uint64) *CSIVolume {
   248  	out := &CSIVolume{
   249  		ID:          volumeID,
   250  		CreateIndex: index,
   251  		ModifyIndex: index,
   252  	}
   253  
   254  	out.newStructs()
   255  	return out
   256  }
   257  
   258  func (v *CSIVolume) newStructs() {
   259  	if v.Topologies == nil {
   260  		v.Topologies = []*CSITopology{}
   261  	}
   262  
   263  	v.ReadAllocs = map[string]*Allocation{}
   264  	v.WriteAllocs = map[string]*Allocation{}
   265  }
   266  
   267  func (v *CSIVolume) RemoteID() string {
   268  	if v.ExternalID != "" {
   269  		return v.ExternalID
   270  	}
   271  	return v.ID
   272  }
   273  
   274  func (v *CSIVolume) Stub() *CSIVolListStub {
   275  	stub := CSIVolListStub{
   276  		ID:                  v.ID,
   277  		Namespace:           v.Namespace,
   278  		Name:                v.Name,
   279  		ExternalID:          v.ExternalID,
   280  		Topologies:          v.Topologies,
   281  		AccessMode:          v.AccessMode,
   282  		AttachmentMode:      v.AttachmentMode,
   283  		MountOptions:        v.MountOptions,
   284  		CurrentReaders:      len(v.ReadAllocs),
   285  		CurrentWriters:      len(v.WriteAllocs),
   286  		Schedulable:         v.Schedulable,
   287  		PluginID:            v.PluginID,
   288  		Provider:            v.Provider,
   289  		ControllersHealthy:  v.ControllersHealthy,
   290  		ControllersExpected: v.ControllersExpected,
   291  		NodesHealthy:        v.NodesHealthy,
   292  		NodesExpected:       v.NodesExpected,
   293  		CreateIndex:         v.CreateIndex,
   294  		ModifyIndex:         v.ModifyIndex,
   295  	}
   296  
   297  	return &stub
   298  }
   299  
   300  func (v *CSIVolume) ReadSchedulable() bool {
   301  	if !v.Schedulable {
   302  		return false
   303  	}
   304  
   305  	return v.ResourceExhausted == time.Time{}
   306  }
   307  
   308  // WriteSchedulable determines if the volume is schedulable for writes, considering only
   309  // volume health
   310  func (v *CSIVolume) WriteSchedulable() bool {
   311  	if !v.Schedulable {
   312  		return false
   313  	}
   314  
   315  	switch v.AccessMode {
   316  	case CSIVolumeAccessModeSingleNodeWriter, CSIVolumeAccessModeMultiNodeSingleWriter, CSIVolumeAccessModeMultiNodeMultiWriter:
   317  		return v.ResourceExhausted == time.Time{}
   318  	default:
   319  		return false
   320  	}
   321  }
   322  
   323  // WriteFreeClaims determines if there are any free write claims available
   324  func (v *CSIVolume) WriteFreeClaims() bool {
   325  	switch v.AccessMode {
   326  	case CSIVolumeAccessModeSingleNodeWriter, CSIVolumeAccessModeMultiNodeSingleWriter, CSIVolumeAccessModeMultiNodeMultiWriter:
   327  		return len(v.WriteAllocs) == 0
   328  	default:
   329  		return false
   330  	}
   331  }
   332  
   333  // InUse tests whether any allocations are actively using the volume
   334  func (v *CSIVolume) InUse() bool {
   335  	return len(v.ReadAllocs) != 0 ||
   336  		len(v.WriteAllocs) != 0
   337  }
   338  
   339  // Copy returns a copy of the volume, which shares only the Topologies slice
   340  func (v *CSIVolume) Copy() *CSIVolume {
   341  	copy := *v
   342  	out := &copy
   343  	out.newStructs()
   344  
   345  	for k, v := range v.ReadAllocs {
   346  		out.ReadAllocs[k] = v
   347  	}
   348  
   349  	for k, v := range v.WriteAllocs {
   350  		out.WriteAllocs[k] = v
   351  	}
   352  
   353  	return out
   354  }
   355  
   356  // Claim updates the allocations and changes the volume state
   357  func (v *CSIVolume) Claim(claim CSIVolumeClaimMode, alloc *Allocation) error {
   358  	switch claim {
   359  	case CSIVolumeClaimRead:
   360  		return v.ClaimRead(alloc)
   361  	case CSIVolumeClaimWrite:
   362  		return v.ClaimWrite(alloc)
   363  	case CSIVolumeClaimRelease:
   364  		return v.ClaimRelease(alloc)
   365  	}
   366  	return nil
   367  }
   368  
   369  // ClaimRead marks an allocation as using a volume read-only
   370  func (v *CSIVolume) ClaimRead(alloc *Allocation) error {
   371  	if alloc == nil {
   372  		return fmt.Errorf("allocation missing")
   373  	}
   374  	if _, ok := v.ReadAllocs[alloc.ID]; ok {
   375  		return nil
   376  	}
   377  
   378  	if !v.ReadSchedulable() {
   379  		return fmt.Errorf("unschedulable")
   380  	}
   381  
   382  	// Allocations are copy on write, so we want to keep the id but don't need the
   383  	// pointer. We'll get it from the db in denormalize.
   384  	v.ReadAllocs[alloc.ID] = nil
   385  	delete(v.WriteAllocs, alloc.ID)
   386  	return nil
   387  }
   388  
   389  // ClaimWrite marks an allocation as using a volume as a writer
   390  func (v *CSIVolume) ClaimWrite(alloc *Allocation) error {
   391  	if alloc == nil {
   392  		return fmt.Errorf("allocation missing")
   393  	}
   394  	if _, ok := v.WriteAllocs[alloc.ID]; ok {
   395  		return nil
   396  	}
   397  
   398  	if !v.WriteSchedulable() {
   399  		return fmt.Errorf("unschedulable")
   400  	}
   401  
   402  	if !v.WriteFreeClaims() {
   403  		// Check the blocking allocations to see if they belong to this job
   404  		for _, a := range v.WriteAllocs {
   405  			if a.Namespace != alloc.Namespace || a.JobID != alloc.JobID {
   406  				return fmt.Errorf("volume max claim reached")
   407  			}
   408  		}
   409  	}
   410  
   411  	// Allocations are copy on write, so we want to keep the id but don't need the
   412  	// pointer. We'll get it from the db in denormalize.
   413  	v.WriteAllocs[alloc.ID] = nil
   414  	delete(v.ReadAllocs, alloc.ID)
   415  	return nil
   416  }
   417  
   418  // ClaimRelease is called when the allocation has terminated and already stopped using the volume
   419  func (v *CSIVolume) ClaimRelease(alloc *Allocation) error {
   420  	if alloc == nil {
   421  		return fmt.Errorf("allocation missing")
   422  	}
   423  	delete(v.ReadAllocs, alloc.ID)
   424  	delete(v.WriteAllocs, alloc.ID)
   425  	return nil
   426  }
   427  
   428  // Equality by value
   429  func (v *CSIVolume) Equal(o *CSIVolume) bool {
   430  	if v == nil || o == nil {
   431  		return v == o
   432  	}
   433  
   434  	// Omit the plugin health fields, their values are controlled by plugin jobs
   435  	if v.ID == o.ID &&
   436  		v.Namespace == o.Namespace &&
   437  		v.AccessMode == o.AccessMode &&
   438  		v.AttachmentMode == o.AttachmentMode &&
   439  		v.PluginID == o.PluginID {
   440  		// Setwise equality of topologies
   441  		var ok bool
   442  		for _, t := range v.Topologies {
   443  			ok = false
   444  			for _, u := range o.Topologies {
   445  				if t.Equal(u) {
   446  					ok = true
   447  					break
   448  				}
   449  			}
   450  			if !ok {
   451  				return false
   452  			}
   453  		}
   454  		return true
   455  	}
   456  	return false
   457  }
   458  
   459  // Validate validates the volume struct, returning all validation errors at once
   460  func (v *CSIVolume) Validate() error {
   461  	errs := []string{}
   462  
   463  	if v.ID == "" {
   464  		errs = append(errs, "missing volume id")
   465  	}
   466  	if v.PluginID == "" {
   467  		errs = append(errs, "missing plugin id")
   468  	}
   469  	if v.Namespace == "" {
   470  		errs = append(errs, "missing namespace")
   471  	}
   472  	if v.AccessMode == "" {
   473  		errs = append(errs, "missing access mode")
   474  	}
   475  	if v.AttachmentMode == "" {
   476  		errs = append(errs, "missing attachment mode")
   477  	}
   478  
   479  	// TODO: Volume Topologies are optional - We should check to see if the plugin
   480  	//       the volume is being registered with requires them.
   481  	// var ok bool
   482  	// for _, t := range v.Topologies {
   483  	// 	if t != nil && len(t.Segments) > 0 {
   484  	// 		ok = true
   485  	// 		break
   486  	// 	}
   487  	// }
   488  	// if !ok {
   489  	// 	errs = append(errs, "missing topology")
   490  	// }
   491  
   492  	if len(errs) > 0 {
   493  		return fmt.Errorf("validation: %s", strings.Join(errs, ", "))
   494  	}
   495  	return nil
   496  }
   497  
   498  // Request and response wrappers
   499  type CSIVolumeRegisterRequest struct {
   500  	Volumes []*CSIVolume
   501  	WriteRequest
   502  }
   503  
   504  type CSIVolumeRegisterResponse struct {
   505  	QueryMeta
   506  }
   507  
   508  type CSIVolumeDeregisterRequest struct {
   509  	VolumeIDs []string
   510  	WriteRequest
   511  }
   512  
   513  type CSIVolumeDeregisterResponse struct {
   514  	QueryMeta
   515  }
   516  
   517  type CSIVolumeClaimMode int
   518  
   519  const (
   520  	CSIVolumeClaimRead CSIVolumeClaimMode = iota
   521  	CSIVolumeClaimWrite
   522  	CSIVolumeClaimRelease
   523  )
   524  
   525  type CSIVolumeClaimRequest struct {
   526  	VolumeID     string
   527  	AllocationID string
   528  	Claim        CSIVolumeClaimMode
   529  	WriteRequest
   530  }
   531  
   532  type CSIVolumeClaimResponse struct {
   533  	// Opaque static publish properties of the volume. SP MAY use this
   534  	// field to ensure subsequent `NodeStageVolume` or `NodePublishVolume`
   535  	// calls calls have contextual information.
   536  	// The contents of this field SHALL be opaque to nomad.
   537  	// The contents of this field SHALL NOT be mutable.
   538  	// The contents of this field SHALL be safe for the nomad to cache.
   539  	// The contents of this field SHOULD NOT contain sensitive
   540  	// information.
   541  	// The contents of this field SHOULD NOT be used for uniquely
   542  	// identifying a volume. The `volume_id` alone SHOULD be sufficient to
   543  	// identify the volume.
   544  	// This field is OPTIONAL and when present MUST be passed to
   545  	// `NodeStageVolume` or `NodePublishVolume` calls on the client
   546  	PublishContext map[string]string
   547  
   548  	// Volume contains the expanded CSIVolume for use on the client after a Claim
   549  	// has completed.
   550  	Volume *CSIVolume
   551  
   552  	QueryMeta
   553  }
   554  
   555  type CSIVolumeListRequest struct {
   556  	PluginID string
   557  	NodeID   string
   558  	QueryOptions
   559  }
   560  
   561  type CSIVolumeListResponse struct {
   562  	Volumes []*CSIVolListStub
   563  	QueryMeta
   564  }
   565  
   566  type CSIVolumeGetRequest struct {
   567  	ID string
   568  	QueryOptions
   569  }
   570  
   571  type CSIVolumeGetResponse struct {
   572  	Volume *CSIVolume
   573  	QueryMeta
   574  }
   575  
   576  // CSIPlugin collects fingerprint info context for the plugin for clients
   577  type CSIPlugin struct {
   578  	ID                 string
   579  	Provider           string // the vendor name from CSI GetPluginInfoResponse
   580  	Version            string // the vendor verson from  CSI GetPluginInfoResponse
   581  	ControllerRequired bool
   582  
   583  	// Map Node.IDs to fingerprint results, split by type. Monolith type plugins have
   584  	// both sets of fingerprinting results.
   585  	Controllers map[string]*CSIInfo
   586  	Nodes       map[string]*CSIInfo
   587  
   588  	// Allocations are populated by denormalize to show running allocations
   589  	Allocations []*AllocListStub
   590  
   591  	// Cache the count of healthy plugins
   592  	ControllersHealthy int
   593  	NodesHealthy       int
   594  
   595  	CreateIndex uint64
   596  	ModifyIndex uint64
   597  }
   598  
   599  // NewCSIPlugin creates the plugin struct. No side-effects
   600  func NewCSIPlugin(id string, index uint64) *CSIPlugin {
   601  	out := &CSIPlugin{
   602  		ID:          id,
   603  		CreateIndex: index,
   604  		ModifyIndex: index,
   605  	}
   606  
   607  	out.newStructs()
   608  	return out
   609  }
   610  
   611  func (p *CSIPlugin) newStructs() {
   612  	p.Controllers = map[string]*CSIInfo{}
   613  	p.Nodes = map[string]*CSIInfo{}
   614  }
   615  
   616  func (p *CSIPlugin) Copy() *CSIPlugin {
   617  	copy := *p
   618  	out := &copy
   619  	out.newStructs()
   620  
   621  	for k, v := range p.Controllers {
   622  		out.Controllers[k] = v
   623  	}
   624  
   625  	for k, v := range p.Nodes {
   626  		out.Nodes[k] = v
   627  	}
   628  
   629  	return out
   630  }
   631  
   632  // AddPlugin adds a single plugin running on the node. Called from state.NodeUpdate in a
   633  // transaction
   634  func (p *CSIPlugin) AddPlugin(nodeID string, info *CSIInfo) error {
   635  	if info.ControllerInfo != nil {
   636  		p.ControllerRequired = info.RequiresControllerPlugin &&
   637  			info.ControllerInfo.SupportsAttachDetach
   638  
   639  		prev, ok := p.Controllers[nodeID]
   640  		if ok {
   641  			if prev == nil {
   642  				return fmt.Errorf("plugin missing controller: %s", nodeID)
   643  			}
   644  			if prev.Healthy {
   645  				p.ControllersHealthy -= 1
   646  			}
   647  		}
   648  		p.Controllers[nodeID] = info
   649  		if info.Healthy {
   650  			p.ControllersHealthy += 1
   651  		}
   652  	}
   653  
   654  	if info.NodeInfo != nil {
   655  		prev, ok := p.Nodes[nodeID]
   656  		if ok {
   657  			if prev == nil {
   658  				return fmt.Errorf("plugin missing node: %s", nodeID)
   659  			}
   660  			if prev.Healthy {
   661  				p.NodesHealthy -= 1
   662  			}
   663  		}
   664  		p.Nodes[nodeID] = info
   665  		if info.Healthy {
   666  			p.NodesHealthy += 1
   667  		}
   668  	}
   669  
   670  	return nil
   671  }
   672  
   673  // DeleteNode removes all plugins from the node. Called from state.DeleteNode in a
   674  // transaction
   675  func (p *CSIPlugin) DeleteNode(nodeID string) error {
   676  	return p.DeleteNodeForType(nodeID, CSIPluginTypeMonolith)
   677  }
   678  
   679  // DeleteNodeForType deletes a client node from the list of controllers or node instance of
   680  // a plugin. Called from deleteJobFromPlugin during job deregistration, in a transaction
   681  func (p *CSIPlugin) DeleteNodeForType(nodeID string, pluginType CSIPluginType) error {
   682  	switch pluginType {
   683  	case CSIPluginTypeController:
   684  		prev, ok := p.Controllers[nodeID]
   685  		if ok {
   686  			if prev == nil {
   687  				return fmt.Errorf("plugin missing controller: %s", nodeID)
   688  			}
   689  			if prev.Healthy {
   690  				p.ControllersHealthy -= 1
   691  			}
   692  		}
   693  		delete(p.Controllers, nodeID)
   694  
   695  	case CSIPluginTypeNode:
   696  		prev, ok := p.Nodes[nodeID]
   697  		if ok {
   698  			if prev == nil {
   699  				return fmt.Errorf("plugin missing node: %s", nodeID)
   700  			}
   701  			if prev.Healthy {
   702  				p.NodesHealthy -= 1
   703  			}
   704  		}
   705  		delete(p.Nodes, nodeID)
   706  
   707  	case CSIPluginTypeMonolith:
   708  		p.DeleteNodeForType(nodeID, CSIPluginTypeController)
   709  		p.DeleteNodeForType(nodeID, CSIPluginTypeNode)
   710  	}
   711  
   712  	return nil
   713  }
   714  
   715  // DeleteAlloc removes the fingerprint info for the allocation
   716  func (p *CSIPlugin) DeleteAlloc(allocID, nodeID string) error {
   717  	prev, ok := p.Controllers[nodeID]
   718  	if ok {
   719  		if prev == nil {
   720  			return fmt.Errorf("plugin missing controller: %s", nodeID)
   721  		}
   722  		if prev.AllocID == allocID {
   723  			if prev.Healthy {
   724  				p.ControllersHealthy -= 1
   725  			}
   726  			delete(p.Controllers, nodeID)
   727  		}
   728  	}
   729  
   730  	prev, ok = p.Nodes[nodeID]
   731  	if ok {
   732  		if prev == nil {
   733  			return fmt.Errorf("plugin missing node: %s", nodeID)
   734  		}
   735  		if prev.AllocID == allocID {
   736  			if prev.Healthy {
   737  				p.NodesHealthy -= 1
   738  			}
   739  			delete(p.Nodes, nodeID)
   740  		}
   741  	}
   742  
   743  	return nil
   744  }
   745  
   746  type CSIPluginListStub struct {
   747  	ID                  string
   748  	Provider            string
   749  	ControllerRequired  bool
   750  	ControllersHealthy  int
   751  	ControllersExpected int
   752  	NodesHealthy        int
   753  	NodesExpected       int
   754  	CreateIndex         uint64
   755  	ModifyIndex         uint64
   756  }
   757  
   758  func (p *CSIPlugin) Stub() *CSIPluginListStub {
   759  	return &CSIPluginListStub{
   760  		ID:                  p.ID,
   761  		Provider:            p.Provider,
   762  		ControllerRequired:  p.ControllerRequired,
   763  		ControllersHealthy:  p.ControllersHealthy,
   764  		ControllersExpected: len(p.Controllers),
   765  		NodesHealthy:        p.NodesHealthy,
   766  		NodesExpected:       len(p.Nodes),
   767  		CreateIndex:         p.CreateIndex,
   768  		ModifyIndex:         p.ModifyIndex,
   769  	}
   770  }
   771  
   772  func (p *CSIPlugin) IsEmpty() bool {
   773  	return len(p.Controllers) == 0 && len(p.Nodes) == 0
   774  }
   775  
   776  type CSIPluginListRequest struct {
   777  	QueryOptions
   778  }
   779  
   780  type CSIPluginListResponse struct {
   781  	Plugins []*CSIPluginListStub
   782  	QueryMeta
   783  }
   784  
   785  type CSIPluginGetRequest struct {
   786  	ID string
   787  	QueryOptions
   788  }
   789  
   790  type CSIPluginGetResponse struct {
   791  	Plugin *CSIPlugin
   792  	QueryMeta
   793  }