2019-01-12 04:58:27 +00:00
/ *
Copyright 2017 The Kubernetes Authors .
Licensed under the Apache License , Version 2.0 ( the "License" ) ;
you may not use this file except in compliance with the License .
You may obtain a copy of the License at
http : //www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing , software
distributed under the License is distributed on an "AS IS" BASIS ,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
See the License for the specific language governing permissions and
limitations under the License .
* /
package csi
import (
"context"
"crypto/sha256"
"errors"
"fmt"
"os"
"path/filepath"
"strings"
"time"
2021-03-18 22:40:29 +00:00
"k8s.io/apimachinery/pkg/util/clock"
2021-07-02 08:43:15 +00:00
utilfeature "k8s.io/apiserver/pkg/util/feature"
2020-08-10 17:43:49 +00:00
"k8s.io/klog/v2"
2019-01-12 04:58:27 +00:00
2020-03-13 19:24:50 +00:00
v1 "k8s.io/api/core/v1"
2019-04-07 17:07:55 +00:00
storage "k8s.io/api/storage/v1"
2020-03-26 21:07:15 +00:00
apierrors "k8s.io/apimachinery/pkg/api/errors"
2019-01-12 04:58:27 +00:00
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
2020-03-26 21:07:15 +00:00
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2019-01-12 04:58:27 +00:00
"k8s.io/apimachinery/pkg/types"
2021-03-18 22:40:29 +00:00
"k8s.io/apimachinery/pkg/util/wait"
2019-01-12 04:58:27 +00:00
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/kubernetes"
2021-07-02 08:43:15 +00:00
"k8s.io/kubernetes/pkg/features"
2019-01-12 04:58:27 +00:00
"k8s.io/kubernetes/pkg/volume"
2020-03-26 21:07:15 +00:00
volumetypes "k8s.io/kubernetes/pkg/volume/util/types"
2019-01-12 04:58:27 +00:00
)
const (
persistentVolumeInGlobalPath = "pv"
globalMountInGlobalPath = "globalmount"
)
type csiAttacher struct {
2021-03-18 22:40:29 +00:00
plugin * csiPlugin
k8s kubernetes . Interface
watchTimeout time . Duration
2019-01-12 04:58:27 +00:00
csiClient csiClient
}
2019-12-12 01:27:03 +00:00
type verifyAttachDetachStatus func ( attach * storage . VolumeAttachment , volumeHandle string ) ( bool , error )
2019-01-12 04:58:27 +00:00
// volume.Attacher methods
var _ volume . Attacher = & csiAttacher { }
2019-08-30 18:33:25 +00:00
var _ volume . Detacher = & csiAttacher { }
2019-01-12 04:58:27 +00:00
var _ volume . DeviceMounter = & csiAttacher { }
func ( c * csiAttacher ) Attach ( spec * volume . Spec , nodeName types . NodeName ) ( string , error ) {
if spec == nil {
klog . Error ( log ( "attacher.Attach missing volume.Spec" ) )
return "" , errors . New ( "missing spec" )
}
2019-04-07 17:07:55 +00:00
pvSrc , err := getPVSourceFromSpec ( spec )
2019-01-12 04:58:27 +00:00
if err != nil {
2019-09-27 21:51:53 +00:00
return "" , errors . New ( log ( "attacher.Attach failed to get CSIPersistentVolumeSource: %v" , err ) )
2019-01-12 04:58:27 +00:00
}
node := string ( nodeName )
2019-04-07 17:07:55 +00:00
attachID := getAttachmentName ( pvSrc . VolumeHandle , pvSrc . Driver , node )
2019-01-12 04:58:27 +00:00
2021-03-18 22:40:29 +00:00
attachment , err := c . plugin . volumeAttachmentLister . Get ( attachID )
if err != nil && ! apierrors . IsNotFound ( err ) {
return "" , errors . New ( log ( "failed to get volume attachment from lister: %v" , err ) )
}
if attachment == nil {
var vaSrc storage . VolumeAttachmentSource
if spec . InlineVolumeSpecForCSIMigration {
// inline PV scenario - use PV spec to populate VA source.
// The volume spec will be populated by CSI translation API
// for inline volumes. This allows fields required by the CSI
// attacher such as AccessMode and MountOptions (in addition to
// fields in the CSI persistent volume source) to be populated
// as part of CSI translation for inline volumes.
vaSrc = storage . VolumeAttachmentSource {
InlineVolumeSpec : & spec . PersistentVolume . Spec ,
}
} else {
// regular PV scenario - use PV name to populate VA source
pvName := spec . PersistentVolume . GetName ( )
vaSrc = storage . VolumeAttachmentSource {
PersistentVolumeName : & pvName ,
}
2019-08-30 18:33:25 +00:00
}
2019-01-12 04:58:27 +00:00
2021-03-18 22:40:29 +00:00
attachment := & storage . VolumeAttachment {
ObjectMeta : meta . ObjectMeta {
Name : attachID ,
} ,
Spec : storage . VolumeAttachmentSpec {
NodeName : node ,
Attacher : pvSrc . Driver ,
Source : vaSrc ,
} ,
2019-01-12 04:58:27 +00:00
}
2021-03-18 22:40:29 +00:00
_ , err = c . k8s . StorageV1 ( ) . VolumeAttachments ( ) . Create ( context . TODO ( ) , attachment , metav1 . CreateOptions { } )
if err != nil {
if ! apierrors . IsAlreadyExists ( err ) {
return "" , errors . New ( log ( "attacher.Attach failed: %v" , err ) )
}
klog . V ( 4 ) . Info ( log ( "attachment [%v] for volume [%v] already exists (will not be recreated)" , attachID , pvSrc . VolumeHandle ) )
} else {
klog . V ( 4 ) . Info ( log ( "attachment [%v] for volume [%v] created successfully" , attachID , pvSrc . VolumeHandle ) )
}
2019-01-12 04:58:27 +00:00
}
2021-03-18 22:40:29 +00:00
// Attach and detach functionality is exclusive to the CSI plugin that runs in the AttachDetachController,
// and has access to a VolumeAttachment lister that can be polled for the current status.
if err := c . waitForVolumeAttachmentWithLister ( pvSrc . VolumeHandle , attachID , c . watchTimeout ) ; err != nil {
2019-01-12 04:58:27 +00:00
return "" , err
}
klog . V ( 4 ) . Info ( log ( "attacher.Attach finished OK with VolumeAttachment object [%s]" , attachID ) )
2019-08-30 18:33:25 +00:00
// Don't return attachID as a devicePath. We can reconstruct the attachID using getAttachmentName()
return "" , nil
2019-01-12 04:58:27 +00:00
}
func ( c * csiAttacher ) WaitForAttach ( spec * volume . Spec , _ string , pod * v1 . Pod , timeout time . Duration ) ( string , error ) {
2019-04-07 17:07:55 +00:00
source , err := getPVSourceFromSpec ( spec )
2019-01-12 04:58:27 +00:00
if err != nil {
2019-09-27 21:51:53 +00:00
return "" , errors . New ( log ( "attacher.WaitForAttach failed to extract CSI volume source: %v" , err ) )
2019-01-12 04:58:27 +00:00
}
attachID := getAttachmentName ( source . VolumeHandle , source . Driver , string ( c . plugin . host . GetNodeName ( ) ) )
return c . waitForVolumeAttachment ( source . VolumeHandle , attachID , timeout )
}
func ( c * csiAttacher ) waitForVolumeAttachment ( volumeHandle , attachID string , timeout time . Duration ) ( string , error ) {
klog . V ( 4 ) . Info ( log ( "probing for updates from CSI driver for [attachment.ID=%v]" , attachID ) )
timer := time . NewTimer ( timeout ) // TODO (vladimirvivien) investigate making this configurable
defer timer . Stop ( )
return c . waitForVolumeAttachmentInternal ( volumeHandle , attachID , timer , timeout )
}
func ( c * csiAttacher ) waitForVolumeAttachmentInternal ( volumeHandle , attachID string , timer * time . Timer , timeout time . Duration ) ( string , error ) {
2019-12-12 01:27:03 +00:00
2019-01-12 04:58:27 +00:00
klog . V ( 4 ) . Info ( log ( "probing VolumeAttachment [id=%v]" , attachID ) )
2020-03-26 21:07:15 +00:00
attach , err := c . k8s . StorageV1 ( ) . VolumeAttachments ( ) . Get ( context . TODO ( ) , attachID , meta . GetOptions { } )
2019-01-12 04:58:27 +00:00
if err != nil {
klog . Error ( log ( "attacher.WaitForAttach failed for volume [%s] (will continue to try): %v" , volumeHandle , err ) )
return "" , fmt . Errorf ( "volume %v has GET error for volume attachment %v: %v" , volumeHandle , attachID , err )
}
2019-12-12 01:27:03 +00:00
err = c . waitForVolumeAttachDetachStatus ( attach , volumeHandle , attachID , timer , timeout , verifyAttachmentStatus )
2019-01-12 04:58:27 +00:00
if err != nil {
return "" , err
}
2019-12-12 01:27:03 +00:00
return attach . Name , nil
2019-01-12 04:58:27 +00:00
}
2021-03-18 22:40:29 +00:00
func ( c * csiAttacher ) waitForVolumeAttachmentWithLister ( volumeHandle , attachID string , timeout time . Duration ) error {
klog . V ( 4 ) . Info ( log ( "probing VolumeAttachment [id=%v]" , attachID ) )
verifyStatus := func ( ) ( bool , error ) {
volumeAttachment , err := c . plugin . volumeAttachmentLister . Get ( attachID )
if err != nil {
// Ignore "not found" errors in case the VolumeAttachment was just created and hasn't yet made it into the lister.
if ! apierrors . IsNotFound ( err ) {
klog . Error ( log ( "unexpected error waiting for volume attachment, %v" , err ) )
return false , err
}
// The VolumeAttachment is not available yet and we will have to try again.
return false , nil
}
successful , err := verifyAttachmentStatus ( volumeAttachment , volumeHandle )
if err != nil {
return false , err
}
return successful , nil
}
return c . waitForVolumeAttachDetachStatusWithLister ( volumeHandle , attachID , timeout , verifyStatus , "Attach" )
}
2019-01-12 04:58:27 +00:00
func ( c * csiAttacher ) VolumesAreAttached ( specs [ ] * volume . Spec , nodeName types . NodeName ) ( map [ * volume . Spec ] bool , error ) {
klog . V ( 4 ) . Info ( log ( "probing attachment status for %d volume(s) " , len ( specs ) ) )
attached := make ( map [ * volume . Spec ] bool )
for _ , spec := range specs {
if spec == nil {
klog . Error ( log ( "attacher.VolumesAreAttached missing volume.Spec" ) )
return nil , errors . New ( "missing spec" )
}
2019-04-07 17:07:55 +00:00
pvSrc , err := getPVSourceFromSpec ( spec )
2019-01-12 04:58:27 +00:00
if err != nil {
2019-04-07 17:07:55 +00:00
attached [ spec ] = false
klog . Error ( log ( "attacher.VolumesAreAttached failed to get CSIPersistentVolumeSource: %v" , err ) )
2019-01-12 04:58:27 +00:00
continue
}
2019-04-07 17:07:55 +00:00
driverName := pvSrc . Driver
volumeHandle := pvSrc . VolumeHandle
skip , err := c . plugin . skipAttach ( driverName )
2019-01-12 04:58:27 +00:00
if err != nil {
2019-04-07 17:07:55 +00:00
klog . Error ( log ( "Failed to check CSIDriver for %s: %s" , driverName , err ) )
2019-01-12 04:58:27 +00:00
} else {
if skip {
// This volume is not attachable, pretend it's attached
attached [ spec ] = true
continue
}
}
2019-04-07 17:07:55 +00:00
attachID := getAttachmentName ( volumeHandle , driverName , string ( nodeName ) )
2020-07-17 23:14:37 +00:00
var attach * storage . VolumeAttachment
if c . plugin . volumeAttachmentLister != nil {
attach , err = c . plugin . volumeAttachmentLister . Get ( attachID )
if err == nil {
attached [ spec ] = attach . Status . Attached
continue
}
klog . V ( 4 ) . Info ( log ( "attacher.VolumesAreAttached failed in AttachmentLister for attach.ID=%v: %v. Probing the API server." , attachID , err ) )
}
// The cache lookup is not setup or the object is not found in the cache.
// Get the object from the API server.
2019-01-12 04:58:27 +00:00
klog . V ( 4 ) . Info ( log ( "probing attachment status for VolumeAttachment %v" , attachID ) )
2020-07-17 23:14:37 +00:00
attach , err = c . k8s . StorageV1 ( ) . VolumeAttachments ( ) . Get ( context . TODO ( ) , attachID , meta . GetOptions { } )
2019-01-12 04:58:27 +00:00
if err != nil {
attached [ spec ] = false
klog . Error ( log ( "attacher.VolumesAreAttached failed for attach.ID=%v: %v" , attachID , err ) )
continue
}
klog . V ( 4 ) . Info ( log ( "attacher.VolumesAreAttached attachment [%v] has status.attached=%t" , attachID , attach . Status . Attached ) )
attached [ spec ] = attach . Status . Attached
}
return attached , nil
}
func ( c * csiAttacher ) GetDeviceMountPath ( spec * volume . Spec ) ( string , error ) {
klog . V ( 4 ) . Info ( log ( "attacher.GetDeviceMountPath(%v)" , spec ) )
deviceMountPath , err := makeDeviceMountPath ( c . plugin , spec )
if err != nil {
2019-09-27 21:51:53 +00:00
return "" , errors . New ( log ( "attacher.GetDeviceMountPath failed to make device mount path: %v" , err ) )
2019-01-12 04:58:27 +00:00
}
klog . V ( 4 ) . Infof ( "attacher.GetDeviceMountPath succeeded, deviceMountPath: %s" , deviceMountPath )
return deviceMountPath , nil
}
2021-07-02 08:43:15 +00:00
func ( c * csiAttacher ) MountDevice ( spec * volume . Spec , devicePath string , deviceMountPath string , deviceMounterArgs volume . DeviceMounterArgs ) error {
2019-01-12 04:58:27 +00:00
klog . V ( 4 ) . Infof ( log ( "attacher.MountDevice(%s, %s)" , devicePath , deviceMountPath ) )
if deviceMountPath == "" {
2019-09-27 21:51:53 +00:00
return errors . New ( log ( "attacher.MountDevice failed, deviceMountPath is empty" ) )
2019-01-12 04:58:27 +00:00
}
// Setup
if spec == nil {
2019-09-27 21:51:53 +00:00
return errors . New ( log ( "attacher.MountDevice failed, spec is nil" ) )
2019-01-12 04:58:27 +00:00
}
2019-04-07 17:07:55 +00:00
csiSource , err := getPVSourceFromSpec ( spec )
2019-01-12 04:58:27 +00:00
if err != nil {
2019-09-27 21:51:53 +00:00
return errors . New ( log ( "attacher.MountDevice failed to get CSIPersistentVolumeSource: %v" , err ) )
2019-01-12 04:58:27 +00:00
}
2020-03-26 21:07:15 +00:00
// lets check if node/unstage is supported
if c . csiClient == nil {
c . csiClient , err = newCsiDriverClient ( csiDriverName ( csiSource . Driver ) )
if err != nil {
return errors . New ( log ( "attacher.MountDevice failed to create newCsiDriverClient: %v" , err ) )
}
}
csi := c . csiClient
2021-03-18 22:40:29 +00:00
ctx , cancel := createCSIOperationContext ( spec , c . watchTimeout )
2020-03-26 21:07:15 +00:00
defer cancel ( )
// Check whether "STAGE_UNSTAGE_VOLUME" is set
stageUnstageSet , err := csi . NodeSupportsStageUnstage ( ctx )
if err != nil {
return err
}
// Get secrets and publish context required for mountDevice
nodeName := string ( c . plugin . host . GetNodeName ( ) )
publishContext , err := c . plugin . getPublishContext ( c . k8s , csiSource . VolumeHandle , csiSource . Driver , nodeName )
if err != nil {
return volumetypes . NewTransientOperationFailure ( err . Error ( ) )
}
nodeStageSecrets := map [ string ] string { }
// we only require secrets if csiSource has them and volume has NodeStage capability
if csiSource . NodeStageSecretRef != nil && stageUnstageSet {
nodeStageSecrets , err = getCredentialsFromSecret ( c . k8s , csiSource . NodeStageSecretRef )
if err != nil {
err = fmt . Errorf ( "fetching NodeStageSecretRef %s/%s failed: %v" ,
csiSource . NodeStageSecretRef . Namespace , csiSource . NodeStageSecretRef . Name , err )
// if we failed to fetch secret then that could be a transient error
return volumetypes . NewTransientOperationFailure ( err . Error ( ) )
}
}
2019-01-12 04:58:27 +00:00
// Store volume metadata for UnmountDevice. Keep it around even if the
// driver does not support NodeStage, UnmountDevice still needs it.
2020-12-01 01:06:26 +00:00
if err = os . MkdirAll ( deviceMountPath , 0750 ) ; err != nil {
2019-09-27 21:51:53 +00:00
return errors . New ( log ( "attacher.MountDevice failed to create dir %#v: %v" , deviceMountPath , err ) )
2019-01-12 04:58:27 +00:00
}
klog . V ( 4 ) . Info ( log ( "created target path successfully [%s]" , deviceMountPath ) )
dataDir := filepath . Dir ( deviceMountPath )
data := map [ string ] string {
volDataKey . volHandle : csiSource . VolumeHandle ,
volDataKey . driverName : csiSource . Driver ,
}
2021-03-19 18:50:37 +00:00
err = saveVolumeData ( dataDir , volDataFileName , data )
2019-01-12 04:58:27 +00:00
defer func ( ) {
2020-03-26 21:07:15 +00:00
// Only if there was an error and volume operation was considered
// finished, we should remove the directory.
if err != nil && volumetypes . IsOperationFinishedError ( err ) {
2019-01-12 04:58:27 +00:00
// clean up metadata
klog . Errorf ( log ( "attacher.MountDevice failed: %v" , err ) )
if err := removeMountDir ( c . plugin , deviceMountPath ) ; err != nil {
2019-09-27 21:51:53 +00:00
klog . Error ( log ( "attacher.MountDevice failed to remove mount dir after error [%s]: %v" , deviceMountPath , err ) )
2019-01-12 04:58:27 +00:00
}
}
} ( )
2021-03-19 18:50:37 +00:00
if err != nil {
errMsg := log ( "failed to save volume info data: %v" , err )
klog . Error ( errMsg )
return errors . New ( errMsg )
}
2019-01-12 04:58:27 +00:00
if ! stageUnstageSet {
klog . Infof ( log ( "attacher.MountDevice STAGE_UNSTAGE_VOLUME capability not set. Skipping MountDevice..." ) )
// defer does *not* remove the metadata file and it's correct - UnmountDevice needs it there.
return nil
}
//TODO (vladimirvivien) implement better AccessModes mapping between k8s and CSI
accessMode := v1 . ReadWriteOnce
if spec . PersistentVolume . Spec . AccessModes != nil {
accessMode = spec . PersistentVolume . Spec . AccessModes [ 0 ]
}
2019-08-19 16:30:36 +00:00
var mountOptions [ ] string
if spec . PersistentVolume != nil && spec . PersistentVolume . Spec . MountOptions != nil {
mountOptions = spec . PersistentVolume . Spec . MountOptions
}
2021-07-02 08:43:15 +00:00
var nodeStageFSGroupArg * int64
if utilfeature . DefaultFeatureGate . Enabled ( features . DelegateFSGroupToCSIDriver ) {
driverSupportsCSIVolumeMountGroup , err := csi . NodeSupportsVolumeMountGroup ( ctx )
if err != nil {
return volumetypes . NewTransientOperationFailure ( log ( "attacher.MountDevice failed to determine if the node service has VOLUME_MOUNT_GROUP capability: %v" , err ) )
}
if driverSupportsCSIVolumeMountGroup {
klog . V ( 3 ) . Infof ( "Driver %s supports applying FSGroup (has VOLUME_MOUNT_GROUP node capability). Delegating FSGroup application to the driver through NodeStageVolume." , csiSource . Driver )
nodeStageFSGroupArg = deviceMounterArgs . FsGroup
}
}
2019-01-12 04:58:27 +00:00
fsType := csiSource . FSType
err = csi . NodeStageVolume ( ctx ,
csiSource . VolumeHandle ,
publishContext ,
deviceMountPath ,
fsType ,
accessMode ,
nodeStageSecrets ,
2019-08-19 16:30:36 +00:00
csiSource . VolumeAttributes ,
2021-07-02 08:43:15 +00:00
mountOptions ,
nodeStageFSGroupArg )
2019-01-12 04:58:27 +00:00
if err != nil {
return err
}
klog . V ( 4 ) . Infof ( log ( "attacher.MountDevice successfully requested NodeStageVolume [%s]" , deviceMountPath ) )
2020-03-26 21:07:15 +00:00
return err
2019-01-12 04:58:27 +00:00
}
var _ volume . Detacher = & csiAttacher { }
var _ volume . DeviceUnmounter = & csiAttacher { }
func ( c * csiAttacher ) Detach ( volumeName string , nodeName types . NodeName ) error {
2019-04-07 17:07:55 +00:00
var attachID string
var volID string
2019-01-12 04:58:27 +00:00
if volumeName == "" {
klog . Error ( log ( "detacher.Detach missing value for parameter volumeName" ) )
return errors . New ( "missing expected parameter volumeName" )
}
2019-04-07 17:07:55 +00:00
if isAttachmentName ( volumeName ) {
// Detach can also be called with the attach ID as the `volumeName`. This codepath is
// hit only when we have migrated an in-tree volume to CSI and the A/D Controller is shut
// down, the pod with the volume is deleted, and the A/D Controller starts back up in that
// order.
attachID = volumeName
// Vol ID should be the volume handle, except that is not available here.
// It is only used in log messages so in the event that this happens log messages will be
// printing out the attachID instead of the volume handle.
volID = volumeName
} else {
// volumeName in format driverName<SEP>volumeHandle generated by plugin.GetVolumeName()
parts := strings . Split ( volumeName , volNameSep )
if len ( parts ) != 2 {
klog . Error ( log ( "detacher.Detach insufficient info encoded in volumeName" ) )
return errors . New ( "volumeName missing expected data" )
}
driverName := parts [ 0 ]
volID = parts [ 1 ]
attachID = getAttachmentName ( volID , driverName , string ( nodeName ) )
2019-01-12 04:58:27 +00:00
}
2020-03-26 21:07:15 +00:00
if err := c . k8s . StorageV1 ( ) . VolumeAttachments ( ) . Delete ( context . TODO ( ) , attachID , metav1 . DeleteOptions { } ) ; err != nil {
if apierrors . IsNotFound ( err ) {
2019-01-12 04:58:27 +00:00
// object deleted or never existed, done
klog . V ( 4 ) . Info ( log ( "VolumeAttachment object [%v] for volume [%v] not found, object deleted" , attachID , volID ) )
return nil
}
2019-09-27 21:51:53 +00:00
return errors . New ( log ( "detacher.Detach failed to delete VolumeAttachment [%s]: %v" , attachID , err ) )
2019-01-12 04:58:27 +00:00
}
klog . V ( 4 ) . Info ( log ( "detacher deleted ok VolumeAttachment.ID=%s" , attachID ) )
2021-03-18 22:40:29 +00:00
// Attach and detach functionality is exclusive to the CSI plugin that runs in the AttachDetachController,
// and has access to a VolumeAttachment lister that can be polled for the current status.
return c . waitForVolumeDetachmentWithLister ( volID , attachID , c . watchTimeout )
2019-01-12 04:58:27 +00:00
}
2021-03-18 22:40:29 +00:00
func ( c * csiAttacher ) waitForVolumeDetachmentWithLister ( volumeHandle , attachID string , timeout time . Duration ) error {
2019-01-12 04:58:27 +00:00
klog . V ( 4 ) . Info ( log ( "probing VolumeAttachment [id=%v]" , attachID ) )
2021-03-18 22:40:29 +00:00
verifyStatus := func ( ) ( bool , error ) {
volumeAttachment , err := c . plugin . volumeAttachmentLister . Get ( attachID )
if err != nil {
if ! apierrors . IsNotFound ( err ) {
return false , errors . New ( log ( "detacher.WaitForDetach failed for volume [%s] (will continue to try): %v" , volumeHandle , err ) )
}
// Detachment successful.
2019-01-12 04:58:27 +00:00
klog . V ( 4 ) . Info ( log ( "VolumeAttachment object [%v] for volume [%v] not found, object deleted" , attachID , volumeHandle ) )
2021-03-18 22:40:29 +00:00
return true , nil
}
// Detachment is only "successful" once the VolumeAttachment is deleted, however we perform
// this check to make sure the object does not contain any detach errors.
successful , err := verifyDetachmentStatus ( volumeAttachment , volumeHandle )
if err != nil {
return false , err
2019-01-12 04:58:27 +00:00
}
2021-03-18 22:40:29 +00:00
return successful , nil
2019-01-12 04:58:27 +00:00
}
2021-03-18 22:40:29 +00:00
return c . waitForVolumeAttachDetachStatusWithLister ( volumeHandle , attachID , timeout , verifyStatus , "Detach" )
}
func ( c * csiAttacher ) waitForVolumeAttachDetachStatusWithLister ( volumeHandle , attachID string , timeout time . Duration , verifyStatus func ( ) ( bool , error ) , operation string ) error {
var (
initBackoff = 500 * time . Millisecond
// This is approximately the duration between consecutive ticks after two minutes (CSI timeout).
maxBackoff = 7 * time . Second
resetDuration = time . Minute
backoffFactor = 1.05
jitter = 0.1
clock = & clock . RealClock { }
)
backoffMgr := wait . NewExponentialBackoffManager ( initBackoff , maxBackoff , resetDuration , backoffFactor , jitter , clock )
defer backoffMgr . Backoff ( ) . Stop ( )
ctx , cancel := context . WithTimeout ( context . Background ( ) , timeout )
defer cancel ( )
for {
select {
case <- backoffMgr . Backoff ( ) . C ( ) :
successful , err := verifyStatus ( )
if err != nil {
return err
}
if successful {
return nil
}
case <- ctx . Done ( ) :
klog . Error ( log ( "%s timeout after %v [volume=%v; attachment.ID=%v]" , operation , timeout , volumeHandle , attachID ) )
return fmt . Errorf ( "%s timeout for volume %v" , operation , volumeHandle )
}
2019-12-12 01:27:03 +00:00
}
}
func ( c * csiAttacher ) waitForVolumeAttachDetachStatus ( attach * storage . VolumeAttachment , volumeHandle , attachID string ,
timer * time . Timer , timeout time . Duration , verifyStatus verifyAttachDetachStatus ) error {
successful , err := verifyStatus ( attach , volumeHandle )
if err != nil {
return err
}
if successful {
return nil
2019-01-12 04:58:27 +00:00
}
2020-03-26 21:07:15 +00:00
watcher , err := c . k8s . StorageV1 ( ) . VolumeAttachments ( ) . Watch ( context . TODO ( ) , meta . SingleObject ( meta . ObjectMeta { Name : attachID , ResourceVersion : attach . ResourceVersion } ) )
2019-01-12 04:58:27 +00:00
if err != nil {
2019-12-12 01:27:03 +00:00
return fmt . Errorf ( "watch error:%v for volume %v" , err , volumeHandle )
2019-01-12 04:58:27 +00:00
}
2019-12-12 01:27:03 +00:00
ch := watcher . ResultChan ( )
defer watcher . Stop ( )
2019-01-12 04:58:27 +00:00
for {
select {
case event , ok := <- ch :
if ! ok {
klog . Errorf ( "[attachment.ID=%v] watch channel had been closed" , attachID )
return errors . New ( "volume attachment watch channel had been closed" )
}
switch event . Type {
case watch . Added , watch . Modified :
attach , _ := event . Object . ( * storage . VolumeAttachment )
2019-12-12 01:27:03 +00:00
successful , err := verifyStatus ( attach , volumeHandle )
if err != nil {
return err
}
if successful {
return nil
2019-01-12 04:58:27 +00:00
}
case watch . Deleted :
2019-12-12 01:27:03 +00:00
// set attach nil to get different results
// for detachment, a deleted event means successful detachment, should return success
// for attachment, should return fail
if successful , err := verifyStatus ( nil , volumeHandle ) ; ! successful {
return err
}
2019-01-12 04:58:27 +00:00
klog . V ( 4 ) . Info ( log ( "VolumeAttachment object [%v] for volume [%v] has been deleted" , attachID , volumeHandle ) )
return nil
case watch . Error :
2019-12-12 01:27:03 +00:00
klog . Warningf ( "waitForVolumeAttachDetachInternal received watch error: %v" , event )
2019-01-12 04:58:27 +00:00
}
case <- timer . C :
2019-12-12 01:27:03 +00:00
klog . Error ( log ( "attachdetacher.WaitForDetach timeout after %v [volume=%v; attachment.ID=%v]" , timeout , volumeHandle , attachID ) )
return fmt . Errorf ( "attachdetachment timeout for volume %v" , volumeHandle )
2019-01-12 04:58:27 +00:00
}
}
}
func ( c * csiAttacher ) UnmountDevice ( deviceMountPath string ) error {
klog . V ( 4 ) . Info ( log ( "attacher.UnmountDevice(%s)" , deviceMountPath ) )
// Setup
var driverName , volID string
dataDir := filepath . Dir ( deviceMountPath )
data , err := loadVolumeData ( dataDir , volDataFileName )
if err == nil {
driverName = data [ volDataKey . driverName ]
volID = data [ volDataKey . volHandle ]
} else {
klog . Error ( log ( "UnmountDevice failed to load volume data file [%s]: %v" , dataDir , err ) )
// The volume might have been mounted by old CSI volume plugin. Fall back to the old behavior: read PV from API server
driverName , volID , err = getDriverAndVolNameFromDeviceMountPath ( c . k8s , deviceMountPath )
if err != nil {
klog . Errorf ( log ( "attacher.UnmountDevice failed to get driver and volume name from device mount path: %v" , err ) )
return err
}
}
if c . csiClient == nil {
c . csiClient , err = newCsiDriverClient ( csiDriverName ( driverName ) )
if err != nil {
2019-09-27 21:51:53 +00:00
return errors . New ( log ( "attacher.UnmountDevice failed to create newCsiDriverClient: %v" , err ) )
2019-01-12 04:58:27 +00:00
}
}
csi := c . csiClient
2021-03-18 22:40:29 +00:00
// could not get whether this is migrated because there is no spec
ctx , cancel := createCSIOperationContext ( nil , csiTimeout )
2019-01-12 04:58:27 +00:00
defer cancel ( )
// Check whether "STAGE_UNSTAGE_VOLUME" is set
stageUnstageSet , err := csi . NodeSupportsStageUnstage ( ctx )
if err != nil {
2019-09-27 21:51:53 +00:00
return errors . New ( log ( "attacher.UnmountDevice failed to check whether STAGE_UNSTAGE_VOLUME set: %v" , err ) )
2019-01-12 04:58:27 +00:00
}
if ! stageUnstageSet {
klog . Infof ( log ( "attacher.UnmountDevice STAGE_UNSTAGE_VOLUME capability not set. Skipping UnmountDevice..." ) )
// Just delete the global directory + json file
if err := removeMountDir ( c . plugin , deviceMountPath ) ; err != nil {
2019-09-27 21:51:53 +00:00
return errors . New ( log ( "failed to clean up global mount %s: %s" , dataDir , err ) )
2019-01-12 04:58:27 +00:00
}
return nil
}
// Start UnmountDevice
err = csi . NodeUnstageVolume ( ctx ,
volID ,
deviceMountPath )
if err != nil {
2019-09-27 21:51:53 +00:00
return errors . New ( log ( "attacher.UnmountDevice failed: %v" , err ) )
2019-01-12 04:58:27 +00:00
}
// Delete the global directory + json file
if err := removeMountDir ( c . plugin , deviceMountPath ) ; err != nil {
2019-09-27 21:51:53 +00:00
return errors . New ( log ( "failed to clean up global mount %s: %s" , dataDir , err ) )
2019-01-12 04:58:27 +00:00
}
2019-09-27 21:51:53 +00:00
klog . V ( 4 ) . Infof ( log ( "attacher.UnmountDevice successfully requested NodeUnStageVolume [%s]" , deviceMountPath ) )
2019-01-12 04:58:27 +00:00
return nil
}
2019-04-07 17:07:55 +00:00
// getAttachmentName returns csi-<sha256(volName,csiDriverName,NodeName)>
2019-01-12 04:58:27 +00:00
func getAttachmentName ( volName , csiDriverName , nodeName string ) string {
result := sha256 . Sum256 ( [ ] byte ( fmt . Sprintf ( "%s%s%s" , volName , csiDriverName , nodeName ) ) )
return fmt . Sprintf ( "csi-%x" , result )
}
2019-04-07 17:07:55 +00:00
// isAttachmentName returns true if the string given is of the form of an Attach ID
// and false otherwise
func isAttachmentName ( unknownString string ) bool {
// 68 == "csi-" + len(sha256hash)
2021-07-02 08:43:15 +00:00
return strings . HasPrefix ( unknownString , "csi-" ) && len ( unknownString ) == 68
2019-04-07 17:07:55 +00:00
}
2019-01-12 04:58:27 +00:00
func makeDeviceMountPath ( plugin * csiPlugin , spec * volume . Spec ) ( string , error ) {
if spec == nil {
2019-09-27 21:51:53 +00:00
return "" , errors . New ( log ( "makeDeviceMountPath failed, spec is nil" ) )
2019-01-12 04:58:27 +00:00
}
pvName := spec . PersistentVolume . Name
if pvName == "" {
2019-09-27 21:51:53 +00:00
return "" , errors . New ( log ( "makeDeviceMountPath failed, pv name empty" ) )
2019-01-12 04:58:27 +00:00
}
2019-08-30 18:33:25 +00:00
return filepath . Join ( plugin . host . GetPluginDir ( plugin . GetPluginName ( ) ) , persistentVolumeInGlobalPath , pvName , globalMountInGlobalPath ) , nil
2019-01-12 04:58:27 +00:00
}
func getDriverAndVolNameFromDeviceMountPath ( k8s kubernetes . Interface , deviceMountPath string ) ( string , string , error ) {
// deviceMountPath structure: /var/lib/kubelet/plugins/kubernetes.io/csi/pv/{pvname}/globalmount
dir := filepath . Dir ( deviceMountPath )
if file := filepath . Base ( deviceMountPath ) ; file != globalMountInGlobalPath {
2019-09-27 21:51:53 +00:00
return "" , "" , errors . New ( log ( "getDriverAndVolNameFromDeviceMountPath failed, path did not end in %s" , globalMountInGlobalPath ) )
2019-01-12 04:58:27 +00:00
}
// dir is now /var/lib/kubelet/plugins/kubernetes.io/csi/pv/{pvname}
pvName := filepath . Base ( dir )
// Get PV and check for errors
2020-03-26 21:07:15 +00:00
pv , err := k8s . CoreV1 ( ) . PersistentVolumes ( ) . Get ( context . TODO ( ) , pvName , meta . GetOptions { } )
2019-01-12 04:58:27 +00:00
if err != nil {
return "" , "" , err
}
if pv == nil || pv . Spec . CSI == nil {
2019-09-27 21:51:53 +00:00
return "" , "" , errors . New ( log ( "getDriverAndVolNameFromDeviceMountPath could not find CSI Persistent Volume Source for pv: %s" , pvName ) )
2019-01-12 04:58:27 +00:00
}
// Get VolumeHandle and PluginName from pv
csiSource := pv . Spec . CSI
if csiSource . Driver == "" {
2019-09-27 21:51:53 +00:00
return "" , "" , errors . New ( log ( "getDriverAndVolNameFromDeviceMountPath failed, driver name empty" ) )
2019-01-12 04:58:27 +00:00
}
if csiSource . VolumeHandle == "" {
2019-09-27 21:51:53 +00:00
return "" , "" , errors . New ( log ( "getDriverAndVolNameFromDeviceMountPath failed, VolumeHandle empty" ) )
2019-01-12 04:58:27 +00:00
}
return csiSource . Driver , csiSource . VolumeHandle , nil
}
2019-12-12 01:27:03 +00:00
func verifyAttachmentStatus ( attachment * storage . VolumeAttachment , volumeHandle string ) ( bool , error ) {
// when we received a deleted event during attachment, fail fast
if attachment == nil {
klog . Error ( log ( "VolumeAttachment [%s] has been deleted, will not continue to wait for attachment" , volumeHandle ) )
return false , errors . New ( "volume attachment has been deleted" )
}
// if being deleted, fail fast
if attachment . GetDeletionTimestamp ( ) != nil {
klog . Error ( log ( "VolumeAttachment [%s] has deletion timestamp, will not continue to wait for attachment" , attachment . Name ) )
return false , errors . New ( "volume attachment is being deleted" )
}
// attachment OK
if attachment . Status . Attached {
return true , nil
}
// driver reports attach error
attachErr := attachment . Status . AttachError
if attachErr != nil {
klog . Error ( log ( "attachment for %v failed: %v" , volumeHandle , attachErr . Message ) )
return false , errors . New ( attachErr . Message )
}
return false , nil
}
func verifyDetachmentStatus ( attachment * storage . VolumeAttachment , volumeHandle string ) ( bool , error ) {
// when we received a deleted event during detachment
// it means we have successfully detached it.
if attachment == nil {
return true , nil
}
// driver reports detach error
detachErr := attachment . Status . DetachError
if detachErr != nil {
klog . Error ( log ( "detachment for VolumeAttachment for volume [%s] failed: %v" , volumeHandle , detachErr . Message ) )
return false , errors . New ( detachErr . Message )
}
return false , nil
}