github.com/Ilhicas/nomad@v1.0.4-0.20210304152020-e86851182bc3/nomad/structs/csi.go (about)

     1  package structs
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  	"time"
     7  
     8  	multierror "github.com/hashicorp/go-multierror"
     9  	"github.com/hashicorp/nomad/helper"
    10  )
    11  
    12  // CSISocketName is the filename that Nomad expects plugins to create inside the
    13  // PluginMountDir.
    14  const CSISocketName = "csi.sock"
    15  
    16  // CSIIntermediaryDirname is the name of the directory inside the PluginMountDir
    17  // where Nomad will expect plugins to create intermediary mounts for volumes.
    18  const CSIIntermediaryDirname = "volumes"
    19  
    20  // VolumeTypeCSI is the type in the volume stanza of a TaskGroup
    21  const VolumeTypeCSI = "csi"
    22  
    23  // CSIPluginType is an enum string that encapsulates the valid options for a
    24  // CSIPlugin stanza's Type. These modes will allow the plugin to be used in
    25  // different ways by the client.
    26  type CSIPluginType string
    27  
    28  const (
    29  	// CSIPluginTypeNode indicates that Nomad should only use the plugin for
    30  	// performing Node RPCs against the provided plugin.
    31  	CSIPluginTypeNode CSIPluginType = "node"
    32  
    33  	// CSIPluginTypeController indicates that Nomad should only use the plugin for
    34  	// performing Controller RPCs against the provided plugin.
    35  	CSIPluginTypeController CSIPluginType = "controller"
    36  
    37  	// CSIPluginTypeMonolith indicates that Nomad can use the provided plugin for
    38  	// both controller and node rpcs.
    39  	CSIPluginTypeMonolith CSIPluginType = "monolith"
    40  )
    41  
    42  // CSIPluginTypeIsValid validates the given CSIPluginType string and returns
    43  // true only when a correct plugin type is specified.
    44  func CSIPluginTypeIsValid(pt CSIPluginType) bool {
    45  	switch pt {
    46  	case CSIPluginTypeNode, CSIPluginTypeController, CSIPluginTypeMonolith:
    47  		return true
    48  	default:
    49  		return false
    50  	}
    51  }
    52  
    53  // TaskCSIPluginConfig contains the data that is required to setup a task as a
    54  // CSI plugin. This will be used by the csi_plugin_supervisor_hook to configure
    55  // mounts for the plugin and initiate the connection to the plugin catalog.
    56  type TaskCSIPluginConfig struct {
    57  	// ID is the identifier of the plugin.
    58  	// Ideally this should be the FQDN of the plugin.
    59  	ID string
    60  
    61  	// Type instructs Nomad on how to handle processing a plugin
    62  	Type CSIPluginType
    63  
    64  	// MountDir is the destination that nomad should mount in its CSI
    65  	// directory for the plugin. It will then expect a file called CSISocketName
    66  	// to be created by the plugin, and will provide references into
    67  	// "MountDir/CSIIntermediaryDirname/{VolumeName}/{AllocID} for mounts.
    68  	MountDir string
    69  }
    70  
    71  func (t *TaskCSIPluginConfig) Copy() *TaskCSIPluginConfig {
    72  	if t == nil {
    73  		return nil
    74  	}
    75  
    76  	nt := new(TaskCSIPluginConfig)
    77  	*nt = *t
    78  
    79  	return nt
    80  }
    81  
    82  // CSIVolumeAttachmentMode chooses the type of storage api that will be used to
    83  // interact with the device.
    84  type CSIVolumeAttachmentMode string
    85  
    86  const (
    87  	CSIVolumeAttachmentModeUnknown     CSIVolumeAttachmentMode = ""
    88  	CSIVolumeAttachmentModeBlockDevice CSIVolumeAttachmentMode = "block-device"
    89  	CSIVolumeAttachmentModeFilesystem  CSIVolumeAttachmentMode = "file-system"
    90  )
    91  
    92  func ValidCSIVolumeAttachmentMode(attachmentMode CSIVolumeAttachmentMode) bool {
    93  	switch attachmentMode {
    94  	case CSIVolumeAttachmentModeBlockDevice, CSIVolumeAttachmentModeFilesystem:
    95  		return true
    96  	default:
    97  		return false
    98  	}
    99  }
   100  
   101  // CSIVolumeAccessMode indicates how a volume should be used in a storage topology
   102  // e.g whether the provider should make the volume available concurrently.
   103  type CSIVolumeAccessMode string
   104  
   105  const (
   106  	CSIVolumeAccessModeUnknown CSIVolumeAccessMode = ""
   107  
   108  	CSIVolumeAccessModeSingleNodeReader CSIVolumeAccessMode = "single-node-reader-only"
   109  	CSIVolumeAccessModeSingleNodeWriter CSIVolumeAccessMode = "single-node-writer"
   110  
   111  	CSIVolumeAccessModeMultiNodeReader       CSIVolumeAccessMode = "multi-node-reader-only"
   112  	CSIVolumeAccessModeMultiNodeSingleWriter CSIVolumeAccessMode = "multi-node-single-writer"
   113  	CSIVolumeAccessModeMultiNodeMultiWriter  CSIVolumeAccessMode = "multi-node-multi-writer"
   114  )
   115  
   116  // ValidCSIVolumeAccessMode checks to see that the provided access mode is a valid,
   117  // non-empty access mode.
   118  func ValidCSIVolumeAccessMode(accessMode CSIVolumeAccessMode) bool {
   119  	switch accessMode {
   120  	case CSIVolumeAccessModeSingleNodeReader, CSIVolumeAccessModeSingleNodeWriter,
   121  		CSIVolumeAccessModeMultiNodeReader, CSIVolumeAccessModeMultiNodeSingleWriter,
   122  		CSIVolumeAccessModeMultiNodeMultiWriter:
   123  		return true
   124  	default:
   125  		return false
   126  	}
   127  }
   128  
   129  // ValidCSIVolumeAccessMode checks for a writable access mode
   130  func ValidCSIVolumeWriteAccessMode(accessMode CSIVolumeAccessMode) bool {
   131  	switch accessMode {
   132  	case CSIVolumeAccessModeSingleNodeWriter,
   133  		CSIVolumeAccessModeMultiNodeSingleWriter,
   134  		CSIVolumeAccessModeMultiNodeMultiWriter:
   135  		return true
   136  	default:
   137  		return false
   138  	}
   139  }
   140  
   141  // CSIMountOptions contain optional additional configuration that can be used
   142  // when specifying that a Volume should be used with VolumeAccessTypeMount.
   143  type CSIMountOptions struct {
   144  	// FSType is an optional field that allows an operator to specify the type
   145  	// of the filesystem.
   146  	FSType string
   147  
   148  	// MountFlags contains additional options that may be used when mounting the
   149  	// volume by the plugin. This may contain sensitive data and should not be
   150  	// leaked.
   151  	MountFlags []string
   152  }
   153  
   154  func (o *CSIMountOptions) Copy() *CSIMountOptions {
   155  	if o == nil {
   156  		return nil
   157  	}
   158  
   159  	no := *o
   160  	no.MountFlags = helper.CopySliceString(o.MountFlags)
   161  	return &no
   162  }
   163  
   164  func (o *CSIMountOptions) Merge(p *CSIMountOptions) {
   165  	if p == nil {
   166  		return
   167  	}
   168  	if p.FSType != "" {
   169  		o.FSType = p.FSType
   170  	}
   171  	if p.MountFlags != nil {
   172  		o.MountFlags = p.MountFlags
   173  	}
   174  }
   175  
   176  // CSIMountOptions implements the Stringer and GoStringer interfaces to prevent
   177  // accidental leakage of sensitive mount flags via logs.
   178  var _ fmt.Stringer = &CSIMountOptions{}
   179  var _ fmt.GoStringer = &CSIMountOptions{}
   180  
   181  func (v *CSIMountOptions) String() string {
   182  	mountFlagsString := "nil"
   183  	if len(v.MountFlags) != 0 {
   184  		mountFlagsString = "[REDACTED]"
   185  	}
   186  
   187  	return fmt.Sprintf("csi.CSIOptions(FSType: %s, MountFlags: %s)", v.FSType, mountFlagsString)
   188  }
   189  
   190  func (v *CSIMountOptions) GoString() string {
   191  	return v.String()
   192  }
   193  
   194  // CSISecrets contain optional additional configuration that can be used
   195  // when specifying that a Volume should be used with VolumeAccessTypeMount.
   196  type CSISecrets map[string]string
   197  
   198  // CSISecrets implements the Stringer and GoStringer interfaces to prevent
   199  // accidental leakage of secrets via logs.
   200  var _ fmt.Stringer = &CSISecrets{}
   201  var _ fmt.GoStringer = &CSISecrets{}
   202  
   203  func (s *CSISecrets) String() string {
   204  	redacted := map[string]string{}
   205  	for k := range *s {
   206  		redacted[k] = "[REDACTED]"
   207  	}
   208  	return fmt.Sprintf("csi.CSISecrets(%v)", redacted)
   209  }
   210  
   211  func (s *CSISecrets) GoString() string {
   212  	return s.String()
   213  }
   214  
   215  type CSIVolumeClaim struct {
   216  	AllocationID   string
   217  	NodeID         string
   218  	ExternalNodeID string
   219  	Mode           CSIVolumeClaimMode
   220  	State          CSIVolumeClaimState
   221  }
   222  
   223  type CSIVolumeClaimState int
   224  
   225  const (
   226  	CSIVolumeClaimStateTaken CSIVolumeClaimState = iota
   227  	CSIVolumeClaimStateNodeDetached
   228  	CSIVolumeClaimStateControllerDetached
   229  	CSIVolumeClaimStateReadyToFree
   230  	CSIVolumeClaimStateUnpublishing
   231  )
   232  
   233  // CSIVolume is the full representation of a CSI Volume
   234  type CSIVolume struct {
   235  	// ID is a namespace unique URL safe identifier for the volume
   236  	ID string
   237  	// Name is a display name for the volume, not required to be unique
   238  	Name string
   239  	// ExternalID identifies the volume for the CSI interface, may be URL unsafe
   240  	ExternalID     string
   241  	Namespace      string
   242  	Topologies     []*CSITopology
   243  	AccessMode     CSIVolumeAccessMode
   244  	AttachmentMode CSIVolumeAttachmentMode
   245  	MountOptions   *CSIMountOptions
   246  	Secrets        CSISecrets
   247  	Parameters     map[string]string
   248  	Context        map[string]string
   249  
   250  	// Allocations, tracking claim status
   251  	ReadAllocs  map[string]*Allocation // AllocID -> Allocation
   252  	WriteAllocs map[string]*Allocation // AllocID -> Allocation
   253  
   254  	ReadClaims  map[string]*CSIVolumeClaim // AllocID -> claim
   255  	WriteClaims map[string]*CSIVolumeClaim // AllocID -> claim
   256  	PastClaims  map[string]*CSIVolumeClaim // AllocID -> claim
   257  
   258  	// Schedulable is true if all the denormalized plugin health fields are true, and the
   259  	// volume has not been marked for garbage collection
   260  	Schedulable         bool
   261  	PluginID            string
   262  	Provider            string
   263  	ProviderVersion     string
   264  	ControllerRequired  bool
   265  	ControllersHealthy  int
   266  	ControllersExpected int
   267  	NodesHealthy        int
   268  	NodesExpected       int
   269  	ResourceExhausted   time.Time
   270  
   271  	CreateIndex uint64
   272  	ModifyIndex uint64
   273  }
   274  
   275  // CSIVolListStub is partial representation of a CSI Volume for inclusion in lists
   276  type CSIVolListStub struct {
   277  	ID                  string
   278  	Namespace           string
   279  	Name                string
   280  	ExternalID          string
   281  	Topologies          []*CSITopology
   282  	AccessMode          CSIVolumeAccessMode
   283  	AttachmentMode      CSIVolumeAttachmentMode
   284  	CurrentReaders      int
   285  	CurrentWriters      int
   286  	Schedulable         bool
   287  	PluginID            string
   288  	Provider            string
   289  	ControllersHealthy  int
   290  	ControllersExpected int
   291  	NodesHealthy        int
   292  	NodesExpected       int
   293  	CreateIndex         uint64
   294  	ModifyIndex         uint64
   295  }
   296  
   297  // NewCSIVolume creates the volume struct. No side-effects
   298  func NewCSIVolume(volumeID string, index uint64) *CSIVolume {
   299  	out := &CSIVolume{
   300  		ID:          volumeID,
   301  		CreateIndex: index,
   302  		ModifyIndex: index,
   303  	}
   304  
   305  	out.newStructs()
   306  	return out
   307  }
   308  
   309  func (v *CSIVolume) newStructs() {
   310  	v.Topologies = []*CSITopology{}
   311  	v.MountOptions = new(CSIMountOptions)
   312  	v.Secrets = CSISecrets{}
   313  	v.Parameters = map[string]string{}
   314  	v.Context = map[string]string{}
   315  
   316  	v.ReadAllocs = map[string]*Allocation{}
   317  	v.WriteAllocs = map[string]*Allocation{}
   318  	v.ReadClaims = map[string]*CSIVolumeClaim{}
   319  	v.WriteClaims = map[string]*CSIVolumeClaim{}
   320  	v.PastClaims = map[string]*CSIVolumeClaim{}
   321  }
   322  
   323  func (v *CSIVolume) RemoteID() string {
   324  	if v.ExternalID != "" {
   325  		return v.ExternalID
   326  	}
   327  	return v.ID
   328  }
   329  
   330  func (v *CSIVolume) Stub() *CSIVolListStub {
   331  	stub := CSIVolListStub{
   332  		ID:                  v.ID,
   333  		Namespace:           v.Namespace,
   334  		Name:                v.Name,
   335  		ExternalID:          v.ExternalID,
   336  		Topologies:          v.Topologies,
   337  		AccessMode:          v.AccessMode,
   338  		AttachmentMode:      v.AttachmentMode,
   339  		CurrentReaders:      len(v.ReadAllocs),
   340  		CurrentWriters:      len(v.WriteAllocs),
   341  		Schedulable:         v.Schedulable,
   342  		PluginID:            v.PluginID,
   343  		Provider:            v.Provider,
   344  		ControllersHealthy:  v.ControllersHealthy,
   345  		ControllersExpected: v.ControllersExpected,
   346  		NodesHealthy:        v.NodesHealthy,
   347  		NodesExpected:       v.NodesExpected,
   348  		CreateIndex:         v.CreateIndex,
   349  		ModifyIndex:         v.ModifyIndex,
   350  	}
   351  
   352  	return &stub
   353  }
   354  
   355  func (v *CSIVolume) ReadSchedulable() bool {
   356  	if !v.Schedulable {
   357  		return false
   358  	}
   359  
   360  	return v.ResourceExhausted == time.Time{}
   361  }
   362  
   363  // WriteSchedulable determines if the volume is schedulable for writes, considering only
   364  // volume health
   365  func (v *CSIVolume) WriteSchedulable() bool {
   366  	if !v.Schedulable {
   367  		return false
   368  	}
   369  
   370  	switch v.AccessMode {
   371  	case CSIVolumeAccessModeSingleNodeWriter, CSIVolumeAccessModeMultiNodeSingleWriter, CSIVolumeAccessModeMultiNodeMultiWriter:
   372  		return v.ResourceExhausted == time.Time{}
   373  	default:
   374  		return false
   375  	}
   376  }
   377  
   378  // WriteFreeClaims determines if there are any free write claims available
   379  func (v *CSIVolume) WriteFreeClaims() bool {
   380  	switch v.AccessMode {
   381  	case CSIVolumeAccessModeSingleNodeWriter, CSIVolumeAccessModeMultiNodeSingleWriter:
   382  		return len(v.WriteClaims) == 0
   383  	case CSIVolumeAccessModeMultiNodeMultiWriter:
   384  		// the CSI spec doesn't allow for setting a max number of writers.
   385  		// we track node resource exhaustion through v.ResourceExhausted
   386  		// which is checked in WriteSchedulable
   387  		return true
   388  	default:
   389  		return false
   390  	}
   391  }
   392  
   393  // InUse tests whether any allocations are actively using the volume
   394  func (v *CSIVolume) InUse() bool {
   395  	return len(v.ReadAllocs) != 0 ||
   396  		len(v.WriteAllocs) != 0
   397  }
   398  
   399  // Copy returns a copy of the volume, which shares only the Topologies slice
   400  func (v *CSIVolume) Copy() *CSIVolume {
   401  	out := new(CSIVolume)
   402  	*out = *v
   403  	out.newStructs() // zero-out the non-primitive structs
   404  
   405  	for _, t := range v.Topologies {
   406  		out.Topologies = append(out.Topologies, t.Copy())
   407  	}
   408  	if v.MountOptions != nil {
   409  		*out.MountOptions = *v.MountOptions
   410  	}
   411  	for k, v := range v.Secrets {
   412  		out.Secrets[k] = v
   413  	}
   414  	for k, v := range v.Parameters {
   415  		out.Parameters[k] = v
   416  	}
   417  	for k, v := range v.Context {
   418  		out.Context[k] = v
   419  	}
   420  
   421  	for k, alloc := range v.ReadAllocs {
   422  		out.ReadAllocs[k] = alloc.Copy()
   423  	}
   424  	for k, alloc := range v.WriteAllocs {
   425  		out.WriteAllocs[k] = alloc.Copy()
   426  	}
   427  
   428  	for k, v := range v.ReadClaims {
   429  		claim := *v
   430  		out.ReadClaims[k] = &claim
   431  	}
   432  	for k, v := range v.WriteClaims {
   433  		claim := *v
   434  		out.WriteClaims[k] = &claim
   435  	}
   436  	for k, v := range v.PastClaims {
   437  		claim := *v
   438  		out.PastClaims[k] = &claim
   439  	}
   440  
   441  	return out
   442  }
   443  
   444  // Claim updates the allocations and changes the volume state
   445  func (v *CSIVolume) Claim(claim *CSIVolumeClaim, alloc *Allocation) error {
   446  
   447  	if claim.State == CSIVolumeClaimStateTaken {
   448  		switch claim.Mode {
   449  		case CSIVolumeClaimRead:
   450  			return v.ClaimRead(claim, alloc)
   451  		case CSIVolumeClaimWrite:
   452  			return v.ClaimWrite(claim, alloc)
   453  		}
   454  	}
   455  	// either GC or a Unpublish checkpoint
   456  	return v.ClaimRelease(claim)
   457  }
   458  
   459  // ClaimRead marks an allocation as using a volume read-only
   460  func (v *CSIVolume) ClaimRead(claim *CSIVolumeClaim, alloc *Allocation) error {
   461  	if _, ok := v.ReadAllocs[claim.AllocationID]; ok {
   462  		return nil
   463  	}
   464  	if alloc == nil {
   465  		return fmt.Errorf("allocation missing: %s", claim.AllocationID)
   466  	}
   467  
   468  	if !v.ReadSchedulable() {
   469  		return fmt.Errorf("unschedulable")
   470  	}
   471  
   472  	// Allocations are copy on write, so we want to keep the id but don't need the
   473  	// pointer. We'll get it from the db in denormalize.
   474  	v.ReadAllocs[claim.AllocationID] = nil
   475  	delete(v.WriteAllocs, claim.AllocationID)
   476  
   477  	v.ReadClaims[claim.AllocationID] = claim
   478  	delete(v.WriteClaims, claim.AllocationID)
   479  	delete(v.PastClaims, claim.AllocationID)
   480  
   481  	return nil
   482  }
   483  
   484  // ClaimWrite marks an allocation as using a volume as a writer
   485  func (v *CSIVolume) ClaimWrite(claim *CSIVolumeClaim, alloc *Allocation) error {
   486  	if _, ok := v.WriteAllocs[claim.AllocationID]; ok {
   487  		return nil
   488  	}
   489  	if alloc == nil {
   490  		return fmt.Errorf("allocation missing: %s", claim.AllocationID)
   491  	}
   492  
   493  	if !v.WriteSchedulable() {
   494  		return fmt.Errorf("unschedulable")
   495  	}
   496  
   497  	if !v.WriteFreeClaims() {
   498  		// Check the blocking allocations to see if they belong to this job
   499  		for _, a := range v.WriteAllocs {
   500  			if a != nil && (a.Namespace != alloc.Namespace || a.JobID != alloc.JobID) {
   501  				return fmt.Errorf("volume max claim reached")
   502  			}
   503  		}
   504  	}
   505  
   506  	// Allocations are copy on write, so we want to keep the id but don't need the
   507  	// pointer. We'll get it from the db in denormalize.
   508  	v.WriteAllocs[alloc.ID] = nil
   509  	delete(v.ReadAllocs, alloc.ID)
   510  
   511  	v.WriteClaims[alloc.ID] = claim
   512  	delete(v.ReadClaims, alloc.ID)
   513  	delete(v.PastClaims, alloc.ID)
   514  
   515  	return nil
   516  }
   517  
   518  // ClaimRelease is called when the allocation has terminated and
   519  // already stopped using the volume
   520  func (v *CSIVolume) ClaimRelease(claim *CSIVolumeClaim) error {
   521  	if claim.State == CSIVolumeClaimStateReadyToFree {
   522  		delete(v.ReadAllocs, claim.AllocationID)
   523  		delete(v.WriteAllocs, claim.AllocationID)
   524  		delete(v.ReadClaims, claim.AllocationID)
   525  		delete(v.WriteClaims, claim.AllocationID)
   526  		delete(v.PastClaims, claim.AllocationID)
   527  	} else {
   528  		v.PastClaims[claim.AllocationID] = claim
   529  	}
   530  	return nil
   531  }
   532  
   533  // Equality by value
   534  func (v *CSIVolume) Equal(o *CSIVolume) bool {
   535  	if v == nil || o == nil {
   536  		return v == o
   537  	}
   538  
   539  	// Omit the plugin health fields, their values are controlled by plugin jobs
   540  	if v.ID == o.ID &&
   541  		v.Namespace == o.Namespace &&
   542  		v.AccessMode == o.AccessMode &&
   543  		v.AttachmentMode == o.AttachmentMode &&
   544  		v.PluginID == o.PluginID {
   545  		// Setwise equality of topologies
   546  		var ok bool
   547  		for _, t := range v.Topologies {
   548  			ok = false
   549  			for _, u := range o.Topologies {
   550  				if t.Equal(u) {
   551  					ok = true
   552  					break
   553  				}
   554  			}
   555  			if !ok {
   556  				return false
   557  			}
   558  		}
   559  		return true
   560  	}
   561  	return false
   562  }
   563  
   564  // Validate validates the volume struct, returning all validation errors at once
   565  func (v *CSIVolume) Validate() error {
   566  	errs := []string{}
   567  
   568  	if v.ID == "" {
   569  		errs = append(errs, "missing volume id")
   570  	}
   571  	if v.PluginID == "" {
   572  		errs = append(errs, "missing plugin id")
   573  	}
   574  	if v.Namespace == "" {
   575  		errs = append(errs, "missing namespace")
   576  	}
   577  	if v.AccessMode == "" {
   578  		errs = append(errs, "missing access mode")
   579  	}
   580  	if v.AttachmentMode == "" {
   581  		errs = append(errs, "missing attachment mode")
   582  	}
   583  	if v.AttachmentMode == CSIVolumeAttachmentModeBlockDevice {
   584  		if v.MountOptions != nil {
   585  			if v.MountOptions.FSType != "" {
   586  				errs = append(errs, "mount options not allowed for block-device")
   587  			}
   588  			if v.MountOptions.MountFlags != nil && len(v.MountOptions.MountFlags) != 0 {
   589  				errs = append(errs, "mount options not allowed for block-device")
   590  			}
   591  		}
   592  	}
   593  
   594  	// TODO: Volume Topologies are optional - We should check to see if the plugin
   595  	//       the volume is being registered with requires them.
   596  	// var ok bool
   597  	// for _, t := range v.Topologies {
   598  	// 	if t != nil && len(t.Segments) > 0 {
   599  	// 		ok = true
   600  	// 		break
   601  	// 	}
   602  	// }
   603  	// if !ok {
   604  	// 	errs = append(errs, "missing topology")
   605  	// }
   606  
   607  	if len(errs) > 0 {
   608  		return fmt.Errorf("validation: %s", strings.Join(errs, ", "))
   609  	}
   610  	return nil
   611  }
   612  
   613  // Request and response wrappers
   614  type CSIVolumeRegisterRequest struct {
   615  	Volumes []*CSIVolume
   616  	WriteRequest
   617  }
   618  
   619  type CSIVolumeRegisterResponse struct {
   620  	QueryMeta
   621  }
   622  
   623  type CSIVolumeDeregisterRequest struct {
   624  	VolumeIDs []string
   625  	Force     bool
   626  	WriteRequest
   627  }
   628  
   629  type CSIVolumeDeregisterResponse struct {
   630  	QueryMeta
   631  }
   632  
   633  type CSIVolumeClaimMode int
   634  
   635  const (
   636  	CSIVolumeClaimRead CSIVolumeClaimMode = iota
   637  	CSIVolumeClaimWrite
   638  
   639  	// for GC we don't have a specific claim to set the state on, so instead we
   640  	// create a new claim for GC in order to bump the ModifyIndex and trigger
   641  	// volumewatcher
   642  	CSIVolumeClaimGC
   643  )
   644  
   645  type CSIVolumeClaimBatchRequest struct {
   646  	Claims []CSIVolumeClaimRequest
   647  }
   648  
   649  type CSIVolumeClaimRequest struct {
   650  	VolumeID       string
   651  	AllocationID   string
   652  	NodeID         string
   653  	ExternalNodeID string
   654  	Claim          CSIVolumeClaimMode
   655  	State          CSIVolumeClaimState
   656  	WriteRequest
   657  }
   658  
   659  func (req *CSIVolumeClaimRequest) ToClaim() *CSIVolumeClaim {
   660  	return &CSIVolumeClaim{
   661  		AllocationID:   req.AllocationID,
   662  		NodeID:         req.NodeID,
   663  		ExternalNodeID: req.ExternalNodeID,
   664  		Mode:           req.Claim,
   665  		State:          req.State,
   666  	}
   667  }
   668  
   669  type CSIVolumeClaimResponse struct {
   670  	// Opaque static publish properties of the volume. SP MAY use this
   671  	// field to ensure subsequent `NodeStageVolume` or `NodePublishVolume`
   672  	// calls calls have contextual information.
   673  	// The contents of this field SHALL be opaque to nomad.
   674  	// The contents of this field SHALL NOT be mutable.
   675  	// The contents of this field SHALL be safe for the nomad to cache.
   676  	// The contents of this field SHOULD NOT contain sensitive
   677  	// information.
   678  	// The contents of this field SHOULD NOT be used for uniquely
   679  	// identifying a volume. The `volume_id` alone SHOULD be sufficient to
   680  	// identify the volume.
   681  	// This field is OPTIONAL and when present MUST be passed to
   682  	// `NodeStageVolume` or `NodePublishVolume` calls on the client
   683  	PublishContext map[string]string
   684  
   685  	// Volume contains the expanded CSIVolume for use on the client after a Claim
   686  	// has completed.
   687  	Volume *CSIVolume
   688  
   689  	QueryMeta
   690  }
   691  
   692  type CSIVolumeListRequest struct {
   693  	PluginID string
   694  	NodeID   string
   695  	QueryOptions
   696  }
   697  
   698  type CSIVolumeListResponse struct {
   699  	Volumes []*CSIVolListStub
   700  	QueryMeta
   701  }
   702  
   703  type CSIVolumeGetRequest struct {
   704  	ID string
   705  	QueryOptions
   706  }
   707  
   708  type CSIVolumeGetResponse struct {
   709  	Volume *CSIVolume
   710  	QueryMeta
   711  }
   712  
   713  type CSIVolumeUnpublishRequest struct {
   714  	VolumeID string
   715  	Claim    *CSIVolumeClaim
   716  	WriteRequest
   717  }
   718  
   719  type CSIVolumeUnpublishResponse struct {
   720  	QueryMeta
   721  }
   722  
   723  // CSIPlugin collects fingerprint info context for the plugin for clients
   724  type CSIPlugin struct {
   725  	ID                 string
   726  	Provider           string // the vendor name from CSI GetPluginInfoResponse
   727  	Version            string // the vendor verson from  CSI GetPluginInfoResponse
   728  	ControllerRequired bool
   729  
   730  	// Map Node.IDs to fingerprint results, split by type. Monolith type plugins have
   731  	// both sets of fingerprinting results.
   732  	Controllers map[string]*CSIInfo
   733  	Nodes       map[string]*CSIInfo
   734  
   735  	// Allocations are populated by denormalize to show running allocations
   736  	Allocations []*AllocListStub
   737  
   738  	// Jobs are populated to by job update to support expected counts and the UI
   739  	ControllerJobs JobDescriptions
   740  	NodeJobs       JobDescriptions
   741  
   742  	// Cache the count of healthy plugins
   743  	ControllersHealthy  int
   744  	ControllersExpected int
   745  	NodesHealthy        int
   746  	NodesExpected       int
   747  
   748  	CreateIndex uint64
   749  	ModifyIndex uint64
   750  }
   751  
   752  // NewCSIPlugin creates the plugin struct. No side-effects
   753  func NewCSIPlugin(id string, index uint64) *CSIPlugin {
   754  	out := &CSIPlugin{
   755  		ID:          id,
   756  		CreateIndex: index,
   757  		ModifyIndex: index,
   758  	}
   759  
   760  	out.newStructs()
   761  	return out
   762  }
   763  
   764  func (p *CSIPlugin) newStructs() {
   765  	p.Controllers = map[string]*CSIInfo{}
   766  	p.Nodes = map[string]*CSIInfo{}
   767  	p.ControllerJobs = make(JobDescriptions)
   768  	p.NodeJobs = make(JobDescriptions)
   769  }
   770  
   771  func (p *CSIPlugin) Copy() *CSIPlugin {
   772  	copy := *p
   773  	out := &copy
   774  	out.newStructs()
   775  
   776  	for k, v := range p.Controllers {
   777  		out.Controllers[k] = v.Copy()
   778  	}
   779  
   780  	for k, v := range p.Nodes {
   781  		out.Nodes[k] = v.Copy()
   782  	}
   783  
   784  	for k, v := range p.ControllerJobs {
   785  		out.ControllerJobs[k] = v.Copy()
   786  	}
   787  
   788  	for k, v := range p.NodeJobs {
   789  		out.NodeJobs[k] = v.Copy()
   790  	}
   791  
   792  	return out
   793  }
   794  
   795  // AddPlugin adds a single plugin running on the node. Called from state.NodeUpdate in a
   796  // transaction
   797  func (p *CSIPlugin) AddPlugin(nodeID string, info *CSIInfo) error {
   798  	if info.ControllerInfo != nil {
   799  		p.ControllerRequired = info.RequiresControllerPlugin &&
   800  			(info.ControllerInfo.SupportsAttachDetach ||
   801  				info.ControllerInfo.SupportsReadOnlyAttach)
   802  
   803  		prev, ok := p.Controllers[nodeID]
   804  		if ok {
   805  			if prev == nil {
   806  				return fmt.Errorf("plugin missing controller: %s", nodeID)
   807  			}
   808  			if prev.Healthy {
   809  				p.ControllersHealthy -= 1
   810  			}
   811  		}
   812  
   813  		// note: for this to work as expected, only a single
   814  		// controller for a given plugin can be on a given Nomad
   815  		// client, they also conflict on the client so this should be
   816  		// ok
   817  		if prev != nil || info.Healthy {
   818  			p.Controllers[nodeID] = info
   819  		}
   820  		if info.Healthy {
   821  			p.ControllersHealthy += 1
   822  		}
   823  	}
   824  
   825  	if info.NodeInfo != nil {
   826  		prev, ok := p.Nodes[nodeID]
   827  		if ok {
   828  			if prev == nil {
   829  				return fmt.Errorf("plugin missing node: %s", nodeID)
   830  			}
   831  			if prev.Healthy {
   832  				p.NodesHealthy -= 1
   833  			}
   834  		}
   835  		if prev != nil || info.Healthy {
   836  			p.Nodes[nodeID] = info
   837  		}
   838  		if info.Healthy {
   839  			p.NodesHealthy += 1
   840  		}
   841  	}
   842  
   843  	return nil
   844  }
   845  
   846  // DeleteNode removes all plugins from the node. Called from state.DeleteNode in a
   847  // transaction
   848  func (p *CSIPlugin) DeleteNode(nodeID string) error {
   849  	return p.DeleteNodeForType(nodeID, CSIPluginTypeMonolith)
   850  }
   851  
   852  // DeleteNodeForType deletes a client node from the list of controllers or node instance of
   853  // a plugin. Called from deleteJobFromPlugin during job deregistration, in a transaction
   854  func (p *CSIPlugin) DeleteNodeForType(nodeID string, pluginType CSIPluginType) error {
   855  	switch pluginType {
   856  	case CSIPluginTypeController:
   857  		if prev, ok := p.Controllers[nodeID]; ok {
   858  			if prev == nil {
   859  				return fmt.Errorf("plugin missing controller: %s", nodeID)
   860  			}
   861  			if prev.Healthy {
   862  				p.ControllersHealthy--
   863  			}
   864  			delete(p.Controllers, nodeID)
   865  		}
   866  
   867  	case CSIPluginTypeNode:
   868  		if prev, ok := p.Nodes[nodeID]; ok {
   869  			if prev == nil {
   870  				return fmt.Errorf("plugin missing node: %s", nodeID)
   871  			}
   872  			if prev.Healthy {
   873  				p.NodesHealthy--
   874  			}
   875  			delete(p.Nodes, nodeID)
   876  		}
   877  
   878  	case CSIPluginTypeMonolith:
   879  		var result error
   880  
   881  		err := p.DeleteNodeForType(nodeID, CSIPluginTypeController)
   882  		if err != nil {
   883  			result = multierror.Append(result, err)
   884  		}
   885  
   886  		err = p.DeleteNodeForType(nodeID, CSIPluginTypeNode)
   887  		if err != nil {
   888  			result = multierror.Append(result, err)
   889  		}
   890  
   891  		return result
   892  	}
   893  
   894  	return nil
   895  }
   896  
   897  // DeleteAlloc removes the fingerprint info for the allocation
   898  func (p *CSIPlugin) DeleteAlloc(allocID, nodeID string) error {
   899  	prev, ok := p.Controllers[nodeID]
   900  	if ok {
   901  		if prev == nil {
   902  			return fmt.Errorf("plugin missing controller: %s", nodeID)
   903  		}
   904  		if prev.AllocID == allocID {
   905  			if prev.Healthy {
   906  				p.ControllersHealthy -= 1
   907  			}
   908  			delete(p.Controllers, nodeID)
   909  		}
   910  	}
   911  
   912  	prev, ok = p.Nodes[nodeID]
   913  	if ok {
   914  		if prev == nil {
   915  			return fmt.Errorf("plugin missing node: %s", nodeID)
   916  		}
   917  		if prev.AllocID == allocID {
   918  			if prev.Healthy {
   919  				p.NodesHealthy -= 1
   920  			}
   921  			delete(p.Nodes, nodeID)
   922  		}
   923  	}
   924  
   925  	return nil
   926  }
   927  
   928  // AddJob adds a job to the plugin and increments expected
   929  func (p *CSIPlugin) AddJob(job *Job, summary *JobSummary) {
   930  	p.UpdateExpectedWithJob(job, summary, false)
   931  }
   932  
   933  // DeleteJob removes the job from the plugin and decrements expected
   934  func (p *CSIPlugin) DeleteJob(job *Job, summary *JobSummary) {
   935  	p.UpdateExpectedWithJob(job, summary, true)
   936  }
   937  
   938  // UpdateExpectedWithJob maintains the expected instance count
   939  // we use the summary to add non-allocation expected counts
   940  func (p *CSIPlugin) UpdateExpectedWithJob(job *Job, summary *JobSummary, terminal bool) {
   941  	var count int
   942  
   943  	for _, tg := range job.TaskGroups {
   944  		if job.Type == JobTypeSystem {
   945  			if summary == nil {
   946  				continue
   947  			}
   948  
   949  			s, ok := summary.Summary[tg.Name]
   950  			if !ok {
   951  				continue
   952  			}
   953  
   954  			count = s.Running + s.Queued + s.Starting
   955  		} else {
   956  			count = tg.Count
   957  		}
   958  
   959  		for _, t := range tg.Tasks {
   960  			if t.CSIPluginConfig == nil ||
   961  				t.CSIPluginConfig.ID != p.ID {
   962  				continue
   963  			}
   964  
   965  			// Change the correct plugin expected, monolith should change both
   966  			if t.CSIPluginConfig.Type == CSIPluginTypeController ||
   967  				t.CSIPluginConfig.Type == CSIPluginTypeMonolith {
   968  				if terminal {
   969  					p.ControllerJobs.Delete(job)
   970  				} else {
   971  					p.ControllerJobs.Add(job, count)
   972  				}
   973  			}
   974  
   975  			if t.CSIPluginConfig.Type == CSIPluginTypeNode ||
   976  				t.CSIPluginConfig.Type == CSIPluginTypeMonolith {
   977  				if terminal {
   978  					p.NodeJobs.Delete(job)
   979  				} else {
   980  					p.NodeJobs.Add(job, count)
   981  				}
   982  			}
   983  		}
   984  	}
   985  
   986  	p.ControllersExpected = p.ControllerJobs.Count()
   987  	p.NodesExpected = p.NodeJobs.Count()
   988  }
   989  
   990  // JobDescription records Job identification and the count of expected plugin instances
   991  type JobDescription struct {
   992  	Namespace string
   993  	ID        string
   994  	Expected  int
   995  }
   996  
   997  // JobNamespacedDescriptions maps Job.ID to JobDescription
   998  type JobNamespacedDescriptions map[string]JobDescription
   999  
  1000  func (j JobNamespacedDescriptions) Copy() JobNamespacedDescriptions {
  1001  	copy := JobNamespacedDescriptions{}
  1002  	for k, v := range j {
  1003  		copy[k] = v
  1004  	}
  1005  	return copy
  1006  }
  1007  
  1008  // JobDescriptions maps Namespace to a mapping of Job.ID to JobDescription
  1009  type JobDescriptions map[string]JobNamespacedDescriptions
  1010  
  1011  // Add the Job to the JobDescriptions, creating maps as necessary
  1012  func (j JobDescriptions) Add(job *Job, expected int) {
  1013  	if j == nil {
  1014  		j = make(JobDescriptions)
  1015  	}
  1016  	if j[job.Namespace] == nil {
  1017  		j[job.Namespace] = make(JobNamespacedDescriptions)
  1018  	}
  1019  	j[job.Namespace][job.ID] = JobDescription{
  1020  		Namespace: job.Namespace,
  1021  		ID:        job.ID,
  1022  		Expected:  expected,
  1023  	}
  1024  }
  1025  
  1026  // Count the Expected instances for all JobDescriptions
  1027  func (j JobDescriptions) Count() int {
  1028  	if j == nil {
  1029  		return 0
  1030  	}
  1031  	count := 0
  1032  	for _, jnd := range j {
  1033  		for _, jd := range jnd {
  1034  			count += jd.Expected
  1035  		}
  1036  	}
  1037  	return count
  1038  }
  1039  
  1040  // Delete the Job from the JobDescriptions
  1041  func (j JobDescriptions) Delete(job *Job) {
  1042  	if j != nil &&
  1043  		j[job.Namespace] != nil {
  1044  		delete(j[job.Namespace], job.ID)
  1045  	}
  1046  }
  1047  
  1048  type CSIPluginListStub struct {
  1049  	ID                  string
  1050  	Provider            string
  1051  	ControllerRequired  bool
  1052  	ControllersHealthy  int
  1053  	ControllersExpected int
  1054  	NodesHealthy        int
  1055  	NodesExpected       int
  1056  	CreateIndex         uint64
  1057  	ModifyIndex         uint64
  1058  }
  1059  
  1060  func (p *CSIPlugin) Stub() *CSIPluginListStub {
  1061  	return &CSIPluginListStub{
  1062  		ID:                  p.ID,
  1063  		Provider:            p.Provider,
  1064  		ControllerRequired:  p.ControllerRequired,
  1065  		ControllersHealthy:  p.ControllersHealthy,
  1066  		ControllersExpected: p.ControllersExpected,
  1067  		NodesHealthy:        p.NodesHealthy,
  1068  		NodesExpected:       p.NodesExpected,
  1069  		CreateIndex:         p.CreateIndex,
  1070  		ModifyIndex:         p.ModifyIndex,
  1071  	}
  1072  }
  1073  
  1074  func (p *CSIPlugin) IsEmpty() bool {
  1075  	return p == nil ||
  1076  		len(p.Controllers) == 0 &&
  1077  			len(p.Nodes) == 0 &&
  1078  			p.ControllerJobs.Count() == 0 &&
  1079  			p.NodeJobs.Count() == 0
  1080  }
  1081  
  1082  type CSIPluginListRequest struct {
  1083  	QueryOptions
  1084  }
  1085  
  1086  type CSIPluginListResponse struct {
  1087  	Plugins []*CSIPluginListStub
  1088  	QueryMeta
  1089  }
  1090  
  1091  type CSIPluginGetRequest struct {
  1092  	ID string
  1093  	QueryOptions
  1094  }
  1095  
  1096  type CSIPluginGetResponse struct {
  1097  	Plugin *CSIPlugin
  1098  	QueryMeta
  1099  }
  1100  
  1101  type CSIPluginDeleteRequest struct {
  1102  	ID string
  1103  	QueryOptions
  1104  }
  1105  
  1106  type CSIPluginDeleteResponse struct {
  1107  	QueryMeta
  1108  }