util.go 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648
  1. /*
  2. Copyright 2015 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package util
  14. import (
  15. "context"
  16. "fmt"
  17. "io/ioutil"
  18. "os"
  19. "path/filepath"
  20. "reflect"
  21. "runtime"
  22. "strings"
  23. "k8s.io/klog"
  24. utilexec "k8s.io/utils/exec"
  25. "k8s.io/utils/mount"
  26. utilstrings "k8s.io/utils/strings"
  27. v1 "k8s.io/api/core/v1"
  28. storage "k8s.io/api/storage/v1"
  29. "k8s.io/apimachinery/pkg/api/resource"
  30. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  31. "k8s.io/apimachinery/pkg/labels"
  32. apiruntime "k8s.io/apimachinery/pkg/runtime"
  33. utypes "k8s.io/apimachinery/pkg/types"
  34. "k8s.io/apimachinery/pkg/util/sets"
  35. utilfeature "k8s.io/apiserver/pkg/util/feature"
  36. clientset "k8s.io/client-go/kubernetes"
  37. "k8s.io/kubernetes/pkg/api/legacyscheme"
  38. podutil "k8s.io/kubernetes/pkg/api/v1/pod"
  39. v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper"
  40. "k8s.io/kubernetes/pkg/features"
  41. "k8s.io/kubernetes/pkg/volume"
  42. "k8s.io/kubernetes/pkg/volume/util/types"
  43. "k8s.io/kubernetes/pkg/volume/util/volumepathhandler"
  44. )
  45. const (
  46. readyFileName = "ready"
  47. // ControllerManagedAttachAnnotation is the key of the annotation on Node
  48. // objects that indicates attach/detach operations for the node should be
  49. // managed by the attach/detach controller
  50. ControllerManagedAttachAnnotation string = "volumes.kubernetes.io/controller-managed-attach-detach"
  51. // KeepTerminatedPodVolumesAnnotation is the key of the annotation on Node
  52. // that decides if pod volumes are unmounted when pod is terminated
  53. KeepTerminatedPodVolumesAnnotation string = "volumes.kubernetes.io/keep-terminated-pod-volumes"
  54. // MountsInGlobalPDPath is name of the directory appended to a volume plugin
  55. // name to create the place for volume mounts in the global PD path.
  56. MountsInGlobalPDPath = "mounts"
  57. // VolumeGidAnnotationKey is the of the annotation on the PersistentVolume
  58. // object that specifies a supplemental GID.
  59. VolumeGidAnnotationKey = "pv.beta.kubernetes.io/gid"
  60. // VolumeDynamicallyCreatedByKey is the key of the annotation on PersistentVolume
  61. // object created dynamically
  62. VolumeDynamicallyCreatedByKey = "kubernetes.io/createdby"
  63. )
  64. // IsReady checks for the existence of a regular file
  65. // called 'ready' in the given directory and returns
  66. // true if that file exists.
  67. func IsReady(dir string) bool {
  68. readyFile := filepath.Join(dir, readyFileName)
  69. s, err := os.Stat(readyFile)
  70. if err != nil {
  71. return false
  72. }
  73. if !s.Mode().IsRegular() {
  74. klog.Errorf("ready-file is not a file: %s", readyFile)
  75. return false
  76. }
  77. return true
  78. }
  79. // SetReady creates a file called 'ready' in the given
  80. // directory. It logs an error if the file cannot be
  81. // created.
  82. func SetReady(dir string) {
  83. if err := os.MkdirAll(dir, 0750); err != nil && !os.IsExist(err) {
  84. klog.Errorf("Can't mkdir %s: %v", dir, err)
  85. return
  86. }
  87. readyFile := filepath.Join(dir, readyFileName)
  88. file, err := os.Create(readyFile)
  89. if err != nil {
  90. klog.Errorf("Can't touch %s: %v", readyFile, err)
  91. return
  92. }
  93. file.Close()
  94. }
  95. // GetSecretForPod locates secret by name in the pod's namespace and returns secret map
  96. func GetSecretForPod(pod *v1.Pod, secretName string, kubeClient clientset.Interface) (map[string]string, error) {
  97. secret := make(map[string]string)
  98. if kubeClient == nil {
  99. return secret, fmt.Errorf("Cannot get kube client")
  100. }
  101. secrets, err := kubeClient.CoreV1().Secrets(pod.Namespace).Get(context.TODO(), secretName, metav1.GetOptions{})
  102. if err != nil {
  103. return secret, err
  104. }
  105. for name, data := range secrets.Data {
  106. secret[name] = string(data)
  107. }
  108. return secret, nil
  109. }
  110. // GetSecretForPV locates secret by name and namespace, verifies the secret type, and returns secret map
  111. func GetSecretForPV(secretNamespace, secretName, volumePluginName string, kubeClient clientset.Interface) (map[string]string, error) {
  112. secret := make(map[string]string)
  113. if kubeClient == nil {
  114. return secret, fmt.Errorf("Cannot get kube client")
  115. }
  116. secrets, err := kubeClient.CoreV1().Secrets(secretNamespace).Get(context.TODO(), secretName, metav1.GetOptions{})
  117. if err != nil {
  118. return secret, err
  119. }
  120. if secrets.Type != v1.SecretType(volumePluginName) {
  121. return secret, fmt.Errorf("Cannot get secret of type %s", volumePluginName)
  122. }
  123. for name, data := range secrets.Data {
  124. secret[name] = string(data)
  125. }
  126. return secret, nil
  127. }
  128. // GetClassForVolume locates storage class by persistent volume
  129. func GetClassForVolume(kubeClient clientset.Interface, pv *v1.PersistentVolume) (*storage.StorageClass, error) {
  130. if kubeClient == nil {
  131. return nil, fmt.Errorf("Cannot get kube client")
  132. }
  133. className := v1helper.GetPersistentVolumeClass(pv)
  134. if className == "" {
  135. return nil, fmt.Errorf("Volume has no storage class")
  136. }
  137. class, err := kubeClient.StorageV1().StorageClasses().Get(context.TODO(), className, metav1.GetOptions{})
  138. if err != nil {
  139. return nil, err
  140. }
  141. return class, nil
  142. }
  143. // CheckNodeAffinity looks at the PV node affinity, and checks if the node has the same corresponding labels
  144. // This ensures that we don't mount a volume that doesn't belong to this node
  145. func CheckNodeAffinity(pv *v1.PersistentVolume, nodeLabels map[string]string) error {
  146. return checkVolumeNodeAffinity(pv, nodeLabels)
  147. }
  148. func checkVolumeNodeAffinity(pv *v1.PersistentVolume, nodeLabels map[string]string) error {
  149. if pv.Spec.NodeAffinity == nil {
  150. return nil
  151. }
  152. if pv.Spec.NodeAffinity.Required != nil {
  153. terms := pv.Spec.NodeAffinity.Required.NodeSelectorTerms
  154. klog.V(10).Infof("Match for Required node selector terms %+v", terms)
  155. if !v1helper.MatchNodeSelectorTerms(terms, labels.Set(nodeLabels), nil) {
  156. return fmt.Errorf("No matching NodeSelectorTerms")
  157. }
  158. }
  159. return nil
  160. }
  161. // LoadPodFromFile will read, decode, and return a Pod from a file.
  162. func LoadPodFromFile(filePath string) (*v1.Pod, error) {
  163. if filePath == "" {
  164. return nil, fmt.Errorf("file path not specified")
  165. }
  166. podDef, err := ioutil.ReadFile(filePath)
  167. if err != nil {
  168. return nil, fmt.Errorf("failed to read file path %s: %+v", filePath, err)
  169. }
  170. if len(podDef) == 0 {
  171. return nil, fmt.Errorf("file was empty: %s", filePath)
  172. }
  173. pod := &v1.Pod{}
  174. codec := legacyscheme.Codecs.UniversalDecoder()
  175. if err := apiruntime.DecodeInto(codec, podDef, pod); err != nil {
  176. return nil, fmt.Errorf("failed decoding file: %v", err)
  177. }
  178. return pod, nil
  179. }
  180. // CalculateTimeoutForVolume calculates time for a Recycler pod to complete a
  181. // recycle operation. The calculation and return value is either the
  182. // minimumTimeout or the timeoutIncrement per Gi of storage size, whichever is
  183. // greater.
  184. func CalculateTimeoutForVolume(minimumTimeout, timeoutIncrement int, pv *v1.PersistentVolume) int64 {
  185. giQty := resource.MustParse("1Gi")
  186. pvQty := pv.Spec.Capacity[v1.ResourceStorage]
  187. giSize := giQty.Value()
  188. pvSize := pvQty.Value()
  189. timeout := (pvSize / giSize) * int64(timeoutIncrement)
  190. if timeout < int64(minimumTimeout) {
  191. return int64(minimumTimeout)
  192. }
  193. return timeout
  194. }
  195. // GenerateVolumeName returns a PV name with clusterName prefix. The function
  196. // should be used to generate a name of GCE PD or Cinder volume. It basically
  197. // adds "<clusterName>-dynamic-" before the PV name, making sure the resulting
  198. // string fits given length and cuts "dynamic" if not.
  199. func GenerateVolumeName(clusterName, pvName string, maxLength int) string {
  200. prefix := clusterName + "-dynamic"
  201. pvLen := len(pvName)
  202. // cut the "<clusterName>-dynamic" to fit full pvName into maxLength
  203. // +1 for the '-' dash
  204. if pvLen+1+len(prefix) > maxLength {
  205. prefix = prefix[:maxLength-pvLen-1]
  206. }
  207. return prefix + "-" + pvName
  208. }
  209. // GetPath checks if the path from the mounter is empty.
  210. func GetPath(mounter volume.Mounter) (string, error) {
  211. path := mounter.GetPath()
  212. if path == "" {
  213. return "", fmt.Errorf("Path is empty %s", reflect.TypeOf(mounter).String())
  214. }
  215. return path, nil
  216. }
  217. // UnmountViaEmptyDir delegates the tear down operation for secret, configmap, git_repo and downwardapi
  218. // to empty_dir
  219. func UnmountViaEmptyDir(dir string, host volume.VolumeHost, volName string, volSpec volume.Spec, podUID utypes.UID) error {
  220. klog.V(3).Infof("Tearing down volume %v for pod %v at %v", volName, podUID, dir)
  221. // Wrap EmptyDir, let it do the teardown.
  222. wrapped, err := host.NewWrapperUnmounter(volName, volSpec, podUID)
  223. if err != nil {
  224. return err
  225. }
  226. return wrapped.TearDownAt(dir)
  227. }
  228. // MountOptionFromSpec extracts and joins mount options from volume spec with supplied options
  229. func MountOptionFromSpec(spec *volume.Spec, options ...string) []string {
  230. pv := spec.PersistentVolume
  231. if pv != nil {
  232. // Use beta annotation first
  233. if mo, ok := pv.Annotations[v1.MountOptionAnnotation]; ok {
  234. moList := strings.Split(mo, ",")
  235. return JoinMountOptions(moList, options)
  236. }
  237. if len(pv.Spec.MountOptions) > 0 {
  238. return JoinMountOptions(pv.Spec.MountOptions, options)
  239. }
  240. }
  241. return options
  242. }
  243. // JoinMountOptions joins mount options eliminating duplicates
  244. func JoinMountOptions(userOptions []string, systemOptions []string) []string {
  245. allMountOptions := sets.NewString()
  246. for _, mountOption := range userOptions {
  247. if len(mountOption) > 0 {
  248. allMountOptions.Insert(mountOption)
  249. }
  250. }
  251. for _, mountOption := range systemOptions {
  252. allMountOptions.Insert(mountOption)
  253. }
  254. return allMountOptions.List()
  255. }
  256. // AccessModesContains returns whether the requested mode is contained by modes
  257. func AccessModesContains(modes []v1.PersistentVolumeAccessMode, mode v1.PersistentVolumeAccessMode) bool {
  258. for _, m := range modes {
  259. if m == mode {
  260. return true
  261. }
  262. }
  263. return false
  264. }
  265. // AccessModesContainedInAll returns whether all of the requested modes are contained by modes
  266. func AccessModesContainedInAll(indexedModes []v1.PersistentVolumeAccessMode, requestedModes []v1.PersistentVolumeAccessMode) bool {
  267. for _, mode := range requestedModes {
  268. if !AccessModesContains(indexedModes, mode) {
  269. return false
  270. }
  271. }
  272. return true
  273. }
  274. // GetWindowsPath get a windows path
  275. func GetWindowsPath(path string) string {
  276. windowsPath := strings.Replace(path, "/", "\\", -1)
  277. if strings.HasPrefix(windowsPath, "\\") {
  278. windowsPath = "c:" + windowsPath
  279. }
  280. return windowsPath
  281. }
  282. // GetUniquePodName returns a unique identifier to reference a pod by
  283. func GetUniquePodName(pod *v1.Pod) types.UniquePodName {
  284. return types.UniquePodName(pod.UID)
  285. }
  286. // GetUniqueVolumeName returns a unique name representing the volume/plugin.
  287. // Caller should ensure that volumeName is a name/ID uniquely identifying the
  288. // actual backing device, directory, path, etc. for a particular volume.
  289. // The returned name can be used to uniquely reference the volume, for example,
  290. // to prevent operations (attach/detach or mount/unmount) from being triggered
  291. // on the same volume.
  292. func GetUniqueVolumeName(pluginName, volumeName string) v1.UniqueVolumeName {
  293. return v1.UniqueVolumeName(fmt.Sprintf("%s/%s", pluginName, volumeName))
  294. }
  295. // GetUniqueVolumeNameFromSpecWithPod returns a unique volume name with pod
  296. // name included. This is useful to generate different names for different pods
  297. // on same volume.
  298. func GetUniqueVolumeNameFromSpecWithPod(
  299. podName types.UniquePodName, volumePlugin volume.VolumePlugin, volumeSpec *volume.Spec) v1.UniqueVolumeName {
  300. return v1.UniqueVolumeName(
  301. fmt.Sprintf("%s/%v-%s", volumePlugin.GetPluginName(), podName, volumeSpec.Name()))
  302. }
  303. // GetUniqueVolumeNameFromSpec uses the given VolumePlugin to generate a unique
  304. // name representing the volume defined in the specified volume spec.
  305. // This returned name can be used to uniquely reference the actual backing
  306. // device, directory, path, etc. referenced by the given volumeSpec.
  307. // If the given plugin does not support the volume spec, this returns an error.
  308. func GetUniqueVolumeNameFromSpec(
  309. volumePlugin volume.VolumePlugin,
  310. volumeSpec *volume.Spec) (v1.UniqueVolumeName, error) {
  311. if volumePlugin == nil {
  312. return "", fmt.Errorf(
  313. "volumePlugin should not be nil. volumeSpec.Name=%q",
  314. volumeSpec.Name())
  315. }
  316. volumeName, err := volumePlugin.GetVolumeName(volumeSpec)
  317. if err != nil || volumeName == "" {
  318. return "", fmt.Errorf(
  319. "failed to GetVolumeName from volumePlugin for volumeSpec %q err=%v",
  320. volumeSpec.Name(),
  321. err)
  322. }
  323. return GetUniqueVolumeName(
  324. volumePlugin.GetPluginName(),
  325. volumeName),
  326. nil
  327. }
  328. // IsPodTerminated checks if pod is terminated
  329. func IsPodTerminated(pod *v1.Pod, podStatus v1.PodStatus) bool {
  330. return podStatus.Phase == v1.PodFailed || podStatus.Phase == v1.PodSucceeded || (pod.DeletionTimestamp != nil && notRunning(podStatus.ContainerStatuses))
  331. }
  332. // notRunning returns true if every status is terminated or waiting, or the status list
  333. // is empty.
  334. func notRunning(statuses []v1.ContainerStatus) bool {
  335. for _, status := range statuses {
  336. if status.State.Terminated == nil && status.State.Waiting == nil {
  337. return false
  338. }
  339. }
  340. return true
  341. }
  342. // SplitUniqueName splits the unique name to plugin name and volume name strings. It expects the uniqueName to follow
  343. // the format plugin_name/volume_name and the plugin name must be namespaced as described by the plugin interface,
  344. // i.e. namespace/plugin containing exactly one '/'. This means the unique name will always be in the form of
  345. // plugin_namespace/plugin/volume_name, see k8s.io/kubernetes/pkg/volume/plugins.go VolumePlugin interface
  346. // description and pkg/volume/util/volumehelper/volumehelper.go GetUniqueVolumeNameFromSpec that constructs
  347. // the unique volume names.
  348. func SplitUniqueName(uniqueName v1.UniqueVolumeName) (string, string, error) {
  349. components := strings.SplitN(string(uniqueName), "/", 3)
  350. if len(components) != 3 {
  351. return "", "", fmt.Errorf("cannot split volume unique name %s to plugin/volume components", uniqueName)
  352. }
  353. pluginName := fmt.Sprintf("%s/%s", components[0], components[1])
  354. return pluginName, components[2], nil
  355. }
  356. // NewSafeFormatAndMountFromHost creates a new SafeFormatAndMount with Mounter
  357. // and Exec taken from given VolumeHost.
  358. func NewSafeFormatAndMountFromHost(pluginName string, host volume.VolumeHost) *mount.SafeFormatAndMount {
  359. mounter := host.GetMounter(pluginName)
  360. exec := host.GetExec(pluginName)
  361. return &mount.SafeFormatAndMount{Interface: mounter, Exec: exec}
  362. }
  363. // GetVolumeMode retrieves VolumeMode from pv.
  364. // If the volume doesn't have PersistentVolume, it's an inline volume,
  365. // should return volumeMode as filesystem to keep existing behavior.
  366. func GetVolumeMode(volumeSpec *volume.Spec) (v1.PersistentVolumeMode, error) {
  367. if volumeSpec == nil || volumeSpec.PersistentVolume == nil {
  368. return v1.PersistentVolumeFilesystem, nil
  369. }
  370. if volumeSpec.PersistentVolume.Spec.VolumeMode != nil {
  371. return *volumeSpec.PersistentVolume.Spec.VolumeMode, nil
  372. }
  373. return "", fmt.Errorf("cannot get volumeMode for volume: %v", volumeSpec.Name())
  374. }
  375. // GetPersistentVolumeClaimQualifiedName returns a qualified name for pvc.
  376. func GetPersistentVolumeClaimQualifiedName(claim *v1.PersistentVolumeClaim) string {
  377. return utilstrings.JoinQualifiedName(claim.GetNamespace(), claim.GetName())
  378. }
  379. // CheckVolumeModeFilesystem checks VolumeMode.
  380. // If the mode is Filesystem, return true otherwise return false.
  381. func CheckVolumeModeFilesystem(volumeSpec *volume.Spec) (bool, error) {
  382. if utilfeature.DefaultFeatureGate.Enabled(features.BlockVolume) {
  383. volumeMode, err := GetVolumeMode(volumeSpec)
  384. if err != nil {
  385. return true, err
  386. }
  387. if volumeMode == v1.PersistentVolumeBlock {
  388. return false, nil
  389. }
  390. }
  391. return true, nil
  392. }
  393. // CheckPersistentVolumeClaimModeBlock checks VolumeMode.
  394. // If the mode is Block, return true otherwise return false.
  395. func CheckPersistentVolumeClaimModeBlock(pvc *v1.PersistentVolumeClaim) bool {
  396. return utilfeature.DefaultFeatureGate.Enabled(features.BlockVolume) && pvc.Spec.VolumeMode != nil && *pvc.Spec.VolumeMode == v1.PersistentVolumeBlock
  397. }
  398. // IsWindowsUNCPath checks if path is prefixed with \\
  399. // This can be used to skip any processing of paths
  400. // that point to SMB shares, local named pipes and local UNC path
  401. func IsWindowsUNCPath(goos, path string) bool {
  402. if goos != "windows" {
  403. return false
  404. }
  405. // Check for UNC prefix \\
  406. if strings.HasPrefix(path, `\\`) {
  407. return true
  408. }
  409. return false
  410. }
  411. // IsWindowsLocalPath checks if path is a local path
  412. // prefixed with "/" or "\" like "/foo/bar" or "\foo\bar"
  413. func IsWindowsLocalPath(goos, path string) bool {
  414. if goos != "windows" {
  415. return false
  416. }
  417. if IsWindowsUNCPath(goos, path) {
  418. return false
  419. }
  420. if strings.Contains(path, ":") {
  421. return false
  422. }
  423. if !(strings.HasPrefix(path, `/`) || strings.HasPrefix(path, `\`)) {
  424. return false
  425. }
  426. return true
  427. }
  428. // MakeAbsolutePath convert path to absolute path according to GOOS
  429. func MakeAbsolutePath(goos, path string) string {
  430. if goos != "windows" {
  431. return filepath.Clean("/" + path)
  432. }
  433. // These are all for windows
  434. // If there is a colon, give up.
  435. if strings.Contains(path, ":") {
  436. return path
  437. }
  438. // If there is a slash, but no drive, add 'c:'
  439. if strings.HasPrefix(path, "/") || strings.HasPrefix(path, "\\") {
  440. return "c:" + path
  441. }
  442. // Otherwise, add 'c:\'
  443. return "c:\\" + path
  444. }
  445. // MapBlockVolume is a utility function to provide a common way of mapping
  446. // block device path for a specified volume and pod. This function should be
  447. // called by volume plugins that implements volume.BlockVolumeMapper.Map() method.
  448. func MapBlockVolume(
  449. blkUtil volumepathhandler.BlockVolumePathHandler,
  450. devicePath,
  451. globalMapPath,
  452. podVolumeMapPath,
  453. volumeMapName string,
  454. podUID utypes.UID,
  455. ) error {
  456. // map devicePath to global node path as bind mount
  457. mapErr := blkUtil.MapDevice(devicePath, globalMapPath, string(podUID), true /* bindMount */)
  458. if mapErr != nil {
  459. return fmt.Errorf("blkUtil.MapDevice failed. devicePath: %s, globalMapPath:%s, podUID: %s, bindMount: %v: %v",
  460. devicePath, globalMapPath, string(podUID), true, mapErr)
  461. }
  462. // map devicePath to pod volume path
  463. mapErr = blkUtil.MapDevice(devicePath, podVolumeMapPath, volumeMapName, false /* bindMount */)
  464. if mapErr != nil {
  465. return fmt.Errorf("blkUtil.MapDevice failed. devicePath: %s, podVolumeMapPath:%s, volumeMapName: %s, bindMount: %v: %v",
  466. devicePath, podVolumeMapPath, volumeMapName, false, mapErr)
  467. }
  468. // Take file descriptor lock to keep a block device opened. Otherwise, there is a case
  469. // that the block device is silently removed and attached another device with the same name.
  470. // Container runtime can't handle this problem. To avoid unexpected condition fd lock
  471. // for the block device is required.
  472. _, mapErr = blkUtil.AttachFileDevice(filepath.Join(globalMapPath, string(podUID)))
  473. if mapErr != nil {
  474. return fmt.Errorf("blkUtil.AttachFileDevice failed. globalMapPath:%s, podUID: %s: %v",
  475. globalMapPath, string(podUID), mapErr)
  476. }
  477. return nil
  478. }
  479. // UnmapBlockVolume is a utility function to provide a common way of unmapping
  480. // block device path for a specified volume and pod. This function should be
  481. // called by volume plugins that implements volume.BlockVolumeMapper.Map() method.
  482. func UnmapBlockVolume(
  483. blkUtil volumepathhandler.BlockVolumePathHandler,
  484. globalUnmapPath,
  485. podDeviceUnmapPath,
  486. volumeMapName string,
  487. podUID utypes.UID,
  488. ) error {
  489. // Release file descriptor lock.
  490. err := blkUtil.DetachFileDevice(filepath.Join(globalUnmapPath, string(podUID)))
  491. if err != nil {
  492. return fmt.Errorf("blkUtil.DetachFileDevice failed. globalUnmapPath:%s, podUID: %s: %v",
  493. globalUnmapPath, string(podUID), err)
  494. }
  495. // unmap devicePath from pod volume path
  496. unmapDeviceErr := blkUtil.UnmapDevice(podDeviceUnmapPath, volumeMapName, false /* bindMount */)
  497. if unmapDeviceErr != nil {
  498. return fmt.Errorf("blkUtil.DetachFileDevice failed. podDeviceUnmapPath:%s, volumeMapName: %s, bindMount: %v: %v",
  499. podDeviceUnmapPath, volumeMapName, false, unmapDeviceErr)
  500. }
  501. // unmap devicePath from global node path
  502. unmapDeviceErr = blkUtil.UnmapDevice(globalUnmapPath, string(podUID), true /* bindMount */)
  503. if unmapDeviceErr != nil {
  504. return fmt.Errorf("blkUtil.DetachFileDevice failed. globalUnmapPath:%s, podUID: %s, bindMount: %v: %v",
  505. globalUnmapPath, string(podUID), true, unmapDeviceErr)
  506. }
  507. return nil
  508. }
  509. // GetPluginMountDir returns the global mount directory name appended
  510. // to the given plugin name's plugin directory
  511. func GetPluginMountDir(host volume.VolumeHost, name string) string {
  512. mntDir := filepath.Join(host.GetPluginDir(name), MountsInGlobalPDPath)
  513. return mntDir
  514. }
  515. // IsLocalEphemeralVolume determines whether the argument is a local ephemeral
  516. // volume vs. some other type
  517. func IsLocalEphemeralVolume(volume v1.Volume) bool {
  518. return volume.GitRepo != nil ||
  519. (volume.EmptyDir != nil && volume.EmptyDir.Medium != v1.StorageMediumMemory) ||
  520. volume.ConfigMap != nil || volume.DownwardAPI != nil
  521. }
  522. // GetPodVolumeNames returns names of volumes that are used in a pod,
  523. // either as filesystem mount or raw block device.
  524. func GetPodVolumeNames(pod *v1.Pod) (mounts sets.String, devices sets.String) {
  525. mounts = sets.NewString()
  526. devices = sets.NewString()
  527. podutil.VisitContainers(&pod.Spec, func(container *v1.Container) bool {
  528. if container.VolumeMounts != nil {
  529. for _, mount := range container.VolumeMounts {
  530. mounts.Insert(mount.Name)
  531. }
  532. }
  533. // TODO: remove feature gate check after no longer needed
  534. if utilfeature.DefaultFeatureGate.Enabled(features.BlockVolume) &&
  535. container.VolumeDevices != nil {
  536. for _, device := range container.VolumeDevices {
  537. devices.Insert(device.Name)
  538. }
  539. }
  540. return true
  541. })
  542. return
  543. }
  544. // HasMountRefs checks if the given mountPath has mountRefs.
  545. // TODO: this is a workaround for the unmount device issue caused by gci mounter.
  546. // In GCI cluster, if gci mounter is used for mounting, the container started by mounter
  547. // script will cause additional mounts created in the container. Since these mounts are
  548. // irrelevant to the original mounts, they should be not considered when checking the
  549. // mount references. Current solution is to filter out those mount paths that contain
  550. // the string of original mount path.
  551. // Plan to work on better approach to solve this issue.
  552. func HasMountRefs(mountPath string, mountRefs []string) bool {
  553. for _, ref := range mountRefs {
  554. if !strings.Contains(ref, mountPath) {
  555. return true
  556. }
  557. }
  558. return false
  559. }
  560. //WriteVolumeCache flush disk data given the spcified mount path
  561. func WriteVolumeCache(deviceMountPath string, exec utilexec.Interface) error {
  562. // If runtime os is windows, execute Write-VolumeCache powershell command on the disk
  563. if runtime.GOOS == "windows" {
  564. cmd := fmt.Sprintf("Get-Volume -FilePath %s | Write-Volumecache", deviceMountPath)
  565. output, err := exec.Command("powershell", "/c", cmd).CombinedOutput()
  566. klog.Infof("command (%q) execeuted: %v, output: %q", cmd, err, string(output))
  567. if err != nil {
  568. return fmt.Errorf("command (%q) failed: %v, output: %q", cmd, err, string(output))
  569. }
  570. }
  571. // For linux runtime, it skips because unmount will automatically flush disk data
  572. return nil
  573. }