csi-driver-nfs/simplenfs/plugin.go

219 lines
7.5 KiB
Go

// +build simplenfs
package main
import (
"encoding/base64"
"errors"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/container-storage-interface/spec/lib/go/csi"
"github.com/golang/glog"
"github.com/kubernetes-csi/csi-driver-nfs/pkg/nfs"
"golang.org/x/net/context"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"k8s.io/apimachinery/pkg/util/uuid"
"k8s.io/kubernetes/pkg/util/mount"
)
const (
mountPathBase = "/csi-nfs-volume"
)
// csPlugin is an implementation of ControllerPlugin
type csPlugin struct {
name string
}
var _ nfs.ControllerPlugin = csPlugin{}
var _ nfs.CreateDeleteVolumeControllerPlugin = csPlugin{}
var NfsPlugin = csPlugin{"NfsPlugin"}
// CreateVolume is an implenetaiton that is required by CreateDeleteVolumeControllerPlugin interface
func (p csPlugin) CreateVolume(ctx context.Context, cs *nfs.ControllerServer, req *csi.CreateVolumeRequest) (*csi.CreateVolumeResponse, error) {
glog.Infof("plugin.CreateVolume called")
var volSize int64
if req.GetCapacityRange() != nil {
volSize = req.GetCapacityRange().GetRequiredBytes()
}
volInfo := volumeInfo{req.GetParameters()["server"], req.GetParameters()["rootpath"], req.GetName()}
volID, err := encodeVolID(volInfo)
if err != nil {
glog.Warningf("encodeVolID for volInfo %v failed: %v", volInfo, err)
return nil, status.Error(codes.Internal, err.Error())
}
// Create /csi-nfs-volume/{UUID}/ directory and mount nfs rootpath to it
mountPath := filepath.Join(mountPathBase, string(uuid.NewUUID()))
if err := setupMountPath(mountPath, volInfo.server, volInfo.rootpath); err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
// Unmount nfs rootpath from /csi-nfs-volume/{UUID}/{volID} directory and delete the directory
defer teardownMountPath(mountPath)
// Create directory in nfs rootpath by creating directory /csi-nfs-volume/{UUID}/{volID}
fullPath := filepath.Join(mountPath, volID)
if _, err := os.Stat(fullPath); os.IsNotExist(err) {
glog.V(4).Infof("creating path %s", fullPath)
if err := os.MkdirAll(fullPath, 0777); err != nil {
return nil, errors.New("unable to create directory to create volume: " + err.Error())
}
os.Chmod(fullPath, 0777)
}
// Add share:{rootPath}/{volID} to volumeContext
volContext := req.GetParameters()
volContext["share"] = filepath.Join(volInfo.rootpath, volID)
return &csi.CreateVolumeResponse{
Volume: &csi.Volume{
VolumeId: volID,
CapacityBytes: volSize,
VolumeContext: volContext,
},
}, nil
}
// DeleteVolume is an implenetaiton that is required by CreateDeleteVolumeControllerPlugin interface
func (p csPlugin) DeleteVolume(ctx context.Context, cs *nfs.ControllerServer, req *csi.DeleteVolumeRequest) (*csi.DeleteVolumeResponse, error) {
glog.Infof("plugin.DeleteVolume called")
volumeID := req.GetVolumeId()
if volumeID == "" {
return nil, status.Error(codes.InvalidArgument, "Empty volume ID in request")
}
glog.Infof("volumeID: %s", volumeID)
volInfo, err := decodeVolID(volumeID)
if err != nil {
glog.Warningf("decodeVolID for volumeID %s failed: %v", volumeID, err)
return nil, status.Error(codes.Internal, err.Error())
}
// Create /csi-nfs-volume/{UUID}/ directory and mount nfs rootpath to it
mountPath := filepath.Join(mountPathBase, string(uuid.NewUUID()))
if err := setupMountPath(mountPath, volInfo.server, volInfo.rootpath); err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
// Unmount nfs rootpath from /csi-nfs-volume/{UUID}/{volID} directory and delete the directory
defer teardownMountPath(mountPath)
// Delete directory in nfs rootpath by deleting directory /csi-nfs-volume/{UUID}/{volID}
fullPath := filepath.Join(mountPath, volumeID)
glog.V(4).Infof("creating path %s", fullPath)
if _, err := os.Stat(fullPath); os.IsNotExist(err) {
glog.Warningf("path %s does not exist, deletion skipped", fullPath)
return &csi.DeleteVolumeResponse{}, nil
}
if err := os.RemoveAll(fullPath); err != nil {
glog.Warningf("Failed to remove %s: %v", fullPath, err)
return nil, status.Error(codes.Internal, err.Error())
}
return &csi.DeleteVolumeResponse{}, nil
}
// ValidateVolumeCapabilities is an implenetaiton that is required by ControllerPlugin interface
func (p csPlugin) ValidateVolumeCapabilities(ctx context.Context, cs *nfs.ControllerServer, req *csi.ValidateVolumeCapabilitiesRequest) (*csi.ValidateVolumeCapabilitiesResponse, error) {
if req.GetVolumeId() == "" {
return nil, status.Error(codes.InvalidArgument, "Empty volume ID in request")
}
if len(req.VolumeCapabilities) == 0 {
return nil, status.Error(codes.InvalidArgument, "Empty volume capabilities in request")
}
return &csi.ValidateVolumeCapabilitiesResponse{
Confirmed: &csi.ValidateVolumeCapabilitiesResponse_Confirmed{
VolumeCapabilities: req.VolumeCapabilities,
},
}, nil
}
func setupMountPath(mountPath string, server string, rootpath string) error {
// Create mountPath /csi-nfs-volume/{UUID}
if err := os.MkdirAll(mountPath, 0750); err != nil {
glog.Warningf("Failed to create mountPath %s: %v", mountPath, err)
return err
}
// Mount nfs rootpath to mountPath /csi-nfs-volume/{UUID}
source := fmt.Sprintf("%s:%s", server, rootpath)
mounter := mount.New("")
if err := mounter.Mount(source, mountPath, "nfs", []string{"nolock"}); err != nil {
glog.Warningf("Failed to mount source %s to mountPath %s: %v", source, mountPath, err)
return err
}
return nil
}
func teardownMountPath(mountPath string) error {
// Unmount nfs rootpath from mountPath /csi-nfs-volume/{UUID} and delete the path
if err := mount.CleanupMountPoint(mountPath, mount.New(""), false); err != nil {
glog.Warningf("Failed to cleanup mountPath %s: %v", mountPath, err)
return err
}
return nil
}
type volumeInfo struct {
server string
rootpath string
volID string
}
func encodeVolID(vol volumeInfo) (string, error) {
if len(vol.server) == 0 {
return "", fmt.Errorf("Server information in VolumeInfo shouldn't be empty: %v", vol)
}
if len(vol.rootpath) == 0 {
return "", fmt.Errorf("Rootpath information in VolumeInfo shouldn't be empty: %v", vol)
}
if len(vol.volID) == 0 {
return "", fmt.Errorf("volID information in VolumeInfo shouldn't be empty: %v", vol)
}
encServer := strings.ReplaceAll(base64.RawStdEncoding.EncodeToString([]byte(vol.server)), "/", "-")
encRootpath := strings.ReplaceAll(base64.RawStdEncoding.EncodeToString([]byte(vol.rootpath)), "/", "-")
encVolID := strings.ReplaceAll(base64.RawStdEncoding.EncodeToString([]byte(vol.volID)), "/", "-")
return strings.Join([]string{encServer, encRootpath, encVolID}, "_"), nil
}
func decodeVolID(volID string) (*volumeInfo, error) {
var volInfo volumeInfo
volIDs := strings.SplitN(volID, "_", 3)
if len(volIDs) != 3 {
return nil, fmt.Errorf("Failed to decode information from %s: not enough fields", volID)
}
serverByte, err := base64.RawStdEncoding.DecodeString(strings.ReplaceAll(volIDs[0], "-", "/"))
if err != nil {
return nil, fmt.Errorf("Failed to decode server information from %s: %v", volID, err)
}
volInfo.server = string(serverByte)
rootpathByte, err := base64.RawStdEncoding.DecodeString(strings.ReplaceAll(volIDs[1], "-", "/"))
if err != nil {
return nil, fmt.Errorf("Failed to decode rootpath information from %s: %v", volID, err)
}
volInfo.rootpath = string(rootpathByte)
volIDByte, err := base64.RawStdEncoding.DecodeString(strings.ReplaceAll(volIDs[2], "-", "/"))
if err != nil {
return nil, fmt.Errorf("Failed to decode volID information from %s: %v", volID, err)
}
volInfo.volID = string(volIDByte)
return &volInfo, nil
}