github.com/jingruilea/kubeedge@v1.2.0-beta.0.0.20200410162146-4bb8902b3879/edge/pkg/edged/volume/csi/csi_attacher.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_attacher.go 20 and make some modifications including: 21 1. empty Attach function 22 2. empty Detach function. 23 3. change VolumeAttachments Watch into Get in waitForVolumeAttachment. 24 4. change VolumeAttachments Watch into Get in waitForVolumeDetachment. 25 5. remove skipAttach reference. 26 */ 27 28 package csi 29 30 import ( 31 "context" 32 "crypto/sha256" 33 "errors" 34 "fmt" 35 "os" 36 "path/filepath" 37 "strings" 38 "time" 39 40 v1 "k8s.io/api/core/v1" 41 storage "k8s.io/api/storage/v1" 42 apierrs "k8s.io/apimachinery/pkg/api/errors" 43 meta "k8s.io/apimachinery/pkg/apis/meta/v1" 44 "k8s.io/apimachinery/pkg/types" 45 "k8s.io/apimachinery/pkg/util/wait" 46 "k8s.io/client-go/kubernetes" 47 "k8s.io/klog" 48 "k8s.io/kubernetes/pkg/volume" 49 ) 50 51 const ( 52 persistentVolumeInGlobalPath = "pv" 53 globalMountInGlobalPath = "globalmount" 54 ) 55 56 type csiAttacher struct { 57 plugin *csiPlugin 58 k8s kubernetes.Interface 59 waitSleepTime time.Duration 60 61 csiClient csiClient 62 } 63 64 // volume.Attacher methods 65 var _ volume.Attacher = &csiAttacher{} 66 67 var _ volume.Detacher = &csiAttacher{} 68 69 var _ volume.DeviceMounter = &csiAttacher{} 70 71 func (c *csiAttacher) Attach(spec *volume.Spec, nodeName types.NodeName) (string, error) { 72 return "", nil 73 } 74 75 func (c *csiAttacher) WaitForAttach(spec *volume.Spec, _ string, pod *v1.Pod, timeout time.Duration) (string, error) { 76 source, err := getPVSourceFromSpec(spec) 77 if err != nil { 78 klog.Error(log("attacher.WaitForAttach failed to extract CSI volume source: %v", err)) 79 return "", err 80 } 81 82 attachID := getAttachmentName(source.VolumeHandle, source.Driver, string(c.plugin.host.GetNodeName())) 83 84 return c.waitForVolumeAttachment(source.VolumeHandle, attachID, timeout) 85 } 86 87 func (c *csiAttacher) waitForVolumeAttachment(volumeHandle, attachID string, timeout time.Duration) (string, error) { 88 klog.V(4).Info(log("probing for updates from CSI driver for [attachment.ID=%v]", attachID)) 89 90 err := wait.PollImmediate(time.Second*5, time.Minute*5, func() (bool, error) { 91 klog.V(4).Info(log("probing VolumeAttachment [id=%v]", attachID)) 92 attach, err := c.k8s.StorageV1().VolumeAttachments().Get(attachID, meta.GetOptions{}) 93 if err != nil { 94 return false, fmt.Errorf("volume %v has GET error for volume attachment %v: %v", volumeHandle, attachID, err) 95 } 96 successful, err := verifyAttachmentStatus(attach, volumeHandle) 97 return successful, err 98 }) 99 if err != nil { 100 return "", err 101 } 102 return attachID, nil 103 } 104 105 func verifyAttachmentStatus(attachment *storage.VolumeAttachment, volumeHandle string) (bool, error) { 106 // if being deleted, fail fast 107 if attachment.GetDeletionTimestamp() != nil { 108 klog.Error(log("VolumeAttachment [%s] has deletion timestamp, will not continue to wait for attachment", attachment.Name)) 109 return false, errors.New("volume attachment is being deleted") 110 } 111 // attachment OK 112 if attachment.Status.Attached { 113 return true, nil 114 } 115 // driver reports attach error 116 attachErr := attachment.Status.AttachError 117 if attachErr != nil { 118 klog.Error(log("attachment for %v failed: %v", volumeHandle, attachErr.Message)) 119 return false, errors.New(attachErr.Message) 120 } 121 return false, nil 122 } 123 124 func (c *csiAttacher) VolumesAreAttached(specs []*volume.Spec, nodeName types.NodeName) (map[*volume.Spec]bool, error) { 125 klog.V(4).Info(log("probing attachment status for %d volume(s) ", len(specs))) 126 127 attached := make(map[*volume.Spec]bool) 128 129 for _, spec := range specs { 130 if spec == nil { 131 klog.Error(log("attacher.VolumesAreAttached missing volume.Spec")) 132 return nil, errors.New("missing spec") 133 } 134 pvSrc, err := getPVSourceFromSpec(spec) 135 if err != nil { 136 attached[spec] = false 137 klog.Error(log("attacher.VolumesAreAttached failed to get CSIPersistentVolumeSource: %v", err)) 138 continue 139 } 140 driverName := pvSrc.Driver 141 volumeHandle := pvSrc.VolumeHandle 142 143 attachID := getAttachmentName(volumeHandle, driverName, string(nodeName)) 144 klog.V(4).Info(log("probing attachment status for VolumeAttachment %v", attachID)) 145 attach, err := c.k8s.StorageV1().VolumeAttachments().Get(attachID, meta.GetOptions{}) 146 if err != nil { 147 attached[spec] = false 148 klog.Error(log("attacher.VolumesAreAttached failed for attach.ID=%v: %v", attachID, err)) 149 continue 150 } 151 klog.V(4).Info(log("attacher.VolumesAreAttached attachment [%v] has status.attached=%t", attachID, attach.Status.Attached)) 152 attached[spec] = attach.Status.Attached 153 } 154 155 return attached, nil 156 } 157 158 func (c *csiAttacher) GetDeviceMountPath(spec *volume.Spec) (string, error) { 159 klog.V(4).Info(log("attacher.GetDeviceMountPath(%v)", spec)) 160 deviceMountPath, err := makeDeviceMountPath(c.plugin, spec) 161 if err != nil { 162 klog.Error(log("attacher.GetDeviceMountPath failed to make device mount path: %v", err)) 163 return "", err 164 } 165 klog.V(4).Infof("attacher.GetDeviceMountPath succeeded, deviceMountPath: %s", deviceMountPath) 166 return deviceMountPath, nil 167 } 168 169 func (c *csiAttacher) MountDevice(spec *volume.Spec, devicePath string, deviceMountPath string) (err error) { 170 klog.V(4).Infof(log("attacher.MountDevice(%s, %s)", devicePath, deviceMountPath)) 171 172 if deviceMountPath == "" { 173 err = fmt.Errorf("attacher.MountDevice failed, deviceMountPath is empty") 174 return err 175 } 176 177 mounted, err := isDirMounted(c.plugin, deviceMountPath) 178 if err != nil { 179 klog.Error(log("attacher.MountDevice failed while checking mount status for dir [%s]", deviceMountPath)) 180 return err 181 } 182 183 if mounted { 184 klog.V(4).Info(log("attacher.MountDevice skipping mount, dir already mounted [%s]", deviceMountPath)) 185 return nil 186 } 187 188 // Setup 189 if spec == nil { 190 return fmt.Errorf("attacher.MountDevice failed, spec is nil") 191 } 192 csiSource, err := getPVSourceFromSpec(spec) 193 if err != nil { 194 klog.Error(log("attacher.MountDevice failed to get CSIPersistentVolumeSource: %v", err)) 195 return err 196 } 197 198 // Store volume metadata for UnmountDevice. Keep it around even if the 199 // driver does not support NodeStage, UnmountDevice still needs it. 200 if err = os.MkdirAll(deviceMountPath, 0750); err != nil { 201 klog.Error(log("attacher.MountDevice failed to create dir %#v: %v", deviceMountPath, err)) 202 return err 203 } 204 klog.V(4).Info(log("created target path successfully [%s]", deviceMountPath)) 205 dataDir := filepath.Dir(deviceMountPath) 206 data := map[string]string{ 207 volDataKey.volHandle: csiSource.VolumeHandle, 208 volDataKey.driverName: csiSource.Driver, 209 } 210 if err = saveVolumeData(dataDir, volDataFileName, data); err != nil { 211 klog.Error(log("failed to save volume info data: %v", err)) 212 if cleanerr := os.RemoveAll(dataDir); err != nil { 213 klog.Error(log("failed to remove dir after error [%s]: %v", dataDir, cleanerr)) 214 } 215 return err 216 } 217 defer func() { 218 if err != nil { 219 // clean up metadata 220 klog.Errorf(log("attacher.MountDevice failed: %v", err)) 221 if err := removeMountDir(c.plugin, deviceMountPath); err != nil { 222 klog.Error(log("attacher.MountDevice failed to remove mount dir after errir [%s]: %v", deviceMountPath, err)) 223 } 224 } 225 }() 226 227 if c.csiClient == nil { 228 c.csiClient, err = newCsiDriverClient(csiDriverName(csiSource.Driver)) 229 if err != nil { 230 klog.Errorf(log("attacher.MountDevice failed to create newCsiDriverClient: %v", err)) 231 return err 232 } 233 } 234 csi := c.csiClient 235 236 ctx, cancel := context.WithTimeout(context.Background(), csiTimeout) 237 defer cancel() 238 // Check whether "STAGE_UNSTAGE_VOLUME" is set 239 stageUnstageSet, err := csi.NodeSupportsStageUnstage(ctx) 240 if err != nil { 241 return err 242 } 243 if !stageUnstageSet { 244 klog.Infof(log("attacher.MountDevice STAGE_UNSTAGE_VOLUME capability not set. Skipping MountDevice...")) 245 // defer does *not* remove the metadata file and it's correct - UnmountDevice needs it there. 246 return nil 247 } 248 249 // Start MountDevice 250 nodeName := string(c.plugin.host.GetNodeName()) 251 publishContext, err := c.plugin.getPublishContext(c.k8s, csiSource.VolumeHandle, csiSource.Driver, nodeName) 252 253 nodeStageSecrets := map[string]string{} 254 if csiSource.NodeStageSecretRef != nil { 255 nodeStageSecrets, err = getCredentialsFromSecret(c.k8s, csiSource.NodeStageSecretRef) 256 if err != nil { 257 err = fmt.Errorf("fetching NodeStageSecretRef %s/%s failed: %v", 258 csiSource.NodeStageSecretRef.Namespace, csiSource.NodeStageSecretRef.Name, err) 259 return err 260 } 261 } 262 263 //TODO (vladimirvivien) implement better AccessModes mapping between k8s and CSI 264 accessMode := v1.ReadWriteOnce 265 if spec.PersistentVolume.Spec.AccessModes != nil { 266 accessMode = spec.PersistentVolume.Spec.AccessModes[0] 267 } 268 269 fsType := csiSource.FSType 270 err = csi.NodeStageVolume(ctx, 271 csiSource.VolumeHandle, 272 publishContext, 273 deviceMountPath, 274 fsType, 275 accessMode, 276 nodeStageSecrets, 277 csiSource.VolumeAttributes) 278 279 if err != nil { 280 return err 281 } 282 283 klog.V(4).Infof(log("attacher.MountDevice successfully requested NodeStageVolume [%s]", deviceMountPath)) 284 return nil 285 } 286 287 var _ volume.Detacher = &csiAttacher{} 288 289 var _ volume.DeviceUnmounter = &csiAttacher{} 290 291 func (c *csiAttacher) Detach(volumeName string, nodeName types.NodeName) error { 292 return nil 293 } 294 295 func (c *csiAttacher) waitForVolumeDetachment(volumeHandle, attachID string) error { 296 klog.V(4).Info(log("probing for updates from CSI driver for [attachment.ID=%v]", attachID)) 297 298 err := wait.PollImmediate(time.Second*5, c.waitSleepTime*10, func() (bool, error) { 299 klog.V(4).Info(log("probing VolumeAttachment [id=%v]", attachID)) 300 attach, err := c.k8s.StorageV1().VolumeAttachments().Get(attachID, meta.GetOptions{}) 301 if err != nil { 302 if apierrs.IsNotFound(err) { 303 //object deleted or never existed, done 304 klog.V(4).Info(log("VolumeAttachment object [%v] for volume [%v] not found, object deleted", attachID, volumeHandle)) 305 return true, nil 306 } 307 return false, err 308 } 309 // driver reports attach error 310 detachErr := attach.Status.DetachError 311 if detachErr != nil { 312 return false, errors.New(detachErr.Message) 313 } 314 return false, nil 315 }) 316 317 return err 318 } 319 320 func (c *csiAttacher) UnmountDevice(deviceMountPath string) error { 321 klog.V(4).Info(log("attacher.UnmountDevice(%s)", deviceMountPath)) 322 323 // Setup 324 var driverName, volID string 325 dataDir := filepath.Dir(deviceMountPath) 326 data, err := loadVolumeData(dataDir, volDataFileName) 327 if err == nil { 328 driverName = data[volDataKey.driverName] 329 volID = data[volDataKey.volHandle] 330 } else { 331 klog.Error(log("UnmountDevice failed to load volume data file [%s]: %v", dataDir, err)) 332 333 // The volume might have been mounted by old CSI volume plugin. Fall back to the old behavior: read PV from API server 334 driverName, volID, err = getDriverAndVolNameFromDeviceMountPath(c.k8s, deviceMountPath) 335 if err != nil { 336 klog.Errorf(log("attacher.UnmountDevice failed to get driver and volume name from device mount path: %v", err)) 337 return err 338 } 339 } 340 341 if c.csiClient == nil { 342 c.csiClient, err = newCsiDriverClient(csiDriverName(driverName)) 343 if err != nil { 344 klog.Errorf(log("attacher.UnmountDevice failed to create newCsiDriverClient: %v", err)) 345 return err 346 } 347 } 348 csi := c.csiClient 349 350 ctx, cancel := context.WithTimeout(context.Background(), csiTimeout) 351 defer cancel() 352 // Check whether "STAGE_UNSTAGE_VOLUME" is set 353 stageUnstageSet, err := csi.NodeSupportsStageUnstage(ctx) 354 if err != nil { 355 klog.Errorf(log("attacher.UnmountDevice failed to check whether STAGE_UNSTAGE_VOLUME set: %v", err)) 356 return err 357 } 358 if !stageUnstageSet { 359 klog.Infof(log("attacher.UnmountDevice STAGE_UNSTAGE_VOLUME capability not set. Skipping UnmountDevice...")) 360 // Just delete the global directory + json file 361 if err := removeMountDir(c.plugin, deviceMountPath); err != nil { 362 return fmt.Errorf("failed to clean up gloubal mount %s: %s", dataDir, err) 363 } 364 365 return nil 366 } 367 368 // Start UnmountDevice 369 err = csi.NodeUnstageVolume(ctx, 370 volID, 371 deviceMountPath) 372 373 if err != nil { 374 klog.Errorf(log("attacher.UnmountDevice failed: %v", err)) 375 return err 376 } 377 378 // Delete the global directory + json file 379 if err := removeMountDir(c.plugin, deviceMountPath); err != nil { 380 return fmt.Errorf("failed to clean up gloubal mount %s: %s", dataDir, err) 381 } 382 383 klog.V(4).Infof(log("attacher.UnmountDevice successfully requested NodeStageVolume [%s]", deviceMountPath)) 384 return nil 385 } 386 387 // getAttachmentName returns csi-<sha256(volName,csiDriverName,NodeName)> 388 func getAttachmentName(volName, csiDriverName, nodeName string) string { 389 result := sha256.Sum256([]byte(fmt.Sprintf("%s%s%s", volName, csiDriverName, nodeName))) 390 return fmt.Sprintf("csi-%x", result) 391 } 392 393 // isAttachmentName returns true if the string given is of the form of an Attach ID 394 // and false otherwise 395 func isAttachmentName(unknownString string) bool { 396 // 68 == "csi-" + len(sha256hash) 397 if strings.HasPrefix(unknownString, "csi-") && len(unknownString) == 68 { 398 return true 399 } 400 return false 401 } 402 403 func makeDeviceMountPath(plugin *csiPlugin, spec *volume.Spec) (string, error) { 404 if spec == nil { 405 return "", fmt.Errorf("makeDeviceMountPath failed, spec is nil") 406 } 407 408 pvName := spec.PersistentVolume.Name 409 if pvName == "" { 410 return "", fmt.Errorf("makeDeviceMountPath failed, pv name empty") 411 } 412 413 return filepath.Join(plugin.host.GetPluginDir(plugin.GetPluginName()), persistentVolumeInGlobalPath, pvName, globalMountInGlobalPath), nil 414 } 415 416 func getDriverAndVolNameFromDeviceMountPath(k8s kubernetes.Interface, deviceMountPath string) (string, string, error) { 417 // deviceMountPath structure: /var/lib/kubelet/plugins/kubernetes.io/csi/pv/{pvname}/globalmount 418 dir := filepath.Dir(deviceMountPath) 419 if file := filepath.Base(deviceMountPath); file != globalMountInGlobalPath { 420 return "", "", fmt.Errorf("getDriverAndVolNameFromDeviceMountPath failed, path did not end in %s", globalMountInGlobalPath) 421 } 422 // dir is now /var/lib/kubelet/plugins/kubernetes.io/csi/pv/{pvname} 423 pvName := filepath.Base(dir) 424 425 // Get PV and check for errors 426 pv, err := k8s.CoreV1().PersistentVolumes().Get(pvName, meta.GetOptions{}) 427 if err != nil { 428 return "", "", err 429 } 430 if pv == nil || pv.Spec.CSI == nil { 431 return "", "", fmt.Errorf("getDriverAndVolNameFromDeviceMountPath could not find CSI Persistent Volume Source for pv: %s", pvName) 432 } 433 434 // Get VolumeHandle and PluginName from pv 435 csiSource := pv.Spec.CSI 436 if csiSource.Driver == "" { 437 return "", "", fmt.Errorf("getDriverAndVolNameFromDeviceMountPath failed, driver name empty") 438 } 439 if csiSource.VolumeHandle == "" { 440 return "", "", fmt.Errorf("getDriverAndVolNameFromDeviceMountPath failed, VolumeHandle empty") 441 } 442 443 return csiSource.Driver, csiSource.VolumeHandle, nil 444 }