azure_provision.go 10 KB

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