1// +build !providerless 2 3/* 4Copyright 2016 The Kubernetes Authors. 5 6Licensed under the Apache License, Version 2.0 (the "License"); 7you may not use this file except in compliance with the License. 8You may obtain a copy of the License at 9 10 http://www.apache.org/licenses/LICENSE-2.0 11 12Unless required by applicable law or agreed to in writing, software 13distributed under the License is distributed on an "AS IS" BASIS, 14WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15See the License for the specific language governing permissions and 16limitations under the License. 17*/ 18 19package azuredd 20 21import ( 22 "context" 23 "fmt" 24 "strings" 25 26 "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2019-12-01/compute" 27 "github.com/Azure/azure-sdk-for-go/services/storage/mgmt/2019-06-01/storage" 28 "k8s.io/klog/v2" 29 30 v1 "k8s.io/api/core/v1" 31 "k8s.io/apimachinery/pkg/api/resource" 32 "k8s.io/apimachinery/pkg/types" 33 "k8s.io/apimachinery/pkg/util/sets" 34 "k8s.io/kubernetes/pkg/volume" 35 "k8s.io/kubernetes/pkg/volume/util" 36 "k8s.io/legacy-cloud-providers/azure" 37) 38 39// DiskController interface exposed by the cloud provider implementing Disk functionality 40type DiskController interface { 41 CreateBlobDisk(dataDiskName string, storageAccountType storage.SkuName, sizeGB int) (string, error) 42 DeleteBlobDisk(diskURI string) error 43 44 CreateManagedDisk(options *azure.ManagedDiskOptions) (string, error) 45 DeleteManagedDisk(diskURI string) error 46 47 // Attaches the disk to the host machine. 48 AttachDisk(isManagedDisk bool, diskName, diskURI string, nodeName types.NodeName, cachingMode compute.CachingTypes) (int32, error) 49 // Detaches the disk, identified by disk name or uri, from the host machine. 50 DetachDisk(diskName, diskURI string, nodeName types.NodeName) error 51 52 // Check if a list of volumes are attached to the node with the specified NodeName 53 DisksAreAttached(diskNames []string, nodeName types.NodeName) (map[string]bool, error) 54 55 // Get the LUN number of the disk that is attached to the host 56 GetDiskLun(diskName, diskURI string, nodeName types.NodeName) (int32, error) 57 // Get the next available LUN number to attach a new VHD 58 GetNextDiskLun(nodeName types.NodeName) (int32, error) 59 60 // Create a VHD blob 61 CreateVolume(name, storageAccount, storageAccountType, location string, requestGB int) (string, string, int, error) 62 // Delete a VHD blob 63 DeleteVolume(diskURI string) error 64 65 // Expand the disk to new size 66 ResizeDisk(diskURI string, oldSize resource.Quantity, newSize resource.Quantity) (resource.Quantity, error) 67 68 // GetAzureDiskLabels gets availability zone labels for Azuredisk. 69 GetAzureDiskLabels(diskURI string) (map[string]string, error) 70 71 // GetActiveZones returns all the zones in which k8s nodes are currently running. 72 GetActiveZones() (sets.String, error) 73 74 // GetLocation returns the location in which k8s cluster is currently running. 75 GetLocation() string 76} 77 78type azureDataDiskPlugin struct { 79 host volume.VolumeHost 80} 81 82var _ volume.VolumePlugin = &azureDataDiskPlugin{} 83var _ volume.PersistentVolumePlugin = &azureDataDiskPlugin{} 84var _ volume.DeletableVolumePlugin = &azureDataDiskPlugin{} 85var _ volume.ProvisionableVolumePlugin = &azureDataDiskPlugin{} 86var _ volume.AttachableVolumePlugin = &azureDataDiskPlugin{} 87var _ volume.VolumePluginWithAttachLimits = &azureDataDiskPlugin{} 88var _ volume.ExpandableVolumePlugin = &azureDataDiskPlugin{} 89var _ volume.DeviceMountableVolumePlugin = &azureDataDiskPlugin{} 90 91const ( 92 azureDataDiskPluginName = "kubernetes.io/azure-disk" 93 defaultAzureVolumeLimit = 16 94) 95 96// ProbeVolumePlugins is the primary entrypoint for volume plugins. 97func ProbeVolumePlugins() []volume.VolumePlugin { 98 return []volume.VolumePlugin{&azureDataDiskPlugin{}} 99} 100 101func (plugin *azureDataDiskPlugin) Init(host volume.VolumeHost) error { 102 plugin.host = host 103 return nil 104} 105 106func (plugin *azureDataDiskPlugin) GetPluginName() string { 107 return azureDataDiskPluginName 108} 109 110func (plugin *azureDataDiskPlugin) GetVolumeName(spec *volume.Spec) (string, error) { 111 volumeSource, _, err := getVolumeSource(spec) 112 if err != nil { 113 return "", err 114 } 115 116 return volumeSource.DataDiskURI, nil 117} 118 119func (plugin *azureDataDiskPlugin) CanSupport(spec *volume.Spec) bool { 120 return (spec.PersistentVolume != nil && spec.PersistentVolume.Spec.AzureDisk != nil) || 121 (spec.Volume != nil && spec.Volume.AzureDisk != nil) 122} 123 124func (plugin *azureDataDiskPlugin) RequiresRemount(spec *volume.Spec) bool { 125 return false 126} 127 128func (plugin *azureDataDiskPlugin) SupportsMountOption() bool { 129 return true 130} 131 132func (plugin *azureDataDiskPlugin) SupportsBulkVolumeVerification() bool { 133 return false 134} 135 136func (plugin *azureDataDiskPlugin) GetVolumeLimits() (map[string]int64, error) { 137 volumeLimits := map[string]int64{ 138 util.AzureVolumeLimitKey: defaultAzureVolumeLimit, 139 } 140 141 az, err := getCloud(plugin.host) 142 if err != nil { 143 // if we can't fetch cloudprovider we return an error 144 // hoping external CCM or admin can set it. Returning 145 // default values from here will mean, no one can 146 // override them. 147 return nil, fmt.Errorf("failed to get azure cloud in GetVolumeLimits, plugin.host: %s", plugin.host.GetHostName()) 148 } 149 150 instances, ok := az.Instances() 151 if !ok { 152 klog.Warningf("Failed to get instances from cloud provider") 153 return volumeLimits, nil 154 } 155 156 instanceType, err := instances.InstanceType(context.TODO(), plugin.host.GetNodeName()) 157 if err != nil { 158 klog.Errorf("Failed to get instance type from Azure cloud provider, nodeName: %s", plugin.host.GetNodeName()) 159 return volumeLimits, nil 160 } 161 162 volumeLimits = map[string]int64{ 163 util.AzureVolumeLimitKey: getMaxDataDiskCount(instanceType), 164 } 165 166 return volumeLimits, nil 167} 168 169func getMaxDataDiskCount(instanceType string) int64 { 170 vmsize := strings.ToUpper(instanceType) 171 maxDataDiskCount, exists := maxDataDiskCountMap[vmsize] 172 if exists { 173 klog.V(12).Infof("got a matching size in getMaxDataDiskCount, VM Size: %s, MaxDataDiskCount: %d", vmsize, maxDataDiskCount) 174 return maxDataDiskCount 175 } 176 177 klog.V(12).Infof("not found a matching size in getMaxDataDiskCount, VM Size: %s, use default volume limit: %d", vmsize, defaultAzureVolumeLimit) 178 return defaultAzureVolumeLimit 179} 180 181func (plugin *azureDataDiskPlugin) VolumeLimitKey(spec *volume.Spec) string { 182 return util.AzureVolumeLimitKey 183} 184 185func (plugin *azureDataDiskPlugin) GetAccessModes() []v1.PersistentVolumeAccessMode { 186 return []v1.PersistentVolumeAccessMode{ 187 v1.ReadWriteOnce, 188 } 189} 190 191// NewAttacher initializes an Attacher 192func (plugin *azureDataDiskPlugin) NewAttacher() (volume.Attacher, error) { 193 azure, err := getCloud(plugin.host) 194 if err != nil { 195 klog.Errorf("failed to get azure cloud in NewAttacher, plugin.host : %s, err:%v", plugin.host.GetHostName(), err) 196 return nil, err 197 } 198 199 return &azureDiskAttacher{ 200 plugin: plugin, 201 cloud: azure, 202 }, nil 203} 204 205func (plugin *azureDataDiskPlugin) NewDetacher() (volume.Detacher, error) { 206 azure, err := getCloud(plugin.host) 207 if err != nil { 208 klog.V(4).Infof("failed to get azure cloud in NewDetacher, plugin.host : %s", plugin.host.GetHostName()) 209 return nil, err 210 } 211 212 return &azureDiskDetacher{ 213 plugin: plugin, 214 cloud: azure, 215 }, nil 216} 217 218func (plugin *azureDataDiskPlugin) CanAttach(spec *volume.Spec) (bool, error) { 219 return true, nil 220} 221 222func (plugin *azureDataDiskPlugin) CanDeviceMount(spec *volume.Spec) (bool, error) { 223 return true, nil 224} 225 226func (plugin *azureDataDiskPlugin) NewDeleter(spec *volume.Spec) (volume.Deleter, error) { 227 volumeSource, _, err := getVolumeSource(spec) 228 if err != nil { 229 return nil, err 230 } 231 232 disk := makeDataDisk(spec.Name(), "", volumeSource.DiskName, plugin.host, plugin) 233 234 return &azureDiskDeleter{ 235 spec: spec, 236 plugin: plugin, 237 dataDisk: disk, 238 }, nil 239} 240 241func (plugin *azureDataDiskPlugin) NewProvisioner(options volume.VolumeOptions) (volume.Provisioner, error) { 242 if len(options.PVC.Spec.AccessModes) == 0 { 243 options.PVC.Spec.AccessModes = plugin.GetAccessModes() 244 } 245 246 return &azureDiskProvisioner{ 247 plugin: plugin, 248 options: options, 249 }, nil 250} 251 252func (plugin *azureDataDiskPlugin) NewMounter(spec *volume.Spec, pod *v1.Pod, options volume.VolumeOptions) (volume.Mounter, error) { 253 volumeSource, _, err := getVolumeSource(spec) 254 if err != nil { 255 return nil, err 256 } 257 disk := makeDataDisk(spec.Name(), pod.UID, volumeSource.DiskName, plugin.host, plugin) 258 259 return &azureDiskMounter{ 260 plugin: plugin, 261 spec: spec, 262 options: options, 263 dataDisk: disk, 264 }, nil 265} 266 267func (plugin *azureDataDiskPlugin) NewUnmounter(volName string, podUID types.UID) (volume.Unmounter, error) { 268 disk := makeDataDisk(volName, podUID, "", plugin.host, plugin) 269 270 return &azureDiskUnmounter{ 271 plugin: plugin, 272 dataDisk: disk, 273 }, nil 274} 275 276func (plugin *azureDataDiskPlugin) RequiresFSResize() bool { 277 return true 278} 279 280func (plugin *azureDataDiskPlugin) ExpandVolumeDevice( 281 spec *volume.Spec, 282 newSize resource.Quantity, 283 oldSize resource.Quantity) (resource.Quantity, error) { 284 if spec.PersistentVolume == nil || spec.PersistentVolume.Spec.AzureDisk == nil { 285 return oldSize, fmt.Errorf("invalid PV spec") 286 } 287 288 diskController, err := getDiskController(plugin.host) 289 if err != nil { 290 return oldSize, err 291 } 292 293 return diskController.ResizeDisk(spec.PersistentVolume.Spec.AzureDisk.DataDiskURI, oldSize, newSize) 294} 295 296func (plugin *azureDataDiskPlugin) NodeExpand(resizeOptions volume.NodeResizeOptions) (bool, error) { 297 fsVolume, err := util.CheckVolumeModeFilesystem(resizeOptions.VolumeSpec) 298 if err != nil { 299 return false, fmt.Errorf("error checking VolumeMode: %v", err) 300 } 301 // if volume is not a fs file system, there is nothing for us to do here. 302 if !fsVolume { 303 return true, nil 304 } 305 _, err = util.GenericResizeFS(plugin.host, plugin.GetPluginName(), resizeOptions.DevicePath, resizeOptions.DeviceMountPath) 306 if err != nil { 307 return false, err 308 } 309 return true, nil 310} 311 312var _ volume.NodeExpandableVolumePlugin = &azureDataDiskPlugin{} 313 314func (plugin *azureDataDiskPlugin) ConstructVolumeSpec(volumeName, mountPath string) (*volume.Spec, error) { 315 mounter := plugin.host.GetMounter(plugin.GetPluginName()) 316 kvh, ok := plugin.host.(volume.KubeletVolumeHost) 317 if !ok { 318 return nil, fmt.Errorf("plugin volume host does not implement KubeletVolumeHost interface") 319 } 320 hu := kvh.GetHostUtil() 321 pluginMntDir := util.GetPluginMountDir(plugin.host, plugin.GetPluginName()) 322 sourceName, err := hu.GetDeviceNameFromMount(mounter, mountPath, pluginMntDir) 323 324 if err != nil { 325 return nil, err 326 } 327 328 azureVolume := &v1.Volume{ 329 Name: volumeName, 330 VolumeSource: v1.VolumeSource{ 331 AzureDisk: &v1.AzureDiskVolumeSource{ 332 DataDiskURI: sourceName, 333 }, 334 }, 335 } 336 return volume.NewSpecFromVolume(azureVolume), nil 337} 338 339func (plugin *azureDataDiskPlugin) GetDeviceMountRefs(deviceMountPath string) ([]string, error) { 340 m := plugin.host.GetMounter(plugin.GetPluginName()) 341 return m.GetMountRefs(deviceMountPath) 342} 343 344func (plugin *azureDataDiskPlugin) NewDeviceMounter() (volume.DeviceMounter, error) { 345 return plugin.NewAttacher() 346} 347 348func (plugin *azureDataDiskPlugin) NewDeviceUnmounter() (volume.DeviceUnmounter, error) { 349 return plugin.NewDetacher() 350} 351