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 }