azure_provision.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374
  1. // +build !providerless
  2. /*
  3. Copyright 2017 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 azure_dd
  15. import (
  16. "errors"
  17. "fmt"
  18. "strconv"
  19. "strings"
  20. "github.com/Azure/azure-sdk-for-go/services/storage/mgmt/2019-06-01/storage"
  21. v1 "k8s.io/api/core/v1"
  22. "k8s.io/apimachinery/pkg/api/resource"
  23. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  24. "k8s.io/apimachinery/pkg/util/sets"
  25. utilfeature "k8s.io/apiserver/pkg/util/feature"
  26. volumehelpers "k8s.io/cloud-provider/volume/helpers"
  27. "k8s.io/kubernetes/pkg/features"
  28. "k8s.io/kubernetes/pkg/volume"
  29. "k8s.io/kubernetes/pkg/volume/util"
  30. "k8s.io/legacy-cloud-providers/azure"
  31. )
  32. type azureDiskProvisioner struct {
  33. plugin *azureDataDiskPlugin
  34. options volume.VolumeOptions
  35. }
  36. type azureDiskDeleter struct {
  37. *dataDisk
  38. spec *volume.Spec
  39. plugin *azureDataDiskPlugin
  40. }
  41. var _ volume.Provisioner = &azureDiskProvisioner{}
  42. var _ volume.Deleter = &azureDiskDeleter{}
  43. func (d *azureDiskDeleter) GetPath() string {
  44. return getPath(d.podUID, d.dataDisk.diskName, d.plugin.host)
  45. }
  46. func (d *azureDiskDeleter) Delete() error {
  47. volumeSource, _, err := getVolumeSource(d.spec)
  48. if err != nil {
  49. return err
  50. }
  51. diskController, err := getDiskController(d.plugin.host)
  52. if err != nil {
  53. return err
  54. }
  55. managed := (*volumeSource.Kind == v1.AzureManagedDisk)
  56. if managed {
  57. return diskController.DeleteManagedDisk(volumeSource.DataDiskURI)
  58. }
  59. return diskController.DeleteBlobDisk(volumeSource.DataDiskURI)
  60. }
  61. // parseZoned parsed 'zoned' for storage class. If zoned is not specified (empty string),
  62. // then it defaults to true for managed disks.
  63. func parseZoned(zonedString string, kind v1.AzureDataDiskKind) (bool, error) {
  64. if zonedString == "" {
  65. return kind == v1.AzureManagedDisk, nil
  66. }
  67. zoned, err := strconv.ParseBool(zonedString)
  68. if err != nil {
  69. return false, fmt.Errorf("failed to parse 'zoned': %v", err)
  70. }
  71. if zoned && kind != v1.AzureManagedDisk {
  72. return false, fmt.Errorf("zoned is only supported by managed disks")
  73. }
  74. return zoned, nil
  75. }
  76. func (p *azureDiskProvisioner) Provision(selectedNode *v1.Node, allowedTopologies []v1.TopologySelectorTerm) (*v1.PersistentVolume, error) {
  77. if !util.AccessModesContainedInAll(p.plugin.GetAccessModes(), p.options.PVC.Spec.AccessModes) {
  78. return nil, fmt.Errorf("invalid AccessModes %v: only AccessModes %v are supported", p.options.PVC.Spec.AccessModes, p.plugin.GetAccessModes())
  79. }
  80. supportedModes := p.plugin.GetAccessModes()
  81. // perform static validation first
  82. if p.options.PVC.Spec.Selector != nil {
  83. return nil, fmt.Errorf("azureDisk - claim.Spec.Selector is not supported for dynamic provisioning on Azure disk")
  84. }
  85. if len(p.options.PVC.Spec.AccessModes) > 1 {
  86. return nil, fmt.Errorf("AzureDisk - multiple access modes are not supported on AzureDisk plugin")
  87. }
  88. if len(p.options.PVC.Spec.AccessModes) == 1 {
  89. if p.options.PVC.Spec.AccessModes[0] != supportedModes[0] {
  90. return nil, fmt.Errorf("AzureDisk - mode %s is not supported by AzureDisk plugin (supported mode is %s)", p.options.PVC.Spec.AccessModes[0], supportedModes)
  91. }
  92. }
  93. var (
  94. location, account string
  95. storageAccountType, fsType string
  96. cachingMode v1.AzureDataDiskCachingMode
  97. strKind string
  98. err error
  99. resourceGroup string
  100. zoned bool
  101. zonePresent bool
  102. zonesPresent bool
  103. strZoned string
  104. availabilityZone string
  105. availabilityZones sets.String
  106. selectedAvailabilityZone string
  107. writeAcceleratorEnabled string
  108. diskIopsReadWrite string
  109. diskMbpsReadWrite string
  110. diskEncryptionSetID string
  111. )
  112. // maxLength = 79 - (4 for ".vhd") = 75
  113. name := util.GenerateVolumeName(p.options.ClusterName, p.options.PVName, 75)
  114. capacity := p.options.PVC.Spec.Resources.Requests[v1.ResourceName(v1.ResourceStorage)]
  115. requestGiB, err := volumehelpers.RoundUpToGiBInt(capacity)
  116. if err != nil {
  117. return nil, err
  118. }
  119. for k, v := range p.options.Parameters {
  120. switch strings.ToLower(k) {
  121. case "skuname":
  122. storageAccountType = v
  123. case "location":
  124. location = v
  125. case "storageaccount":
  126. account = v
  127. case "storageaccounttype":
  128. storageAccountType = v
  129. case "kind":
  130. strKind = v
  131. case "cachingmode":
  132. cachingMode = v1.AzureDataDiskCachingMode(v)
  133. case volume.VolumeParameterFSType:
  134. fsType = strings.ToLower(v)
  135. case "resourcegroup":
  136. resourceGroup = v
  137. case "zone":
  138. zonePresent = true
  139. availabilityZone = v
  140. case "zones":
  141. zonesPresent = true
  142. availabilityZones, err = volumehelpers.ZonesToSet(v)
  143. if err != nil {
  144. return nil, fmt.Errorf("error parsing zones %s, must be strings separated by commas: %v", v, err)
  145. }
  146. case "zoned":
  147. strZoned = v
  148. case "diskiopsreadwrite":
  149. diskIopsReadWrite = v
  150. case "diskmbpsreadwrite":
  151. diskMbpsReadWrite = v
  152. case "diskencryptionsetid":
  153. diskEncryptionSetID = v
  154. case azure.WriteAcceleratorEnabled:
  155. writeAcceleratorEnabled = v
  156. default:
  157. return nil, fmt.Errorf("AzureDisk - invalid option %s in storage class", k)
  158. }
  159. }
  160. // normalize values
  161. skuName, err := normalizeStorageAccountType(storageAccountType)
  162. if err != nil {
  163. return nil, err
  164. }
  165. kind, err := normalizeKind(strFirstLetterToUpper(strKind))
  166. if err != nil {
  167. return nil, err
  168. }
  169. zoned, err = parseZoned(strZoned, kind)
  170. if err != nil {
  171. return nil, err
  172. }
  173. if kind != v1.AzureManagedDisk {
  174. if resourceGroup != "" {
  175. return nil, errors.New("StorageClass option 'resourceGroup' can be used only for managed disks")
  176. }
  177. if zoned {
  178. return nil, errors.New("StorageClass option 'zoned' parameter is only supported for managed disks")
  179. }
  180. }
  181. if !zoned && (zonePresent || zonesPresent || len(allowedTopologies) > 0) {
  182. return nil, fmt.Errorf("zone, zones and allowedTopologies StorageClass parameters must be used together with zoned parameter")
  183. }
  184. if cachingMode, err = normalizeCachingMode(cachingMode); err != nil {
  185. return nil, err
  186. }
  187. diskController, err := getDiskController(p.plugin.host)
  188. if err != nil {
  189. return nil, err
  190. }
  191. // Select zone for managed disks based on zone, zones and allowedTopologies.
  192. if zoned {
  193. activeZones, err := diskController.GetActiveZones()
  194. if err != nil {
  195. return nil, fmt.Errorf("error querying active zones: %v", err)
  196. }
  197. if availabilityZone != "" || availabilityZones.Len() != 0 || activeZones.Len() != 0 || len(allowedTopologies) != 0 {
  198. selectedAvailabilityZone, err = volumehelpers.SelectZoneForVolume(zonePresent, zonesPresent, availabilityZone, availabilityZones, activeZones, selectedNode, allowedTopologies, p.options.PVC.Name)
  199. if err != nil {
  200. return nil, err
  201. }
  202. }
  203. }
  204. // create disk
  205. diskURI := ""
  206. labels := map[string]string{}
  207. if kind == v1.AzureManagedDisk {
  208. tags := make(map[string]string)
  209. if p.options.CloudTags != nil {
  210. tags = *(p.options.CloudTags)
  211. }
  212. if strings.EqualFold(writeAcceleratorEnabled, "true") {
  213. tags[azure.WriteAcceleratorEnabled] = "true"
  214. }
  215. volumeOptions := &azure.ManagedDiskOptions{
  216. DiskName: name,
  217. StorageAccountType: skuName,
  218. ResourceGroup: resourceGroup,
  219. PVCName: p.options.PVC.Name,
  220. SizeGB: requestGiB,
  221. Tags: tags,
  222. AvailabilityZone: selectedAvailabilityZone,
  223. DiskIOPSReadWrite: diskIopsReadWrite,
  224. DiskMBpsReadWrite: diskMbpsReadWrite,
  225. DiskEncryptionSetID: diskEncryptionSetID,
  226. }
  227. diskURI, err = diskController.CreateManagedDisk(volumeOptions)
  228. if err != nil {
  229. return nil, err
  230. }
  231. labels, err = diskController.GetAzureDiskLabels(diskURI)
  232. if err != nil {
  233. return nil, err
  234. }
  235. } else {
  236. if kind == v1.AzureDedicatedBlobDisk {
  237. _, diskURI, _, err = diskController.CreateVolume(name, account, storageAccountType, location, requestGiB)
  238. if err != nil {
  239. return nil, err
  240. }
  241. } else {
  242. diskURI, err = diskController.CreateBlobDisk(name, storage.SkuName(skuName), requestGiB)
  243. if err != nil {
  244. return nil, err
  245. }
  246. }
  247. }
  248. var volumeMode *v1.PersistentVolumeMode
  249. if utilfeature.DefaultFeatureGate.Enabled(features.BlockVolume) {
  250. volumeMode = p.options.PVC.Spec.VolumeMode
  251. if volumeMode != nil && *volumeMode == v1.PersistentVolumeBlock {
  252. // Block volumes should not have any FSType
  253. fsType = ""
  254. }
  255. }
  256. pv := &v1.PersistentVolume{
  257. ObjectMeta: metav1.ObjectMeta{
  258. Name: p.options.PVName,
  259. Labels: labels,
  260. Annotations: map[string]string{
  261. "volumehelper.VolumeDynamicallyCreatedByKey": "azure-disk-dynamic-provisioner",
  262. },
  263. },
  264. Spec: v1.PersistentVolumeSpec{
  265. PersistentVolumeReclaimPolicy: p.options.PersistentVolumeReclaimPolicy,
  266. AccessModes: supportedModes,
  267. Capacity: v1.ResourceList{
  268. v1.ResourceName(v1.ResourceStorage): resource.MustParse(fmt.Sprintf("%dGi", requestGiB)),
  269. },
  270. VolumeMode: volumeMode,
  271. PersistentVolumeSource: v1.PersistentVolumeSource{
  272. AzureDisk: &v1.AzureDiskVolumeSource{
  273. CachingMode: &cachingMode,
  274. DiskName: name,
  275. DataDiskURI: diskURI,
  276. Kind: &kind,
  277. FSType: &fsType,
  278. },
  279. },
  280. MountOptions: p.options.MountOptions,
  281. },
  282. }
  283. nodeSelectorTerms := make([]v1.NodeSelectorTerm, 0)
  284. if zoned {
  285. // Set node affinity labels based on availability zone labels.
  286. if len(labels) > 0 {
  287. requirements := make([]v1.NodeSelectorRequirement, 0)
  288. for k, v := range labels {
  289. requirements = append(requirements, v1.NodeSelectorRequirement{Key: k, Operator: v1.NodeSelectorOpIn, Values: []string{v}})
  290. }
  291. nodeSelectorTerms = append(nodeSelectorTerms, v1.NodeSelectorTerm{
  292. MatchExpressions: requirements,
  293. })
  294. }
  295. } else {
  296. // Set node affinity labels based on fault domains.
  297. // This is required because unzoned AzureDisk can't be attached to zoned nodes.
  298. // There are at most 3 fault domains available in each region.
  299. // Refer https://docs.microsoft.com/en-us/azure/virtual-machines/windows/manage-availability.
  300. for i := 0; i < 3; i++ {
  301. requirements := []v1.NodeSelectorRequirement{
  302. {
  303. Key: v1.LabelZoneRegion,
  304. Operator: v1.NodeSelectorOpIn,
  305. Values: []string{diskController.GetLocation()},
  306. },
  307. {
  308. Key: v1.LabelZoneFailureDomain,
  309. Operator: v1.NodeSelectorOpIn,
  310. Values: []string{strconv.Itoa(i)},
  311. },
  312. }
  313. nodeSelectorTerms = append(nodeSelectorTerms, v1.NodeSelectorTerm{
  314. MatchExpressions: requirements,
  315. })
  316. }
  317. }
  318. if len(nodeSelectorTerms) > 0 {
  319. pv.Spec.NodeAffinity = &v1.VolumeNodeAffinity{
  320. Required: &v1.NodeSelector{
  321. NodeSelectorTerms: nodeSelectorTerms,
  322. },
  323. }
  324. }
  325. return pv, nil
  326. }