github.com/jingruilea/kubeedge@v1.2.0-beta.0.0.20200410162146-4bb8902b3879/edge/pkg/edged/volume/csi/csi_mounter.go (about) 1 /* 2 Copyright 2017 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 16 @CHANGELOG 17 KubeEdge Authors: To create mini-kubelet for edge deployment scenario, 18 this file is derived from kubernetes v1.15.3, 19 and the full file path is k8s.io/kubernetes/pkg/volume/csi/csi_mounter.go 20 and make some modifications including: 21 1. remove podAttributes in SetUpAt function. 22 */ 23 24 package csi 25 26 import ( 27 "context" 28 "crypto/sha256" 29 "fmt" 30 "os" 31 "path" 32 "path/filepath" 33 34 api "k8s.io/api/core/v1" 35 apierrs "k8s.io/apimachinery/pkg/api/errors" 36 "k8s.io/apimachinery/pkg/types" 37 utilfeature "k8s.io/apiserver/pkg/util/feature" 38 "k8s.io/client-go/kubernetes" 39 "k8s.io/klog" 40 "k8s.io/kubernetes/pkg/features" 41 "k8s.io/kubernetes/pkg/volume" 42 utilstrings "k8s.io/utils/strings" 43 ) 44 45 //TODO (vladimirvivien) move this in a central loc later 46 var ( 47 volDataKey = struct { 48 specVolID, 49 volHandle, 50 driverName, 51 nodeName, 52 attachmentID, 53 driverMode string 54 }{ 55 "specVolID", 56 "volumeHandle", 57 "driverName", 58 "nodeName", 59 "attachmentID", 60 "driverMode", 61 } 62 ) 63 64 type csiMountMgr struct { 65 csiClientGetter 66 k8s kubernetes.Interface 67 plugin *csiPlugin 68 driverName csiDriverName 69 driverMode driverMode 70 volumeID string 71 specVolumeID string 72 readOnly bool 73 supportsSELinux bool 74 spec *volume.Spec 75 pod *api.Pod 76 podUID types.UID 77 options volume.VolumeOptions 78 publishContext map[string]string 79 kubeVolHost volume.KubeletVolumeHost 80 volume.MetricsProvider 81 } 82 83 // volume.Volume methods 84 var _ volume.Volume = &csiMountMgr{} 85 86 func (c *csiMountMgr) GetPath() string { 87 dir := filepath.Join(getTargetPath(c.podUID, c.specVolumeID, c.plugin.host), "/mount") 88 klog.V(4).Info(log("mounter.GetPath generated [%s]", dir)) 89 return dir 90 } 91 92 func getTargetPath(uid types.UID, specVolumeID string, host volume.VolumeHost) string { 93 specVolID := utilstrings.EscapeQualifiedName(specVolumeID) 94 return host.GetPodVolumeDir(uid, utilstrings.EscapeQualifiedName(CSIPluginName), specVolID) 95 } 96 97 // volume.Mounter methods 98 var _ volume.Mounter = &csiMountMgr{} 99 100 func (c *csiMountMgr) CanMount() error { 101 return nil 102 } 103 104 func (c *csiMountMgr) SetUp(mounterArgs volume.MounterArgs) error { 105 return c.SetUpAt(c.GetPath(), mounterArgs) 106 } 107 108 func (c *csiMountMgr) SetUpAt(dir string, mounterArgs volume.MounterArgs) error { 109 klog.V(4).Infof(log("Mounter.SetUpAt(%s)", dir)) 110 111 mounted, err := isDirMounted(c.plugin, dir) 112 if err != nil { 113 klog.Error(log("mounter.SetUpAt failed while checking mount status for dir [%s]", dir)) 114 return err 115 } 116 117 if mounted { 118 klog.V(4).Info(log("mounter.SetUpAt skipping mount, dir already mounted [%s]", dir)) 119 return nil 120 } 121 122 csi, err := c.csiClientGetter.Get() 123 if err != nil { 124 klog.Error(log("mounter.SetUpAt failed to get CSI client: %v", err)) 125 return err 126 } 127 ctx, cancel := context.WithTimeout(context.Background(), csiTimeout) 128 defer cancel() 129 130 volSrc, pvSrc, err := getSourceFromSpec(c.spec) 131 if err != nil { 132 klog.Error(log("mounter.SetupAt failed to get CSI persistent source: %v", err)) 133 return err 134 } 135 136 driverName := c.driverName 137 volumeHandle := c.volumeID 138 readOnly := c.readOnly 139 accessMode := api.ReadWriteOnce 140 141 var ( 142 fsType string 143 volAttribs map[string]string 144 nodePublishSecrets map[string]string 145 publishContext map[string]string 146 mountOptions []string 147 deviceMountPath string 148 secretRef *api.SecretReference 149 ) 150 151 switch { 152 case volSrc != nil: 153 if !utilfeature.DefaultFeatureGate.Enabled(features.CSIInlineVolume) { 154 return fmt.Errorf("CSIInlineVolume feature required") 155 } 156 if c.driverMode != ephemeralDriverMode { 157 return fmt.Errorf("unexpected driver mode: %s", c.driverMode) 158 } 159 if volSrc.FSType != nil { 160 fsType = *volSrc.FSType 161 } 162 163 volAttribs = volSrc.VolumeAttributes 164 165 if volSrc.NodePublishSecretRef != nil { 166 secretName := volSrc.NodePublishSecretRef.Name 167 ns := c.pod.Namespace 168 secretRef = &api.SecretReference{Name: secretName, Namespace: ns} 169 } 170 case pvSrc != nil: 171 if c.driverMode != persistentDriverMode { 172 return fmt.Errorf("unexpected driver mode: %s", c.driverMode) 173 } 174 175 fsType = pvSrc.FSType 176 177 volAttribs = pvSrc.VolumeAttributes 178 179 if pvSrc.NodePublishSecretRef != nil { 180 secretRef = pvSrc.NodePublishSecretRef 181 } 182 183 //TODO (vladimirvivien) implement better AccessModes mapping between k8s and CSI 184 if c.spec.PersistentVolume.Spec.AccessModes != nil { 185 accessMode = c.spec.PersistentVolume.Spec.AccessModes[0] 186 } 187 188 mountOptions = c.spec.PersistentVolume.Spec.MountOptions 189 190 // Check for STAGE_UNSTAGE_VOLUME set and populate deviceMountPath if so 191 stageUnstageSet, err := csi.NodeSupportsStageUnstage(ctx) 192 if err != nil { 193 klog.Error(log("mounter.SetUpAt failed to check for STAGE_UNSTAGE_VOLUME capabilty: %v", err)) 194 return err 195 } 196 197 if stageUnstageSet { 198 deviceMountPath, err = makeDeviceMountPath(c.plugin, c.spec) 199 if err != nil { 200 klog.Error(log("mounter.SetUpAt failed to make device mount path: %v", err)) 201 return err 202 } 203 } 204 205 // search for attachment by VolumeAttachment.Spec.Source.PersistentVolumeName 206 if c.publishContext == nil { 207 nodeName := string(c.plugin.host.GetNodeName()) 208 c.publishContext, err = c.plugin.getPublishContext(c.k8s, volumeHandle, string(driverName), nodeName) 209 if err != nil { 210 return err 211 } 212 publishContext = c.publishContext 213 } 214 215 default: 216 return fmt.Errorf("volume source not found in volume.Spec") 217 } 218 219 // create target_dir before call to NodePublish 220 if err := os.MkdirAll(dir, 0750); err != nil { 221 klog.Error(log("mouter.SetUpAt failed to create dir %#v: %v", dir, err)) 222 return err 223 } 224 klog.V(4).Info(log("created target path successfully [%s]", dir)) 225 226 nodePublishSecrets = map[string]string{} 227 if secretRef != nil { 228 nodePublishSecrets, err = getCredentialsFromSecret(c.k8s, secretRef) 229 if err != nil { 230 return fmt.Errorf("fetching NodePublishSecretRef %s/%s failed: %v", 231 secretRef.Namespace, secretRef.Name, err) 232 } 233 234 } 235 236 err = csi.NodePublishVolume( 237 ctx, 238 volumeHandle, 239 readOnly, 240 deviceMountPath, 241 dir, 242 accessMode, 243 publishContext, 244 volAttribs, 245 nodePublishSecrets, 246 fsType, 247 mountOptions, 248 ) 249 250 if err != nil { 251 klog.Errorf(log("mounter.SetupAt failed: %v", err)) 252 if removeMountDirErr := removeMountDir(c.plugin, dir); removeMountDirErr != nil { 253 klog.Error(log("mounter.SetupAt failed to remove mount dir after a NodePublish() error [%s]: %v", dir, removeMountDirErr)) 254 } 255 return err 256 } 257 258 c.supportsSELinux, err = c.kubeVolHost.GetHostUtil().GetSELinuxSupport(dir) 259 if err != nil { 260 klog.V(2).Info(log("error checking for SELinux support: %s", err)) 261 } 262 263 // apply volume ownership 264 // The following logic is derived from https://github.com/kubernetes/kubernetes/issues/66323 265 // if fstype is "", then skip fsgroup (could be indication of non-block filesystem) 266 // if fstype is provided and pv.AccessMode == ReadWriteOnly, then apply fsgroup 267 268 err = c.applyFSGroup(fsType, mounterArgs.FsGroup) 269 if err != nil { 270 // attempt to rollback mount. 271 fsGrpErr := fmt.Errorf("applyFSGroup failed for vol %s: %v", c.volumeID, err) 272 if unpubErr := csi.NodeUnpublishVolume(ctx, c.volumeID, dir); unpubErr != nil { 273 klog.Error(log("NodeUnpublishVolume failed for [%s]: %v", c.volumeID, unpubErr)) 274 return fsGrpErr 275 } 276 277 if unmountErr := removeMountDir(c.plugin, dir); unmountErr != nil { 278 klog.Error(log("removeMountDir failed for [%s]: %v", dir, unmountErr)) 279 return fsGrpErr 280 } 281 return fsGrpErr 282 } 283 284 klog.V(4).Infof(log("mounter.SetUp successfully requested NodePublish [%s]", dir)) 285 return nil 286 } 287 288 func (c *csiMountMgr) podAttributes() (map[string]string, error) { 289 if !utilfeature.DefaultFeatureGate.Enabled(features.CSIDriverRegistry) { 290 return nil, nil 291 } 292 293 kletHost, ok := c.plugin.host.(volume.KubeletVolumeHost) 294 if ok { 295 kletHost.WaitForCacheSync() 296 } 297 298 if c.plugin.csiDriverLister == nil { 299 return nil, fmt.Errorf("CSIDriverLister not found") 300 } 301 302 csiDriver, err := c.plugin.csiDriverLister.Get(string(c.driverName)) 303 if err != nil { 304 if apierrs.IsNotFound(err) { 305 klog.V(4).Infof(log("CSIDriver %q not found, not adding pod information", c.driverName)) 306 return nil, nil 307 } 308 return nil, err 309 } 310 311 // if PodInfoOnMount is not set or false we do not set pod attributes 312 if csiDriver.Spec.PodInfoOnMount == nil || *csiDriver.Spec.PodInfoOnMount == false { 313 klog.V(4).Infof(log("CSIDriver %q does not require pod information", c.driverName)) 314 return nil, nil 315 } 316 317 attrs := map[string]string{ 318 "csi.storage.k8s.io/pod.name": c.pod.Name, 319 "csi.storage.k8s.io/pod.namespace": c.pod.Namespace, 320 "csi.storage.k8s.io/pod.uid": string(c.pod.UID), 321 "csi.storage.k8s.io/serviceAccount.name": c.pod.Spec.ServiceAccountName, 322 } 323 klog.V(4).Infof(log("CSIDriver %q requires pod information", c.driverName)) 324 return attrs, nil 325 } 326 327 func (c *csiMountMgr) GetAttributes() volume.Attributes { 328 path := c.GetPath() 329 supportSelinux, err := c.kubeVolHost.GetHostUtil().GetSELinuxSupport(path) 330 if err != nil { 331 klog.V(2).Info(log("error checking for SELinux support: %s", err)) 332 // Best guess 333 supportSelinux = false 334 } 335 336 return volume.Attributes{ 337 ReadOnly: c.readOnly, 338 Managed: !c.readOnly, 339 SupportsSELinux: supportSelinux, 340 } 341 } 342 343 // volume.Unmounter methods 344 var _ volume.Unmounter = &csiMountMgr{} 345 346 func (c *csiMountMgr) TearDown() error { 347 return c.TearDownAt(c.GetPath()) 348 } 349 func (c *csiMountMgr) TearDownAt(dir string) error { 350 klog.V(4).Infof(log("Unmounter.TearDown(%s)", dir)) 351 352 volID := c.volumeID 353 csi, err := c.csiClientGetter.Get() 354 if err != nil { 355 klog.Error(log("mounter.SetUpAt failed to get CSI client: %v", err)) 356 return err 357 } 358 359 ctx, cancel := context.WithTimeout(context.Background(), csiTimeout) 360 defer cancel() 361 362 if err := csi.NodeUnpublishVolume(ctx, volID, dir); err != nil { 363 klog.Errorf(log("mounter.TearDownAt failed: %v", err)) 364 return err 365 } 366 367 // clean mount point dir 368 if err := removeMountDir(c.plugin, dir); err != nil { 369 klog.Error(log("mounter.TearDownAt failed to clean mount dir [%s]: %v", dir, err)) 370 return err 371 } 372 klog.V(4).Infof(log("mounter.TearDownAt successfully unmounted dir [%s]", dir)) 373 374 return nil 375 } 376 377 // applyFSGroup applies the volume ownership it derives its logic 378 // from https://github.com/kubernetes/kubernetes/issues/66323 379 // 1) if fstype is "", then skip fsgroup (could be indication of non-block filesystem) 380 // 2) if fstype is provided and pv.AccessMode == ReadWriteOnly and !c.spec.ReadOnly then apply fsgroup 381 func (c *csiMountMgr) applyFSGroup(fsType string, fsGroup *int64) error { 382 if fsGroup != nil { 383 if fsType == "" { 384 klog.V(4).Info(log("mounter.SetupAt WARNING: skipping fsGroup, fsType not provided")) 385 return nil 386 } 387 388 accessModes := c.spec.PersistentVolume.Spec.AccessModes 389 if c.spec.PersistentVolume.Spec.AccessModes == nil { 390 klog.V(4).Info(log("mounter.SetupAt WARNING: skipping fsGroup, access modes not provided")) 391 return nil 392 } 393 if !hasReadWriteOnce(accessModes) { 394 klog.V(4).Info(log("mounter.SetupAt WARNING: skipping fsGroup, only support ReadWriteOnce access mode")) 395 return nil 396 } 397 398 if c.readOnly { 399 klog.V(4).Info(log("mounter.SetupAt WARNING: skipping fsGroup, volume is readOnly")) 400 return nil 401 } 402 403 err := volume.SetVolumeOwnership(c, fsGroup) 404 if err != nil { 405 return err 406 } 407 408 klog.V(4).Info(log("mounter.SetupAt fsGroup [%d] applied successfully to %s", *fsGroup, c.volumeID)) 409 } 410 411 return nil 412 } 413 414 // isDirMounted returns the !notMounted result from IsLikelyNotMountPoint check 415 func isDirMounted(plug *csiPlugin, dir string) (bool, error) { 416 mounter := plug.host.GetMounter(plug.GetPluginName()) 417 notMnt, err := mounter.IsLikelyNotMountPoint(dir) 418 if err != nil && !os.IsNotExist(err) { 419 klog.Error(log("isDirMounted IsLikelyNotMountPoint test failed for dir [%v]", dir)) 420 return false, err 421 } 422 return !notMnt, nil 423 } 424 425 // removeMountDir cleans the mount dir when dir is not mounted and removed the volume data file in dir 426 func removeMountDir(plug *csiPlugin, mountPath string) error { 427 klog.V(4).Info(log("removing mount path [%s]", mountPath)) 428 429 mnt, err := isDirMounted(plug, mountPath) 430 if err != nil { 431 return err 432 } 433 if !mnt { 434 klog.V(4).Info(log("dir not mounted, deleting it [%s]", mountPath)) 435 if err := os.Remove(mountPath); err != nil && !os.IsNotExist(err) { 436 klog.Error(log("failed to remove dir [%s]: %v", mountPath, err)) 437 return err 438 } 439 // remove volume data file as well 440 volPath := path.Dir(mountPath) 441 dataFile := filepath.Join(volPath, volDataFileName) 442 klog.V(4).Info(log("also deleting volume info data file [%s]", dataFile)) 443 if err := os.Remove(dataFile); err != nil && !os.IsNotExist(err) { 444 klog.Error(log("failed to delete volume data file [%s]: %v", dataFile, err)) 445 return err 446 } 447 // remove volume path 448 klog.V(4).Info(log("deleting volume path [%s]", volPath)) 449 if err := os.Remove(volPath); err != nil && !os.IsNotExist(err) { 450 klog.Error(log("failed to delete volume path [%s]: %v", volPath, err)) 451 return err 452 } 453 } 454 return nil 455 } 456 457 // makeVolumeHandle returns csi-<sha256(podUID,volSourceSpecName)> 458 func makeVolumeHandle(podUID, volSourceSpecName string) string { 459 result := sha256.Sum256([]byte(fmt.Sprintf("%s%s", podUID, volSourceSpecName))) 460 return fmt.Sprintf("csi-%x", result) 461 }