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

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