cinder_util.go 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  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 cinder
  14. import (
  15. "errors"
  16. "fmt"
  17. "io/ioutil"
  18. "os"
  19. "strings"
  20. "time"
  21. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  22. "k8s.io/klog"
  23. "k8s.io/api/core/v1"
  24. "k8s.io/apimachinery/pkg/util/sets"
  25. clientset "k8s.io/client-go/kubernetes"
  26. volumehelpers "k8s.io/cloud-provider/volume/helpers"
  27. "k8s.io/kubernetes/pkg/volume"
  28. volutil "k8s.io/kubernetes/pkg/volume/util"
  29. "k8s.io/utils/exec"
  30. )
  31. // DiskUtil has utility/helper methods
  32. type DiskUtil struct{}
  33. // AttachDisk attaches a disk specified by a volume.CinderPersistenDisk to the current kubelet.
  34. // Mounts the disk to its global path.
  35. func (util *DiskUtil) AttachDisk(b *cinderVolumeMounter, globalPDPath string) error {
  36. options := []string{}
  37. if b.readOnly {
  38. options = append(options, "ro")
  39. }
  40. cloud, err := b.plugin.getCloudProvider()
  41. if err != nil {
  42. return err
  43. }
  44. instanceid, err := cloud.InstanceID()
  45. if err != nil {
  46. return err
  47. }
  48. diskid, err := cloud.AttachDisk(instanceid, b.pdName)
  49. if err != nil {
  50. return err
  51. }
  52. var devicePath string
  53. numTries := 0
  54. for {
  55. devicePath = cloud.GetDevicePath(diskid)
  56. probeAttachedVolume()
  57. _, err := os.Stat(devicePath)
  58. if err == nil {
  59. break
  60. }
  61. if err != nil && !os.IsNotExist(err) {
  62. return err
  63. }
  64. numTries++
  65. if numTries == 10 {
  66. return errors.New("Could not attach disk: Timeout after 60s")
  67. }
  68. time.Sleep(time.Second * 6)
  69. }
  70. notmnt, err := b.mounter.IsLikelyNotMountPoint(globalPDPath)
  71. if err != nil {
  72. if os.IsNotExist(err) {
  73. if err := os.MkdirAll(globalPDPath, 0750); err != nil {
  74. return err
  75. }
  76. notmnt = true
  77. } else {
  78. return err
  79. }
  80. }
  81. if notmnt {
  82. err = b.blockDeviceMounter.FormatAndMount(devicePath, globalPDPath, b.fsType, options)
  83. if err != nil {
  84. os.Remove(globalPDPath)
  85. return err
  86. }
  87. klog.V(2).Infof("Safe mount successful: %q\n", devicePath)
  88. }
  89. return nil
  90. }
  91. // DetachDisk unmounts the device and detaches the disk from the kubelet's host machine.
  92. func (util *DiskUtil) DetachDisk(cd *cinderVolumeUnmounter) error {
  93. globalPDPath := makeGlobalPDName(cd.plugin.host, cd.pdName)
  94. if err := cd.mounter.Unmount(globalPDPath); err != nil {
  95. return err
  96. }
  97. if err := os.Remove(globalPDPath); err != nil {
  98. return err
  99. }
  100. klog.V(2).Infof("Successfully unmounted main device: %s\n", globalPDPath)
  101. cloud, err := cd.plugin.getCloudProvider()
  102. if err != nil {
  103. return err
  104. }
  105. instanceid, err := cloud.InstanceID()
  106. if err != nil {
  107. return err
  108. }
  109. if err = cloud.DetachDisk(instanceid, cd.pdName); err != nil {
  110. return err
  111. }
  112. klog.V(2).Infof("Successfully detached cinder volume %s", cd.pdName)
  113. return nil
  114. }
  115. // DeleteVolume uses the cloud entrypoint to delete specified volume
  116. func (util *DiskUtil) DeleteVolume(cd *cinderVolumeDeleter) error {
  117. cloud, err := cd.plugin.getCloudProvider()
  118. if err != nil {
  119. return err
  120. }
  121. if err = cloud.DeleteVolume(cd.pdName); err != nil {
  122. // OpenStack cloud provider returns volume.tryAgainError when necessary,
  123. // no handling needed here.
  124. klog.V(2).Infof("Error deleting cinder volume %s: %v", cd.pdName, err)
  125. return err
  126. }
  127. klog.V(2).Infof("Successfully deleted cinder volume %s", cd.pdName)
  128. return nil
  129. }
  130. func getZonesFromNodes(kubeClient clientset.Interface) (sets.String, error) {
  131. // TODO: caching, currently it is overkill because it calls this function
  132. // only when it creates dynamic PV
  133. zones := make(sets.String)
  134. nodes, err := kubeClient.CoreV1().Nodes().List(metav1.ListOptions{})
  135. if err != nil {
  136. klog.V(2).Infof("Error listing nodes")
  137. return zones, err
  138. }
  139. for _, node := range nodes.Items {
  140. if zone, ok := node.Labels[v1.LabelZoneFailureDomain]; ok {
  141. zones.Insert(zone)
  142. }
  143. }
  144. klog.V(4).Infof("zones found: %v", zones)
  145. return zones, nil
  146. }
  147. // CreateVolume uses the cloud provider entrypoint for creating a volume
  148. func (util *DiskUtil) CreateVolume(c *cinderVolumeProvisioner, node *v1.Node, allowedTopologies []v1.TopologySelectorTerm) (volumeID string, volumeSizeGB int, volumeLabels map[string]string, fstype string, err error) {
  149. cloud, err := c.plugin.getCloudProvider()
  150. if err != nil {
  151. return "", 0, nil, "", err
  152. }
  153. capacity := c.options.PVC.Spec.Resources.Requests[v1.ResourceName(v1.ResourceStorage)]
  154. // Cinder works with gigabytes, convert to GiB with rounding up
  155. volSizeGiB, err := volumehelpers.RoundUpToGiBInt(capacity)
  156. if err != nil {
  157. return "", 0, nil, "", err
  158. }
  159. name := volutil.GenerateVolumeName(c.options.ClusterName, c.options.PVName, 255) // Cinder volume name can have up to 255 characters
  160. vtype := ""
  161. availability := ""
  162. // Apply ProvisionerParameters (case-insensitive). We leave validation of
  163. // the values to the cloud provider.
  164. for k, v := range c.options.Parameters {
  165. switch strings.ToLower(k) {
  166. case "type":
  167. vtype = v
  168. case "availability":
  169. availability = v
  170. case volume.VolumeParameterFSType:
  171. fstype = v
  172. default:
  173. return "", 0, nil, "", fmt.Errorf("invalid option %q for volume plugin %s", k, c.plugin.GetPluginName())
  174. }
  175. }
  176. // TODO: implement PVC.Selector parsing
  177. if c.options.PVC.Spec.Selector != nil {
  178. return "", 0, nil, "", fmt.Errorf("claim.Spec.Selector is not supported for dynamic provisioning on Cinder")
  179. }
  180. if availability == "" {
  181. // No zone specified, choose one randomly in the same region
  182. zones, err := getZonesFromNodes(c.plugin.host.GetKubeClient())
  183. if err != nil {
  184. klog.V(2).Infof("error getting zone information: %v", err)
  185. return "", 0, nil, "", err
  186. }
  187. // if we did not get any zones, lets leave it blank and gophercloud will
  188. // use zone "nova" as default
  189. if len(zones) > 0 {
  190. availability, err = volumehelpers.SelectZoneForVolume(false, false, "", nil, zones, node, allowedTopologies, c.options.PVC.Name)
  191. if err != nil {
  192. klog.V(2).Infof("error selecting zone for volume: %v", err)
  193. return "", 0, nil, "", err
  194. }
  195. }
  196. }
  197. volumeID, volumeAZ, volumeRegion, IgnoreVolumeAZ, err := cloud.CreateVolume(name, volSizeGiB, vtype, availability, c.options.CloudTags)
  198. if err != nil {
  199. klog.V(2).Infof("Error creating cinder volume: %v", err)
  200. return "", 0, nil, "", err
  201. }
  202. klog.V(2).Infof("Successfully created cinder volume %s", volumeID)
  203. // these are needed that pod is spawning to same AZ
  204. volumeLabels = make(map[string]string)
  205. if IgnoreVolumeAZ == false {
  206. if volumeAZ != "" {
  207. volumeLabels[v1.LabelZoneFailureDomain] = volumeAZ
  208. }
  209. if volumeRegion != "" {
  210. volumeLabels[v1.LabelZoneRegion] = volumeRegion
  211. }
  212. }
  213. return volumeID, volSizeGiB, volumeLabels, fstype, nil
  214. }
  215. func probeAttachedVolume() error {
  216. // rescan scsi bus
  217. scsiHostRescan()
  218. executor := exec.New()
  219. // udevadm settle waits for udevd to process the device creation
  220. // events for all hardware devices, thus ensuring that any device
  221. // nodes have been created successfully before proceeding.
  222. argsSettle := []string{"settle"}
  223. cmdSettle := executor.Command("udevadm", argsSettle...)
  224. _, errSettle := cmdSettle.CombinedOutput()
  225. if errSettle != nil {
  226. klog.Errorf("error running udevadm settle %v\n", errSettle)
  227. }
  228. args := []string{"trigger"}
  229. cmd := executor.Command("udevadm", args...)
  230. _, err := cmd.CombinedOutput()
  231. if err != nil {
  232. klog.Errorf("error running udevadm trigger %v\n", err)
  233. return err
  234. }
  235. klog.V(4).Infof("Successfully probed all attachments")
  236. return nil
  237. }
  238. func scsiHostRescan() {
  239. scsiPath := "/sys/class/scsi_host/"
  240. if dirs, err := ioutil.ReadDir(scsiPath); err == nil {
  241. for _, f := range dirs {
  242. name := scsiPath + f.Name() + "/scan"
  243. data := []byte("- - -")
  244. ioutil.WriteFile(name, data, 0666)
  245. }
  246. }
  247. }