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

     1  package structs
     2  
     3  import (
     4  	"fmt"
     5  
     6  	multierror "github.com/hashicorp/go-multierror"
     7  )
     8  
     9  const (
    10  	VolumeTypeHost = "host"
    11  )
    12  
    13  const (
    14  	VolumeMountPropagationPrivate       = "private"
    15  	VolumeMountPropagationHostToTask    = "host-to-task"
    16  	VolumeMountPropagationBidirectional = "bidirectional"
    17  )
    18  
    19  func MountPropagationModeIsValid(propagationMode string) bool {
    20  	switch propagationMode {
    21  	case "", VolumeMountPropagationPrivate, VolumeMountPropagationHostToTask, VolumeMountPropagationBidirectional:
    22  		return true
    23  	default:
    24  		return false
    25  	}
    26  }
    27  
    28  // ClientHostVolumeConfig is used to configure access to host paths on a Nomad Client
    29  type ClientHostVolumeConfig struct {
    30  	Name     string `hcl:",key"`
    31  	Path     string `hcl:"path"`
    32  	ReadOnly bool   `hcl:"read_only"`
    33  }
    34  
    35  func (p *ClientHostVolumeConfig) Copy() *ClientHostVolumeConfig {
    36  	if p == nil {
    37  		return nil
    38  	}
    39  
    40  	c := new(ClientHostVolumeConfig)
    41  	*c = *p
    42  	return c
    43  }
    44  
    45  func CopyMapStringClientHostVolumeConfig(m map[string]*ClientHostVolumeConfig) map[string]*ClientHostVolumeConfig {
    46  	if m == nil {
    47  		return nil
    48  	}
    49  
    50  	nm := make(map[string]*ClientHostVolumeConfig, len(m))
    51  	for k, v := range m {
    52  		nm[k] = v.Copy()
    53  	}
    54  
    55  	return nm
    56  }
    57  
    58  func CopySliceClientHostVolumeConfig(s []*ClientHostVolumeConfig) []*ClientHostVolumeConfig {
    59  	l := len(s)
    60  	if l == 0 {
    61  		return nil
    62  	}
    63  
    64  	ns := make([]*ClientHostVolumeConfig, l)
    65  	for idx, cfg := range s {
    66  		ns[idx] = cfg.Copy()
    67  	}
    68  
    69  	return ns
    70  }
    71  
    72  func HostVolumeSliceMerge(a, b []*ClientHostVolumeConfig) []*ClientHostVolumeConfig {
    73  	n := make([]*ClientHostVolumeConfig, len(a))
    74  	seenKeys := make(map[string]int, len(a))
    75  
    76  	for i, config := range a {
    77  		n[i] = config.Copy()
    78  		seenKeys[config.Name] = i
    79  	}
    80  
    81  	for _, config := range b {
    82  		if fIndex, ok := seenKeys[config.Name]; ok {
    83  			n[fIndex] = config.Copy()
    84  			continue
    85  		}
    86  
    87  		n = append(n, config.Copy())
    88  	}
    89  
    90  	return n
    91  }
    92  
    93  // VolumeRequest is a representation of a storage volume that a TaskGroup wishes to use.
    94  type VolumeRequest struct {
    95  	Name           string
    96  	Type           string
    97  	Source         string
    98  	ReadOnly       bool
    99  	AccessMode     CSIVolumeAccessMode
   100  	AttachmentMode CSIVolumeAttachmentMode
   101  	MountOptions   *CSIMountOptions
   102  	PerAlloc       bool
   103  }
   104  
   105  func (v *VolumeRequest) Validate(taskGroupCount, canaries int) error {
   106  	if !(v.Type == VolumeTypeHost ||
   107  		v.Type == VolumeTypeCSI) {
   108  		return fmt.Errorf("volume has unrecognized type %s", v.Type)
   109  	}
   110  
   111  	var mErr multierror.Error
   112  	addErr := func(msg string, args ...interface{}) {
   113  		mErr.Errors = append(mErr.Errors, fmt.Errorf(msg, args...))
   114  	}
   115  
   116  	if v.Source == "" {
   117  		addErr("volume has an empty source")
   118  	}
   119  
   120  	switch v.Type {
   121  
   122  	case VolumeTypeHost:
   123  		if v.AttachmentMode != CSIVolumeAttachmentModeUnknown {
   124  			addErr("host volumes cannot have an attachment mode")
   125  		}
   126  		if v.AccessMode != CSIVolumeAccessModeUnknown {
   127  			addErr("host volumes cannot have an access mode")
   128  		}
   129  		if v.MountOptions != nil {
   130  			addErr("host volumes cannot have mount options")
   131  		}
   132  		if v.PerAlloc {
   133  			addErr("host volumes do not support per_alloc")
   134  		}
   135  
   136  	case VolumeTypeCSI:
   137  
   138  		switch v.AttachmentMode {
   139  		case CSIVolumeAttachmentModeUnknown:
   140  			addErr("CSI volumes must have an attachment mode")
   141  		case CSIVolumeAttachmentModeBlockDevice:
   142  			if v.MountOptions != nil {
   143  				addErr("block devices cannot have mount options")
   144  			}
   145  		}
   146  
   147  		switch v.AccessMode {
   148  		case CSIVolumeAccessModeUnknown:
   149  			addErr("CSI volumes must have an access mode")
   150  		case CSIVolumeAccessModeSingleNodeReader:
   151  			if !v.ReadOnly {
   152  				addErr("%s volumes must be read-only", v.AccessMode)
   153  			}
   154  			if taskGroupCount > 1 && !v.PerAlloc {
   155  				addErr("volume with %s access mode allows only one reader", v.AccessMode)
   156  			}
   157  		case CSIVolumeAccessModeSingleNodeWriter:
   158  			// note: we allow read-only mount of this volume, but only one
   159  			if taskGroupCount > 1 && !v.PerAlloc {
   160  				addErr("volume with %s access mode allows only one reader or writer", v.AccessMode)
   161  			}
   162  		case CSIVolumeAccessModeMultiNodeReader:
   163  			if !v.ReadOnly {
   164  				addErr("%s volumes must be read-only", v.AccessMode)
   165  			}
   166  		case CSIVolumeAccessModeMultiNodeSingleWriter:
   167  			if !v.ReadOnly && taskGroupCount > 1 && !v.PerAlloc {
   168  				addErr("volume with %s access mode allows only one writer", v.AccessMode)
   169  			}
   170  		case CSIVolumeAccessModeMultiNodeMultiWriter:
   171  			// note: we intentionally allow read-only mount of this mode
   172  		}
   173  
   174  		if v.PerAlloc && canaries > 0 {
   175  			addErr("volume cannot be per_alloc when canaries are in use")
   176  		}
   177  
   178  	}
   179  
   180  	return mErr.ErrorOrNil()
   181  }
   182  
   183  func (v *VolumeRequest) Copy() *VolumeRequest {
   184  	if v == nil {
   185  		return nil
   186  	}
   187  	nv := new(VolumeRequest)
   188  	*nv = *v
   189  
   190  	if v.MountOptions != nil {
   191  		nv.MountOptions = v.MountOptions.Copy()
   192  	}
   193  
   194  	return nv
   195  }
   196  
   197  func CopyMapVolumeRequest(s map[string]*VolumeRequest) map[string]*VolumeRequest {
   198  	if s == nil {
   199  		return nil
   200  	}
   201  
   202  	l := len(s)
   203  	c := make(map[string]*VolumeRequest, l)
   204  	for k, v := range s {
   205  		c[k] = v.Copy()
   206  	}
   207  	return c
   208  }
   209  
   210  // VolumeMount represents the relationship between a destination path in a task
   211  // and the task group volume that should be mounted there.
   212  type VolumeMount struct {
   213  	Volume          string
   214  	Destination     string
   215  	ReadOnly        bool
   216  	PropagationMode string
   217  }
   218  
   219  func (v *VolumeMount) Copy() *VolumeMount {
   220  	if v == nil {
   221  		return nil
   222  	}
   223  
   224  	nv := new(VolumeMount)
   225  	*nv = *v
   226  	return nv
   227  }
   228  
   229  func CopySliceVolumeMount(s []*VolumeMount) []*VolumeMount {
   230  	l := len(s)
   231  	if l == 0 {
   232  		return nil
   233  	}
   234  
   235  	c := make([]*VolumeMount, l)
   236  	for i, v := range s {
   237  		c[i] = v.Copy()
   238  	}
   239  	return c
   240  }