219 lines
7.5 KiB
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
|
|
}
|