1/* 2Copyright 2019 The Kubernetes Authors. 3 4Licensed under the Apache License, Version 2.0 (the "License"); 5you may not use this file except in compliance with the License. 6You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10Unless required by applicable law or agreed to in writing, software 11distributed under the License is distributed on an "AS IS" BASIS, 12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13See the License for the specific language governing permissions and 14limitations under the License. 15*/ 16 17package plugins 18 19import ( 20 "fmt" 21 "regexp" 22 "strings" 23 24 v1 "k8s.io/api/core/v1" 25 storage "k8s.io/api/storage/v1" 26 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 27 "k8s.io/klog/v2" 28) 29 30const ( 31 // AzureFileDriverName is the name of the CSI driver for Azure File 32 AzureFileDriverName = "file.csi.azure.com" 33 // AzureFileInTreePluginName is the name of the intree plugin for Azure file 34 AzureFileInTreePluginName = "kubernetes.io/azure-file" 35 36 separator = "#" 37 volumeIDTemplate = "%s#%s#%s#%s" 38 // Parameter names defined in azure file CSI driver, refer to 39 // https://github.com/kubernetes-sigs/azurefile-csi-driver/blob/master/docs/driver-parameters.md 40 shareNameField = "sharename" 41 secretNameField = "secretname" 42 secretNamespaceField = "secretnamespace" 43 secretNameTemplate = "azure-storage-account-%s-secret" 44 defaultSecretNamespace = "default" 45 resourceGroupAnnotation = "kubernetes.io/azure-file-resource-group" 46) 47 48var _ InTreePlugin = &azureFileCSITranslator{} 49 50var secretNameFormatRE = regexp.MustCompile(`azure-storage-account-(.+)-secret`) 51 52// azureFileCSITranslator handles translation of PV spec from In-tree 53// Azure File to CSI Azure File and vice versa 54type azureFileCSITranslator struct{} 55 56// NewAzureFileCSITranslator returns a new instance of azureFileTranslator 57func NewAzureFileCSITranslator() InTreePlugin { 58 return &azureFileCSITranslator{} 59} 60 61// TranslateInTreeStorageClassParametersToCSI translates InTree Azure File storage class parameters to CSI storage class 62func (t *azureFileCSITranslator) TranslateInTreeStorageClassToCSI(sc *storage.StorageClass) (*storage.StorageClass, error) { 63 return sc, nil 64} 65 66// TranslateInTreeInlineVolumeToCSI takes a Volume with AzureFile set from in-tree 67// and converts the AzureFile source to a CSIPersistentVolumeSource 68func (t *azureFileCSITranslator) TranslateInTreeInlineVolumeToCSI(volume *v1.Volume, podNamespace string) (*v1.PersistentVolume, error) { 69 if volume == nil || volume.AzureFile == nil { 70 return nil, fmt.Errorf("volume is nil or Azure File not defined on volume") 71 } 72 73 azureSource := volume.AzureFile 74 accountName, err := getStorageAccountName(azureSource.SecretName) 75 if err != nil { 76 klog.Warningf("getStorageAccountName(%s) returned with error: %v", azureSource.SecretName, err) 77 accountName = azureSource.SecretName 78 } 79 80 secretNamespace := defaultSecretNamespace 81 if podNamespace != "" { 82 secretNamespace = podNamespace 83 } 84 85 var ( 86 pv = &v1.PersistentVolume{ 87 ObjectMeta: metav1.ObjectMeta{ 88 // Must be unique per disk as it is used as the unique part of the 89 // staging path 90 Name: fmt.Sprintf("%s-%s", AzureFileDriverName, azureSource.ShareName), 91 }, 92 Spec: v1.PersistentVolumeSpec{ 93 PersistentVolumeSource: v1.PersistentVolumeSource{ 94 CSI: &v1.CSIPersistentVolumeSource{ 95 Driver: AzureFileDriverName, 96 VolumeHandle: fmt.Sprintf(volumeIDTemplate, "", accountName, azureSource.ShareName, ""), 97 ReadOnly: azureSource.ReadOnly, 98 VolumeAttributes: map[string]string{shareNameField: azureSource.ShareName}, 99 NodeStageSecretRef: &v1.SecretReference{ 100 Name: azureSource.SecretName, 101 Namespace: secretNamespace, 102 }, 103 }, 104 }, 105 AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteMany}, 106 }, 107 } 108 ) 109 110 return pv, nil 111} 112 113// TranslateInTreePVToCSI takes a PV with AzureFile set from in-tree 114// and converts the AzureFile source to a CSIPersistentVolumeSource 115func (t *azureFileCSITranslator) TranslateInTreePVToCSI(pv *v1.PersistentVolume) (*v1.PersistentVolume, error) { 116 if pv == nil || pv.Spec.AzureFile == nil { 117 return nil, fmt.Errorf("pv is nil or Azure File source not defined on pv") 118 } 119 120 azureSource := pv.Spec.PersistentVolumeSource.AzureFile 121 accountName, err := getStorageAccountName(azureSource.SecretName) 122 if err != nil { 123 klog.Warningf("getStorageAccountName(%s) returned with error: %v", azureSource.SecretName, err) 124 accountName = azureSource.SecretName 125 } 126 resourceGroup := "" 127 if pv.ObjectMeta.Annotations != nil { 128 if v, ok := pv.ObjectMeta.Annotations[resourceGroupAnnotation]; ok { 129 resourceGroup = v 130 } 131 } 132 volumeID := fmt.Sprintf(volumeIDTemplate, resourceGroup, accountName, azureSource.ShareName, "") 133 134 var ( 135 // refer to https://github.com/kubernetes-sigs/azurefile-csi-driver/blob/master/docs/driver-parameters.md 136 csiSource = &v1.CSIPersistentVolumeSource{ 137 Driver: AzureFileDriverName, 138 NodeStageSecretRef: &v1.SecretReference{ 139 Name: azureSource.SecretName, 140 Namespace: defaultSecretNamespace, 141 }, 142 ReadOnly: azureSource.ReadOnly, 143 VolumeAttributes: map[string]string{shareNameField: azureSource.ShareName}, 144 VolumeHandle: volumeID, 145 } 146 ) 147 148 if azureSource.SecretNamespace != nil { 149 csiSource.NodeStageSecretRef.Namespace = *azureSource.SecretNamespace 150 } 151 152 pv.Spec.PersistentVolumeSource.AzureFile = nil 153 pv.Spec.PersistentVolumeSource.CSI = csiSource 154 155 return pv, nil 156} 157 158// TranslateCSIPVToInTree takes a PV with CSIPersistentVolumeSource set and 159// translates the Azure File CSI source to a AzureFile source. 160func (t *azureFileCSITranslator) TranslateCSIPVToInTree(pv *v1.PersistentVolume) (*v1.PersistentVolume, error) { 161 if pv == nil || pv.Spec.CSI == nil { 162 return nil, fmt.Errorf("pv is nil or CSI source not defined on pv") 163 } 164 csiSource := pv.Spec.CSI 165 166 // refer to https://github.com/kubernetes-sigs/azurefile-csi-driver/blob/master/docs/driver-parameters.md 167 azureSource := &v1.AzureFilePersistentVolumeSource{ 168 ReadOnly: csiSource.ReadOnly, 169 } 170 171 for k, v := range csiSource.VolumeAttributes { 172 switch strings.ToLower(k) { 173 case shareNameField: 174 azureSource.ShareName = v 175 case secretNameField: 176 azureSource.SecretName = v 177 case secretNamespaceField: 178 ns := v 179 azureSource.SecretNamespace = &ns 180 } 181 } 182 183 resourceGroup := "" 184 if csiSource.NodeStageSecretRef != nil && csiSource.NodeStageSecretRef.Name != "" { 185 azureSource.SecretName = csiSource.NodeStageSecretRef.Name 186 azureSource.SecretNamespace = &csiSource.NodeStageSecretRef.Namespace 187 } 188 if azureSource.ShareName == "" || azureSource.SecretName == "" { 189 rg, storageAccount, fileShareName, _, err := getFileShareInfo(csiSource.VolumeHandle) 190 if err != nil { 191 return nil, err 192 } 193 if azureSource.ShareName == "" { 194 azureSource.ShareName = fileShareName 195 } 196 if azureSource.SecretName == "" { 197 azureSource.SecretName = fmt.Sprintf(secretNameTemplate, storageAccount) 198 } 199 resourceGroup = rg 200 } 201 202 if azureSource.SecretNamespace == nil { 203 ns := defaultSecretNamespace 204 azureSource.SecretNamespace = &ns 205 } 206 207 pv.Spec.CSI = nil 208 pv.Spec.AzureFile = azureSource 209 if pv.ObjectMeta.Annotations == nil { 210 pv.ObjectMeta.Annotations = map[string]string{} 211 } 212 if resourceGroup != "" { 213 pv.ObjectMeta.Annotations[resourceGroupAnnotation] = resourceGroup 214 } 215 216 return pv, nil 217} 218 219// CanSupport tests whether the plugin supports a given volume 220// specification from the API. The spec pointer should be considered 221// const. 222func (t *azureFileCSITranslator) CanSupport(pv *v1.PersistentVolume) bool { 223 return pv != nil && pv.Spec.AzureFile != nil 224} 225 226// CanSupportInline tests whether the plugin supports a given inline volume 227// specification from the API. The spec pointer should be considered 228// const. 229func (t *azureFileCSITranslator) CanSupportInline(volume *v1.Volume) bool { 230 return volume != nil && volume.AzureFile != nil 231} 232 233// GetInTreePluginName returns the name of the intree plugin driver 234func (t *azureFileCSITranslator) GetInTreePluginName() string { 235 return AzureFileInTreePluginName 236} 237 238// GetCSIPluginName returns the name of the CSI plugin 239func (t *azureFileCSITranslator) GetCSIPluginName() string { 240 return AzureFileDriverName 241} 242 243func (t *azureFileCSITranslator) RepairVolumeHandle(volumeHandle, nodeID string) (string, error) { 244 return volumeHandle, nil 245} 246 247// get file share info according to volume id, e.g. 248// input: "rg#f5713de20cde511e8ba4900#pvc-file-dynamic-17e43f84-f474-11e8-acd0-000d3a00df41#diskname.vhd" 249// output: rg, f5713de20cde511e8ba4900, pvc-file-dynamic-17e43f84-f474-11e8-acd0-000d3a00df41, diskname.vhd 250func getFileShareInfo(id string) (string, string, string, string, error) { 251 segments := strings.Split(id, separator) 252 if len(segments) < 3 { 253 return "", "", "", "", fmt.Errorf("error parsing volume id: %q, should at least contain two #", id) 254 } 255 var diskName string 256 if len(segments) > 3 { 257 diskName = segments[3] 258 } 259 return segments[0], segments[1], segments[2], diskName, nil 260} 261 262// get storage account name from secret name 263func getStorageAccountName(secretName string) (string, error) { 264 matches := secretNameFormatRE.FindStringSubmatch(secretName) 265 if len(matches) != 2 { 266 return "", fmt.Errorf("could not get account name from %s, correct format: %s", secretName, secretNameFormatRE) 267 } 268 return matches[1], nil 269} 270