diff --git a/pkg/gce-pd-csi-driver/controller_test.go b/pkg/gce-pd-csi-driver/controller_test.go index 4d4934761..a427871d9 100644 --- a/pkg/gce-pd-csi-driver/controller_test.go +++ b/pkg/gce-pd-csi-driver/controller_test.go @@ -617,7 +617,7 @@ func TestCreateVolumeArguments(t *testing.T) { }, }, { - name: "fail with block volume capability", + name: "success with block volume capability", req: &csi.CreateVolumeRequest{ Name: name, CapacityRange: stdCapRange, @@ -632,7 +632,12 @@ func TestCreateVolumeArguments(t *testing.T) { }, }, }, - expErrCode: codes.InvalidArgument, + expVol: &csi.Volume{ + CapacityBytes: common.GbToBytes(20), + VolumeId: testVolumeId, + VolumeContext: nil, + AccessibleTopology: stdTopology, + }, }, { name: "fail with both mount and block volume capability", diff --git a/pkg/gce-pd-csi-driver/node.go b/pkg/gce-pd-csi-driver/node.go index 24fa81162..8354b196b 100644 --- a/pkg/gce-pd-csi-driver/node.go +++ b/pkg/gce-pd-csi-driver/node.go @@ -95,18 +95,60 @@ func (ns *GCENodeServer) NodePublishVolume(ctx context.Context, req *csi.NodePub return &csi.NodePublishVolumeResponse{}, nil } - if err := ns.Mounter.Interface.MakeDir(targetPath); err != nil { - glog.Errorf("mkdir failed on disk %s (%v)", targetPath, err) - return nil, err - } - // Perform a bind mount to the full path to allow duplicate mounts of the same PD. + fstype := "" + sourcePath := "" options := []string{"bind"} if readOnly { options = append(options, "ro") } - err = ns.Mounter.Interface.Mount(stagingTargetPath, targetPath, "ext4", options) + if mnt := volumeCapability.GetMount(); mnt != nil { + if mnt.FsType != "" { + fstype = mnt.FsType + } else { + // Default fstype is ext4 + fstype = "ext4" + } + + glog.V(4).Infof("NodePublishVolume with filesystem %s", fstype) + + for _, flag := range mnt.MountFlags { + options = append(options, flag) + } + + sourcePath = stagingTargetPath + + if err := ns.Mounter.Interface.MakeDir(targetPath); err != nil { + glog.Errorf("mkdir failed on disk %s (%v)", targetPath, err) + return nil, err + } + } else if blk := volumeCapability.GetBlock(); blk != nil { + glog.V(4).Infof("NodePublishVolume with block volume mode") + + partition := "" + if part, ok := req.GetVolumeContext()[common.VolumeAttributePartition]; ok { + partition = part + } + + sourcePath, err = ns.getDevicePath(volumeID, partition) + if err != nil { + return nil, status.Error(codes.Internal, fmt.Sprintf("Error when getting device path: %v", err)) + } + + // Expose block volume as file at target path + err = ns.Mounter.MakeFile(targetPath) + if err != nil { + if removeErr := os.Remove(targetPath); removeErr != nil { + return nil, status.Error(codes.Internal, fmt.Sprintf("Error removing block file at target path %v: %v, mounti error: %v", targetPath, removeErr, err)) + } + return nil, status.Error(codes.Internal, fmt.Sprintf("Failed to create block file at target path %v: %v", targetPath, err)) + } + } else { + return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("NodePublishVolume volume capability must specify either mount or block mode")) + } + + err = ns.Mounter.Interface.Mount(sourcePath, targetPath, fstype, options) if err != nil { notMnt, mntErr := ns.Mounter.Interface.IsLikelyNotMountPoint(targetPath) if mntErr != nil { @@ -197,19 +239,9 @@ func (ns *GCENodeServer) NodeStageVolume(ctx context.Context, req *csi.NodeStage partition = part } - deviceName, err := common.GetDeviceName(volumeKey) + devicePath, err := ns.getDevicePath(volumeID, partition) if err != nil { - status.Error(codes.Internal, fmt.Sprintf("error getting device name: %v", err)) - } - - devicePaths := ns.DeviceUtils.GetDiskByIdPaths(deviceName, partition) - devicePath, err := ns.DeviceUtils.VerifyDevicePath(devicePaths) - - if err != nil { - return nil, status.Error(codes.Internal, fmt.Sprintf("Error verifying GCE PD (%q) is attached: %v", volumeKey.Name, err)) - } - if devicePath == "" { - return nil, status.Error(codes.Internal, fmt.Sprintf("Unable to find device path out of attempted paths: %v", devicePaths)) + return nil, status.Error(codes.Internal, fmt.Sprintf("Error when getting device path: %v", err)) } glog.V(4).Infof("Successfully found attached GCE PD %q at device path %s.", volumeKey.Name, devicePath) @@ -251,8 +283,8 @@ func (ns *GCENodeServer) NodeStageVolume(ctx context.Context, req *csi.NodeStage options = append(options, flag) } } else if blk := volumeCapability.GetBlock(); blk != nil { - // TODO(#64): Block volume support - return nil, status.Error(codes.Unimplemented, fmt.Sprintf("Block volume support is not yet implemented")) + // Noop for Block NodeStageVolume + return &csi.NodeStageVolumeResponse{}, nil } err = ns.Mounter.FormatAndMount(devicePath, stagingTargetPath, fstype, options) @@ -333,3 +365,25 @@ func (ns *GCENodeServer) GetVolumeLimits() (int64, error) { } return volumeLimits, nil } + +func (ns *GCENodeServer) getDevicePath(volumeID string, partition string) (string, error) { + volumeKey, err := common.VolumeIDToKey(volumeID) + if err != nil { + return "", err + } + deviceName, err := common.GetDeviceName(volumeKey) + if err != nil { + return "", fmt.Errorf("error getting device name: %v", err) + } + + devicePaths := ns.DeviceUtils.GetDiskByIdPaths(deviceName, partition) + devicePath, err := ns.DeviceUtils.VerifyDevicePath(devicePaths) + + if err != nil { + return "", fmt.Errorf("error verifying GCE PD (%q) is attached: %v", volumeKey.Name, err) + } + if devicePath == "" { + return "", fmt.Errorf("unable to find device path out of attempted paths: %v", devicePaths) + } + return devicePath, nil +} diff --git a/pkg/gce-pd-csi-driver/utils.go b/pkg/gce-pd-csi-driver/utils.go index e15fb93db..14170d8a8 100644 --- a/pkg/gce-pd-csi-driver/utils.go +++ b/pkg/gce-pd-csi-driver/utils.go @@ -64,14 +64,29 @@ func logGRPC(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, h } func validateVolumeCapabilities(vcs []*csi.VolumeCapability) error { + isMnt := false + isBlk := false + if vcs == nil { return errors.New("volume capabilities is nil") } + for _, vc := range vcs { if err := validateVolumeCapability(vc); err != nil { return err } + if blk := vc.GetBlock(); blk != nil { + isBlk = true + } + if mnt := vc.GetMount(); mnt != nil { + isMnt = true + } } + + if isBlk && isMnt { + return errors.New("both mount and block volume capabilities specified") + } + return nil } @@ -79,13 +94,13 @@ func validateVolumeCapability(vc *csi.VolumeCapability) error { if err := validateAccessMode(vc.GetAccessMode()); err != nil { return err } - if blk := vc.GetBlock(); blk != nil { - // TODO(#64): Block volume support - return errors.New("Block volume support is not yet implemented") + blk := vc.GetBlock() + mnt := vc.GetMount() + if mnt == nil && blk == nil { + return errors.New("must specify an access type") } - if mnt := vc.GetMount(); mnt == nil { - // TODO(#64): Change error message after block volume support - return errors.New("Must specify an access type of Mount") + if mnt != nil && blk != nil { + return errors.New("specified both mount and block access types") } return nil } diff --git a/pkg/gce-pd-csi-driver/utils_test.go b/pkg/gce-pd-csi-driver/utils_test.go index ab93ef818..bc90558a8 100644 --- a/pkg/gce-pd-csi-driver/utils_test.go +++ b/pkg/gce-pd-csi-driver/utils_test.go @@ -126,7 +126,7 @@ func TestValidateVolumeCapabilities(t *testing.T) { expErr: true, }, { - name: "fail with block capabilities", + name: "success with block capabilities", vc: []*csi.VolumeCapability{ { AccessType: &csi.VolumeCapability_Block{ @@ -137,7 +137,6 @@ func TestValidateVolumeCapabilities(t *testing.T) { }, }, }, - expErr: true, }, { name: "success with reader + writer capabilities", diff --git a/test/k8s-integration/config/test-config-template.in b/test/k8s-integration/config/test-config-template.in index 5cbd8ead6..4d4631dbd 100644 --- a/test/k8s-integration/config/test-config-template.in +++ b/test/k8s-integration/config/test-config-template.in @@ -16,6 +16,6 @@ DriverInfo: multipods: true fsGroup: true exec: true - # block: true + block: true # dataSource: true # RWX: true