cinder_util.go 7.9 KB

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