2018-01-09 18:59:50 +00:00
/ *
2019-04-03 08:46:15 +00:00
Copyright 2018 The Ceph - CSI Authors .
2018-01-09 18:59:50 +00:00
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 rbd
import (
2019-08-24 09:14:15 +00:00
"context"
2018-01-16 01:52:28 +00:00
"fmt"
2018-01-09 18:59:50 +00:00
"os"
2018-01-16 01:52:28 +00:00
"strings"
2018-01-09 18:59:50 +00:00
2019-05-13 04:47:17 +00:00
csicommon "github.com/ceph/ceph-csi/pkg/csi-common"
2019-04-22 21:35:39 +00:00
"github.com/ceph/ceph-csi/pkg/util"
2018-01-09 18:59:50 +00:00
2018-11-24 19:18:24 +00:00
"github.com/container-storage-interface/spec/lib/go/csi"
2018-01-09 18:59:50 +00:00
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
2019-02-18 11:30:28 +00:00
"k8s.io/klog"
2018-01-09 18:59:50 +00:00
"k8s.io/kubernetes/pkg/util/mount"
)
2019-01-28 11:47:06 +00:00
// NodeServer struct of ceph rbd driver with supported methods of CSI
// node server spec
2019-01-17 07:51:06 +00:00
type NodeServer struct {
2018-01-09 18:59:50 +00:00
* csicommon . DefaultNodeServer
2018-10-15 14:59:41 +00:00
mounter mount . Interface
2019-09-12 04:53:37 +00:00
// A map storing all volumes with ongoing operations so that additional operations
// for that same volume (as defined by VolumeID) return an Aborted error
VolumeLocks * util . VolumeLocks
2018-01-09 18:59:50 +00:00
}
2019-07-03 10:02:36 +00:00
// NodeStageVolume mounts the volume to a staging path on the node.
2019-07-31 16:24:19 +00:00
// Implementation notes:
// - stagingTargetPath is the directory passed in the request where the volume needs to be staged
// - We stage the volume into a directory, named after the VolumeID inside stagingTargetPath if
2019-08-03 22:11:28 +00:00
// it is a file system
2019-07-31 16:24:19 +00:00
// - We stage the volume into a file, named after the VolumeID inside stagingTargetPath if it is
2019-08-03 22:11:28 +00:00
// a block volume
// - Order of operation execution: (useful for defer stacking and when Unstaging to ensure steps
// are done in reverse, this is done in undoStagingTransaction)
// - Stash image metadata under staging path
// - Map the image (creates a device)
// - Create the staging file/directory under staging path
// - Stage the device (mount the device mapped for image)
2019-07-03 10:02:36 +00:00
func ( ns * NodeServer ) NodeStageVolume ( ctx context . Context , req * csi . NodeStageVolumeRequest ) ( * csi . NodeStageVolumeResponse , error ) {
if err := util . ValidateNodeStageVolumeRequest ( req ) ; err != nil {
return nil , err
2019-04-22 21:35:39 +00:00
}
2019-01-28 13:59:16 +00:00
2019-07-03 10:02:36 +00:00
isBlock := req . GetVolumeCapability ( ) . GetBlock ( ) != nil
disableInUseChecks := false
// MULTI_NODE_MULTI_WRITER is supported by default for Block access type volumes
if req . VolumeCapability . AccessMode . Mode == csi . VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER {
if isBlock {
disableInUseChecks = true
} else {
2019-08-22 16:57:23 +00:00
klog . Warningf ( util . Log ( ctx , "MULTI_NODE_MULTI_WRITER currently only supported with volumes of access type `block`, invalid AccessMode for volume: %v" ) , req . GetVolumeId ( ) )
2019-07-03 10:02:36 +00:00
return nil , status . Error ( codes . InvalidArgument , "rbd: RWX access mode request is only valid for volumes with access type `block`" )
}
2019-04-22 21:35:39 +00:00
}
2019-07-03 10:02:36 +00:00
volID := req . GetVolumeId ( )
2019-04-22 21:35:39 +00:00
2019-06-25 19:29:17 +00:00
cr , err := util . NewUserCredentials ( req . GetSecrets ( ) )
2019-06-01 21:26:42 +00:00
if err != nil {
return nil , status . Error ( codes . Internal , err . Error ( ) )
}
2019-06-25 19:29:17 +00:00
defer cr . DeleteCredentials ( )
2019-06-01 21:26:42 +00:00
2019-09-12 04:53:37 +00:00
if acquired := ns . VolumeLocks . TryAcquire ( volID ) ; ! acquired {
klog . Infof ( util . Log ( ctx , util . VolumeOperationAlreadyExistsFmt ) , volID )
return nil , status . Errorf ( codes . Aborted , util . VolumeOperationAlreadyExistsFmt , volID )
}
defer ns . VolumeLocks . Release ( volID )
2019-05-31 18:09:24 +00:00
isLegacyVolume := false
2019-09-12 04:53:37 +00:00
volName , err := getVolumeName ( volID )
2019-05-31 18:09:24 +00:00
if err != nil {
// error ErrInvalidVolID may mean this is an 1.0.0 version volume, check for name
// pattern match in addition to error to ensure this is a likely v1.0.0 volume
2019-09-12 04:53:37 +00:00
if _ , ok := err . ( ErrInvalidVolID ) ; ! ok || ! isLegacyVolumeID ( volID ) {
2019-05-31 18:09:24 +00:00
return nil , status . Error ( codes . InvalidArgument , err . Error ( ) )
}
2019-07-03 10:02:36 +00:00
volName , err = getLegacyVolumeName ( req . GetStagingTargetPath ( ) )
2019-05-31 18:09:24 +00:00
if err != nil {
return nil , status . Error ( codes . InvalidArgument , err . Error ( ) )
}
isLegacyVolume = true
}
2019-08-03 22:11:28 +00:00
stagingParentPath := req . GetStagingTargetPath ( )
2019-09-12 04:53:37 +00:00
stagingTargetPath := stagingParentPath + "/" + volID
2019-07-03 10:02:36 +00:00
2019-07-25 09:01:10 +00:00
var isNotMnt bool
// check if stagingPath is already mounted
isNotMnt , err = mount . IsNotMountPoint ( ns . mounter , stagingTargetPath )
if err != nil && ! os . IsNotExist ( err ) {
2019-07-03 10:02:36 +00:00
return nil , status . Error ( codes . Internal , err . Error ( ) )
2018-01-09 18:59:50 +00:00
}
2019-03-14 00:18:04 +00:00
2019-07-03 10:02:36 +00:00
if ! isNotMnt {
2019-08-22 16:57:23 +00:00
klog . Infof ( util . Log ( ctx , "rbd: volume %s is already mounted to %s, skipping" ) , req . GetVolumeId ( ) , stagingTargetPath )
2019-07-03 10:02:36 +00:00
return & csi . NodeStageVolumeResponse { } , nil
2019-03-14 00:18:04 +00:00
}
2019-08-22 16:57:23 +00:00
volOptions , err := genVolFromVolumeOptions ( ctx , req . GetVolumeContext ( ) , req . GetSecrets ( ) , disableInUseChecks , isLegacyVolume )
Move locks to more granular locking than CPU count based
As detailed in issue #279, current lock scheme has hash
buckets that are count of CPUs. This causes a lot of contention
when parallel requests are made to the CSI plugin. To reduce
lock contention, this commit introduces granular locks per
identifier.
The commit also changes the timeout for gRPC requests to Create
and Delete volumes, as the current timeout is 10s (kubernetes
documentation says 15s but code defaults are 10s). A virtual
setup takes about 12-15s to complete a request at times, that leads
to unwanted retries of the same request, hence the increased
timeout to enable operation completion with minimal retries.
Tests to create PVCs before and after these changes look like so,
Before:
Default master code + sidecar provisioner --timeout option set
to 30 seconds
20 PVCs
Creation: 3 runs, 396/391/400 seconds
Deletion: 3 runs, 218/271/118 seconds
- Once was stalled for more than 8 minutes and cancelled the run
After:
Current commit + sidecar provisioner --timeout option set to 30 sec
20 PVCs
Creation: 3 runs, 42/59/65 seconds
Deletion: 3 runs, 32/32/31 seconds
Fixes: #279
Signed-off-by: ShyamsundarR <srangana@redhat.com>
2019-06-22 16:43:28 +00:00
if err != nil {
2019-07-03 10:02:36 +00:00
return nil , status . Error ( codes . Internal , err . Error ( ) )
Move locks to more granular locking than CPU count based
As detailed in issue #279, current lock scheme has hash
buckets that are count of CPUs. This causes a lot of contention
when parallel requests are made to the CSI plugin. To reduce
lock contention, this commit introduces granular locks per
identifier.
The commit also changes the timeout for gRPC requests to Create
and Delete volumes, as the current timeout is 10s (kubernetes
documentation says 15s but code defaults are 10s). A virtual
setup takes about 12-15s to complete a request at times, that leads
to unwanted retries of the same request, hence the increased
timeout to enable operation completion with minimal retries.
Tests to create PVCs before and after these changes look like so,
Before:
Default master code + sidecar provisioner --timeout option set
to 30 seconds
20 PVCs
Creation: 3 runs, 396/391/400 seconds
Deletion: 3 runs, 218/271/118 seconds
- Once was stalled for more than 8 minutes and cancelled the run
After:
Current commit + sidecar provisioner --timeout option set to 30 sec
20 PVCs
Creation: 3 runs, 42/59/65 seconds
Deletion: 3 runs, 32/32/31 seconds
Fixes: #279
Signed-off-by: ShyamsundarR <srangana@redhat.com>
2019-06-22 16:43:28 +00:00
}
2019-04-22 21:35:39 +00:00
volOptions . RbdImageName = volName
Move locks to more granular locking than CPU count based
As detailed in issue #279, current lock scheme has hash
buckets that are count of CPUs. This causes a lot of contention
when parallel requests are made to the CSI plugin. To reduce
lock contention, this commit introduces granular locks per
identifier.
The commit also changes the timeout for gRPC requests to Create
and Delete volumes, as the current timeout is 10s (kubernetes
documentation says 15s but code defaults are 10s). A virtual
setup takes about 12-15s to complete a request at times, that leads
to unwanted retries of the same request, hence the increased
timeout to enable operation completion with minimal retries.
Tests to create PVCs before and after these changes look like so,
Before:
Default master code + sidecar provisioner --timeout option set
to 30 seconds
20 PVCs
Creation: 3 runs, 396/391/400 seconds
Deletion: 3 runs, 218/271/118 seconds
- Once was stalled for more than 8 minutes and cancelled the run
After:
Current commit + sidecar provisioner --timeout option set to 30 sec
20 PVCs
Creation: 3 runs, 42/59/65 seconds
Deletion: 3 runs, 32/32/31 seconds
Fixes: #279
Signed-off-by: ShyamsundarR <srangana@redhat.com>
2019-06-22 16:43:28 +00:00
2019-08-03 22:11:28 +00:00
isMounted := false
isStagePathCreated := false
devicePath := ""
// Stash image details prior to mapping the image (useful during Unstage as it has no
// voloptions passed to the RPC as per the CSI spec)
err = stashRBDImageMetadata ( volOptions , stagingParentPath )
2018-01-09 18:59:50 +00:00
if err != nil {
2019-07-03 10:02:36 +00:00
return nil , status . Error ( codes . Internal , err . Error ( ) )
2018-01-09 18:59:50 +00:00
}
2019-07-24 13:18:23 +00:00
defer func ( ) {
if err != nil {
2019-08-22 16:57:23 +00:00
ns . undoStagingTransaction ( ctx , stagingParentPath , devicePath , volID , isStagePathCreated , isMounted )
2019-07-24 13:18:23 +00:00
}
} ( )
2019-08-03 22:11:28 +00:00
// Mapping RBD image
2019-08-22 16:57:23 +00:00
devicePath , err = attachRBDImage ( ctx , volOptions , cr )
2019-08-03 22:11:28 +00:00
if err != nil {
return nil , status . Error ( codes . Internal , err . Error ( ) )
}
2019-08-22 16:57:23 +00:00
klog . V ( 4 ) . Infof ( util . Log ( ctx , "rbd image: %s/%s was successfully mapped at %s\n" ) , req . GetVolumeId ( ) , volOptions . Pool , devicePath )
2019-08-03 22:11:28 +00:00
2019-08-22 16:57:23 +00:00
err = ns . createStageMountPoint ( ctx , stagingTargetPath , isBlock )
2019-07-25 09:01:10 +00:00
if err != nil {
return nil , status . Error ( codes . Internal , err . Error ( ) )
}
isStagePathCreated = true
2019-07-03 10:02:36 +00:00
// nodeStage Path
2019-08-22 16:57:23 +00:00
err = ns . mountVolumeToStagePath ( ctx , req , stagingTargetPath , devicePath )
2019-01-28 19:55:10 +00:00
if err != nil {
2019-07-03 10:02:36 +00:00
return nil , status . Error ( codes . Internal , err . Error ( ) )
2019-01-28 19:55:10 +00:00
}
2019-07-24 13:18:23 +00:00
isMounted = true
2019-07-25 09:01:10 +00:00
Address security concerns reported by 'gosec'
gosec reports several issues, none of them looks very critical. With
this change the following concerns have been addressed:
[pkg/cephfs/nodeserver.go:229] - G302: Expect file permissions to be 0600 or less (Confidence: HIGH, Severity: MEDIUM)
> os.Chmod(targetPath, 0777)
[pkg/cephfs/util.go:39] - G204: Subprocess launched with variable (Confidence: HIGH, Severity: MEDIUM)
> exec.Command(program, args...)
[pkg/rbd/nodeserver.go:156] - G302: Expect file permissions to be 0600 or less (Confidence: HIGH, Severity: MEDIUM)
> os.Chmod(stagingTargetPath, 0777)
[pkg/rbd/nodeserver.go:205] - G302: Expect file permissions to be 0600 or less (Confidence: HIGH, Severity: MEDIUM)
> os.OpenFile(mountPath, os.O_CREATE|os.O_RDWR, 0750)
[pkg/rbd/rbd_util.go:797] - G304: Potential file inclusion via variable (Confidence: HIGH, Severity: MEDIUM)
> ioutil.ReadFile(fPath)
[pkg/util/cephcmds.go:35] - G204: Subprocess launched with variable (Confidence: HIGH, Severity: MEDIUM)
> exec.Command(program, args...)
[pkg/util/credentials.go:47] - G104: Errors unhandled. (Confidence: HIGH, Severity: LOW)
> os.Remove(tmpfile.Name())
[pkg/util/credentials.go:92] - G104: Errors unhandled. (Confidence: HIGH, Severity: LOW)
> os.Remove(cr.KeyFile)
[pkg/util/pidlimit.go:74] - G304: Potential file inclusion via variable (Confidence: HIGH, Severity: MEDIUM)
> os.Open(pidsMax)
URL: https://github.com/securego/gosec
Signed-off-by: Niels de Vos <ndevos@redhat.com>
2019-08-30 10:23:10 +00:00
// #nosec - allow anyone to write inside the target path
2019-07-03 10:02:36 +00:00
err = os . Chmod ( stagingTargetPath , 0777 )
2019-06-11 12:40:31 +00:00
if err != nil {
return nil , status . Error ( codes . Internal , err . Error ( ) )
}
2019-07-03 10:02:36 +00:00
2019-08-22 16:57:23 +00:00
klog . Infof ( util . Log ( ctx , "rbd: successfully mounted volume %s to stagingTargetPath %s" ) , req . GetVolumeId ( ) , stagingTargetPath )
2019-07-03 10:02:36 +00:00
return & csi . NodeStageVolumeResponse { } , nil
}
2019-08-22 16:57:23 +00:00
func ( ns * NodeServer ) undoStagingTransaction ( ctx context . Context , stagingParentPath , devicePath , volID string , isStagePathCreated , isMounted bool ) {
2019-07-24 13:18:23 +00:00
var err error
2019-08-03 22:11:28 +00:00
stagingTargetPath := stagingParentPath + "/" + volID
2019-07-24 13:18:23 +00:00
if isMounted {
err = ns . mounter . Unmount ( stagingTargetPath )
if err != nil {
2019-08-22 16:57:23 +00:00
klog . Errorf ( util . Log ( ctx , "failed to unmount stagingtargetPath: %s with error: %v" ) , stagingTargetPath , err )
2019-08-03 22:11:28 +00:00
return
2019-07-24 13:18:23 +00:00
}
}
2019-08-03 22:11:28 +00:00
// remove the file/directory created on staging path
2019-07-31 16:24:19 +00:00
if isStagePathCreated {
2019-07-24 13:18:23 +00:00
err = os . Remove ( stagingTargetPath )
if err != nil {
2019-08-22 16:57:23 +00:00
klog . Errorf ( util . Log ( ctx , "failed to remove stagingtargetPath: %s with error: %v" ) , stagingTargetPath , err )
2019-08-03 22:11:28 +00:00
// continue on failure to unmap the image, as leaving stale images causes more issues than a stale file/directory
2019-07-24 13:18:23 +00:00
}
}
2019-08-03 22:11:28 +00:00
2019-07-24 13:18:23 +00:00
// Unmapping rbd device
2019-08-03 22:11:28 +00:00
if devicePath != "" {
2019-08-22 16:57:23 +00:00
err = detachRBDDevice ( ctx , devicePath )
2019-08-03 22:11:28 +00:00
if err != nil {
2019-08-22 16:57:23 +00:00
klog . Errorf ( util . Log ( ctx , "failed to unmap rbd device: %s for volume %s with error: %v" ) , devicePath , volID , err )
2019-08-03 22:11:28 +00:00
// continue on failure to delete the stash file, as kubernetes will fail to delete the staging path otherwise
}
}
// Cleanup the stashed image metadata
if err = cleanupRBDImageMetadataStash ( stagingParentPath ) ; err != nil {
2019-08-22 16:57:23 +00:00
klog . Errorf ( util . Log ( ctx , "failed to cleanup image metadata stash (%v)" ) , err )
2019-08-03 22:11:28 +00:00
return
2019-07-24 13:18:23 +00:00
}
}
2019-08-22 16:57:23 +00:00
func ( ns * NodeServer ) createStageMountPoint ( ctx context . Context , mountPath string , isBlock bool ) error {
2019-07-25 09:01:10 +00:00
if isBlock {
Address security concerns reported by 'gosec'
gosec reports several issues, none of them looks very critical. With
this change the following concerns have been addressed:
[pkg/cephfs/nodeserver.go:229] - G302: Expect file permissions to be 0600 or less (Confidence: HIGH, Severity: MEDIUM)
> os.Chmod(targetPath, 0777)
[pkg/cephfs/util.go:39] - G204: Subprocess launched with variable (Confidence: HIGH, Severity: MEDIUM)
> exec.Command(program, args...)
[pkg/rbd/nodeserver.go:156] - G302: Expect file permissions to be 0600 or less (Confidence: HIGH, Severity: MEDIUM)
> os.Chmod(stagingTargetPath, 0777)
[pkg/rbd/nodeserver.go:205] - G302: Expect file permissions to be 0600 or less (Confidence: HIGH, Severity: MEDIUM)
> os.OpenFile(mountPath, os.O_CREATE|os.O_RDWR, 0750)
[pkg/rbd/rbd_util.go:797] - G304: Potential file inclusion via variable (Confidence: HIGH, Severity: MEDIUM)
> ioutil.ReadFile(fPath)
[pkg/util/cephcmds.go:35] - G204: Subprocess launched with variable (Confidence: HIGH, Severity: MEDIUM)
> exec.Command(program, args...)
[pkg/util/credentials.go:47] - G104: Errors unhandled. (Confidence: HIGH, Severity: LOW)
> os.Remove(tmpfile.Name())
[pkg/util/credentials.go:92] - G104: Errors unhandled. (Confidence: HIGH, Severity: LOW)
> os.Remove(cr.KeyFile)
[pkg/util/pidlimit.go:74] - G304: Potential file inclusion via variable (Confidence: HIGH, Severity: MEDIUM)
> os.Open(pidsMax)
URL: https://github.com/securego/gosec
Signed-off-by: Niels de Vos <ndevos@redhat.com>
2019-08-30 10:23:10 +00:00
pathFile , err := os . OpenFile ( mountPath , os . O_CREATE | os . O_RDWR , 0600 )
2019-07-25 09:01:10 +00:00
if err != nil {
2019-08-22 16:57:23 +00:00
klog . Errorf ( util . Log ( ctx , "failed to create mountPath:%s with error: %v" ) , mountPath , err )
2019-07-25 09:01:10 +00:00
return status . Error ( codes . Internal , err . Error ( ) )
}
if err = pathFile . Close ( ) ; err != nil {
2019-08-22 16:57:23 +00:00
klog . Errorf ( util . Log ( ctx , "failed to close mountPath:%s with error: %v" ) , mountPath , err )
2019-07-25 09:01:10 +00:00
return status . Error ( codes . Internal , err . Error ( ) )
}
2019-07-31 16:24:19 +00:00
return nil
2019-07-25 09:01:10 +00:00
}
2019-07-31 16:24:19 +00:00
err := os . Mkdir ( mountPath , 0750 )
if err != nil {
2019-08-03 22:11:28 +00:00
if ! os . IsExist ( err ) {
2019-08-22 16:57:23 +00:00
klog . Errorf ( util . Log ( ctx , "failed to create mountPath:%s with error: %v" ) , mountPath , err )
2019-08-03 22:11:28 +00:00
return status . Error ( codes . Internal , err . Error ( ) )
}
2019-07-31 16:24:19 +00:00
}
2019-07-25 09:01:10 +00:00
return nil
}
2019-07-03 10:02:36 +00:00
// NodePublishVolume mounts the volume mounted to the device path to the target
// path
func ( ns * NodeServer ) NodePublishVolume ( ctx context . Context , req * csi . NodePublishVolumeRequest ) ( * csi . NodePublishVolumeResponse , error ) {
err := util . ValidateNodePublishVolumeRequest ( req )
if err != nil {
return nil , err
}
targetPath := req . GetTargetPath ( )
isBlock := req . GetVolumeCapability ( ) . GetBlock ( ) != nil
stagingPath := req . GetStagingTargetPath ( )
2019-09-12 04:53:37 +00:00
volID := req . GetVolumeId ( )
stagingPath += "/" + volID
2019-07-03 10:02:36 +00:00
2019-09-12 04:53:37 +00:00
if acquired := ns . VolumeLocks . TryAcquire ( volID ) ; ! acquired {
klog . Infof ( util . Log ( ctx , util . VolumeOperationAlreadyExistsFmt ) , volID )
return nil , status . Errorf ( codes . Aborted , util . VolumeOperationAlreadyExistsFmt , volID )
}
defer ns . VolumeLocks . Release ( volID )
2019-07-03 10:02:36 +00:00
// Check if that target path exists properly
2019-08-09 17:11:21 +00:00
notMnt , err := ns . createTargetMountPath ( ctx , targetPath , isBlock )
2019-07-03 10:02:36 +00:00
if err != nil {
return nil , err
}
if ! notMnt {
return & csi . NodePublishVolumeResponse { } , nil
}
// Publish Path
2019-08-09 17:11:21 +00:00
err = ns . mountVolume ( ctx , stagingPath , req )
2019-07-03 10:02:36 +00:00
if err != nil {
return nil , err
}
2019-08-22 16:57:23 +00:00
klog . Infof ( util . Log ( ctx , "rbd: successfully mounted stagingPath %s to targetPath %s" ) , stagingPath , targetPath )
2019-01-28 19:55:10 +00:00
return & csi . NodePublishVolumeResponse { } , nil
}
2019-07-03 10:02:36 +00:00
func getVolumeName ( volID string ) ( string , error ) {
2019-04-22 21:35:39 +00:00
var vi util . CSIIdentifier
2019-07-03 10:02:36 +00:00
err := vi . DecomposeCSIID ( volID )
2019-04-22 21:35:39 +00:00
if err != nil {
2019-07-03 10:02:36 +00:00
err = fmt . Errorf ( "error decoding volume ID (%s) (%s)" , err , volID )
2019-05-31 18:09:24 +00:00
return "" , ErrInvalidVolID { err }
2019-01-28 19:55:10 +00:00
}
2019-04-22 21:35:39 +00:00
2019-05-14 19:15:01 +00:00
return volJournal . NamingPrefix ( ) + vi . ObjectUUID , nil
2019-01-28 19:55:10 +00:00
}
2019-07-03 10:02:36 +00:00
func getLegacyVolumeName ( mountPath string ) ( string , error ) {
2019-05-31 18:09:24 +00:00
var volName string
2019-07-03 10:02:36 +00:00
if strings . HasSuffix ( mountPath , "/globalmount" ) {
s := strings . Split ( strings . TrimSuffix ( mountPath , "/globalmount" ) , "/" )
2019-05-31 18:09:24 +00:00
volName = s [ len ( s ) - 1 ]
2019-07-03 10:02:36 +00:00
return volName , nil
}
if strings . HasSuffix ( mountPath , "/mount" ) {
s := strings . Split ( strings . TrimSuffix ( mountPath , "/mount" ) , "/" )
2019-05-31 18:09:24 +00:00
volName = s [ len ( s ) - 1 ]
2019-07-03 10:02:36 +00:00
return volName , nil
2019-05-31 18:09:24 +00:00
}
2019-07-03 10:02:36 +00:00
// get volume name for block volume
s := strings . Split ( mountPath , "/" )
if len ( s ) == 0 {
return "" , fmt . Errorf ( "rbd: malformed value of stage target path: %s" , mountPath )
}
volName = s [ len ( s ) - 1 ]
2019-05-31 18:09:24 +00:00
return volName , nil
}
2019-08-22 16:57:23 +00:00
func ( ns * NodeServer ) mountVolumeToStagePath ( ctx context . Context , req * csi . NodeStageVolumeRequest , stagingPath , devicePath string ) error {
2019-07-03 10:02:36 +00:00
fsType := req . GetVolumeCapability ( ) . GetMount ( ) . GetFsType ( )
diskMounter := & mount . SafeFormatAndMount { Interface : ns . mounter , Exec : mount . NewOsExec ( ) }
2019-10-03 16:54:02 +00:00
// rbd images are thin-provisioned and return zeros for unwritten areas. A freshly created
// image will not benefit from discard and we also want to avoid as much unnecessary zeroing
// as possible. Open-code mkfs here because FormatAndMount() doesn't accept custom mkfs
// options.
//
// Note that "freshly" is very important here. While discard is more of a nice to have,
// lazy_journal_init=1 is plain unsafe if the image has been written to before and hasn't
// been zeroed afterwards (unlike the name suggests, it leaves the journal completely
// uninitialized and carries a risk until the journal is overwritten and wraps around for
// the first time).
2019-09-19 17:11:32 +00:00
existingFormat , err := diskMounter . GetDiskFormat ( devicePath )
if err != nil {
klog . Errorf ( util . Log ( ctx , "failed to get disk format for path %s, error: %v" ) , devicePath , err )
return err
}
2019-10-03 16:54:02 +00:00
// TODO: update this when adding support for static (pre-provisioned) PVs
if existingFormat == "" /* && !staticVol */ {
2019-09-19 17:11:32 +00:00
args := [ ] string { }
if fsType == "ext4" {
2019-10-03 16:54:02 +00:00
args = [ ] string { "-m0" , "-Enodiscard,lazy_itable_init=1,lazy_journal_init=1" , devicePath }
2019-09-19 17:11:32 +00:00
} else if fsType == "xfs" {
args = [ ] string { "-K" , devicePath }
}
2019-10-03 16:54:02 +00:00
if len ( args ) > 0 {
_ , err = diskMounter . Exec . Run ( "mkfs." + fsType , args ... )
if err != nil {
klog . Errorf ( util . Log ( ctx , "failed to run mkfs, error: %v" ) , err )
return err
}
2019-09-19 17:11:32 +00:00
}
}
2019-07-03 10:02:36 +00:00
opt := [ ] string { }
isBlock := req . GetVolumeCapability ( ) . GetBlock ( ) != nil
if isBlock {
opt = append ( opt , "bind" )
err = diskMounter . Mount ( devicePath , stagingPath , fsType , opt )
} else {
err = diskMounter . FormatAndMount ( devicePath , stagingPath , fsType , opt )
}
if err != nil {
2019-08-22 16:57:23 +00:00
klog . Errorf ( util . Log ( ctx , "failed to mount device path (%s) to staging path (%s) for volume (%s) error %s" ) , devicePath , stagingPath , req . GetVolumeId ( ) , err )
2019-07-03 10:02:36 +00:00
}
return err
}
2019-08-09 17:11:21 +00:00
func ( ns * NodeServer ) mountVolume ( ctx context . Context , stagingPath string , req * csi . NodePublishVolumeRequest ) error {
2018-11-01 01:03:03 +00:00
// Publish Path
fsType := req . GetVolumeCapability ( ) . GetMount ( ) . GetFsType ( )
2018-01-09 18:59:50 +00:00
readOnly := req . GetReadonly ( )
mountFlags := req . GetVolumeCapability ( ) . GetMount ( ) . GetMountFlags ( )
2019-01-28 19:55:10 +00:00
isBlock := req . GetVolumeCapability ( ) . GetBlock ( ) != nil
targetPath := req . GetTargetPath ( )
2019-08-22 16:57:23 +00:00
klog . V ( 4 ) . Infof ( util . Log ( ctx , "target %v\nisBlock %v\nfstype %v\nstagingPath %v\nreadonly %v\nmountflags %v\n" ) ,
2019-08-14 18:45:30 +00:00
targetPath , isBlock , fsType , stagingPath , readOnly , mountFlags )
2019-07-03 10:02:36 +00:00
mountFlags = append ( mountFlags , "bind" )
if readOnly {
mountFlags = append ( mountFlags , "ro" )
}
2019-07-31 16:24:19 +00:00
if err := util . Mount ( stagingPath , targetPath , fsType , mountFlags ) ; err != nil {
return status . Error ( codes . Internal , err . Error ( ) )
2018-01-09 18:59:50 +00:00
}
2019-07-31 16:24:19 +00:00
2019-01-28 19:55:10 +00:00
return nil
}
2019-08-09 17:11:21 +00:00
func ( ns * NodeServer ) createTargetMountPath ( ctx context . Context , mountPath string , isBlock bool ) ( bool , error ) {
2019-07-03 10:02:36 +00:00
// Check if that mount path exists properly
notMnt , err := mount . IsNotMountPoint ( ns . mounter , mountPath )
2019-01-28 19:55:10 +00:00
if err != nil {
if os . IsNotExist ( err ) {
if isBlock {
// #nosec
2019-07-03 10:02:36 +00:00
pathFile , e := os . OpenFile ( mountPath , os . O_CREATE | os . O_RDWR , 0750 )
2019-01-28 19:55:10 +00:00
if e != nil {
2019-08-22 16:57:23 +00:00
klog . V ( 4 ) . Infof ( util . Log ( ctx , "Failed to create mountPath:%s with error: %v" ) , mountPath , err )
2019-01-28 19:55:10 +00:00
return notMnt , status . Error ( codes . Internal , e . Error ( ) )
}
2019-07-03 10:02:36 +00:00
if err = pathFile . Close ( ) ; err != nil {
2019-08-22 16:57:23 +00:00
klog . V ( 4 ) . Infof ( util . Log ( ctx , "Failed to close mountPath:%s with error: %v" ) , mountPath , err )
2019-01-28 19:55:10 +00:00
return notMnt , status . Error ( codes . Internal , err . Error ( ) )
}
} else {
// Create a directory
2019-07-03 10:02:36 +00:00
if err = util . CreateMountPoint ( mountPath ) ; err != nil {
2019-01-28 19:55:10 +00:00
return notMnt , status . Error ( codes . Internal , err . Error ( ) )
}
}
notMnt = true
} else {
return false , status . Error ( codes . Internal , err . Error ( ) )
}
}
return notMnt , err
2018-01-09 18:59:50 +00:00
}
2019-01-28 11:47:06 +00:00
// NodeUnpublishVolume unmounts the volume from the target path
2019-01-17 07:51:06 +00:00
func ( ns * NodeServer ) NodeUnpublishVolume ( ctx context . Context , req * csi . NodeUnpublishVolumeRequest ) ( * csi . NodeUnpublishVolumeResponse , error ) {
2019-07-03 10:02:36 +00:00
err := util . ValidateNodeUnpublishVolumeRequest ( req )
if err != nil {
return nil , err
2019-04-22 21:35:39 +00:00
}
2019-07-03 10:02:36 +00:00
targetPath := req . GetTargetPath ( )
2019-09-12 04:53:37 +00:00
volID := req . GetVolumeId ( )
if acquired := ns . VolumeLocks . TryAcquire ( volID ) ; ! acquired {
klog . Infof ( util . Log ( ctx , util . VolumeOperationAlreadyExistsFmt ) , volID )
return nil , status . Errorf ( codes . Aborted , util . VolumeOperationAlreadyExistsFmt , volID )
}
defer ns . VolumeLocks . Release ( volID )
2019-06-24 07:58:39 +00:00
notMnt , err := mount . IsNotMountPoint ( ns . mounter , targetPath )
2018-11-15 02:06:42 +00:00
if err != nil {
if os . IsNotExist ( err ) {
// targetPath has already been deleted
2019-08-22 16:57:23 +00:00
klog . V ( 4 ) . Infof ( util . Log ( ctx , "targetPath: %s has already been deleted" ) , targetPath )
2018-11-15 02:06:42 +00:00
return & csi . NodeUnpublishVolumeResponse { } , nil
}
return nil , status . Error ( codes . NotFound , err . Error ( ) )
}
2018-11-15 20:40:19 +00:00
if notMnt {
2019-07-03 10:02:36 +00:00
if err = os . RemoveAll ( targetPath ) ; err != nil {
return nil , status . Error ( codes . Internal , err . Error ( ) )
}
return & csi . NodeUnpublishVolumeResponse { } , nil
}
if err = ns . mounter . Unmount ( targetPath ) ; err != nil {
return nil , status . Error ( codes . Internal , err . Error ( ) )
}
if err = os . RemoveAll ( targetPath ) ; err != nil {
return nil , status . Error ( codes . Internal , err . Error ( ) )
}
2019-08-22 16:57:23 +00:00
klog . Infof ( util . Log ( ctx , "rbd: successfully unbound volume %s from %s" ) , req . GetVolumeId ( ) , targetPath )
2019-07-03 10:02:36 +00:00
return & csi . NodeUnpublishVolumeResponse { } , nil
}
// NodeUnstageVolume unstages the volume from the staging path
func ( ns * NodeServer ) NodeUnstageVolume ( ctx context . Context , req * csi . NodeUnstageVolumeRequest ) ( * csi . NodeUnstageVolumeResponse , error ) {
var err error
if err = util . ValidateNodeUnstageVolumeRequest ( req ) ; err != nil {
return nil , err
}
2019-09-12 04:53:37 +00:00
volID := req . GetVolumeId ( )
if acquired := ns . VolumeLocks . TryAcquire ( volID ) ; ! acquired {
klog . Infof ( util . Log ( ctx , util . VolumeOperationAlreadyExistsFmt ) , volID )
return nil , status . Errorf ( codes . Aborted , util . VolumeOperationAlreadyExistsFmt , volID )
}
defer ns . VolumeLocks . Release ( volID )
2019-08-03 22:11:28 +00:00
stagingParentPath := req . GetStagingTargetPath ( )
stagingTargetPath := stagingParentPath + "/" + req . GetVolumeId ( )
2019-07-03 10:02:36 +00:00
notMnt , err := mount . IsNotMountPoint ( ns . mounter , stagingTargetPath )
if err != nil {
2019-08-03 22:11:28 +00:00
if ! os . IsNotExist ( err ) {
return nil , status . Error ( codes . NotFound , err . Error ( ) )
2019-07-03 10:02:36 +00:00
}
2019-08-03 22:11:28 +00:00
// Continue on ENOENT errors as we may still have the image mapped
notMnt = true
2018-11-15 20:40:19 +00:00
}
2019-08-03 22:11:28 +00:00
if ! notMnt {
// Unmounting the image
err = ns . mounter . Unmount ( stagingTargetPath )
if err != nil {
2019-08-22 16:57:23 +00:00
klog . V ( 3 ) . Infof ( util . Log ( ctx , "failed to unmount targetPath: %s with error: %v" ) , stagingTargetPath , err )
2019-07-31 16:24:19 +00:00
return nil , status . Error ( codes . Internal , err . Error ( ) )
2019-07-03 10:02:36 +00:00
}
2019-01-29 05:49:16 +00:00
}
2019-07-31 16:24:19 +00:00
if err = os . Remove ( stagingTargetPath ) ; err != nil {
2019-08-03 22:11:28 +00:00
// Any error is critical as Staging path is expected to be empty by Kubernetes, it otherwise
// keeps invoking Unstage. Hence any errors removing files within this path is a critical
// error
if ! os . IsNotExist ( err ) {
2019-08-22 16:57:23 +00:00
klog . Errorf ( util . Log ( ctx , "failed to remove staging target path (%s): (%v)" ) , stagingTargetPath , err )
2019-08-03 22:11:28 +00:00
return nil , status . Error ( codes . Internal , err . Error ( ) )
2018-11-15 20:40:19 +00:00
}
}
2019-08-03 22:11:28 +00:00
imgInfo , err := lookupRBDImageMetadataStash ( stagingParentPath )
2018-01-09 18:59:50 +00:00
if err != nil {
2019-08-22 16:57:23 +00:00
klog . V ( 2 ) . Infof ( util . Log ( ctx , "failed to find image metadata: %v" ) , err )
2019-08-03 22:11:28 +00:00
// It is an error if it was mounted, as we should have found the image metadata file with
// no errors
if ! notMnt {
return nil , status . Error ( codes . Internal , err . Error ( ) )
}
2018-02-20 16:10:59 +00:00
2019-08-03 22:11:28 +00:00
// If not mounted, and error is anything other than metadata file missing, it is an error
if _ , ok := err . ( ErrMissingStash ) ; ! ok {
return nil , status . Error ( codes . Internal , err . Error ( ) )
}
// It was not mounted and image metadata is also missing, we are done as the last step in
// in the staging transaction is complete
return & csi . NodeUnstageVolumeResponse { } , nil
2018-01-09 18:59:50 +00:00
}
2018-02-20 16:10:59 +00:00
// Unmapping rbd device
2019-08-03 22:11:28 +00:00
imageSpec := imgInfo . Pool + "/" + imgInfo . ImageName
2019-08-22 16:57:23 +00:00
if err = detachRBDImageOrDeviceSpec ( ctx , imageSpec , true , imgInfo . NbdAccess ) ; err != nil {
klog . Errorf ( util . Log ( ctx , "error unmapping volume (%s) from staging path (%s): (%v)" ) , req . GetVolumeId ( ) , stagingTargetPath , err )
2019-08-03 22:11:28 +00:00
return nil , status . Error ( codes . Internal , err . Error ( ) )
2018-01-09 18:59:50 +00:00
}
2019-08-22 16:57:23 +00:00
klog . Infof ( util . Log ( ctx , "successfully unmounted volume (%s) from staging path (%s)" ) ,
2019-08-03 22:11:28 +00:00
req . GetVolumeId ( ) , stagingTargetPath )
if err = cleanupRBDImageMetadataStash ( stagingParentPath ) ; err != nil {
2019-08-22 16:57:23 +00:00
klog . Errorf ( util . Log ( ctx , "failed to cleanup image metadata stash (%v)" ) , err )
2019-08-03 22:11:28 +00:00
return nil , status . Error ( codes . Internal , err . Error ( ) )
2018-11-07 02:05:19 +00:00
}
2019-08-03 22:11:28 +00:00
return & csi . NodeUnstageVolumeResponse { } , nil
2018-11-07 02:05:19 +00:00
}
2019-07-03 10:02:36 +00:00
// NodeGetCapabilities returns the supported capabilities of the node server
func ( ns * NodeServer ) NodeGetCapabilities ( ctx context . Context , req * csi . NodeGetCapabilitiesRequest ) ( * csi . NodeGetCapabilitiesResponse , error ) {
return & csi . NodeGetCapabilitiesResponse {
Capabilities : [ ] * csi . NodeServiceCapability {
{
Type : & csi . NodeServiceCapability_Rpc {
Rpc : & csi . NodeServiceCapability_RPC {
Type : csi . NodeServiceCapability_RPC_STAGE_UNSTAGE_VOLUME ,
} ,
} ,
} ,
2019-07-30 06:20:28 +00:00
{
Type : & csi . NodeServiceCapability_Rpc {
Rpc : & csi . NodeServiceCapability_RPC {
Type : csi . NodeServiceCapability_RPC_GET_VOLUME_STATS ,
} ,
} ,
} ,
2019-07-03 10:02:36 +00:00
} ,
} , nil
}