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 (
2019-04-07 17:07:55 +00:00
"crypto/sha256"
2020-12-01 01:06:26 +00:00
"encoding/json"
2019-09-27 21:51:53 +00:00
"errors"
2019-01-12 04:58:27 +00:00
"fmt"
"os"
2019-08-30 18:33:25 +00:00
"path/filepath"
2019-01-12 04:58:27 +00:00
2020-08-10 17:43:49 +00:00
"k8s.io/klog/v2"
2019-01-12 04:58:27 +00:00
2020-12-01 01:06:26 +00:00
authenticationv1 "k8s.io/api/authentication/v1"
2019-01-12 04:58:27 +00:00
api "k8s.io/api/core/v1"
2020-03-26 21:07:15 +00:00
storage "k8s.io/api/storage/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
2019-01-12 04:58:27 +00:00
"k8s.io/apimachinery/pkg/types"
2019-04-07 17:07:55 +00:00
utilfeature "k8s.io/apiserver/pkg/util/feature"
2019-01-12 04:58:27 +00:00
"k8s.io/client-go/kubernetes"
2019-04-07 17:07:55 +00:00
"k8s.io/kubernetes/pkg/features"
2019-01-12 04:58:27 +00:00
"k8s.io/kubernetes/pkg/volume"
2020-12-01 01:06:26 +00:00
"k8s.io/kubernetes/pkg/volume/util"
2020-03-26 21:07:15 +00:00
volumetypes "k8s.io/kubernetes/pkg/volume/util/types"
2020-12-01 01:06:26 +00:00
"k8s.io/mount-utils"
2019-04-07 17:07:55 +00:00
utilstrings "k8s.io/utils/strings"
2019-01-12 04:58:27 +00:00
)
//TODO (vladimirvivien) move this in a central loc later
var (
volDataKey = struct {
specVolID ,
volHandle ,
driverName ,
nodeName ,
2019-04-07 17:07:55 +00:00
attachmentID ,
2019-09-27 21:51:53 +00:00
volumeLifecycleMode string
2019-01-12 04:58:27 +00:00
} {
"specVolID" ,
"volumeHandle" ,
"driverName" ,
"nodeName" ,
"attachmentID" ,
2019-09-27 21:51:53 +00:00
"volumeLifecycleMode" ,
2019-01-12 04:58:27 +00:00
}
)
type csiMountMgr struct {
2019-03-29 00:03:05 +00:00
csiClientGetter
2019-09-27 21:51:53 +00:00
k8s kubernetes . Interface
plugin * csiPlugin
driverName csiDriverName
volumeLifecycleMode storage . VolumeLifecycleMode
2020-08-10 17:43:49 +00:00
fsGroupPolicy storage . FSGroupPolicy
2019-09-27 21:51:53 +00:00
volumeID string
specVolumeID string
readOnly bool
2019-12-12 01:27:03 +00:00
supportsSELinux bool
2019-09-27 21:51:53 +00:00
spec * volume . Spec
pod * api . Pod
podUID types . UID
publishContext map [ string ] string
kubeVolHost volume . KubeletVolumeHost
2019-08-30 18:33:25 +00:00
volume . MetricsProvider
2019-01-12 04:58:27 +00:00
}
// volume.Volume methods
var _ volume . Volume = & csiMountMgr { }
func ( c * csiMountMgr ) GetPath ( ) string {
2020-08-10 17:43:49 +00:00
dir := GetCSIMounterPath ( filepath . Join ( getTargetPath ( c . podUID , c . specVolumeID , c . plugin . host ) ) )
2019-01-12 04:58:27 +00:00
klog . V ( 4 ) . Info ( log ( "mounter.GetPath generated [%s]" , dir ) )
return dir
}
func getTargetPath ( uid types . UID , specVolumeID string , host volume . VolumeHost ) string {
2019-04-07 17:07:55 +00:00
specVolID := utilstrings . EscapeQualifiedName ( specVolumeID )
return host . GetPodVolumeDir ( uid , utilstrings . EscapeQualifiedName ( CSIPluginName ) , specVolID )
2019-01-12 04:58:27 +00:00
}
// volume.Mounter methods
var _ volume . Mounter = & csiMountMgr { }
func ( c * csiMountMgr ) CanMount ( ) error {
return nil
}
2019-08-30 18:33:25 +00:00
func ( c * csiMountMgr ) SetUp ( mounterArgs volume . MounterArgs ) error {
return c . SetUpAt ( c . GetPath ( ) , mounterArgs )
2019-01-12 04:58:27 +00:00
}
2019-08-30 18:33:25 +00:00
func ( c * csiMountMgr ) SetUpAt ( dir string , mounterArgs volume . MounterArgs ) error {
2019-01-12 04:58:27 +00:00
klog . V ( 4 ) . Infof ( log ( "Mounter.SetUpAt(%s)" , dir ) )
2019-03-29 00:03:05 +00:00
csi , err := c . csiClientGetter . Get ( )
if err != nil {
2020-03-26 21:07:15 +00:00
return volumetypes . NewTransientOperationFailure ( log ( "mounter.SetUpAt failed to get CSI client: %v" , err ) )
2019-03-29 00:03:05 +00:00
}
2021-03-18 22:40:29 +00:00
ctx , cancel := createCSIOperationContext ( c . spec , csiTimeout )
2019-01-12 04:58:27 +00:00
defer cancel ( )
2019-04-07 17:07:55 +00:00
volSrc , pvSrc , err := getSourceFromSpec ( c . spec )
2019-01-12 04:58:27 +00:00
if err != nil {
2019-09-27 21:51:53 +00:00
return errors . New ( log ( "mounter.SetupAt failed to get CSI persistent source: %v" , err ) )
2019-01-12 04:58:27 +00:00
}
2019-04-07 17:07:55 +00:00
driverName := c . driverName
volumeHandle := c . volumeID
readOnly := c . readOnly
accessMode := api . ReadWriteOnce
var (
fsType string
volAttribs map [ string ] string
nodePublishSecrets map [ string ] string
publishContext map [ string ] string
mountOptions [ ] string
deviceMountPath string
secretRef * api . SecretReference
)
switch {
case volSrc != nil :
2019-08-30 18:33:25 +00:00
if ! utilfeature . DefaultFeatureGate . Enabled ( features . CSIInlineVolume ) {
return fmt . Errorf ( "CSIInlineVolume feature required" )
}
2019-09-27 21:51:53 +00:00
if c . volumeLifecycleMode != storage . VolumeLifecycleEphemeral {
return fmt . Errorf ( "unexpected volume mode: %s" , c . volumeLifecycleMode )
2019-08-30 18:33:25 +00:00
}
if volSrc . FSType != nil {
fsType = * volSrc . FSType
}
volAttribs = volSrc . VolumeAttributes
if volSrc . NodePublishSecretRef != nil {
secretName := volSrc . NodePublishSecretRef . Name
ns := c . pod . Namespace
secretRef = & api . SecretReference { Name : secretName , Namespace : ns }
}
2019-04-07 17:07:55 +00:00
case pvSrc != nil :
2019-09-27 21:51:53 +00:00
if c . volumeLifecycleMode != storage . VolumeLifecyclePersistent {
return fmt . Errorf ( "unexpected driver mode: %s" , c . volumeLifecycleMode )
2019-01-12 04:58:27 +00:00
}
2019-04-07 17:07:55 +00:00
fsType = pvSrc . FSType
volAttribs = pvSrc . VolumeAttributes
if pvSrc . NodePublishSecretRef != nil {
secretRef = pvSrc . NodePublishSecretRef
}
//TODO (vladimirvivien) implement better AccessModes mapping between k8s and CSI
if c . spec . PersistentVolume . Spec . AccessModes != nil {
accessMode = c . spec . PersistentVolume . Spec . AccessModes [ 0 ]
}
mountOptions = c . spec . PersistentVolume . Spec . MountOptions
// Check for STAGE_UNSTAGE_VOLUME set and populate deviceMountPath if so
stageUnstageSet , err := csi . NodeSupportsStageUnstage ( ctx )
2019-01-12 04:58:27 +00:00
if err != nil {
2019-09-27 21:51:53 +00:00
return errors . New ( log ( "mounter.SetUpAt failed to check for STAGE_UNSTAGE_VOLUME capability: %v" , err ) )
2019-01-12 04:58:27 +00:00
}
2019-04-07 17:07:55 +00:00
if stageUnstageSet {
deviceMountPath , err = makeDeviceMountPath ( c . plugin , c . spec )
if err != nil {
2019-09-27 21:51:53 +00:00
return errors . New ( log ( "mounter.SetUpAt failed to make device mount path: %v" , err ) )
2019-04-07 17:07:55 +00:00
}
}
2019-01-12 04:58:27 +00:00
2019-04-07 17:07:55 +00:00
// search for attachment by VolumeAttachment.Spec.Source.PersistentVolumeName
if c . publishContext == nil {
nodeName := string ( c . plugin . host . GetNodeName ( ) )
c . publishContext , err = c . plugin . getPublishContext ( c . k8s , volumeHandle , string ( driverName ) , nodeName )
if err != nil {
2020-03-26 21:07:15 +00:00
// we could have a transient error associated with fetching publish context
return volumetypes . NewTransientOperationFailure ( log ( "mounter.SetUpAt failed to fetch publishContext: %v" , err ) )
2019-04-07 17:07:55 +00:00
}
publishContext = c . publishContext
2019-01-12 04:58:27 +00:00
}
2019-04-07 17:07:55 +00:00
default :
return fmt . Errorf ( "volume source not found in volume.Spec" )
2019-01-12 04:58:27 +00:00
}
// create target_dir before call to NodePublish
2020-12-01 01:06:26 +00:00
parentDir := filepath . Dir ( dir )
if err := os . MkdirAll ( parentDir , 0750 ) ; err != nil {
return errors . New ( log ( "mounter.SetUpAt failed to create dir %#v: %v" , parentDir , err ) )
2019-01-12 04:58:27 +00:00
}
2020-12-01 01:06:26 +00:00
klog . V ( 4 ) . Info ( log ( "created target path successfully [%s]" , parentDir ) )
2019-01-12 04:58:27 +00:00
2019-04-07 17:07:55 +00:00
nodePublishSecrets = map [ string ] string { }
if secretRef != nil {
nodePublishSecrets , err = getCredentialsFromSecret ( c . k8s , secretRef )
if err != nil {
2020-03-26 21:07:15 +00:00
return volumetypes . NewTransientOperationFailure ( fmt . Sprintf ( "fetching NodePublishSecretRef %s/%s failed: %v" ,
secretRef . Namespace , secretRef . Name , err ) )
2019-04-07 17:07:55 +00:00
}
2019-01-12 04:58:27 +00:00
}
// Inject pod information into volume_attributes
2021-07-02 08:43:15 +00:00
podInfoEnabled , err := c . plugin . podInfoEnabled ( string ( c . driverName ) )
2019-01-12 04:58:27 +00:00
if err != nil {
2020-03-26 21:07:15 +00:00
return volumetypes . NewTransientOperationFailure ( log ( "mounter.SetUpAt failed to assemble volume attributes: %v" , err ) )
2019-01-12 04:58:27 +00:00
}
2021-07-02 08:43:15 +00:00
if podInfoEnabled {
volAttribs = mergeMap ( volAttribs , getPodInfoAttrs ( c . pod , c . volumeLifecycleMode ) )
}
2020-12-01 01:06:26 +00:00
// Inject pod service account token into volume attributes
2021-07-02 08:43:15 +00:00
serviceAccountTokenAttrs , err := c . podServiceAccountTokenAttrs ( )
if err != nil {
return volumetypes . NewTransientOperationFailure ( log ( "mounter.SetUpAt failed to get service accoount token attributes: %v" , err ) )
}
volAttribs = mergeMap ( volAttribs , serviceAccountTokenAttrs )
driverSupportsCSIVolumeMountGroup := false
var nodePublishFSGroupArg * int64
if utilfeature . DefaultFeatureGate . Enabled ( features . DelegateFSGroupToCSIDriver ) {
driverSupportsCSIVolumeMountGroup , err = csi . NodeSupportsVolumeMountGroup ( ctx )
2020-12-01 01:06:26 +00:00
if err != nil {
2021-07-02 08:43:15 +00:00
return volumetypes . NewTransientOperationFailure ( log ( "mounter.SetUpAt 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 NodePublishVolume." , c . driverName )
nodePublishFSGroupArg = mounterArgs . FsGroup
2019-01-12 04:58:27 +00:00
}
}
err = csi . NodePublishVolume (
ctx ,
2019-04-07 17:07:55 +00:00
volumeHandle ,
readOnly ,
2019-01-12 04:58:27 +00:00
deviceMountPath ,
dir ,
accessMode ,
2019-04-07 17:07:55 +00:00
publishContext ,
volAttribs ,
2019-01-12 04:58:27 +00:00
nodePublishSecrets ,
fsType ,
2019-04-07 17:07:55 +00:00
mountOptions ,
2021-07-02 08:43:15 +00:00
nodePublishFSGroupArg ,
2019-01-12 04:58:27 +00:00
)
if err != nil {
2020-03-26 21:07:15 +00:00
// If operation finished with error then we can remove the mount directory.
if volumetypes . IsOperationFinishedError ( err ) {
if removeMountDirErr := removeMountDir ( c . plugin , dir ) ; removeMountDirErr != nil {
klog . Error ( log ( "mounter.SetupAt failed to remove mount dir after a NodePublish() error [%s]: %v" , dir , removeMountDirErr ) )
}
2019-01-12 04:58:27 +00:00
}
2020-03-26 21:07:15 +00:00
return err
2019-01-12 04:58:27 +00:00
}
2019-12-12 01:27:03 +00:00
c . supportsSELinux , err = c . kubeVolHost . GetHostUtil ( ) . GetSELinuxSupport ( dir )
if err != nil {
klog . V ( 2 ) . Info ( log ( "error checking for SELinux support: %s" , err ) )
}
2021-07-02 08:43:15 +00:00
if ! driverSupportsCSIVolumeMountGroup && c . supportsFSGroup ( fsType , mounterArgs . FsGroup , c . fsGroupPolicy ) {
// Driver doesn't support applying FSGroup. Kubelet must apply it instead.
2020-12-01 01:06:26 +00:00
// fullPluginName helps to distinguish different driver from csi plugin
err := volume . SetVolumeOwnership ( c , mounterArgs . FsGroup , mounterArgs . FSGroupChangePolicy , util . FSGroupCompleteHook ( c . plugin , c . spec ) )
2020-08-10 17:43:49 +00:00
if err != nil {
// At this point mount operation is successful:
// 1. Since volume can not be used by the pod because of invalid permissions, we must return error
// 2. Since mount is successful, we must record volume as mounted in uncertain state, so it can be
// cleaned up.
return volumetypes . NewUncertainProgressError ( fmt . Sprintf ( "applyFSGroup failed for vol %s: %v" , c . volumeID , err ) )
}
klog . V ( 4 ) . Info ( log ( "mounter.SetupAt fsGroup [%d] applied successfully to %s" , * mounterArgs . FsGroup , c . volumeID ) )
2019-01-12 04:58:27 +00:00
}
klog . V ( 4 ) . Infof ( log ( "mounter.SetUp successfully requested NodePublish [%s]" , dir ) )
return nil
}
2020-12-01 01:06:26 +00:00
func ( c * csiMountMgr ) podServiceAccountTokenAttrs ( ) ( map [ string ] string , error ) {
if c . plugin . serviceAccountTokenGetter == nil {
return nil , errors . New ( "ServiceAccountTokenGetter is nil" )
}
csiDriver , err := c . plugin . csiDriverLister . Get ( string ( c . driverName ) )
if err != nil {
if apierrors . IsNotFound ( err ) {
klog . V ( 5 ) . Infof ( log ( "CSIDriver %q not found, not adding service account token information" , c . driverName ) )
return nil , nil
}
return nil , err
}
if len ( csiDriver . Spec . TokenRequests ) == 0 {
return nil , nil
}
outputs := map [ string ] authenticationv1 . TokenRequestStatus { }
for _ , tokenRequest := range csiDriver . Spec . TokenRequests {
audience := tokenRequest . Audience
audiences := [ ] string { audience }
if audience == "" {
audiences = [ ] string { }
}
tr , err := c . plugin . serviceAccountTokenGetter ( c . pod . Namespace , c . pod . Spec . ServiceAccountName , & authenticationv1 . TokenRequest {
Spec : authenticationv1 . TokenRequestSpec {
Audiences : audiences ,
ExpirationSeconds : tokenRequest . ExpirationSeconds ,
BoundObjectRef : & authenticationv1 . BoundObjectReference {
APIVersion : "v1" ,
Kind : "Pod" ,
Name : c . pod . Name ,
UID : c . pod . UID ,
} ,
} ,
} )
if err != nil {
return nil , err
}
outputs [ audience ] = tr . Status
}
klog . V ( 4 ) . Infof ( log ( "Fetched service account token attrs for CSIDriver %q" , c . driverName ) )
tokens , _ := json . Marshal ( outputs )
return map [ string ] string {
"csi.storage.k8s.io/serviceAccount.tokens" : string ( tokens ) ,
} , nil
}
2019-01-12 04:58:27 +00:00
func ( c * csiMountMgr ) GetAttributes ( ) volume . Attributes {
return volume . Attributes {
ReadOnly : c . readOnly ,
Managed : ! c . readOnly ,
2019-12-12 01:27:03 +00:00
SupportsSELinux : c . supportsSELinux ,
2019-01-12 04:58:27 +00:00
}
}
// volume.Unmounter methods
var _ volume . Unmounter = & csiMountMgr { }
func ( c * csiMountMgr ) TearDown ( ) error {
return c . TearDownAt ( c . GetPath ( ) )
}
func ( c * csiMountMgr ) TearDownAt ( dir string ) error {
klog . V ( 4 ) . Infof ( log ( "Unmounter.TearDown(%s)" , dir ) )
volID := c . volumeID
2019-03-29 00:03:05 +00:00
csi , err := c . csiClientGetter . Get ( )
if err != nil {
2019-09-27 21:51:53 +00:00
return errors . New ( log ( "mounter.SetUpAt failed to get CSI client: %v" , err ) )
2019-03-29 00:03:05 +00:00
}
2019-01-12 04:58:27 +00:00
2021-03-18 22:40:29 +00:00
// Could not get spec info on whether this is a migrated operation because c.spec is nil
ctx , cancel := createCSIOperationContext ( c . spec , csiTimeout )
2019-01-12 04:58:27 +00:00
defer cancel ( )
if err := csi . NodeUnpublishVolume ( ctx , volID , dir ) ; err != nil {
2019-09-27 21:51:53 +00:00
return errors . New ( log ( "mounter.TearDownAt failed: %v" , err ) )
2019-01-12 04:58:27 +00:00
}
2021-07-02 08:43:15 +00:00
// Deprecation: Removal of target_path provided in the NodePublish RPC call
// (in this case location `dir`) MUST be done by the CSI plugin according
// to the spec. This will no longer be done directly as part of TearDown
// by the kubelet in the future. Kubelet will only be responsible for
// removal of json data files it creates and parent directories.
2019-01-12 04:58:27 +00:00
if err := removeMountDir ( c . plugin , dir ) ; err != nil {
2019-09-27 21:51:53 +00:00
return errors . New ( log ( "mounter.TearDownAt failed to clean mount dir [%s]: %v" , dir , err ) )
2019-01-12 04:58:27 +00:00
}
2019-04-07 17:07:55 +00:00
klog . V ( 4 ) . Infof ( log ( "mounter.TearDownAt successfully unmounted dir [%s]" , dir ) )
2019-01-12 04:58:27 +00:00
return nil
}
2020-08-10 17:43:49 +00:00
func ( c * csiMountMgr ) supportsFSGroup ( fsType string , fsGroup * int64 , driverPolicy storage . FSGroupPolicy ) bool {
if fsGroup == nil || driverPolicy == storage . NoneFSGroupPolicy || c . readOnly {
return false
}
2019-01-12 04:58:27 +00:00
2020-08-10 17:43:49 +00:00
if driverPolicy == storage . FileFSGroupPolicy {
return true
}
2019-01-12 04:58:27 +00:00
2020-08-10 17:43:49 +00:00
if fsType == "" {
klog . V ( 4 ) . Info ( log ( "mounter.SetupAt WARNING: skipping fsGroup, fsType not provided" ) )
return false
2019-01-12 04:58:27 +00:00
}
2021-07-02 08:43:15 +00:00
if c . spec . PersistentVolume == nil {
klog . V ( 4 ) . Info ( log ( "mounter.SetupAt Warning: skipping fsGroup permission change, no access mode available. The volume may only be accessible to root users." ) )
return false
}
2020-08-10 17:43:49 +00:00
if c . spec . PersistentVolume . Spec . AccessModes == nil {
klog . V ( 4 ) . Info ( log ( "mounter.SetupAt WARNING: skipping fsGroup, access modes not provided" ) )
return false
}
2021-07-02 08:43:15 +00:00
if ! hasReadWriteOnce ( c . spec . PersistentVolume . Spec . AccessModes ) {
2020-08-10 17:43:49 +00:00
klog . V ( 4 ) . Info ( log ( "mounter.SetupAt WARNING: skipping fsGroup, only support ReadWriteOnce access mode" ) )
return false
}
return true
2019-01-12 04:58:27 +00:00
}
// isDirMounted returns the !notMounted result from IsLikelyNotMountPoint check
func isDirMounted ( plug * csiPlugin , dir string ) ( bool , error ) {
mounter := plug . host . GetMounter ( plug . GetPluginName ( ) )
notMnt , err := mounter . IsLikelyNotMountPoint ( dir )
if err != nil && ! os . IsNotExist ( err ) {
klog . Error ( log ( "isDirMounted IsLikelyNotMountPoint test failed for dir [%v]" , dir ) )
return false , err
}
return ! notMnt , nil
}
2020-03-13 19:24:50 +00:00
func isCorruptedDir ( dir string ) bool {
_ , pathErr := mount . PathExists ( dir )
return pathErr != nil && mount . IsCorruptedMnt ( pathErr )
}
2019-01-12 04:58:27 +00:00
// removeMountDir cleans the mount dir when dir is not mounted and removed the volume data file in dir
func removeMountDir ( plug * csiPlugin , mountPath string ) error {
klog . V ( 4 ) . Info ( log ( "removing mount path [%s]" , mountPath ) )
2019-04-07 17:07:55 +00:00
mnt , err := isDirMounted ( plug , mountPath )
2019-01-12 04:58:27 +00:00
if err != nil {
return err
}
2019-04-07 17:07:55 +00:00
if ! mnt {
2019-01-12 04:58:27 +00:00
klog . V ( 4 ) . Info ( log ( "dir not mounted, deleting it [%s]" , mountPath ) )
if err := os . Remove ( mountPath ) ; err != nil && ! os . IsNotExist ( err ) {
2019-09-27 21:51:53 +00:00
return errors . New ( log ( "failed to remove dir [%s]: %v" , mountPath , err ) )
2019-01-12 04:58:27 +00:00
}
// remove volume data file as well
2020-03-26 21:07:15 +00:00
volPath := filepath . Dir ( mountPath )
2019-08-30 18:33:25 +00:00
dataFile := filepath . Join ( volPath , volDataFileName )
2019-01-12 04:58:27 +00:00
klog . V ( 4 ) . Info ( log ( "also deleting volume info data file [%s]" , dataFile ) )
if err := os . Remove ( dataFile ) ; err != nil && ! os . IsNotExist ( err ) {
2019-09-27 21:51:53 +00:00
return errors . New ( log ( "failed to delete volume data file [%s]: %v" , dataFile , err ) )
2019-01-12 04:58:27 +00:00
}
// remove volume path
klog . V ( 4 ) . Info ( log ( "deleting volume path [%s]" , volPath ) )
if err := os . Remove ( volPath ) ; err != nil && ! os . IsNotExist ( err ) {
2019-09-27 21:51:53 +00:00
return errors . New ( log ( "failed to delete volume path [%s]: %v" , volPath , err ) )
2019-01-12 04:58:27 +00:00
}
}
return nil
}
2019-04-07 17:07:55 +00:00
// makeVolumeHandle returns csi-<sha256(podUID,volSourceSpecName)>
func makeVolumeHandle ( podUID , volSourceSpecName string ) string {
result := sha256 . Sum256 ( [ ] byte ( fmt . Sprintf ( "%s%s" , podUID , volSourceSpecName ) ) )
return fmt . Sprintf ( "csi-%x" , result )
}
2020-12-01 01:06:26 +00:00
func mergeMap ( first , second map [ string ] string ) map [ string ] string {
if first == nil && second != nil {
return second
}
for k , v := range second {
first [ k ] = v
}
return first
}