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  }