k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/kubelet/volumemanager/volume_manager.go (about) 1 /* 2 Copyright 2016 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package volumemanager 18 19 import ( 20 "context" 21 "errors" 22 "fmt" 23 "sort" 24 "strconv" 25 "strings" 26 "time" 27 28 utilfeature "k8s.io/apiserver/pkg/util/feature" 29 "k8s.io/klog/v2" 30 "k8s.io/mount-utils" 31 32 v1 "k8s.io/api/core/v1" 33 k8stypes "k8s.io/apimachinery/pkg/types" 34 "k8s.io/apimachinery/pkg/util/runtime" 35 "k8s.io/apimachinery/pkg/util/sets" 36 "k8s.io/apimachinery/pkg/util/wait" 37 clientset "k8s.io/client-go/kubernetes" 38 "k8s.io/client-go/tools/record" 39 csitrans "k8s.io/csi-translation-lib" 40 "k8s.io/kubernetes/pkg/kubelet/config" 41 "k8s.io/kubernetes/pkg/kubelet/container" 42 "k8s.io/kubernetes/pkg/kubelet/volumemanager/cache" 43 "k8s.io/kubernetes/pkg/kubelet/volumemanager/metrics" 44 "k8s.io/kubernetes/pkg/kubelet/volumemanager/populator" 45 "k8s.io/kubernetes/pkg/kubelet/volumemanager/reconciler" 46 "k8s.io/kubernetes/pkg/volume" 47 "k8s.io/kubernetes/pkg/volume/csimigration" 48 "k8s.io/kubernetes/pkg/volume/util" 49 "k8s.io/kubernetes/pkg/volume/util/hostutil" 50 "k8s.io/kubernetes/pkg/volume/util/operationexecutor" 51 "k8s.io/kubernetes/pkg/volume/util/types" 52 "k8s.io/kubernetes/pkg/volume/util/volumepathhandler" 53 ) 54 55 const ( 56 // reconcilerLoopSleepPeriod is the amount of time the reconciler loop waits 57 // between successive executions 58 reconcilerLoopSleepPeriod = 100 * time.Millisecond 59 60 // desiredStateOfWorldPopulatorLoopSleepPeriod is the amount of time the 61 // DesiredStateOfWorldPopulator loop waits between successive executions 62 desiredStateOfWorldPopulatorLoopSleepPeriod = 100 * time.Millisecond 63 64 // podAttachAndMountTimeout is the maximum amount of time the 65 // WaitForAttachAndMount call will wait for all volumes in the specified pod 66 // to be attached and mounted. Even though cloud operations can take several 67 // minutes to complete, we set the timeout to 2 minutes because kubelet 68 // will retry in the next sync iteration. This frees the associated 69 // goroutine of the pod to process newer updates if needed (e.g., a delete 70 // request to the pod). 71 // Value is slightly offset from 2 minutes to make timeouts due to this 72 // constant recognizable. 73 podAttachAndMountTimeout = 2*time.Minute + 3*time.Second 74 75 // podAttachAndMountRetryInterval is the amount of time the GetVolumesForPod 76 // call waits before retrying 77 podAttachAndMountRetryInterval = 300 * time.Millisecond 78 79 // waitForAttachTimeout is the maximum amount of time a 80 // operationexecutor.Mount call will wait for a volume to be attached. 81 // Set to 10 minutes because we've seen attach operations take several 82 // minutes to complete for some volume plugins in some cases. While this 83 // operation is waiting it only blocks other operations on the same device, 84 // other devices are not affected. 85 waitForAttachTimeout = 10 * time.Minute 86 ) 87 88 // VolumeManager runs a set of asynchronous loops that figure out which volumes 89 // need to be attached/mounted/unmounted/detached based on the pods scheduled on 90 // this node and makes it so. 91 type VolumeManager interface { 92 // Starts the volume manager and all the asynchronous loops that it controls 93 Run(sourcesReady config.SourcesReady, stopCh <-chan struct{}) 94 95 // WaitForAttachAndMount processes the volumes referenced in the specified 96 // pod and blocks until they are all attached and mounted (reflected in 97 // actual state of the world). 98 // An error is returned if all volumes are not attached and mounted within 99 // the duration defined in podAttachAndMountTimeout. 100 WaitForAttachAndMount(ctx context.Context, pod *v1.Pod) error 101 102 // WaitForUnmount processes the volumes referenced in the specified 103 // pod and blocks until they are all unmounted (reflected in the actual 104 // state of the world). 105 // An error is returned if all volumes are not unmounted within 106 // the duration defined in podAttachAndMountTimeout. 107 WaitForUnmount(ctx context.Context, pod *v1.Pod) error 108 109 // GetMountedVolumesForPod returns a VolumeMap containing the volumes 110 // referenced by the specified pod that are successfully attached and 111 // mounted. The key in the map is the OuterVolumeSpecName (i.e. 112 // pod.Spec.Volumes[x].Name). It returns an empty VolumeMap if pod has no 113 // volumes. 114 GetMountedVolumesForPod(podName types.UniquePodName) container.VolumeMap 115 116 // GetPossiblyMountedVolumesForPod returns a VolumeMap containing the volumes 117 // referenced by the specified pod that are either successfully attached 118 // and mounted or are "uncertain", i.e. a volume plugin may be mounting 119 // them right now. The key in the map is the OuterVolumeSpecName (i.e. 120 // pod.Spec.Volumes[x].Name). It returns an empty VolumeMap if pod has no 121 // volumes. 122 GetPossiblyMountedVolumesForPod(podName types.UniquePodName) container.VolumeMap 123 124 // GetExtraSupplementalGroupsForPod returns a list of the extra 125 // supplemental groups for the Pod. These extra supplemental groups come 126 // from annotations on persistent volumes that the pod depends on. 127 GetExtraSupplementalGroupsForPod(pod *v1.Pod) []int64 128 129 // GetVolumesInUse returns a list of all volumes that implement the volume.Attacher 130 // interface and are currently in use according to the actual and desired 131 // state of the world caches. A volume is considered "in use" as soon as it 132 // is added to the desired state of world, indicating it *should* be 133 // attached to this node and remains "in use" until it is removed from both 134 // the desired state of the world and the actual state of the world, or it 135 // has been unmounted (as indicated in actual state of world). 136 GetVolumesInUse() []v1.UniqueVolumeName 137 138 // ReconcilerStatesHasBeenSynced returns true only after the actual states in reconciler 139 // has been synced at least once after kubelet starts so that it is safe to update mounted 140 // volume list retrieved from actual state. 141 ReconcilerStatesHasBeenSynced() bool 142 143 // VolumeIsAttached returns true if the given volume is attached to this 144 // node. 145 VolumeIsAttached(volumeName v1.UniqueVolumeName) bool 146 147 // Marks the specified volume as having successfully been reported as "in 148 // use" in the nodes's volume status. 149 MarkVolumesAsReportedInUse(volumesReportedAsInUse []v1.UniqueVolumeName) 150 } 151 152 // podStateProvider can determine if a pod is going to be terminated 153 type PodStateProvider interface { 154 ShouldPodContainersBeTerminating(k8stypes.UID) bool 155 ShouldPodRuntimeBeRemoved(k8stypes.UID) bool 156 } 157 158 // PodManager is the subset of methods the manager needs to observe the actual state of the kubelet. 159 // See pkg/k8s.io/kubernetes/pkg/kubelet/pod.Manager for method godoc. 160 type PodManager interface { 161 GetPodByUID(k8stypes.UID) (*v1.Pod, bool) 162 GetPods() []*v1.Pod 163 } 164 165 // NewVolumeManager returns a new concrete instance implementing the 166 // VolumeManager interface. 167 // 168 // kubeClient - kubeClient is the kube API client used by DesiredStateOfWorldPopulator 169 // to communicate with the API server to fetch PV and PVC objects 170 // 171 // volumePluginMgr - the volume plugin manager used to access volume plugins. 172 // Must be pre-initialized. 173 func NewVolumeManager( 174 controllerAttachDetachEnabled bool, 175 nodeName k8stypes.NodeName, 176 podManager PodManager, 177 podStateProvider PodStateProvider, 178 kubeClient clientset.Interface, 179 volumePluginMgr *volume.VolumePluginMgr, 180 kubeContainerRuntime container.Runtime, 181 mounter mount.Interface, 182 hostutil hostutil.HostUtils, 183 kubeletPodsDir string, 184 recorder record.EventRecorder, 185 blockVolumePathHandler volumepathhandler.BlockVolumePathHandler) VolumeManager { 186 187 seLinuxTranslator := util.NewSELinuxLabelTranslator() 188 vm := &volumeManager{ 189 kubeClient: kubeClient, 190 volumePluginMgr: volumePluginMgr, 191 desiredStateOfWorld: cache.NewDesiredStateOfWorld(volumePluginMgr, seLinuxTranslator), 192 actualStateOfWorld: cache.NewActualStateOfWorld(nodeName, volumePluginMgr), 193 operationExecutor: operationexecutor.NewOperationExecutor(operationexecutor.NewOperationGenerator( 194 kubeClient, 195 volumePluginMgr, 196 recorder, 197 blockVolumePathHandler)), 198 } 199 200 intreeToCSITranslator := csitrans.New() 201 csiMigratedPluginManager := csimigration.NewPluginManager(intreeToCSITranslator, utilfeature.DefaultFeatureGate) 202 203 vm.intreeToCSITranslator = intreeToCSITranslator 204 vm.csiMigratedPluginManager = csiMigratedPluginManager 205 vm.desiredStateOfWorldPopulator = populator.NewDesiredStateOfWorldPopulator( 206 kubeClient, 207 desiredStateOfWorldPopulatorLoopSleepPeriod, 208 podManager, 209 podStateProvider, 210 vm.desiredStateOfWorld, 211 vm.actualStateOfWorld, 212 kubeContainerRuntime, 213 csiMigratedPluginManager, 214 intreeToCSITranslator, 215 volumePluginMgr) 216 vm.reconciler = reconciler.NewReconciler( 217 kubeClient, 218 controllerAttachDetachEnabled, 219 reconcilerLoopSleepPeriod, 220 waitForAttachTimeout, 221 nodeName, 222 vm.desiredStateOfWorld, 223 vm.actualStateOfWorld, 224 vm.desiredStateOfWorldPopulator.HasAddedPods, 225 vm.operationExecutor, 226 mounter, 227 hostutil, 228 volumePluginMgr, 229 kubeletPodsDir) 230 231 return vm 232 } 233 234 // volumeManager implements the VolumeManager interface 235 type volumeManager struct { 236 // kubeClient is the kube API client used by DesiredStateOfWorldPopulator to 237 // communicate with the API server to fetch PV and PVC objects 238 kubeClient clientset.Interface 239 240 // volumePluginMgr is the volume plugin manager used to access volume 241 // plugins. It must be pre-initialized. 242 volumePluginMgr *volume.VolumePluginMgr 243 244 // desiredStateOfWorld is a data structure containing the desired state of 245 // the world according to the volume manager: i.e. what volumes should be 246 // attached and which pods are referencing the volumes). 247 // The data structure is populated by the desired state of the world 248 // populator using the kubelet pod manager. 249 desiredStateOfWorld cache.DesiredStateOfWorld 250 251 // actualStateOfWorld is a data structure containing the actual state of 252 // the world according to the manager: i.e. which volumes are attached to 253 // this node and what pods the volumes are mounted to. 254 // The data structure is populated upon successful completion of attach, 255 // detach, mount, and unmount actions triggered by the reconciler. 256 actualStateOfWorld cache.ActualStateOfWorld 257 258 // operationExecutor is used to start asynchronous attach, detach, mount, 259 // and unmount operations. 260 operationExecutor operationexecutor.OperationExecutor 261 262 // reconciler runs an asynchronous periodic loop to reconcile the 263 // desiredStateOfWorld with the actualStateOfWorld by triggering attach, 264 // detach, mount, and unmount operations using the operationExecutor. 265 reconciler reconciler.Reconciler 266 267 // desiredStateOfWorldPopulator runs an asynchronous periodic loop to 268 // populate the desiredStateOfWorld using the kubelet PodManager. 269 desiredStateOfWorldPopulator populator.DesiredStateOfWorldPopulator 270 271 // csiMigratedPluginManager keeps track of CSI migration status of plugins 272 csiMigratedPluginManager csimigration.PluginManager 273 274 // intreeToCSITranslator translates in-tree volume specs to CSI 275 intreeToCSITranslator csimigration.InTreeToCSITranslator 276 } 277 278 func (vm *volumeManager) Run(sourcesReady config.SourcesReady, stopCh <-chan struct{}) { 279 defer runtime.HandleCrash() 280 281 if vm.kubeClient != nil { 282 // start informer for CSIDriver 283 go vm.volumePluginMgr.Run(stopCh) 284 } 285 286 go vm.desiredStateOfWorldPopulator.Run(sourcesReady, stopCh) 287 klog.V(2).InfoS("The desired_state_of_world populator starts") 288 289 klog.InfoS("Starting Kubelet Volume Manager") 290 go vm.reconciler.Run(stopCh) 291 292 metrics.Register(vm.actualStateOfWorld, vm.desiredStateOfWorld, vm.volumePluginMgr) 293 294 <-stopCh 295 klog.InfoS("Shutting down Kubelet Volume Manager") 296 } 297 298 func (vm *volumeManager) GetMountedVolumesForPod(podName types.UniquePodName) container.VolumeMap { 299 podVolumes := make(container.VolumeMap) 300 for _, mountedVolume := range vm.actualStateOfWorld.GetMountedVolumesForPod(podName) { 301 podVolumes[mountedVolume.OuterVolumeSpecName] = container.VolumeInfo{ 302 Mounter: mountedVolume.Mounter, 303 BlockVolumeMapper: mountedVolume.BlockVolumeMapper, 304 ReadOnly: mountedVolume.VolumeSpec.ReadOnly, 305 InnerVolumeSpecName: mountedVolume.InnerVolumeSpecName, 306 } 307 } 308 return podVolumes 309 } 310 311 func (vm *volumeManager) GetPossiblyMountedVolumesForPod(podName types.UniquePodName) container.VolumeMap { 312 podVolumes := make(container.VolumeMap) 313 for _, mountedVolume := range vm.actualStateOfWorld.GetPossiblyMountedVolumesForPod(podName) { 314 podVolumes[mountedVolume.OuterVolumeSpecName] = container.VolumeInfo{ 315 Mounter: mountedVolume.Mounter, 316 BlockVolumeMapper: mountedVolume.BlockVolumeMapper, 317 ReadOnly: mountedVolume.VolumeSpec.ReadOnly, 318 InnerVolumeSpecName: mountedVolume.InnerVolumeSpecName, 319 } 320 } 321 return podVolumes 322 } 323 324 func (vm *volumeManager) GetExtraSupplementalGroupsForPod(pod *v1.Pod) []int64 { 325 podName := util.GetUniquePodName(pod) 326 supplementalGroups := sets.NewString() 327 328 for _, mountedVolume := range vm.actualStateOfWorld.GetMountedVolumesForPod(podName) { 329 if mountedVolume.VolumeGidValue != "" { 330 supplementalGroups.Insert(mountedVolume.VolumeGidValue) 331 } 332 } 333 334 result := make([]int64, 0, supplementalGroups.Len()) 335 for _, group := range supplementalGroups.List() { 336 iGroup, extra := getExtraSupplementalGid(group, pod) 337 if !extra { 338 continue 339 } 340 341 result = append(result, int64(iGroup)) 342 } 343 344 return result 345 } 346 347 func (vm *volumeManager) GetVolumesInUse() []v1.UniqueVolumeName { 348 // Report volumes in desired state of world and actual state of world so 349 // that volumes are marked in use as soon as the decision is made that the 350 // volume *should* be attached to this node until it is safely unmounted. 351 desiredVolumes := vm.desiredStateOfWorld.GetVolumesToMount() 352 allAttachedVolumes := vm.actualStateOfWorld.GetAttachedVolumes() 353 volumesToReportInUse := make([]v1.UniqueVolumeName, 0, len(desiredVolumes)+len(allAttachedVolumes)) 354 desiredVolumesMap := make(map[v1.UniqueVolumeName]bool, len(desiredVolumes)+len(allAttachedVolumes)) 355 356 for _, volume := range desiredVolumes { 357 if volume.PluginIsAttachable { 358 if _, exists := desiredVolumesMap[volume.VolumeName]; !exists { 359 desiredVolumesMap[volume.VolumeName] = true 360 volumesToReportInUse = append(volumesToReportInUse, volume.VolumeName) 361 } 362 } 363 } 364 365 for _, volume := range allAttachedVolumes { 366 if volume.PluginIsAttachable { 367 if _, exists := desiredVolumesMap[volume.VolumeName]; !exists { 368 volumesToReportInUse = append(volumesToReportInUse, volume.VolumeName) 369 } 370 } 371 } 372 373 sort.Slice(volumesToReportInUse, func(i, j int) bool { 374 return string(volumesToReportInUse[i]) < string(volumesToReportInUse[j]) 375 }) 376 return volumesToReportInUse 377 } 378 379 func (vm *volumeManager) ReconcilerStatesHasBeenSynced() bool { 380 return vm.reconciler.StatesHasBeenSynced() 381 } 382 383 func (vm *volumeManager) VolumeIsAttached( 384 volumeName v1.UniqueVolumeName) bool { 385 return vm.actualStateOfWorld.VolumeExists(volumeName) 386 } 387 388 func (vm *volumeManager) MarkVolumesAsReportedInUse( 389 volumesReportedAsInUse []v1.UniqueVolumeName) { 390 vm.desiredStateOfWorld.MarkVolumesReportedInUse(volumesReportedAsInUse) 391 } 392 393 func (vm *volumeManager) WaitForAttachAndMount(ctx context.Context, pod *v1.Pod) error { 394 if pod == nil { 395 return nil 396 } 397 398 expectedVolumes := getExpectedVolumes(pod) 399 if len(expectedVolumes) == 0 { 400 // No volumes to verify 401 return nil 402 } 403 404 klog.V(3).InfoS("Waiting for volumes to attach and mount for pod", "pod", klog.KObj(pod)) 405 uniquePodName := util.GetUniquePodName(pod) 406 407 // Some pods expect to have Setup called over and over again to update. 408 // Remount plugins for which this is true. (Atomically updating volumes, 409 // like Downward API, depend on this to update the contents of the volume). 410 vm.desiredStateOfWorldPopulator.ReprocessPod(uniquePodName) 411 412 err := wait.PollUntilContextTimeout( 413 ctx, 414 podAttachAndMountRetryInterval, 415 podAttachAndMountTimeout, 416 true, 417 vm.verifyVolumesMountedFunc(uniquePodName, expectedVolumes)) 418 419 if err != nil { 420 unmountedVolumes := 421 vm.getUnmountedVolumes(uniquePodName, expectedVolumes) 422 // Also get unattached volumes and volumes not in dsw for error message 423 unattachedVolumes := 424 vm.getUnattachedVolumes(uniquePodName) 425 volumesNotInDSW := 426 vm.getVolumesNotInDSW(uniquePodName, expectedVolumes) 427 428 if len(unmountedVolumes) == 0 { 429 return nil 430 } 431 432 return fmt.Errorf( 433 "unmounted volumes=%v, unattached volumes=%v, failed to process volumes=%v: %w", 434 unmountedVolumes, 435 unattachedVolumes, 436 volumesNotInDSW, 437 err) 438 } 439 440 klog.V(3).InfoS("All volumes are attached and mounted for pod", "pod", klog.KObj(pod)) 441 return nil 442 } 443 444 func (vm *volumeManager) WaitForUnmount(ctx context.Context, pod *v1.Pod) error { 445 if pod == nil { 446 return nil 447 } 448 449 klog.V(3).InfoS("Waiting for volumes to unmount for pod", "pod", klog.KObj(pod)) 450 uniquePodName := util.GetUniquePodName(pod) 451 452 vm.desiredStateOfWorldPopulator.ReprocessPod(uniquePodName) 453 454 err := wait.PollUntilContextTimeout( 455 ctx, 456 podAttachAndMountRetryInterval, 457 podAttachAndMountTimeout, 458 true, 459 vm.verifyVolumesUnmountedFunc(uniquePodName)) 460 461 if err != nil { 462 var mountedVolumes []string 463 for _, v := range vm.actualStateOfWorld.GetMountedVolumesForPod(uniquePodName) { 464 mountedVolumes = append(mountedVolumes, v.OuterVolumeSpecName) 465 } 466 sort.Strings(mountedVolumes) 467 468 if len(mountedVolumes) == 0 { 469 return nil 470 } 471 472 return fmt.Errorf( 473 "mounted volumes=%v: %w", 474 mountedVolumes, 475 err) 476 } 477 478 klog.V(3).InfoS("All volumes are unmounted for pod", "pod", klog.KObj(pod)) 479 return nil 480 } 481 482 func (vm *volumeManager) getVolumesNotInDSW(uniquePodName types.UniquePodName, expectedVolumes []string) []string { 483 volumesNotInDSW := sets.NewString(expectedVolumes...) 484 485 for _, volumeToMount := range vm.desiredStateOfWorld.GetVolumesToMount() { 486 if volumeToMount.PodName == uniquePodName { 487 volumesNotInDSW.Delete(volumeToMount.OuterVolumeSpecName) 488 } 489 } 490 491 return volumesNotInDSW.List() 492 } 493 494 // getUnattachedVolumes returns a list of the volumes that are expected to be attached but 495 // are not currently attached to the node 496 func (vm *volumeManager) getUnattachedVolumes(uniquePodName types.UniquePodName) []string { 497 unattachedVolumes := []string{} 498 for _, volumeToMount := range vm.desiredStateOfWorld.GetVolumesToMount() { 499 if volumeToMount.PodName == uniquePodName && 500 volumeToMount.PluginIsAttachable && 501 !vm.actualStateOfWorld.VolumeExists(volumeToMount.VolumeName) { 502 unattachedVolumes = append(unattachedVolumes, volumeToMount.OuterVolumeSpecName) 503 } 504 } 505 sort.Strings(unattachedVolumes) 506 507 return unattachedVolumes 508 } 509 510 // verifyVolumesMountedFunc returns a method that returns true when all expected 511 // volumes are mounted. 512 func (vm *volumeManager) verifyVolumesMountedFunc(podName types.UniquePodName, expectedVolumes []string) wait.ConditionWithContextFunc { 513 return func(_ context.Context) (done bool, err error) { 514 if errs := vm.desiredStateOfWorld.PopPodErrors(podName); len(errs) > 0 { 515 return true, errors.New(strings.Join(errs, "; ")) 516 } 517 return len(vm.getUnmountedVolumes(podName, expectedVolumes)) == 0, nil 518 } 519 } 520 521 // verifyVolumesUnmountedFunc returns a method that is true when there are no mounted volumes for this 522 // pod. 523 func (vm *volumeManager) verifyVolumesUnmountedFunc(podName types.UniquePodName) wait.ConditionWithContextFunc { 524 return func(_ context.Context) (done bool, err error) { 525 if errs := vm.desiredStateOfWorld.PopPodErrors(podName); len(errs) > 0 { 526 return true, errors.New(strings.Join(errs, "; ")) 527 } 528 return len(vm.actualStateOfWorld.GetMountedVolumesForPod(podName)) == 0, nil 529 } 530 } 531 532 // getUnmountedVolumes fetches the current list of mounted volumes from 533 // the actual state of the world, and uses it to process the list of 534 // expectedVolumes. It returns a list of unmounted volumes. 535 // The list also includes volume that may be mounted in uncertain state. 536 func (vm *volumeManager) getUnmountedVolumes(podName types.UniquePodName, expectedVolumes []string) []string { 537 mountedVolumes := sets.NewString() 538 for _, mountedVolume := range vm.actualStateOfWorld.GetMountedVolumesForPod(podName) { 539 mountedVolumes.Insert(mountedVolume.OuterVolumeSpecName) 540 } 541 return filterUnmountedVolumes(mountedVolumes, expectedVolumes) 542 } 543 544 // filterUnmountedVolumes adds each element of expectedVolumes that is not in 545 // mountedVolumes to a list of unmountedVolumes and returns it. 546 func filterUnmountedVolumes(mountedVolumes sets.String, expectedVolumes []string) []string { 547 unmountedVolumes := []string{} 548 for _, expectedVolume := range expectedVolumes { 549 if !mountedVolumes.Has(expectedVolume) { 550 unmountedVolumes = append(unmountedVolumes, expectedVolume) 551 } 552 } 553 sort.Strings(unmountedVolumes) 554 555 return unmountedVolumes 556 } 557 558 // getExpectedVolumes returns a list of volumes that must be mounted in order to 559 // consider the volume setup step for this pod satisfied. 560 func getExpectedVolumes(pod *v1.Pod) []string { 561 mounts, devices, _ := util.GetPodVolumeNames(pod) 562 return mounts.Union(devices).UnsortedList() 563 } 564 565 // getExtraSupplementalGid returns the value of an extra supplemental GID as 566 // defined by an annotation on a volume and a boolean indicating whether the 567 // volume defined a GID that the pod doesn't already request. 568 func getExtraSupplementalGid(volumeGidValue string, pod *v1.Pod) (int64, bool) { 569 if volumeGidValue == "" { 570 return 0, false 571 } 572 573 gid, err := strconv.ParseInt(volumeGidValue, 10, 64) 574 if err != nil { 575 return 0, false 576 } 577 578 if pod.Spec.SecurityContext != nil { 579 for _, existingGid := range pod.Spec.SecurityContext.SupplementalGroups { 580 if gid == int64(existingGid) { 581 return 0, false 582 } 583 } 584 } 585 586 return gid, true 587 }