1
16
17 package util
18
19 import (
20 "context"
21 "fmt"
22 "os"
23 "path/filepath"
24 "reflect"
25 "runtime"
26 "strings"
27 "time"
28
29 v1 "k8s.io/api/core/v1"
30 storage "k8s.io/api/storage/v1"
31 "k8s.io/apimachinery/pkg/api/resource"
32 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
33 apiruntime "k8s.io/apimachinery/pkg/runtime"
34 utypes "k8s.io/apimachinery/pkg/types"
35 "k8s.io/apimachinery/pkg/util/sets"
36 "k8s.io/apimachinery/pkg/util/wait"
37 utilfeature "k8s.io/apiserver/pkg/util/feature"
38 clientset "k8s.io/client-go/kubernetes"
39 storagehelpers "k8s.io/component-helpers/storage/volume"
40 "k8s.io/klog/v2"
41 "k8s.io/kubernetes/pkg/api/legacyscheme"
42 podutil "k8s.io/kubernetes/pkg/api/v1/pod"
43 "k8s.io/kubernetes/pkg/features"
44 "k8s.io/kubernetes/pkg/securitycontext"
45 "k8s.io/kubernetes/pkg/volume"
46 "k8s.io/kubernetes/pkg/volume/util/types"
47 "k8s.io/kubernetes/pkg/volume/util/volumepathhandler"
48 "k8s.io/mount-utils"
49 utilexec "k8s.io/utils/exec"
50 "k8s.io/utils/io"
51 utilstrings "k8s.io/utils/strings"
52 )
53
54 const (
55 readyFileName = "ready"
56
57
58
59
60 ControllerManagedAttachAnnotation string = "volumes.kubernetes.io/controller-managed-attach-detach"
61
62
63
64 KeepTerminatedPodVolumesAnnotation string = "volumes.kubernetes.io/keep-terminated-pod-volumes"
65
66
67
68 MountsInGlobalPDPath = "mounts"
69
70
71
72 VolumeGidAnnotationKey = "pv.beta.kubernetes.io/gid"
73
74
75
76 VolumeDynamicallyCreatedByKey = "kubernetes.io/createdby"
77
78
79 kubernetesPluginPathPrefix = "/plugins/kubernetes.io/"
80 )
81
82
83
84
85 func IsReady(dir string) bool {
86 readyFile := filepath.Join(dir, readyFileName)
87 s, err := os.Stat(readyFile)
88 if err != nil {
89 return false
90 }
91
92 if !s.Mode().IsRegular() {
93 klog.Errorf("ready-file is not a file: %s", readyFile)
94 return false
95 }
96
97 return true
98 }
99
100
101
102
103 func SetReady(dir string) {
104 if err := os.MkdirAll(dir, 0750); err != nil && !os.IsExist(err) {
105 klog.Errorf("Can't mkdir %s: %v", dir, err)
106 return
107 }
108
109 readyFile := filepath.Join(dir, readyFileName)
110 file, err := os.Create(readyFile)
111 if err != nil {
112 klog.Errorf("Can't touch %s: %v", readyFile, err)
113 return
114 }
115 file.Close()
116 }
117
118
119 func GetSecretForPod(pod *v1.Pod, secretName string, kubeClient clientset.Interface) (map[string]string, error) {
120 secret := make(map[string]string)
121 if kubeClient == nil {
122 return secret, fmt.Errorf("cannot get kube client")
123 }
124 secrets, err := kubeClient.CoreV1().Secrets(pod.Namespace).Get(context.TODO(), secretName, metav1.GetOptions{})
125 if err != nil {
126 return secret, err
127 }
128 for name, data := range secrets.Data {
129 secret[name] = string(data)
130 }
131 return secret, nil
132 }
133
134
135 func GetSecretForPV(secretNamespace, secretName, volumePluginName string, kubeClient clientset.Interface) (map[string]string, error) {
136 secret := make(map[string]string)
137 if kubeClient == nil {
138 return secret, fmt.Errorf("cannot get kube client")
139 }
140 secrets, err := kubeClient.CoreV1().Secrets(secretNamespace).Get(context.TODO(), secretName, metav1.GetOptions{})
141 if err != nil {
142 return secret, err
143 }
144 if secrets.Type != v1.SecretType(volumePluginName) {
145 return secret, fmt.Errorf("cannot get secret of type %s", volumePluginName)
146 }
147 for name, data := range secrets.Data {
148 secret[name] = string(data)
149 }
150 return secret, nil
151 }
152
153
154 func GetClassForVolume(kubeClient clientset.Interface, pv *v1.PersistentVolume) (*storage.StorageClass, error) {
155 if kubeClient == nil {
156 return nil, fmt.Errorf("cannot get kube client")
157 }
158 className := storagehelpers.GetPersistentVolumeClass(pv)
159 if className == "" {
160 return nil, fmt.Errorf("volume has no storage class")
161 }
162
163 class, err := kubeClient.StorageV1().StorageClasses().Get(context.TODO(), className, metav1.GetOptions{})
164 if err != nil {
165 return nil, err
166 }
167 return class, nil
168 }
169
170
171 func LoadPodFromFile(filePath string) (*v1.Pod, error) {
172 if filePath == "" {
173 return nil, fmt.Errorf("file path not specified")
174 }
175 podDef, err := os.ReadFile(filePath)
176 if err != nil {
177 return nil, fmt.Errorf("failed to read file path %s: %+v", filePath, err)
178 }
179 if len(podDef) == 0 {
180 return nil, fmt.Errorf("file was empty: %s", filePath)
181 }
182 pod := &v1.Pod{}
183
184 codec := legacyscheme.Codecs.UniversalDecoder()
185 if err := apiruntime.DecodeInto(codec, podDef, pod); err != nil {
186 return nil, fmt.Errorf("failed decoding file: %v", err)
187 }
188 return pod, nil
189 }
190
191
192
193
194
195 func CalculateTimeoutForVolume(minimumTimeout, timeoutIncrement int, pv *v1.PersistentVolume) int64 {
196 giQty := resource.MustParse("1Gi")
197 pvQty := pv.Spec.Capacity[v1.ResourceStorage]
198 giSize := giQty.Value()
199 pvSize := pvQty.Value()
200 timeout := (pvSize / giSize) * int64(timeoutIncrement)
201 if timeout < int64(minimumTimeout) {
202 return int64(minimumTimeout)
203 }
204 return timeout
205 }
206
207
208
209
210
211 func GenerateVolumeName(clusterName, pvName string, maxLength int) string {
212 prefix := clusterName + "-dynamic"
213 pvLen := len(pvName)
214
215
216
217 if pvLen+1+len(prefix) > maxLength {
218 prefix = prefix[:maxLength-pvLen-1]
219 }
220 return prefix + "-" + pvName
221 }
222
223
224 func GetPath(mounter volume.Mounter) (string, error) {
225 path := mounter.GetPath()
226 if path == "" {
227 return "", fmt.Errorf("path is empty %s", reflect.TypeOf(mounter).String())
228 }
229 return path, nil
230 }
231
232
233
234 func UnmountViaEmptyDir(dir string, host volume.VolumeHost, volName string, volSpec volume.Spec, podUID utypes.UID) error {
235 klog.V(3).Infof("Tearing down volume %v for pod %v at %v", volName, podUID, dir)
236
237
238 wrapped, err := host.NewWrapperUnmounter(volName, volSpec, podUID)
239 if err != nil {
240 return err
241 }
242 return wrapped.TearDownAt(dir)
243 }
244
245
246 func MountOptionFromSpec(spec *volume.Spec, options ...string) []string {
247 pv := spec.PersistentVolume
248
249 if pv != nil {
250
251 if mo, ok := pv.Annotations[v1.MountOptionAnnotation]; ok {
252 moList := strings.Split(mo, ",")
253 return JoinMountOptions(moList, options)
254 }
255
256 if len(pv.Spec.MountOptions) > 0 {
257 return JoinMountOptions(pv.Spec.MountOptions, options)
258 }
259 }
260
261 return options
262 }
263
264
265 func JoinMountOptions(userOptions []string, systemOptions []string) []string {
266 allMountOptions := sets.NewString()
267
268 for _, mountOption := range userOptions {
269 if len(mountOption) > 0 {
270 allMountOptions.Insert(mountOption)
271 }
272 }
273
274 for _, mountOption := range systemOptions {
275 allMountOptions.Insert(mountOption)
276 }
277 return allMountOptions.List()
278 }
279
280
281 func ContainsAccessMode(modes []v1.PersistentVolumeAccessMode, mode v1.PersistentVolumeAccessMode) bool {
282 for _, m := range modes {
283 if m == mode {
284 return true
285 }
286 }
287 return false
288 }
289
290
291 func ContainsAllAccessModes(indexedModes []v1.PersistentVolumeAccessMode, requestedModes []v1.PersistentVolumeAccessMode) bool {
292 for _, mode := range requestedModes {
293 if !ContainsAccessMode(indexedModes, mode) {
294 return false
295 }
296 }
297 return true
298 }
299
300
301 func GetWindowsPath(path string) string {
302 windowsPath := strings.Replace(path, "/", "\\", -1)
303 if strings.HasPrefix(windowsPath, "\\") {
304 windowsPath = "c:" + windowsPath
305 }
306 return windowsPath
307 }
308
309
310 func GetUniquePodName(pod *v1.Pod) types.UniquePodName {
311 return types.UniquePodName(pod.UID)
312 }
313
314
315
316
317
318
319
320 func GetUniqueVolumeName(pluginName, volumeName string) v1.UniqueVolumeName {
321 return v1.UniqueVolumeName(fmt.Sprintf("%s/%s", pluginName, volumeName))
322 }
323
324
325
326
327 func GetUniqueVolumeNameFromSpecWithPod(
328 podName types.UniquePodName, volumePlugin volume.VolumePlugin, volumeSpec *volume.Spec) v1.UniqueVolumeName {
329 return v1.UniqueVolumeName(
330 fmt.Sprintf("%s/%v-%s", volumePlugin.GetPluginName(), podName, volumeSpec.Name()))
331 }
332
333
334
335
336
337
338 func GetUniqueVolumeNameFromSpec(
339 volumePlugin volume.VolumePlugin,
340 volumeSpec *volume.Spec) (v1.UniqueVolumeName, error) {
341 if volumePlugin == nil {
342 return "", fmt.Errorf(
343 "volumePlugin should not be nil. volumeSpec.Name=%q",
344 volumeSpec.Name())
345 }
346
347 volumeName, err := volumePlugin.GetVolumeName(volumeSpec)
348 if err != nil || volumeName == "" {
349 return "", fmt.Errorf(
350 "failed to GetVolumeName from volumePlugin for volumeSpec %q err=%v",
351 volumeSpec.Name(),
352 err)
353 }
354
355 return GetUniqueVolumeName(
356 volumePlugin.GetPluginName(),
357 volumeName),
358 nil
359 }
360
361
362 func IsPodTerminated(pod *v1.Pod, podStatus v1.PodStatus) bool {
363
364
365
366 return podStatus.Phase == v1.PodFailed || podStatus.Phase == v1.PodSucceeded || (pod.DeletionTimestamp != nil && notRunning(podStatus.InitContainerStatuses) && notRunning(podStatus.ContainerStatuses) && notRunning(podStatus.EphemeralContainerStatuses))
367 }
368
369
370
371 func notRunning(statuses []v1.ContainerStatus) bool {
372 for _, status := range statuses {
373 if status.State.Terminated == nil && status.State.Waiting == nil {
374 return false
375 }
376 }
377 return true
378 }
379
380
381
382
383
384
385
386 func SplitUniqueName(uniqueName v1.UniqueVolumeName) (string, string, error) {
387 components := strings.SplitN(string(uniqueName), "/", 3)
388 if len(components) != 3 {
389 return "", "", fmt.Errorf("cannot split volume unique name %s to plugin/volume components", uniqueName)
390 }
391 pluginName := fmt.Sprintf("%s/%s", components[0], components[1])
392 return pluginName, components[2], nil
393 }
394
395
396
397 func NewSafeFormatAndMountFromHost(pluginName string, host volume.VolumeHost) *mount.SafeFormatAndMount {
398 mounter := host.GetMounter(pluginName)
399 exec := host.GetExec(pluginName)
400 return &mount.SafeFormatAndMount{Interface: mounter, Exec: exec}
401 }
402
403
404
405
406 func GetVolumeMode(volumeSpec *volume.Spec) (v1.PersistentVolumeMode, error) {
407 if volumeSpec == nil || volumeSpec.PersistentVolume == nil {
408 return v1.PersistentVolumeFilesystem, nil
409 }
410 if volumeSpec.PersistentVolume.Spec.VolumeMode != nil {
411 return *volumeSpec.PersistentVolume.Spec.VolumeMode, nil
412 }
413 return "", fmt.Errorf("cannot get volumeMode for volume: %v", volumeSpec.Name())
414 }
415
416
417 func GetPersistentVolumeClaimQualifiedName(claim *v1.PersistentVolumeClaim) string {
418 return utilstrings.JoinQualifiedName(claim.GetNamespace(), claim.GetName())
419 }
420
421
422
423 func CheckVolumeModeFilesystem(volumeSpec *volume.Spec) (bool, error) {
424 volumeMode, err := GetVolumeMode(volumeSpec)
425 if err != nil {
426 return true, err
427 }
428 if volumeMode == v1.PersistentVolumeBlock {
429 return false, nil
430 }
431 return true, nil
432 }
433
434
435
436 func CheckPersistentVolumeClaimModeBlock(pvc *v1.PersistentVolumeClaim) bool {
437 return pvc.Spec.VolumeMode != nil && *pvc.Spec.VolumeMode == v1.PersistentVolumeBlock
438 }
439
440
441
442
443 func IsWindowsUNCPath(goos, path string) bool {
444 if goos != "windows" {
445 return false
446 }
447
448 if strings.HasPrefix(path, `\\`) {
449 return true
450 }
451 return false
452 }
453
454
455
456 func IsWindowsLocalPath(goos, path string) bool {
457 if goos != "windows" {
458 return false
459 }
460 if IsWindowsUNCPath(goos, path) {
461 return false
462 }
463 if strings.Contains(path, ":") {
464 return false
465 }
466 if !(strings.HasPrefix(path, `/`) || strings.HasPrefix(path, `\`)) {
467 return false
468 }
469 return true
470 }
471
472
473 func MakeAbsolutePath(goos, path string) string {
474 if goos != "windows" {
475 return filepath.Clean("/" + path)
476 }
477
478
479 if strings.Contains(path, ":") {
480 return path
481 }
482
483 if strings.HasPrefix(path, "/") || strings.HasPrefix(path, "\\") {
484 return "c:" + path
485 }
486
487 return "c:\\" + path
488 }
489
490
491
492
493 func MapBlockVolume(
494 blkUtil volumepathhandler.BlockVolumePathHandler,
495 devicePath,
496 globalMapPath,
497 podVolumeMapPath,
498 volumeMapName string,
499 podUID utypes.UID,
500 ) error {
501
502 mapErr := blkUtil.MapDevice(devicePath, globalMapPath, string(podUID), true )
503 if mapErr != nil {
504 return fmt.Errorf("blkUtil.MapDevice failed. devicePath: %s, globalMapPath:%s, podUID: %s, bindMount: %v: %v",
505 devicePath, globalMapPath, string(podUID), true, mapErr)
506 }
507
508
509 mapErr = blkUtil.MapDevice(devicePath, podVolumeMapPath, volumeMapName, false )
510 if mapErr != nil {
511 return fmt.Errorf("blkUtil.MapDevice failed. devicePath: %s, podVolumeMapPath:%s, volumeMapName: %s, bindMount: %v: %v",
512 devicePath, podVolumeMapPath, volumeMapName, false, mapErr)
513 }
514
515
516
517
518
519 _, mapErr = blkUtil.AttachFileDevice(filepath.Join(globalMapPath, string(podUID)))
520 if mapErr != nil {
521 return fmt.Errorf("blkUtil.AttachFileDevice failed. globalMapPath:%s, podUID: %s: %v",
522 globalMapPath, string(podUID), mapErr)
523 }
524
525 return nil
526 }
527
528
529
530
531 func UnmapBlockVolume(
532 blkUtil volumepathhandler.BlockVolumePathHandler,
533 globalUnmapPath,
534 podDeviceUnmapPath,
535 volumeMapName string,
536 podUID utypes.UID,
537 ) error {
538
539 err := blkUtil.DetachFileDevice(filepath.Join(globalUnmapPath, string(podUID)))
540 if err != nil {
541 return fmt.Errorf("blkUtil.DetachFileDevice failed. globalUnmapPath:%s, podUID: %s: %v",
542 globalUnmapPath, string(podUID), err)
543 }
544
545
546 unmapDeviceErr := blkUtil.UnmapDevice(podDeviceUnmapPath, volumeMapName, false )
547 if unmapDeviceErr != nil {
548 return fmt.Errorf("blkUtil.DetachFileDevice failed. podDeviceUnmapPath:%s, volumeMapName: %s, bindMount: %v: %v",
549 podDeviceUnmapPath, volumeMapName, false, unmapDeviceErr)
550 }
551
552
553 unmapDeviceErr = blkUtil.UnmapDevice(globalUnmapPath, string(podUID), true )
554 if unmapDeviceErr != nil {
555 return fmt.Errorf("blkUtil.DetachFileDevice failed. globalUnmapPath:%s, podUID: %s, bindMount: %v: %v",
556 globalUnmapPath, string(podUID), true, unmapDeviceErr)
557 }
558 return nil
559 }
560
561
562
563 func GetPluginMountDir(host volume.VolumeHost, name string) string {
564 mntDir := filepath.Join(host.GetPluginDir(name), MountsInGlobalPDPath)
565 return mntDir
566 }
567
568
569
570
571
572 func IsLocalEphemeralVolume(volume v1.Volume) bool {
573 return volume.GitRepo != nil ||
574 (volume.EmptyDir != nil && volume.EmptyDir.Medium == v1.StorageMediumDefault) ||
575 volume.ConfigMap != nil
576 }
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593 func GetLocalPersistentVolumeNodeNames(pv *v1.PersistentVolume) []string {
594 if pv == nil || pv.Spec.NodeAffinity == nil || pv.Spec.NodeAffinity.Required == nil {
595 return nil
596 }
597
598 var result sets.Set[string]
599 for _, term := range pv.Spec.NodeAffinity.Required.NodeSelectorTerms {
600 var nodes sets.Set[string]
601 for _, matchExpr := range term.MatchExpressions {
602 if matchExpr.Key == v1.LabelHostname && matchExpr.Operator == v1.NodeSelectorOpIn {
603 if nodes == nil {
604 nodes = sets.New(matchExpr.Values...)
605 } else {
606 nodes = nodes.Intersection(sets.New(matchExpr.Values...))
607 }
608 }
609 }
610 result = result.Union(nodes)
611 }
612
613 return sets.List(result)
614 }
615
616
617
618
619 func GetPodVolumeNames(pod *v1.Pod) (mounts sets.String, devices sets.String, seLinuxContainerContexts map[string][]*v1.SELinuxOptions) {
620 mounts = sets.NewString()
621 devices = sets.NewString()
622 seLinuxContainerContexts = make(map[string][]*v1.SELinuxOptions)
623
624 podutil.VisitContainers(&pod.Spec, podutil.AllFeatureEnabledContainers(), func(container *v1.Container, containerType podutil.ContainerType) bool {
625 var seLinuxOptions *v1.SELinuxOptions
626 if utilfeature.DefaultFeatureGate.Enabled(features.SELinuxMountReadWriteOncePod) {
627 effectiveContainerSecurity := securitycontext.DetermineEffectiveSecurityContext(pod, container)
628 if effectiveContainerSecurity != nil {
629
630 seLinuxOptions = effectiveContainerSecurity.SELinuxOptions
631 }
632 }
633
634 if container.VolumeMounts != nil {
635 for _, mount := range container.VolumeMounts {
636 mounts.Insert(mount.Name)
637 if seLinuxOptions != nil {
638 seLinuxContainerContexts[mount.Name] = append(seLinuxContainerContexts[mount.Name], seLinuxOptions.DeepCopy())
639 }
640 }
641 }
642 if container.VolumeDevices != nil {
643 for _, device := range container.VolumeDevices {
644 devices.Insert(device.Name)
645 }
646 }
647 return true
648 })
649 return
650 }
651
652
653
654 func FsUserFrom(pod *v1.Pod) *int64 {
655 var fsUser *int64
656
657 podutil.VisitContainers(&pod.Spec, podutil.InitContainers|podutil.Containers, func(container *v1.Container, containerType podutil.ContainerType) bool {
658 runAsUser, ok := securitycontext.DetermineEffectiveRunAsUser(pod, container)
659
660
661 if !ok || (fsUser != nil && *fsUser != *runAsUser) {
662 fsUser = nil
663 return false
664 }
665 if fsUser == nil {
666 fsUser = runAsUser
667 }
668 return true
669 })
670 return fsUser
671 }
672
673
674
675
676
677
678
679
680 func HasMountRefs(mountPath string, mountRefs []string) bool {
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696 pathToFind := mountPath
697 if i := strings.Index(mountPath, kubernetesPluginPathPrefix); i > -1 {
698 pathToFind = mountPath[i:]
699 }
700 for _, ref := range mountRefs {
701 if !strings.Contains(ref, pathToFind) {
702 return true
703 }
704 }
705 return false
706 }
707
708
709 func WriteVolumeCache(deviceMountPath string, exec utilexec.Interface) error {
710
711 if runtime.GOOS == "windows" {
712 cmdString := "Get-Volume -FilePath $env:mountpath | Write-Volumecache"
713 cmd := exec.Command("powershell", "/c", cmdString)
714 env := append(os.Environ(), fmt.Sprintf("mountpath=%s", deviceMountPath))
715 cmd.SetEnv(env)
716 klog.V(8).Infof("Executing command: %q", cmdString)
717 output, err := cmd.CombinedOutput()
718 klog.Infof("command (%q) execeuted: %v, output: %q", cmdString, err, string(output))
719 if err != nil {
720 return fmt.Errorf("command (%q) failed: %v, output: %q", cmdString, err, string(output))
721 }
722 }
723
724 return nil
725 }
726
727
728
729
730
731
732 func IsMultiAttachAllowed(volumeSpec *volume.Spec) bool {
733 if volumeSpec == nil {
734
735 return true
736 }
737
738 if volumeSpec.Volume != nil {
739
740 if volumeSpec.Volume.AzureDisk != nil ||
741 volumeSpec.Volume.Cinder != nil {
742 return false
743 }
744 }
745
746
747
748 if volumeSpec.PersistentVolume != nil {
749
750 if len(volumeSpec.PersistentVolume.Spec.AccessModes) == 0 {
751
752 return true
753 }
754
755
756 for _, accessMode := range volumeSpec.PersistentVolume.Spec.AccessModes {
757 if accessMode == v1.ReadWriteMany || accessMode == v1.ReadOnlyMany {
758 return true
759 }
760 }
761 return false
762 }
763
764
765 return true
766 }
767
768
769 func IsAttachableVolume(volumeSpec *volume.Spec, volumePluginMgr *volume.VolumePluginMgr) bool {
770 attachableVolumePlugin, _ := volumePluginMgr.FindAttachablePluginBySpec(volumeSpec)
771 if attachableVolumePlugin != nil {
772 volumeAttacher, err := attachableVolumePlugin.NewAttacher()
773 if err == nil && volumeAttacher != nil {
774 return true
775 }
776 }
777
778 return false
779 }
780
781
782 func IsDeviceMountableVolume(volumeSpec *volume.Spec, volumePluginMgr *volume.VolumePluginMgr) bool {
783 deviceMountableVolumePlugin, _ := volumePluginMgr.FindDeviceMountablePluginBySpec(volumeSpec)
784 if deviceMountableVolumePlugin != nil {
785 volumeDeviceMounter, err := deviceMountableVolumePlugin.NewDeviceMounter()
786 if err == nil && volumeDeviceMounter != nil {
787 return true
788 }
789 }
790
791 return false
792 }
793
794
795
796
797 func GetReliableMountRefs(mounter mount.Interface, mountPath string) ([]string, error) {
798 var paths []string
799 var lastErr error
800 err := wait.PollImmediate(10*time.Millisecond, time.Minute, func() (bool, error) {
801 var err error
802 paths, err = mounter.GetMountRefs(mountPath)
803 if io.IsInconsistentReadError(err) {
804 lastErr = err
805 return false, nil
806 }
807 if err != nil {
808 return false, err
809 }
810 return true, nil
811 })
812 if err == wait.ErrWaitTimeout {
813 return nil, lastErr
814 }
815 return paths, err
816 }
817
View as plain text