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 }