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