1/* 2Copyright 2018 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 csi 18 19import ( 20 "context" 21 "encoding/json" 22 "errors" 23 "fmt" 24 "os" 25 "path/filepath" 26 "strconv" 27 "time" 28 29 api "k8s.io/api/core/v1" 30 storage "k8s.io/api/storage/v1" 31 meta "k8s.io/apimachinery/pkg/apis/meta/v1" 32 utilfeature "k8s.io/apiserver/pkg/util/feature" 33 "k8s.io/client-go/kubernetes" 34 "k8s.io/klog/v2" 35 "k8s.io/kubernetes/pkg/features" 36 "k8s.io/kubernetes/pkg/volume" 37 utilstrings "k8s.io/utils/strings" 38) 39 40const ( 41 // TestInformerSyncPeriod is informer sync period duration for testing 42 TestInformerSyncPeriod = 100 * time.Millisecond 43 // TestInformerSyncTimeout is informer timeout duration for testing 44 TestInformerSyncTimeout = 30 * time.Second 45) 46 47func getCredentialsFromSecret(k8s kubernetes.Interface, secretRef *api.SecretReference) (map[string]string, error) { 48 credentials := map[string]string{} 49 secret, err := k8s.CoreV1().Secrets(secretRef.Namespace).Get(context.TODO(), secretRef.Name, meta.GetOptions{}) 50 if err != nil { 51 return credentials, errors.New(log("failed to find the secret %s in the namespace %s with error: %v", secretRef.Name, secretRef.Namespace, err)) 52 } 53 for key, value := range secret.Data { 54 credentials[key] = string(value) 55 } 56 57 return credentials, nil 58} 59 60// saveVolumeData persists parameter data as json file at the provided location 61func saveVolumeData(dir string, fileName string, data map[string]string) error { 62 dataFilePath := filepath.Join(dir, fileName) 63 klog.V(4).Info(log("saving volume data file [%s]", dataFilePath)) 64 file, err := os.Create(dataFilePath) 65 if err != nil { 66 return errors.New(log("failed to save volume data file %s: %v", dataFilePath, err)) 67 } 68 defer file.Close() 69 if err := json.NewEncoder(file).Encode(data); err != nil { 70 return errors.New(log("failed to save volume data file %s: %v", dataFilePath, err)) 71 } 72 klog.V(4).Info(log("volume data file saved successfully [%s]", dataFilePath)) 73 return nil 74} 75 76// loadVolumeData loads volume info from specified json file/location 77func loadVolumeData(dir string, fileName string) (map[string]string, error) { 78 // remove /mount at the end 79 dataFileName := filepath.Join(dir, fileName) 80 klog.V(4).Info(log("loading volume data file [%s]", dataFileName)) 81 82 file, err := os.Open(dataFileName) 83 if err != nil { 84 return nil, errors.New(log("failed to open volume data file [%s]: %v", dataFileName, err)) 85 } 86 defer file.Close() 87 data := map[string]string{} 88 if err := json.NewDecoder(file).Decode(&data); err != nil { 89 return nil, errors.New(log("failed to parse volume data file [%s]: %v", dataFileName, err)) 90 } 91 92 return data, nil 93} 94 95func getCSISourceFromSpec(spec *volume.Spec) (*api.CSIPersistentVolumeSource, error) { 96 return getPVSourceFromSpec(spec) 97} 98 99func getReadOnlyFromSpec(spec *volume.Spec) (bool, error) { 100 if spec.PersistentVolume != nil && 101 spec.PersistentVolume.Spec.CSI != nil { 102 return spec.ReadOnly, nil 103 } 104 105 return false, fmt.Errorf("CSIPersistentVolumeSource not defined in spec") 106} 107 108// log prepends log string with `kubernetes.io/csi` 109func log(msg string, parts ...interface{}) string { 110 return fmt.Sprintf(fmt.Sprintf("%s: %s", CSIPluginName, msg), parts...) 111} 112 113// getVolumePluginDir returns the path where CSI plugin keeps metadata for given volume 114func getVolumePluginDir(specVolID string, host volume.VolumeHost) string { 115 sanitizedSpecVolID := utilstrings.EscapeQualifiedName(specVolID) 116 return filepath.Join(host.GetVolumeDevicePluginDir(CSIPluginName), sanitizedSpecVolID) 117} 118 119// getVolumeDevicePluginDir returns the path where the CSI plugin keeps the 120// symlink for a block device associated with a given specVolumeID. 121// path: plugins/kubernetes.io/csi/volumeDevices/{specVolumeID}/dev 122func getVolumeDevicePluginDir(specVolID string, host volume.VolumeHost) string { 123 return filepath.Join(getVolumePluginDir(specVolID, host), "dev") 124} 125 126// getVolumeDeviceDataDir returns the path where the CSI plugin keeps the 127// volume data for a block device associated with a given specVolumeID. 128// path: plugins/kubernetes.io/csi/volumeDevices/{specVolumeID}/data 129func getVolumeDeviceDataDir(specVolID string, host volume.VolumeHost) string { 130 return filepath.Join(getVolumePluginDir(specVolID, host), "data") 131} 132 133// hasReadWriteOnce returns true if modes contains v1.ReadWriteOnce 134func hasReadWriteOnce(modes []api.PersistentVolumeAccessMode) bool { 135 if modes == nil { 136 return false 137 } 138 for _, mode := range modes { 139 if mode == api.ReadWriteOnce { 140 return true 141 } 142 } 143 return false 144} 145 146// getSourceFromSpec returns either CSIVolumeSource or CSIPersistentVolumeSource, but not both 147func getSourceFromSpec(spec *volume.Spec) (*api.CSIVolumeSource, *api.CSIPersistentVolumeSource, error) { 148 if spec == nil { 149 return nil, nil, fmt.Errorf("volume.Spec nil") 150 } 151 if spec.Volume != nil && spec.PersistentVolume != nil { 152 return nil, nil, fmt.Errorf("volume.Spec has both volume and persistent volume sources") 153 } 154 if spec.Volume != nil && spec.Volume.CSI != nil && utilfeature.DefaultFeatureGate.Enabled(features.CSIInlineVolume) { 155 return spec.Volume.CSI, nil, nil 156 } 157 if spec.PersistentVolume != nil && 158 spec.PersistentVolume.Spec.CSI != nil { 159 return nil, spec.PersistentVolume.Spec.CSI, nil 160 } 161 162 return nil, nil, fmt.Errorf("volume source not found in volume.Spec") 163} 164 165// getPVSourceFromSpec ensures only CSIPersistentVolumeSource is present in volume.Spec 166func getPVSourceFromSpec(spec *volume.Spec) (*api.CSIPersistentVolumeSource, error) { 167 volSrc, pvSrc, err := getSourceFromSpec(spec) 168 if err != nil { 169 return nil, err 170 } 171 if volSrc != nil { 172 return nil, fmt.Errorf("unexpected api.CSIVolumeSource found in volume.Spec") 173 } 174 return pvSrc, nil 175} 176 177// GetCSIMounterPath returns the mounter path given the base path. 178func GetCSIMounterPath(path string) string { 179 return filepath.Join(path, "/mount") 180} 181 182// GetCSIDriverName returns the csi driver name 183func GetCSIDriverName(spec *volume.Spec) (string, error) { 184 volSrc, pvSrc, err := getSourceFromSpec(spec) 185 if err != nil { 186 return "", err 187 } 188 189 switch { 190 case volSrc != nil && utilfeature.DefaultFeatureGate.Enabled(features.CSIInlineVolume): 191 return volSrc.Driver, nil 192 case pvSrc != nil: 193 return pvSrc.Driver, nil 194 default: 195 return "", errors.New(log("volume source not found in volume.Spec")) 196 } 197} 198 199func createCSIOperationContext(volumeSpec *volume.Spec, timeout time.Duration) (context.Context, context.CancelFunc) { 200 migrated := false 201 if volumeSpec != nil { 202 migrated = volumeSpec.Migrated 203 } 204 ctx := context.WithValue(context.Background(), additionalInfoKey, additionalInfo{Migrated: strconv.FormatBool(migrated)}) 205 return context.WithTimeout(ctx, timeout) 206} 207 208// getPodInfoAttrs returns pod info for NodePublish 209func getPodInfoAttrs(pod *api.Pod, volumeMode storage.VolumeLifecycleMode) map[string]string { 210 attrs := map[string]string{ 211 "csi.storage.k8s.io/pod.name": pod.Name, 212 "csi.storage.k8s.io/pod.namespace": pod.Namespace, 213 "csi.storage.k8s.io/pod.uid": string(pod.UID), 214 "csi.storage.k8s.io/serviceAccount.name": pod.Spec.ServiceAccountName, 215 } 216 if utilfeature.DefaultFeatureGate.Enabled(features.CSIInlineVolume) { 217 attrs["csi.storage.k8s.io/ephemeral"] = strconv.FormatBool(volumeMode == storage.VolumeLifecycleEphemeral) 218 } 219 return attrs 220} 221