gce_util.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356
  1. /*
  2. Copyright 2014 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 gcepd
  14. import (
  15. "fmt"
  16. "path/filepath"
  17. "regexp"
  18. "strings"
  19. "time"
  20. "k8s.io/api/core/v1"
  21. "k8s.io/apimachinery/pkg/util/sets"
  22. cloudprovider "k8s.io/cloud-provider"
  23. cloudvolume "k8s.io/cloud-provider/volume"
  24. volumehelpers "k8s.io/cloud-provider/volume/helpers"
  25. "k8s.io/klog"
  26. "k8s.io/kubernetes/pkg/util/mount"
  27. "k8s.io/kubernetes/pkg/volume"
  28. volumeutil "k8s.io/kubernetes/pkg/volume/util"
  29. gcecloud "k8s.io/legacy-cloud-providers/gce"
  30. "k8s.io/utils/exec"
  31. utilpath "k8s.io/utils/path"
  32. )
  33. const (
  34. diskByIDPath = "/dev/disk/by-id/"
  35. diskGooglePrefix = "google-"
  36. diskScsiGooglePrefix = "scsi-0Google_PersistentDisk_"
  37. diskPartitionSuffix = "-part"
  38. diskSDPath = "/dev/sd"
  39. diskSDPattern = "/dev/sd*"
  40. maxRetries = 10
  41. checkSleepDuration = time.Second
  42. maxRegionalPDZones = 2
  43. // Replication type constants must be lower case.
  44. replicationTypeNone = "none"
  45. replicationTypeRegionalPD = "regional-pd"
  46. // scsi_id output should be in the form of:
  47. // 0Google PersistentDisk <disk name>
  48. scsiPattern = `^0Google\s+PersistentDisk\s+([\S]+)\s*$`
  49. )
  50. var (
  51. // errorSleepDuration is modified only in unit tests and should be constant
  52. // otherwise.
  53. errorSleepDuration = 5 * time.Second
  54. // regex to parse scsi_id output and extract the serial
  55. scsiRegex = regexp.MustCompile(scsiPattern)
  56. )
  57. // GCEDiskUtil provides operation for GCE PD
  58. type GCEDiskUtil struct{}
  59. // DeleteVolume deletes a GCE PD
  60. // Returns: error
  61. func (util *GCEDiskUtil) DeleteVolume(d *gcePersistentDiskDeleter) error {
  62. cloud, err := getCloudProvider(d.gcePersistentDisk.plugin.host.GetCloudProvider())
  63. if err != nil {
  64. return err
  65. }
  66. if err = cloud.DeleteDisk(d.pdName); err != nil {
  67. klog.V(2).Infof("Error deleting GCE PD volume %s: %v", d.pdName, err)
  68. // GCE cloud provider returns volume.deletedVolumeInUseError when
  69. // necessary, no handling needed here.
  70. return err
  71. }
  72. klog.V(2).Infof("Successfully deleted GCE PD volume %s", d.pdName)
  73. return nil
  74. }
  75. // CreateVolume creates a GCE PD.
  76. // Returns: gcePDName, volumeSizeGB, labels, fsType, error
  77. func (util *GCEDiskUtil) CreateVolume(c *gcePersistentDiskProvisioner, node *v1.Node, allowedTopologies []v1.TopologySelectorTerm) (string, int, map[string]string, string, error) {
  78. cloud, err := getCloudProvider(c.gcePersistentDisk.plugin.host.GetCloudProvider())
  79. if err != nil {
  80. return "", 0, nil, "", err
  81. }
  82. name := volumeutil.GenerateVolumeName(c.options.ClusterName, c.options.PVName, 63) // GCE PD name can have up to 63 characters
  83. capacity := c.options.PVC.Spec.Resources.Requests[v1.ResourceName(v1.ResourceStorage)]
  84. // GCE PDs are allocated in chunks of GiBs
  85. requestGB := volumehelpers.RoundUpToGiB(capacity)
  86. // Apply Parameters.
  87. // Values for parameter "replication-type" are canonicalized to lower case.
  88. // Values for other parameters are case-insensitive, and we leave validation of these values
  89. // to the cloud provider.
  90. diskType := ""
  91. configuredZone := ""
  92. var configuredZones sets.String
  93. zonePresent := false
  94. zonesPresent := false
  95. replicationType := replicationTypeNone
  96. fstype := ""
  97. for k, v := range c.options.Parameters {
  98. switch strings.ToLower(k) {
  99. case "type":
  100. diskType = v
  101. case "zone":
  102. zonePresent = true
  103. configuredZone = v
  104. case "zones":
  105. zonesPresent = true
  106. configuredZones, err = volumehelpers.ZonesToSet(v)
  107. if err != nil {
  108. return "", 0, nil, "", err
  109. }
  110. case "replication-type":
  111. replicationType = strings.ToLower(v)
  112. case volume.VolumeParameterFSType:
  113. fstype = v
  114. default:
  115. return "", 0, nil, "", fmt.Errorf("invalid option %q for volume plugin %s", k, c.plugin.GetPluginName())
  116. }
  117. }
  118. // TODO: implement PVC.Selector parsing
  119. if c.options.PVC.Spec.Selector != nil {
  120. return "", 0, nil, "", fmt.Errorf("claim.Spec.Selector is not supported for dynamic provisioning on GCE")
  121. }
  122. var activezones sets.String
  123. activezones, err = cloud.GetAllCurrentZones()
  124. if err != nil {
  125. return "", 0, nil, "", err
  126. }
  127. switch replicationType {
  128. case replicationTypeRegionalPD:
  129. selectedZones, err := volumehelpers.SelectZonesForVolume(zonePresent, zonesPresent, configuredZone, configuredZones, activezones, node, allowedTopologies, c.options.PVC.Name, maxRegionalPDZones)
  130. if err != nil {
  131. klog.V(2).Infof("Error selecting zones for regional GCE PD volume: %v", err)
  132. return "", 0, nil, "", err
  133. }
  134. if err = cloud.CreateRegionalDisk(
  135. name,
  136. diskType,
  137. selectedZones,
  138. int64(requestGB),
  139. *c.options.CloudTags); err != nil {
  140. klog.V(2).Infof("Error creating regional GCE PD volume: %v", err)
  141. return "", 0, nil, "", err
  142. }
  143. klog.V(2).Infof("Successfully created Regional GCE PD volume %s", name)
  144. case replicationTypeNone:
  145. selectedZone, err := volumehelpers.SelectZoneForVolume(zonePresent, zonesPresent, configuredZone, configuredZones, activezones, node, allowedTopologies, c.options.PVC.Name)
  146. if err != nil {
  147. return "", 0, nil, "", err
  148. }
  149. if err := cloud.CreateDisk(
  150. name,
  151. diskType,
  152. selectedZone,
  153. int64(requestGB),
  154. *c.options.CloudTags); err != nil {
  155. klog.V(2).Infof("Error creating single-zone GCE PD volume: %v", err)
  156. return "", 0, nil, "", err
  157. }
  158. klog.V(2).Infof("Successfully created single-zone GCE PD volume %s", name)
  159. default:
  160. return "", 0, nil, "", fmt.Errorf("replication-type of '%s' is not supported", replicationType)
  161. }
  162. labels, err := cloud.GetAutoLabelsForPD(name, "" /* zone */)
  163. if err != nil {
  164. // We don't really want to leak the volume here...
  165. klog.Errorf("error getting labels for volume %q: %v", name, err)
  166. }
  167. return name, int(requestGB), labels, fstype, nil
  168. }
  169. // Returns the first path that exists, or empty string if none exist.
  170. func verifyDevicePath(devicePaths []string, sdBeforeSet sets.String, diskName string) (string, error) {
  171. if err := udevadmChangeToNewDrives(sdBeforeSet); err != nil {
  172. // It's possible udevadm was called on other disks so it should not block this
  173. // call. If it did fail on this disk, then the devicePath will either
  174. // not exist or be wrong. If it's wrong, then the scsi_id check below will fail.
  175. klog.Errorf("udevadmChangeToNewDrives failed with: %v", err)
  176. }
  177. for _, path := range devicePaths {
  178. if pathExists, err := mount.PathExists(path); err != nil {
  179. return "", fmt.Errorf("Error checking if path exists: %v", err)
  180. } else if pathExists {
  181. // validate that the path actually resolves to the correct disk
  182. serial, err := getScsiSerial(path, diskName)
  183. if err != nil {
  184. return "", fmt.Errorf("failed to get scsi serial %v", err)
  185. }
  186. if serial != diskName {
  187. // The device link is not pointing to the correct device
  188. // Trigger udev on this device to try to fix the link
  189. if udevErr := udevadmChangeToDrive(path); udevErr != nil {
  190. klog.Errorf("udevadmChangeToDrive %q failed with: %v", path, err)
  191. }
  192. // Return error to retry WaitForAttach and verifyDevicePath
  193. return "", fmt.Errorf("scsi_id serial %q for device %q doesn't match disk %q", serial, path, diskName)
  194. }
  195. // The device link is correct
  196. return path, nil
  197. }
  198. }
  199. return "", nil
  200. }
  201. // Calls scsi_id on the given devicePath to get the serial number reported by that device.
  202. func getScsiSerial(devicePath, diskName string) (string, error) {
  203. exists, err := utilpath.Exists(utilpath.CheckFollowSymlink, "/lib/udev/scsi_id")
  204. if err != nil {
  205. return "", fmt.Errorf("failed to check scsi_id existence: %v", err)
  206. }
  207. if !exists {
  208. klog.V(6).Infof("scsi_id doesn't exist; skipping check for %v", devicePath)
  209. return diskName, nil
  210. }
  211. out, err := exec.New().Command(
  212. "/lib/udev/scsi_id",
  213. "--page=0x83",
  214. "--whitelisted",
  215. fmt.Sprintf("--device=%v", devicePath)).CombinedOutput()
  216. if err != nil {
  217. return "", fmt.Errorf("scsi_id failed for device %q with %v", devicePath, err)
  218. }
  219. return parseScsiSerial(string(out))
  220. }
  221. // Parse the output returned by scsi_id and extract the serial number
  222. func parseScsiSerial(output string) (string, error) {
  223. substrings := scsiRegex.FindStringSubmatch(output)
  224. if substrings == nil {
  225. return "", fmt.Errorf("scsi_id output cannot be parsed: %q", output)
  226. }
  227. return substrings[1], nil
  228. }
  229. // Returns list of all /dev/disk/by-id/* paths for given PD.
  230. func getDiskByIDPaths(pdName string, partition string) []string {
  231. devicePaths := []string{
  232. filepath.Join(diskByIDPath, diskGooglePrefix+pdName),
  233. filepath.Join(diskByIDPath, diskScsiGooglePrefix+pdName),
  234. }
  235. if partition != "" {
  236. for i, path := range devicePaths {
  237. devicePaths[i] = path + diskPartitionSuffix + partition
  238. }
  239. }
  240. return devicePaths
  241. }
  242. // Return cloud provider
  243. func getCloudProvider(cloudProvider cloudprovider.Interface) (*gcecloud.Cloud, error) {
  244. var err error
  245. for numRetries := 0; numRetries < maxRetries; numRetries++ {
  246. gceCloudProvider, ok := cloudProvider.(*gcecloud.Cloud)
  247. if !ok || gceCloudProvider == nil {
  248. // Retry on error. See issue #11321
  249. klog.Errorf("Failed to get GCE Cloud Provider. plugin.host.GetCloudProvider returned %v instead", cloudProvider)
  250. time.Sleep(errorSleepDuration)
  251. continue
  252. }
  253. return gceCloudProvider, nil
  254. }
  255. return nil, fmt.Errorf("Failed to get GCE GCECloudProvider with error %v", err)
  256. }
  257. // Triggers the application of udev rules by calling "udevadm trigger
  258. // --action=change" for newly created "/dev/sd*" drives (exist only in
  259. // after set). This is workaround for Issue #7972. Once the underlying
  260. // issue has been resolved, this may be removed.
  261. func udevadmChangeToNewDrives(sdBeforeSet sets.String) error {
  262. sdAfter, err := filepath.Glob(diskSDPattern)
  263. if err != nil {
  264. return fmt.Errorf("error filepath.Glob(\"%s\"): %v\r", diskSDPattern, err)
  265. }
  266. for _, sd := range sdAfter {
  267. if !sdBeforeSet.Has(sd) {
  268. return udevadmChangeToDrive(sd)
  269. }
  270. }
  271. return nil
  272. }
  273. // Calls "udevadm trigger --action=change" on the specified drive.
  274. // drivePath must be the block device path to trigger on, in the format "/dev/sd*", or a symlink to it.
  275. // This is workaround for Issue #7972. Once the underlying issue has been resolved, this may be removed.
  276. func udevadmChangeToDrive(drivePath string) error {
  277. klog.V(5).Infof("udevadmChangeToDrive: drive=%q", drivePath)
  278. // Evaluate symlink, if any
  279. drive, err := filepath.EvalSymlinks(drivePath)
  280. if err != nil {
  281. return fmt.Errorf("udevadmChangeToDrive: filepath.EvalSymlinks(%q) failed with %v", drivePath, err)
  282. }
  283. klog.V(5).Infof("udevadmChangeToDrive: symlink path is %q", drive)
  284. // Check to make sure input is "/dev/sd*"
  285. if !strings.Contains(drive, diskSDPath) {
  286. return fmt.Errorf("udevadmChangeToDrive: expected input in the form \"%s\" but drive is %q", diskSDPattern, drive)
  287. }
  288. // Call "udevadm trigger --action=change --property-match=DEVNAME=/dev/sd..."
  289. _, err = exec.New().Command(
  290. "udevadm",
  291. "trigger",
  292. "--action=change",
  293. fmt.Sprintf("--property-match=DEVNAME=%s", drive)).CombinedOutput()
  294. if err != nil {
  295. return fmt.Errorf("udevadmChangeToDrive: udevadm trigger failed for drive %q with %v", drive, err)
  296. }
  297. return nil
  298. }
  299. // Checks whether the given GCE PD volume spec is associated with a regional PD.
  300. func isRegionalPD(spec *volume.Spec) bool {
  301. if spec.PersistentVolume != nil {
  302. zonesLabel := spec.PersistentVolume.Labels[v1.LabelZoneFailureDomain]
  303. zones := strings.Split(zonesLabel, cloudvolume.LabelMultiZoneDelimiter)
  304. return len(zones) > 1
  305. }
  306. return false
  307. }