attacher.go 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. // +build !providerless
  2. /*
  3. Copyright 2016 The Kubernetes Authors.
  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. http://www.apache.org/licenses/LICENSE-2.0
  8. Unless required by applicable law or agreed to in writing, software
  9. distributed under the License is distributed on an "AS IS" BASIS,
  10. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  11. See the License for the specific language governing permissions and
  12. limitations under the License.
  13. */
  14. package awsebs
  15. import (
  16. "fmt"
  17. "os"
  18. "path"
  19. "path/filepath"
  20. "runtime"
  21. "strconv"
  22. "time"
  23. "k8s.io/klog"
  24. "k8s.io/utils/mount"
  25. v1 "k8s.io/api/core/v1"
  26. "k8s.io/apimachinery/pkg/types"
  27. "k8s.io/kubernetes/pkg/volume"
  28. volumeutil "k8s.io/kubernetes/pkg/volume/util"
  29. "k8s.io/legacy-cloud-providers/aws"
  30. )
  31. type awsElasticBlockStoreAttacher struct {
  32. host volume.VolumeHost
  33. awsVolumes aws.Volumes
  34. }
  35. var _ volume.Attacher = &awsElasticBlockStoreAttacher{}
  36. var _ volume.DeviceMounter = &awsElasticBlockStoreAttacher{}
  37. var _ volume.AttachableVolumePlugin = &awsElasticBlockStorePlugin{}
  38. var _ volume.DeviceMountableVolumePlugin = &awsElasticBlockStorePlugin{}
  39. func (plugin *awsElasticBlockStorePlugin) NewAttacher() (volume.Attacher, error) {
  40. awsCloud, err := getCloudProvider(plugin.host.GetCloudProvider())
  41. if err != nil {
  42. return nil, err
  43. }
  44. return &awsElasticBlockStoreAttacher{
  45. host: plugin.host,
  46. awsVolumes: awsCloud,
  47. }, nil
  48. }
  49. func (plugin *awsElasticBlockStorePlugin) NewDeviceMounter() (volume.DeviceMounter, error) {
  50. return plugin.NewAttacher()
  51. }
  52. func (plugin *awsElasticBlockStorePlugin) GetDeviceMountRefs(deviceMountPath string) ([]string, error) {
  53. mounter := plugin.host.GetMounter(plugin.GetPluginName())
  54. return mounter.GetMountRefs(deviceMountPath)
  55. }
  56. func (attacher *awsElasticBlockStoreAttacher) Attach(spec *volume.Spec, nodeName types.NodeName) (string, error) {
  57. volumeSource, _, err := getVolumeSource(spec)
  58. if err != nil {
  59. return "", err
  60. }
  61. volumeID := aws.KubernetesVolumeID(volumeSource.VolumeID)
  62. // awsCloud.AttachDisk checks if disk is already attached to node and
  63. // succeeds in that case, so no need to do that separately.
  64. devicePath, err := attacher.awsVolumes.AttachDisk(volumeID, nodeName)
  65. if err != nil {
  66. klog.Errorf("Error attaching volume %q to node %q: %+v", volumeID, nodeName, err)
  67. return "", err
  68. }
  69. return devicePath, nil
  70. }
  71. func (attacher *awsElasticBlockStoreAttacher) VolumesAreAttached(specs []*volume.Spec, nodeName types.NodeName) (map[*volume.Spec]bool, error) {
  72. klog.Warningf("Attacher.VolumesAreAttached called for node %q - Please use BulkVerifyVolumes for AWS", nodeName)
  73. volumeNodeMap := map[types.NodeName][]*volume.Spec{
  74. nodeName: specs,
  75. }
  76. nodeVolumesResult := make(map[*volume.Spec]bool)
  77. nodesVerificationMap, err := attacher.BulkVerifyVolumes(volumeNodeMap)
  78. if err != nil {
  79. klog.Errorf("Attacher.VolumesAreAttached - error checking volumes for node %q with %v", nodeName, err)
  80. return nodeVolumesResult, err
  81. }
  82. if result, ok := nodesVerificationMap[nodeName]; ok {
  83. return result, nil
  84. }
  85. return nodeVolumesResult, nil
  86. }
  87. func (attacher *awsElasticBlockStoreAttacher) BulkVerifyVolumes(volumesByNode map[types.NodeName][]*volume.Spec) (map[types.NodeName]map[*volume.Spec]bool, error) {
  88. volumesAttachedCheck := make(map[types.NodeName]map[*volume.Spec]bool)
  89. diskNamesByNode := make(map[types.NodeName][]aws.KubernetesVolumeID)
  90. volumeSpecMap := make(map[aws.KubernetesVolumeID]*volume.Spec)
  91. for nodeName, volumeSpecs := range volumesByNode {
  92. for _, volumeSpec := range volumeSpecs {
  93. volumeSource, _, err := getVolumeSource(volumeSpec)
  94. if err != nil {
  95. klog.Errorf("Error getting volume (%q) source : %v", volumeSpec.Name(), err)
  96. continue
  97. }
  98. name := aws.KubernetesVolumeID(volumeSource.VolumeID)
  99. diskNamesByNode[nodeName] = append(diskNamesByNode[nodeName], name)
  100. nodeDisk, nodeDiskExists := volumesAttachedCheck[nodeName]
  101. if !nodeDiskExists {
  102. nodeDisk = make(map[*volume.Spec]bool)
  103. }
  104. nodeDisk[volumeSpec] = true
  105. volumeSpecMap[name] = volumeSpec
  106. volumesAttachedCheck[nodeName] = nodeDisk
  107. }
  108. }
  109. attachedResult, err := attacher.awsVolumes.DisksAreAttached(diskNamesByNode)
  110. if err != nil {
  111. klog.Errorf("Error checking if volumes are attached to nodes err = %v", err)
  112. return volumesAttachedCheck, err
  113. }
  114. for nodeName, nodeDisks := range attachedResult {
  115. for diskName, attached := range nodeDisks {
  116. if !attached {
  117. spec := volumeSpecMap[diskName]
  118. setNodeDisk(volumesAttachedCheck, spec, nodeName, false)
  119. }
  120. }
  121. }
  122. return volumesAttachedCheck, nil
  123. }
  124. func (attacher *awsElasticBlockStoreAttacher) WaitForAttach(spec *volume.Spec, devicePath string, _ *v1.Pod, timeout time.Duration) (string, error) {
  125. volumeSource, _, err := getVolumeSource(spec)
  126. if err != nil {
  127. return "", err
  128. }
  129. volumeID := volumeSource.VolumeID
  130. partition := ""
  131. if volumeSource.Partition != 0 {
  132. partition = strconv.Itoa(int(volumeSource.Partition))
  133. }
  134. if devicePath == "" {
  135. return "", fmt.Errorf("waitForAttach failed for AWS Volume %q: devicePath is empty", volumeID)
  136. }
  137. ticker := time.NewTicker(checkSleepDuration)
  138. defer ticker.Stop()
  139. timer := time.NewTimer(timeout)
  140. defer timer.Stop()
  141. for {
  142. select {
  143. case <-ticker.C:
  144. klog.V(5).Infof("Checking AWS Volume %q is attached at devicePath %q.", volumeID, devicePath)
  145. path, err := attacher.getDevicePath(volumeSource.VolumeID, partition, devicePath)
  146. if err != nil {
  147. // Log error, if any, and continue checking periodically. See issue #11321
  148. klog.Errorf("Error verifying AWS Volume (%q) is attached at devicePath %q: %v", volumeID, devicePath, err)
  149. } else if path != "" {
  150. // A device path has successfully been created for the PD
  151. klog.Infof("Successfully found attached AWS Volume %q at path %q.", volumeID, path)
  152. return path, nil
  153. }
  154. case <-timer.C:
  155. return "", fmt.Errorf("could not find attached AWS Volume %q. Timeout waiting for mount paths to be created", volumeID)
  156. }
  157. }
  158. }
  159. func (attacher *awsElasticBlockStoreAttacher) GetDeviceMountPath(
  160. spec *volume.Spec) (string, error) {
  161. volumeSource, _, err := getVolumeSource(spec)
  162. if err != nil {
  163. return "", err
  164. }
  165. return makeGlobalPDPath(attacher.host, aws.KubernetesVolumeID(volumeSource.VolumeID)), nil
  166. }
  167. // FIXME: this method can be further pruned.
  168. func (attacher *awsElasticBlockStoreAttacher) MountDevice(spec *volume.Spec, devicePath string, deviceMountPath string) error {
  169. mounter := attacher.host.GetMounter(awsElasticBlockStorePluginName)
  170. notMnt, err := mounter.IsLikelyNotMountPoint(deviceMountPath)
  171. if err != nil {
  172. if os.IsNotExist(err) {
  173. dir := deviceMountPath
  174. if runtime.GOOS == "windows" {
  175. // On Windows, FormatAndMount will mklink (create a symbolic link) at deviceMountPath later, so don't create a
  176. // directory at deviceMountPath now. Otherwise mklink will error: "Cannot create a file when that file already exists".
  177. // Instead, create the parent of deviceMountPath. For example when deviceMountPath is:
  178. // C:\var\lib\kubelet\plugins\kubernetes.io\aws-ebs\mounts\aws\us-west-2b\vol-xxx
  179. // create us-west-2b. FormatAndMount will make vol-xxx a symlink to the drive (e.g. D:\)
  180. dir = filepath.Dir(deviceMountPath)
  181. }
  182. if err := os.MkdirAll(dir, 0750); err != nil {
  183. return fmt.Errorf("making dir %s failed with %s", dir, err)
  184. }
  185. notMnt = true
  186. } else {
  187. return err
  188. }
  189. }
  190. volumeSource, readOnly, err := getVolumeSource(spec)
  191. if err != nil {
  192. return err
  193. }
  194. options := []string{}
  195. if readOnly {
  196. options = append(options, "ro")
  197. }
  198. if notMnt {
  199. diskMounter := volumeutil.NewSafeFormatAndMountFromHost(awsElasticBlockStorePluginName, attacher.host)
  200. mountOptions := volumeutil.MountOptionFromSpec(spec, options...)
  201. err = diskMounter.FormatAndMount(devicePath, deviceMountPath, volumeSource.FSType, mountOptions)
  202. if err != nil {
  203. os.Remove(deviceMountPath)
  204. return err
  205. }
  206. }
  207. return nil
  208. }
  209. type awsElasticBlockStoreDetacher struct {
  210. mounter mount.Interface
  211. awsVolumes aws.Volumes
  212. }
  213. var _ volume.Detacher = &awsElasticBlockStoreDetacher{}
  214. var _ volume.DeviceUnmounter = &awsElasticBlockStoreDetacher{}
  215. func (plugin *awsElasticBlockStorePlugin) NewDetacher() (volume.Detacher, error) {
  216. awsCloud, err := getCloudProvider(plugin.host.GetCloudProvider())
  217. if err != nil {
  218. return nil, err
  219. }
  220. return &awsElasticBlockStoreDetacher{
  221. mounter: plugin.host.GetMounter(plugin.GetPluginName()),
  222. awsVolumes: awsCloud,
  223. }, nil
  224. }
  225. func (plugin *awsElasticBlockStorePlugin) NewDeviceUnmounter() (volume.DeviceUnmounter, error) {
  226. return plugin.NewDetacher()
  227. }
  228. func (detacher *awsElasticBlockStoreDetacher) Detach(volumeName string, nodeName types.NodeName) error {
  229. volumeID := aws.KubernetesVolumeID(path.Base(volumeName))
  230. if _, err := detacher.awsVolumes.DetachDisk(volumeID, nodeName); err != nil {
  231. klog.Errorf("Error detaching volumeID %q: %v", volumeID, err)
  232. return err
  233. }
  234. return nil
  235. }
  236. func (detacher *awsElasticBlockStoreDetacher) UnmountDevice(deviceMountPath string) error {
  237. return mount.CleanupMountPoint(deviceMountPath, detacher.mounter, false)
  238. }
  239. func (plugin *awsElasticBlockStorePlugin) CanAttach(spec *volume.Spec) (bool, error) {
  240. return true, nil
  241. }
  242. func (plugin *awsElasticBlockStorePlugin) CanDeviceMount(spec *volume.Spec) (bool, error) {
  243. return true, nil
  244. }
  245. func setNodeDisk(
  246. nodeDiskMap map[types.NodeName]map[*volume.Spec]bool,
  247. volumeSpec *volume.Spec,
  248. nodeName types.NodeName,
  249. check bool) {
  250. volumeMap := nodeDiskMap[nodeName]
  251. if volumeMap == nil {
  252. volumeMap = make(map[*volume.Spec]bool)
  253. nodeDiskMap[nodeName] = volumeMap
  254. }
  255. volumeMap[volumeSpec] = check
  256. }