1/*
2Copyright 2015 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8    http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package util
18
19import (
20	"context"
21	"fmt"
22	"io/ioutil"
23	"os"
24	"path/filepath"
25	"reflect"
26	"runtime"
27	"strings"
28	"time"
29
30	v1 "k8s.io/api/core/v1"
31	storage "k8s.io/api/storage/v1"
32	"k8s.io/apimachinery/pkg/api/resource"
33	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
34	apiruntime "k8s.io/apimachinery/pkg/runtime"
35	utypes "k8s.io/apimachinery/pkg/types"
36	"k8s.io/apimachinery/pkg/util/sets"
37	"k8s.io/apimachinery/pkg/util/wait"
38	clientset "k8s.io/client-go/kubernetes"
39	"k8s.io/component-helpers/scheduling/corev1"
40	storagehelpers "k8s.io/component-helpers/storage/volume"
41	"k8s.io/klog/v2"
42	"k8s.io/kubernetes/pkg/api/legacyscheme"
43	podutil "k8s.io/kubernetes/pkg/api/v1/pod"
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
54const (
55	readyFileName = "ready"
56
57	// ControllerManagedAttachAnnotation is the key of the annotation on Node
58	// objects that indicates attach/detach operations for the node should be
59	// managed by the attach/detach controller
60	ControllerManagedAttachAnnotation string = "volumes.kubernetes.io/controller-managed-attach-detach"
61
62	// KeepTerminatedPodVolumesAnnotation is the key of the annotation on Node
63	// that decides if pod volumes are unmounted when pod is terminated
64	KeepTerminatedPodVolumesAnnotation string = "volumes.kubernetes.io/keep-terminated-pod-volumes"
65
66	// MountsInGlobalPDPath is name of the directory appended to a volume plugin
67	// name to create the place for volume mounts in the global PD path.
68	MountsInGlobalPDPath = "mounts"
69
70	// VolumeGidAnnotationKey is the of the annotation on the PersistentVolume
71	// object that specifies a supplemental GID.
72	VolumeGidAnnotationKey = "pv.beta.kubernetes.io/gid"
73
74	// VolumeDynamicallyCreatedByKey is the key of the annotation on PersistentVolume
75	// object created dynamically
76	VolumeDynamicallyCreatedByKey = "kubernetes.io/createdby"
77)
78
79// IsReady checks for the existence of a regular file
80// called 'ready' in the given directory and returns
81// true if that file exists.
82func IsReady(dir string) bool {
83	readyFile := filepath.Join(dir, readyFileName)
84	s, err := os.Stat(readyFile)
85	if err != nil {
86		return false
87	}
88
89	if !s.Mode().IsRegular() {
90		klog.Errorf("ready-file is not a file: %s", readyFile)
91		return false
92	}
93
94	return true
95}
96
97// SetReady creates a file called 'ready' in the given
98// directory.  It logs an error if the file cannot be
99// created.
100func SetReady(dir string) {
101	if err := os.MkdirAll(dir, 0750); err != nil && !os.IsExist(err) {
102		klog.Errorf("Can't mkdir %s: %v", dir, err)
103		return
104	}
105
106	readyFile := filepath.Join(dir, readyFileName)
107	file, err := os.Create(readyFile)
108	if err != nil {
109		klog.Errorf("Can't touch %s: %v", readyFile, err)
110		return
111	}
112	file.Close()
113}
114
115// GetSecretForPod locates secret by name in the pod's namespace and returns secret map
116func GetSecretForPod(pod *v1.Pod, secretName string, kubeClient clientset.Interface) (map[string]string, error) {
117	secret := make(map[string]string)
118	if kubeClient == nil {
119		return secret, fmt.Errorf("cannot get kube client")
120	}
121	secrets, err := kubeClient.CoreV1().Secrets(pod.Namespace).Get(context.TODO(), secretName, metav1.GetOptions{})
122	if err != nil {
123		return secret, err
124	}
125	for name, data := range secrets.Data {
126		secret[name] = string(data)
127	}
128	return secret, nil
129}
130
131// GetSecretForPV locates secret by name and namespace, verifies the secret type, and returns secret map
132func GetSecretForPV(secretNamespace, secretName, volumePluginName string, kubeClient clientset.Interface) (map[string]string, error) {
133	secret := make(map[string]string)
134	if kubeClient == nil {
135		return secret, fmt.Errorf("cannot get kube client")
136	}
137	secrets, err := kubeClient.CoreV1().Secrets(secretNamespace).Get(context.TODO(), secretName, metav1.GetOptions{})
138	if err != nil {
139		return secret, err
140	}
141	if secrets.Type != v1.SecretType(volumePluginName) {
142		return secret, fmt.Errorf("cannot get secret of type %s", volumePluginName)
143	}
144	for name, data := range secrets.Data {
145		secret[name] = string(data)
146	}
147	return secret, nil
148}
149
150// GetClassForVolume locates storage class by persistent volume
151func GetClassForVolume(kubeClient clientset.Interface, pv *v1.PersistentVolume) (*storage.StorageClass, error) {
152	if kubeClient == nil {
153		return nil, fmt.Errorf("cannot get kube client")
154	}
155	className := storagehelpers.GetPersistentVolumeClass(pv)
156	if className == "" {
157		return nil, fmt.Errorf("volume has no storage class")
158	}
159
160	class, err := kubeClient.StorageV1().StorageClasses().Get(context.TODO(), className, metav1.GetOptions{})
161	if err != nil {
162		return nil, err
163	}
164	return class, nil
165}
166
167// CheckNodeAffinity looks at the PV node affinity, and checks if the node has the same corresponding labels
168// This ensures that we don't mount a volume that doesn't belong to this node
169func CheckNodeAffinity(pv *v1.PersistentVolume, nodeLabels map[string]string) error {
170	return checkVolumeNodeAffinity(pv, &v1.Node{ObjectMeta: metav1.ObjectMeta{Labels: nodeLabels}})
171}
172
173func checkVolumeNodeAffinity(pv *v1.PersistentVolume, node *v1.Node) error {
174	if pv.Spec.NodeAffinity == nil {
175		return nil
176	}
177
178	if pv.Spec.NodeAffinity.Required != nil {
179		terms := pv.Spec.NodeAffinity.Required
180		klog.V(10).Infof("Match for Required node selector terms %+v", terms)
181		if matches, err := corev1.MatchNodeSelectorTerms(node, terms); err != nil {
182			return err
183		} else if !matches {
184			return fmt.Errorf("no matching NodeSelectorTerms")
185		}
186	}
187
188	return nil
189}
190
191// LoadPodFromFile will read, decode, and return a Pod from a file.
192func LoadPodFromFile(filePath string) (*v1.Pod, error) {
193	if filePath == "" {
194		return nil, fmt.Errorf("file path not specified")
195	}
196	podDef, err := ioutil.ReadFile(filePath)
197	if err != nil {
198		return nil, fmt.Errorf("failed to read file path %s: %+v", filePath, err)
199	}
200	if len(podDef) == 0 {
201		return nil, fmt.Errorf("file was empty: %s", filePath)
202	}
203	pod := &v1.Pod{}
204
205	codec := legacyscheme.Codecs.UniversalDecoder()
206	if err := apiruntime.DecodeInto(codec, podDef, pod); err != nil {
207		return nil, fmt.Errorf("failed decoding file: %v", err)
208	}
209	return pod, nil
210}
211
212// CalculateTimeoutForVolume calculates time for a Recycler pod to complete a
213// recycle operation. The calculation and return value is either the
214// minimumTimeout or the timeoutIncrement per Gi of storage size, whichever is
215// greater.
216func CalculateTimeoutForVolume(minimumTimeout, timeoutIncrement int, pv *v1.PersistentVolume) int64 {
217	giQty := resource.MustParse("1Gi")
218	pvQty := pv.Spec.Capacity[v1.ResourceStorage]
219	giSize := giQty.Value()
220	pvSize := pvQty.Value()
221	timeout := (pvSize / giSize) * int64(timeoutIncrement)
222	if timeout < int64(minimumTimeout) {
223		return int64(minimumTimeout)
224	}
225	return timeout
226}
227
228// GenerateVolumeName returns a PV name with clusterName prefix. The function
229// should be used to generate a name of GCE PD or Cinder volume. It basically
230// adds "<clusterName>-dynamic-" before the PV name, making sure the resulting
231// string fits given length and cuts "dynamic" if not.
232func GenerateVolumeName(clusterName, pvName string, maxLength int) string {
233	prefix := clusterName + "-dynamic"
234	pvLen := len(pvName)
235
236	// cut the "<clusterName>-dynamic" to fit full pvName into maxLength
237	// +1 for the '-' dash
238	if pvLen+1+len(prefix) > maxLength {
239		prefix = prefix[:maxLength-pvLen-1]
240	}
241	return prefix + "-" + pvName
242}
243
244// GetPath checks if the path from the mounter is empty.
245func GetPath(mounter volume.Mounter) (string, error) {
246	path := mounter.GetPath()
247	if path == "" {
248		return "", fmt.Errorf("path is empty %s", reflect.TypeOf(mounter).String())
249	}
250	return path, nil
251}
252
253// UnmountViaEmptyDir delegates the tear down operation for secret, configmap, git_repo and downwardapi
254// to empty_dir
255func UnmountViaEmptyDir(dir string, host volume.VolumeHost, volName string, volSpec volume.Spec, podUID utypes.UID) error {
256	klog.V(3).Infof("Tearing down volume %v for pod %v at %v", volName, podUID, dir)
257
258	// Wrap EmptyDir, let it do the teardown.
259	wrapped, err := host.NewWrapperUnmounter(volName, volSpec, podUID)
260	if err != nil {
261		return err
262	}
263	return wrapped.TearDownAt(dir)
264}
265
266// MountOptionFromSpec extracts and joins mount options from volume spec with supplied options
267func MountOptionFromSpec(spec *volume.Spec, options ...string) []string {
268	pv := spec.PersistentVolume
269
270	if pv != nil {
271		// Use beta annotation first
272		if mo, ok := pv.Annotations[v1.MountOptionAnnotation]; ok {
273			moList := strings.Split(mo, ",")
274			return JoinMountOptions(moList, options)
275		}
276
277		if len(pv.Spec.MountOptions) > 0 {
278			return JoinMountOptions(pv.Spec.MountOptions, options)
279		}
280	}
281
282	return options
283}
284
285// JoinMountOptions joins mount options eliminating duplicates
286func JoinMountOptions(userOptions []string, systemOptions []string) []string {
287	allMountOptions := sets.NewString()
288
289	for _, mountOption := range userOptions {
290		if len(mountOption) > 0 {
291			allMountOptions.Insert(mountOption)
292		}
293	}
294
295	for _, mountOption := range systemOptions {
296		allMountOptions.Insert(mountOption)
297	}
298	return allMountOptions.List()
299}
300
301// ContainsAccessMode returns whether the requested mode is contained by modes
302func ContainsAccessMode(modes []v1.PersistentVolumeAccessMode, mode v1.PersistentVolumeAccessMode) bool {
303	for _, m := range modes {
304		if m == mode {
305			return true
306		}
307	}
308	return false
309}
310
311// ContainsAllAccessModes returns whether all of the requested modes are contained by modes
312func ContainsAllAccessModes(indexedModes []v1.PersistentVolumeAccessMode, requestedModes []v1.PersistentVolumeAccessMode) bool {
313	for _, mode := range requestedModes {
314		if !ContainsAccessMode(indexedModes, mode) {
315			return false
316		}
317	}
318	return true
319}
320
321// GetWindowsPath get a windows path
322func GetWindowsPath(path string) string {
323	windowsPath := strings.Replace(path, "/", "\\", -1)
324	if strings.HasPrefix(windowsPath, "\\") {
325		windowsPath = "c:" + windowsPath
326	}
327	return windowsPath
328}
329
330// GetUniquePodName returns a unique identifier to reference a pod by
331func GetUniquePodName(pod *v1.Pod) types.UniquePodName {
332	return types.UniquePodName(pod.UID)
333}
334
335// GetUniqueVolumeName returns a unique name representing the volume/plugin.
336// Caller should ensure that volumeName is a name/ID uniquely identifying the
337// actual backing device, directory, path, etc. for a particular volume.
338// The returned name can be used to uniquely reference the volume, for example,
339// to prevent operations (attach/detach or mount/unmount) from being triggered
340// on the same volume.
341func GetUniqueVolumeName(pluginName, volumeName string) v1.UniqueVolumeName {
342	return v1.UniqueVolumeName(fmt.Sprintf("%s/%s", pluginName, volumeName))
343}
344
345// GetUniqueVolumeNameFromSpecWithPod returns a unique volume name with pod
346// name included. This is useful to generate different names for different pods
347// on same volume.
348func GetUniqueVolumeNameFromSpecWithPod(
349	podName types.UniquePodName, volumePlugin volume.VolumePlugin, volumeSpec *volume.Spec) v1.UniqueVolumeName {
350	return v1.UniqueVolumeName(
351		fmt.Sprintf("%s/%v-%s", volumePlugin.GetPluginName(), podName, volumeSpec.Name()))
352}
353
354// GetUniqueVolumeNameFromSpec uses the given VolumePlugin to generate a unique
355// name representing the volume defined in the specified volume spec.
356// This returned name can be used to uniquely reference the actual backing
357// device, directory, path, etc. referenced by the given volumeSpec.
358// If the given plugin does not support the volume spec, this returns an error.
359func GetUniqueVolumeNameFromSpec(
360	volumePlugin volume.VolumePlugin,
361	volumeSpec *volume.Spec) (v1.UniqueVolumeName, error) {
362	if volumePlugin == nil {
363		return "", fmt.Errorf(
364			"volumePlugin should not be nil. volumeSpec.Name=%q",
365			volumeSpec.Name())
366	}
367
368	volumeName, err := volumePlugin.GetVolumeName(volumeSpec)
369	if err != nil || volumeName == "" {
370		return "", fmt.Errorf(
371			"failed to GetVolumeName from volumePlugin for volumeSpec %q err=%v",
372			volumeSpec.Name(),
373			err)
374	}
375
376	return GetUniqueVolumeName(
377			volumePlugin.GetPluginName(),
378			volumeName),
379		nil
380}
381
382// IsPodTerminated checks if pod is terminated
383func IsPodTerminated(pod *v1.Pod, podStatus v1.PodStatus) bool {
384	// TODO: the guarantees provided by kubelet status are not sufficient to guarantee it's safe to ignore a deleted pod,
385	// even if everything is notRunning (kubelet does not guarantee that when pod status is waiting that it isn't trying
386	// to start a container).
387	return podStatus.Phase == v1.PodFailed || podStatus.Phase == v1.PodSucceeded || (pod.DeletionTimestamp != nil && notRunning(podStatus.InitContainerStatuses) && notRunning(podStatus.ContainerStatuses) && notRunning(podStatus.EphemeralContainerStatuses))
388}
389
390// notRunning returns true if every status is terminated or waiting, or the status list
391// is empty.
392func notRunning(statuses []v1.ContainerStatus) bool {
393	for _, status := range statuses {
394		if status.State.Terminated == nil && status.State.Waiting == nil {
395			return false
396		}
397	}
398	return true
399}
400
401// SplitUniqueName splits the unique name to plugin name and volume name strings. It expects the uniqueName to follow
402// the format plugin_name/volume_name and the plugin name must be namespaced as described by the plugin interface,
403// i.e. namespace/plugin containing exactly one '/'. This means the unique name will always be in the form of
404// plugin_namespace/plugin/volume_name, see k8s.io/kubernetes/pkg/volume/plugins.go VolumePlugin interface
405// description and pkg/volume/util/volumehelper/volumehelper.go GetUniqueVolumeNameFromSpec that constructs
406// the unique volume names.
407func SplitUniqueName(uniqueName v1.UniqueVolumeName) (string, string, error) {
408	components := strings.SplitN(string(uniqueName), "/", 3)
409	if len(components) != 3 {
410		return "", "", fmt.Errorf("cannot split volume unique name %s to plugin/volume components", uniqueName)
411	}
412	pluginName := fmt.Sprintf("%s/%s", components[0], components[1])
413	return pluginName, components[2], nil
414}
415
416// NewSafeFormatAndMountFromHost creates a new SafeFormatAndMount with Mounter
417// and Exec taken from given VolumeHost.
418func NewSafeFormatAndMountFromHost(pluginName string, host volume.VolumeHost) *mount.SafeFormatAndMount {
419	mounter := host.GetMounter(pluginName)
420	exec := host.GetExec(pluginName)
421	return &mount.SafeFormatAndMount{Interface: mounter, Exec: exec}
422}
423
424// GetVolumeMode retrieves VolumeMode from pv.
425// If the volume doesn't have PersistentVolume, it's an inline volume,
426// should return volumeMode as filesystem to keep existing behavior.
427func GetVolumeMode(volumeSpec *volume.Spec) (v1.PersistentVolumeMode, error) {
428	if volumeSpec == nil || volumeSpec.PersistentVolume == nil {
429		return v1.PersistentVolumeFilesystem, nil
430	}
431	if volumeSpec.PersistentVolume.Spec.VolumeMode != nil {
432		return *volumeSpec.PersistentVolume.Spec.VolumeMode, nil
433	}
434	return "", fmt.Errorf("cannot get volumeMode for volume: %v", volumeSpec.Name())
435}
436
437// GetPersistentVolumeClaimQualifiedName returns a qualified name for pvc.
438func GetPersistentVolumeClaimQualifiedName(claim *v1.PersistentVolumeClaim) string {
439	return utilstrings.JoinQualifiedName(claim.GetNamespace(), claim.GetName())
440}
441
442// CheckVolumeModeFilesystem checks VolumeMode.
443// If the mode is Filesystem, return true otherwise return false.
444func CheckVolumeModeFilesystem(volumeSpec *volume.Spec) (bool, error) {
445	volumeMode, err := GetVolumeMode(volumeSpec)
446	if err != nil {
447		return true, err
448	}
449	if volumeMode == v1.PersistentVolumeBlock {
450		return false, nil
451	}
452	return true, nil
453}
454
455// CheckPersistentVolumeClaimModeBlock checks VolumeMode.
456// If the mode is Block, return true otherwise return false.
457func CheckPersistentVolumeClaimModeBlock(pvc *v1.PersistentVolumeClaim) bool {
458	return pvc.Spec.VolumeMode != nil && *pvc.Spec.VolumeMode == v1.PersistentVolumeBlock
459}
460
461// IsWindowsUNCPath checks if path is prefixed with \\
462// This can be used to skip any processing of paths
463// that point to SMB shares, local named pipes and local UNC path
464func IsWindowsUNCPath(goos, path string) bool {
465	if goos != "windows" {
466		return false
467	}
468	// Check for UNC prefix \\
469	if strings.HasPrefix(path, `\\`) {
470		return true
471	}
472	return false
473}
474
475// IsWindowsLocalPath checks if path is a local path
476// prefixed with "/" or "\" like "/foo/bar" or "\foo\bar"
477func IsWindowsLocalPath(goos, path string) bool {
478	if goos != "windows" {
479		return false
480	}
481	if IsWindowsUNCPath(goos, path) {
482		return false
483	}
484	if strings.Contains(path, ":") {
485		return false
486	}
487	if !(strings.HasPrefix(path, `/`) || strings.HasPrefix(path, `\`)) {
488		return false
489	}
490	return true
491}
492
493// MakeAbsolutePath convert path to absolute path according to GOOS
494func MakeAbsolutePath(goos, path string) string {
495	if goos != "windows" {
496		return filepath.Clean("/" + path)
497	}
498	// These are all for windows
499	// If there is a colon, give up.
500	if strings.Contains(path, ":") {
501		return path
502	}
503	// If there is a slash, but no drive, add 'c:'
504	if strings.HasPrefix(path, "/") || strings.HasPrefix(path, "\\") {
505		return "c:" + path
506	}
507	// Otherwise, add 'c:\'
508	return "c:\\" + path
509}
510
511// MapBlockVolume is a utility function to provide a common way of mapping
512// block device path for a specified volume and pod.  This function should be
513// called by volume plugins that implements volume.BlockVolumeMapper.Map() method.
514func MapBlockVolume(
515	blkUtil volumepathhandler.BlockVolumePathHandler,
516	devicePath,
517	globalMapPath,
518	podVolumeMapPath,
519	volumeMapName string,
520	podUID utypes.UID,
521) error {
522	// map devicePath to global node path as bind mount
523	mapErr := blkUtil.MapDevice(devicePath, globalMapPath, string(podUID), true /* bindMount */)
524	if mapErr != nil {
525		return fmt.Errorf("blkUtil.MapDevice failed. devicePath: %s, globalMapPath:%s, podUID: %s, bindMount: %v: %v",
526			devicePath, globalMapPath, string(podUID), true, mapErr)
527	}
528
529	// map devicePath to pod volume path
530	mapErr = blkUtil.MapDevice(devicePath, podVolumeMapPath, volumeMapName, false /* bindMount */)
531	if mapErr != nil {
532		return fmt.Errorf("blkUtil.MapDevice failed. devicePath: %s, podVolumeMapPath:%s, volumeMapName: %s, bindMount: %v: %v",
533			devicePath, podVolumeMapPath, volumeMapName, false, mapErr)
534	}
535
536	// Take file descriptor lock to keep a block device opened. Otherwise, there is a case
537	// that the block device is silently removed and attached another device with the same name.
538	// Container runtime can't handle this problem. To avoid unexpected condition fd lock
539	// for the block device is required.
540	_, mapErr = blkUtil.AttachFileDevice(filepath.Join(globalMapPath, string(podUID)))
541	if mapErr != nil {
542		return fmt.Errorf("blkUtil.AttachFileDevice failed. globalMapPath:%s, podUID: %s: %v",
543			globalMapPath, string(podUID), mapErr)
544	}
545
546	return nil
547}
548
549// UnmapBlockVolume is a utility function to provide a common way of unmapping
550// block device path for a specified volume and pod.  This function should be
551// called by volume plugins that implements volume.BlockVolumeMapper.Map() method.
552func UnmapBlockVolume(
553	blkUtil volumepathhandler.BlockVolumePathHandler,
554	globalUnmapPath,
555	podDeviceUnmapPath,
556	volumeMapName string,
557	podUID utypes.UID,
558) error {
559	// Release file descriptor lock.
560	err := blkUtil.DetachFileDevice(filepath.Join(globalUnmapPath, string(podUID)))
561	if err != nil {
562		return fmt.Errorf("blkUtil.DetachFileDevice failed. globalUnmapPath:%s, podUID: %s: %v",
563			globalUnmapPath, string(podUID), err)
564	}
565
566	// unmap devicePath from pod volume path
567	unmapDeviceErr := blkUtil.UnmapDevice(podDeviceUnmapPath, volumeMapName, false /* bindMount */)
568	if unmapDeviceErr != nil {
569		return fmt.Errorf("blkUtil.DetachFileDevice failed. podDeviceUnmapPath:%s, volumeMapName: %s, bindMount: %v: %v",
570			podDeviceUnmapPath, volumeMapName, false, unmapDeviceErr)
571	}
572
573	// unmap devicePath from global node path
574	unmapDeviceErr = blkUtil.UnmapDevice(globalUnmapPath, string(podUID), true /* bindMount */)
575	if unmapDeviceErr != nil {
576		return fmt.Errorf("blkUtil.DetachFileDevice failed. globalUnmapPath:%s, podUID: %s, bindMount: %v: %v",
577			globalUnmapPath, string(podUID), true, unmapDeviceErr)
578	}
579	return nil
580}
581
582// GetPluginMountDir returns the global mount directory name appended
583// to the given plugin name's plugin directory
584func GetPluginMountDir(host volume.VolumeHost, name string) string {
585	mntDir := filepath.Join(host.GetPluginDir(name), MountsInGlobalPDPath)
586	return mntDir
587}
588
589// IsLocalEphemeralVolume determines whether the argument is a local ephemeral
590// volume vs. some other type
591// Local means the volume is using storage from the local disk that is managed by kubelet.
592// Ephemeral means the lifecycle of the volume is the same as the Pod.
593func IsLocalEphemeralVolume(volume v1.Volume) bool {
594	return volume.GitRepo != nil ||
595		(volume.EmptyDir != nil && volume.EmptyDir.Medium == v1.StorageMediumDefault) ||
596		volume.ConfigMap != nil
597}
598
599// GetPodVolumeNames returns names of volumes that are used in a pod,
600// either as filesystem mount or raw block device.
601func GetPodVolumeNames(pod *v1.Pod) (mounts sets.String, devices sets.String) {
602	mounts = sets.NewString()
603	devices = sets.NewString()
604
605	podutil.VisitContainers(&pod.Spec, podutil.AllFeatureEnabledContainers(), func(container *v1.Container, containerType podutil.ContainerType) bool {
606		if container.VolumeMounts != nil {
607			for _, mount := range container.VolumeMounts {
608				mounts.Insert(mount.Name)
609			}
610		}
611		if container.VolumeDevices != nil {
612			for _, device := range container.VolumeDevices {
613				devices.Insert(device.Name)
614			}
615		}
616		return true
617	})
618	return
619}
620
621// FsUserFrom returns FsUser of pod, which is determined by the runAsUser
622// attributes.
623func FsUserFrom(pod *v1.Pod) *int64 {
624	var fsUser *int64
625	// Exclude ephemeral containers because SecurityContext is not allowed.
626	podutil.VisitContainers(&pod.Spec, podutil.InitContainers|podutil.Containers, func(container *v1.Container, containerType podutil.ContainerType) bool {
627		runAsUser, ok := securitycontext.DetermineEffectiveRunAsUser(pod, container)
628		// One container doesn't specify user or there are more than one
629		// non-root UIDs.
630		if !ok || (fsUser != nil && *fsUser != *runAsUser) {
631			fsUser = nil
632			return false
633		}
634		if fsUser == nil {
635			fsUser = runAsUser
636		}
637		return true
638	})
639	return fsUser
640}
641
642// HasMountRefs checks if the given mountPath has mountRefs.
643// TODO: this is a workaround for the unmount device issue caused by gci mounter.
644// In GCI cluster, if gci mounter is used for mounting, the container started by mounter
645// script will cause additional mounts created in the container. Since these mounts are
646// irrelevant to the original mounts, they should be not considered when checking the
647// mount references. Current solution is to filter out those mount paths that contain
648// the string of original mount path.
649// Plan to work on better approach to solve this issue.
650func HasMountRefs(mountPath string, mountRefs []string) bool {
651	for _, ref := range mountRefs {
652		if !strings.Contains(ref, mountPath) {
653			return true
654		}
655	}
656	return false
657}
658
659//WriteVolumeCache flush disk data given the spcified mount path
660func WriteVolumeCache(deviceMountPath string, exec utilexec.Interface) error {
661	// If runtime os is windows, execute Write-VolumeCache powershell command on the disk
662	if runtime.GOOS == "windows" {
663		cmd := fmt.Sprintf("Get-Volume -FilePath %s | Write-Volumecache", deviceMountPath)
664		output, err := exec.Command("powershell", "/c", cmd).CombinedOutput()
665		klog.Infof("command (%q) execeuted: %v, output: %q", cmd, err, string(output))
666		if err != nil {
667			return fmt.Errorf("command (%q) failed: %v, output: %q", cmd, err, string(output))
668		}
669	}
670	// For linux runtime, it skips because unmount will automatically flush disk data
671	return nil
672}
673
674// IsMultiAttachAllowed checks if attaching this volume to multiple nodes is definitely not allowed/possible.
675// In its current form, this function can only reliably say for which volumes it's definitely forbidden. If it returns
676// false, it is not guaranteed that multi-attach is actually supported by the volume type and we must rely on the
677// attacher to fail fast in such cases.
678// Please see https://github.com/kubernetes/kubernetes/issues/40669 and https://github.com/kubernetes/kubernetes/pull/40148#discussion_r98055047
679func IsMultiAttachAllowed(volumeSpec *volume.Spec) bool {
680	if volumeSpec == nil {
681		// we don't know if it's supported or not and let the attacher fail later in cases it's not supported
682		return true
683	}
684
685	if volumeSpec.Volume != nil {
686		// Check for volume types which are known to fail slow or cause trouble when trying to multi-attach
687		if volumeSpec.Volume.AzureDisk != nil ||
688			volumeSpec.Volume.Cinder != nil {
689			return false
690		}
691	}
692
693	// Only if this volume is a persistent volume, we have reliable information on whether it's allowed or not to
694	// multi-attach. We trust in the individual volume implementations to not allow unsupported access modes
695	if volumeSpec.PersistentVolume != nil {
696		// Check for persistent volume types which do not fail when trying to multi-attach
697		if len(volumeSpec.PersistentVolume.Spec.AccessModes) == 0 {
698			// No access mode specified so we don't know for sure. Let the attacher fail if needed
699			return true
700		}
701
702		// check if this volume is allowed to be attached to multiple PODs/nodes, if yes, return false
703		for _, accessMode := range volumeSpec.PersistentVolume.Spec.AccessModes {
704			if accessMode == v1.ReadWriteMany || accessMode == v1.ReadOnlyMany {
705				return true
706			}
707		}
708		return false
709	}
710
711	// we don't know if it's supported or not and let the attacher fail later in cases it's not supported
712	return true
713}
714
715// IsAttachableVolume checks if the given volumeSpec is an attachable volume or not
716func IsAttachableVolume(volumeSpec *volume.Spec, volumePluginMgr *volume.VolumePluginMgr) bool {
717	attachableVolumePlugin, _ := volumePluginMgr.FindAttachablePluginBySpec(volumeSpec)
718	if attachableVolumePlugin != nil {
719		volumeAttacher, err := attachableVolumePlugin.NewAttacher()
720		if err == nil && volumeAttacher != nil {
721			return true
722		}
723	}
724
725	return false
726}
727
728// IsDeviceMountableVolume checks if the given volumeSpec is an device mountable volume or not
729func IsDeviceMountableVolume(volumeSpec *volume.Spec, volumePluginMgr *volume.VolumePluginMgr) bool {
730	deviceMountableVolumePlugin, _ := volumePluginMgr.FindDeviceMountablePluginBySpec(volumeSpec)
731	if deviceMountableVolumePlugin != nil {
732		volumeDeviceMounter, err := deviceMountableVolumePlugin.NewDeviceMounter()
733		if err == nil && volumeDeviceMounter != nil {
734			return true
735		}
736	}
737
738	return false
739}
740
741// GetReliableMountRefs calls mounter.GetMountRefs and retries on IsInconsistentReadError.
742// To be used in volume reconstruction of volume plugins that don't have any protection
743// against mounting a single volume on multiple nodes (such as attach/detach).
744func GetReliableMountRefs(mounter mount.Interface, mountPath string) ([]string, error) {
745	var paths []string
746	var lastErr error
747	err := wait.PollImmediate(10*time.Millisecond, time.Minute, func() (bool, error) {
748		var err error
749		paths, err = mounter.GetMountRefs(mountPath)
750		if io.IsInconsistentReadError(err) {
751			lastErr = err
752			return false, nil
753		}
754		if err != nil {
755			return false, err
756		}
757		return true, nil
758	})
759	if err == wait.ErrWaitTimeout {
760		return nil, lastErr
761	}
762	return paths, err
763}
764