mirror of
https://github.com/SynologyOpenSource/synology-csi.git
synced 2026-08-25 01:27:15 +00:00
Update to version 1.2.0
This commit is contained in:
@@ -69,6 +69,18 @@ func (cs *controllerServer) isVolumeAccessModeSupport(mode csi.VolumeCapability_
|
||||
return false
|
||||
}
|
||||
|
||||
func parseNfsVesrion(ops []string) string {
|
||||
for _, op := range ops {
|
||||
if strings.HasPrefix(op, "nfsvers") {
|
||||
kvpair := strings.Split(op, "=")
|
||||
if len(kvpair) == 2 {
|
||||
return kvpair[1]
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (cs *controllerServer) CreateVolume(ctx context.Context, req *csi.CreateVolumeRequest) (*csi.CreateVolumeResponse, error) {
|
||||
sizeInByte, err := getSizeByCapacityRange(req.GetCapacityRange())
|
||||
volName, volCap := req.GetName(), req.GetVolumeCapabilities()
|
||||
@@ -89,7 +101,7 @@ func (cs *controllerServer) CreateVolume(ctx context.Context, req *csi.CreateVol
|
||||
if volCap == nil {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "No volume capabilities are provided")
|
||||
}
|
||||
|
||||
var mountOptions []string
|
||||
for _, cap := range volCap {
|
||||
accessMode := cap.GetAccessMode().GetMode()
|
||||
|
||||
@@ -102,6 +114,10 @@ func (cs *controllerServer) CreateVolume(ctx context.Context, req *csi.CreateVol
|
||||
} else if accessMode == csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER {
|
||||
multiSession = true
|
||||
}
|
||||
|
||||
if mount := cap.GetMount(); mount != nil {
|
||||
mountOptions = mount.GetMountFlags()
|
||||
}
|
||||
}
|
||||
|
||||
if volContentSrc != nil {
|
||||
@@ -129,8 +145,15 @@ func (cs *controllerServer) CreateVolume(ctx context.Context, req *csi.CreateVol
|
||||
}
|
||||
|
||||
// not needed during CreateVolume method
|
||||
// used only in NodeStageVolume though VolumeContext
|
||||
// used only in NodeStageVolume through VolumeContext
|
||||
formatOptions := params["formatOptions"]
|
||||
mountPermissions := params["mountPermissions"]
|
||||
// check mountPermissions valid
|
||||
if mountPermissions != "" {
|
||||
if _, err := strconv.ParseUint(mountPermissions, 8, 32); err != nil {
|
||||
return nil, status.Errorf(codes.InvalidArgument, fmt.Sprintf("invalid mountPermissions %s in storage class", mountPermissions))
|
||||
}
|
||||
}
|
||||
|
||||
lunDescription := ""
|
||||
if _, ok := params["csi.storage.k8s.io/pvc/name"]; ok {
|
||||
@@ -141,6 +164,11 @@ func (cs *controllerServer) CreateVolume(ctx context.Context, req *csi.CreateVol
|
||||
lunDescription = pvcNamespace + "/" + pvcName
|
||||
}
|
||||
|
||||
nfsVer := parseNfsVesrion(mountOptions)
|
||||
if nfsVer != "" && !isNfsVersionAllowed(nfsVer) {
|
||||
return nil, status.Errorf(codes.InvalidArgument, "Unsupported nfsvers: %s", nfsVer)
|
||||
}
|
||||
|
||||
spec := &models.CreateK8sVolumeSpec{
|
||||
DsmIp: params["dsm"],
|
||||
K8sVolumeName: volName,
|
||||
@@ -156,6 +184,7 @@ func (cs *controllerServer) CreateVolume(ctx context.Context, req *csi.CreateVol
|
||||
SourceSnapshotId: srcSnapshotId,
|
||||
SourceVolumeId: srcVolumeId,
|
||||
Protocol: protocol,
|
||||
NfsVersion: nfsVer,
|
||||
}
|
||||
|
||||
// idempotency
|
||||
@@ -172,7 +201,8 @@ func (cs *controllerServer) CreateVolume(ctx context.Context, req *csi.CreateVol
|
||||
}
|
||||
|
||||
if (k8sVolume.Protocol == utils.ProtocolIscsi && k8sVolume.SizeInBytes != sizeInByte) ||
|
||||
(k8sVolume.Protocol == utils.ProtocolSmb && utils.BytesToMB(k8sVolume.SizeInBytes) != utils.BytesToMBCeil(sizeInByte)) {
|
||||
(k8sVolume.Protocol == utils.ProtocolSmb && utils.BytesToMB(k8sVolume.SizeInBytes) != utils.BytesToMBCeil(sizeInByte)) ||
|
||||
(k8sVolume.Protocol == utils.ProtocolNfs && utils.BytesToMB(k8sVolume.SizeInBytes) != utils.BytesToMBCeil(sizeInByte)) {
|
||||
return nil, status.Errorf(codes.AlreadyExists, "Already existing volume name with different capacity")
|
||||
}
|
||||
|
||||
@@ -182,10 +212,12 @@ func (cs *controllerServer) CreateVolume(ctx context.Context, req *csi.CreateVol
|
||||
CapacityBytes: k8sVolume.SizeInBytes,
|
||||
ContentSource: volContentSrc,
|
||||
VolumeContext: map[string]string{
|
||||
"dsm": k8sVolume.DsmIp,
|
||||
"protocol": k8sVolume.Protocol,
|
||||
"source": k8sVolume.Source,
|
||||
"formatOptions": formatOptions,
|
||||
"dsm": k8sVolume.DsmIp,
|
||||
"protocol": k8sVolume.Protocol,
|
||||
"source": k8sVolume.Source,
|
||||
"formatOptions": formatOptions,
|
||||
"mountPermissions": mountPermissions,
|
||||
"baseDir": k8sVolume.BaseDir,
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
|
||||
@@ -25,12 +25,13 @@ import (
|
||||
|
||||
const (
|
||||
DriverName = "csi.san.synology.com" // CSI dirver name
|
||||
DriverVersion = "1.1.3"
|
||||
DriverVersion = "1.2.0"
|
||||
)
|
||||
|
||||
var (
|
||||
MultipathEnabled = true
|
||||
supportedProtocolList = []string{utils.ProtocolIscsi, utils.ProtocolSmb}
|
||||
supportedProtocolList = []string{utils.ProtocolIscsi, utils.ProtocolSmb, utils.ProtocolNfs}
|
||||
allowedNfsVersionList = []string{"3", "4", "4.0", "4.1"}
|
||||
)
|
||||
|
||||
type IDriver interface {
|
||||
@@ -139,3 +140,7 @@ func (d *Driver) getVolumeCapabilityAccessModes() []*csi.VolumeCapability_Access
|
||||
func isProtocolSupport(protocol string) bool {
|
||||
return utils.SliceContains(supportedProtocolList, protocol)
|
||||
}
|
||||
|
||||
func isNfsVersionAllowed(ver string) bool {
|
||||
return utils.SliceContains(allowedNfsVersionList, ver)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
Copyright 2020 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 driver
|
||||
|
||||
import (
|
||||
"os"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// chmodIfPermissionMismatch only perform chmod when permission mismatches
|
||||
func chmodIfPermissionMismatch(targetPath string, mode os.FileMode) error {
|
||||
info, err := os.Lstat(targetPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
perm := info.Mode() & os.ModePerm
|
||||
if perm != mode {
|
||||
log.Infof("chmod targetPath(%s, mode:0%o) with permissions(0%o)", targetPath, info.Mode(), mode)
|
||||
if err := os.Chmod(targetPath, mode); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
log.Infof("skip chmod on targetPath(%s) since mode is already 0%o)", targetPath, info.Mode())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+134
-6
@@ -21,6 +21,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -111,6 +112,21 @@ func getVolumeMountPath(iscsiDevPaths []string) string {
|
||||
return path
|
||||
}
|
||||
|
||||
func createTargetMountPathNFS(mounter mount.Interface, mountPath string, mountPermissionsUint uint64) (bool, error) {
|
||||
notMount, err := mounter.IsLikelyNotMountPoint(mountPath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
if err := os.MkdirAll(mountPath, os.FileMode(mountPermissionsUint)); err != nil {
|
||||
return notMount, err
|
||||
}
|
||||
notMount = true
|
||||
} else {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
return notMount, nil
|
||||
}
|
||||
|
||||
func createTargetMountPath(mounter mount.Interface, mountPath string, isBlock bool) (bool, error) {
|
||||
notMount, err := mount.IsNotMountPoint(mounter, mountPath)
|
||||
if err != nil {
|
||||
@@ -262,6 +278,48 @@ func (ns *nodeServer) mountSensitiveWithRetry(sourcePath string, targetPath stri
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
func (ns *nodeServer) setNFSVolumePrivilege(sourcePath string, hostname string, authType utils.AuthType) error {
|
||||
// NFSTODO: fix the parsing rule
|
||||
s := strings.Split(strings.TrimPrefix(sourcePath, "//"), "/")
|
||||
if len(s) != 2 {
|
||||
return fmt.Errorf("Failed to parse dsmIp and shareName from source path")
|
||||
}
|
||||
dsmIp, shareName := s[0], s[1]
|
||||
|
||||
dsm, err := ns.dsmService.GetDsm(dsmIp)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to get DSM[%s]", dsmIp)
|
||||
}
|
||||
|
||||
priv := webapi.SharePrivilege{
|
||||
ShareName: shareName,
|
||||
Rule: []webapi.PrivilegeRule{
|
||||
{
|
||||
Async: true,
|
||||
Client: hostname,
|
||||
Crossmnt: true,
|
||||
Insecure: true,
|
||||
Privilege: string(authType),
|
||||
RootSquash: "root",
|
||||
SecurityFlavor: webapi.SecurityFlavor{
|
||||
Kerbros: false,
|
||||
KerbrosIntegrity: false,
|
||||
KerbrosPrivacy: false,
|
||||
Sys: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
err = dsm.ShareNfsPrivilegeSave(priv)
|
||||
if err != nil {
|
||||
log.Printf("Failed to save share NFS privilege. Priv:%v. %v", priv, err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ns *nodeServer) setSMBVolumePermission(sourcePath string, userName string, authType utils.AuthType) error {
|
||||
s := strings.Split(strings.TrimPrefix(sourcePath, "//"), "/")
|
||||
if len(s) != 2 {
|
||||
@@ -391,6 +449,13 @@ func (ns *nodeServer) nodeStageSMBVolume(ctx context.Context, spec *models.NodeS
|
||||
return &csi.NodeStageVolumeResponse{}, nil
|
||||
}
|
||||
|
||||
func (ns *nodeServer) nodeStageNFSVolume(ctx context.Context, spec *models.NodeStageVolumeSpec) (*csi.NodeStageVolumeResponse, error) {
|
||||
if err := ns.setNFSVolumePrivilege(spec.Source, "*", utils.AuthTypeReadWrite); err != nil { //NFSTODO: get workernode IP instead of *
|
||||
return nil, status.Error(codes.Internal, fmt.Sprintf("Failed to set NFS privilege rule, source: %s, err: %v", spec.Source, err))
|
||||
}
|
||||
return &csi.NodeStageVolumeResponse{}, nil
|
||||
}
|
||||
|
||||
func (ns *nodeServer) NodeStageVolume(ctx context.Context, req *csi.NodeStageVolumeRequest) (*csi.NodeStageVolumeResponse, error) {
|
||||
volumeId, stagingTargetPath, volumeCapability :=
|
||||
req.GetVolumeId(), req.GetStagingTargetPath(), req.GetVolumeCapability()
|
||||
@@ -416,6 +481,8 @@ func (ns *nodeServer) NodeStageVolume(ctx context.Context, req *csi.NodeStageVol
|
||||
switch req.VolumeContext["protocol"] {
|
||||
case utils.ProtocolSmb:
|
||||
return ns.nodeStageSMBVolume(ctx, spec, req.GetSecrets())
|
||||
case utils.ProtocolNfs:
|
||||
return ns.nodeStageNFSVolume(ctx, spec)
|
||||
default:
|
||||
return ns.nodeStageISCSIVolume(ctx, spec)
|
||||
}
|
||||
@@ -461,7 +528,71 @@ func (ns *nodeServer) NodePublishVolume(ctx context.Context, req *csi.NodePublis
|
||||
|
||||
isBlock := req.GetVolumeCapability().GetBlock() != nil // raw block, only for iscsi protocol
|
||||
fsType := req.GetVolumeCapability().GetMount().GetFsType()
|
||||
options := []string{}
|
||||
if req.GetReadonly() {
|
||||
options = append(options, "ro")
|
||||
}
|
||||
|
||||
// nfs
|
||||
if req.VolumeContext["protocol"] == utils.ProtocolNfs {
|
||||
options = append(options, req.GetVolumeCapability().GetMount().GetMountFlags()...)
|
||||
|
||||
var server, baseDir string //NFSTODO: subDir
|
||||
var mountPermissionsUint uint64 = 0750 // default
|
||||
for k, v := range req.GetVolumeContext() {
|
||||
switch k {
|
||||
case "dsm":
|
||||
server = v
|
||||
case "baseDir":
|
||||
baseDir = v
|
||||
case "mountPermissions":
|
||||
if v != "" {
|
||||
var err error
|
||||
mountPermissionsUint, err = strconv.ParseUint(v, 8, 32)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.InvalidArgument, fmt.Sprintf("invalid mountPermissions %s", v))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if server == "" || baseDir == "" {
|
||||
return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("Invalid inputs: server(dsm) and baseDir are required."))
|
||||
}
|
||||
source := fmt.Sprintf("%s:%s", server, baseDir)
|
||||
|
||||
notMount, err := createTargetMountPathNFS(ns.Mounter.Interface, targetPath, mountPermissionsUint)
|
||||
if err != nil {
|
||||
return nil, status.Error(codes.Internal, err.Error())
|
||||
}
|
||||
if !notMount {
|
||||
log.Infof("NodePublishVolume: %s is already mounted", targetPath)
|
||||
return &csi.NodePublishVolumeResponse{}, nil
|
||||
}
|
||||
|
||||
log.Debugf("NodePublishVolume: volumeId(%v) source(%s) targetPath(%s) mountflags(%v)", volumeId, source, targetPath, options)
|
||||
err = ns.Mounter.Mount(source, targetPath, "nfs", options)
|
||||
if err != nil {
|
||||
if os.IsPermission(err) {
|
||||
return nil, status.Error(codes.PermissionDenied, err.Error())
|
||||
}
|
||||
if strings.Contains(err.Error(), "invalid argument") {
|
||||
return nil, status.Error(codes.InvalidArgument, err.Error())
|
||||
}
|
||||
return nil, status.Error(codes.Internal, err.Error())
|
||||
}
|
||||
|
||||
if mountPermissionsUint > 0 {
|
||||
if err := chmodIfPermissionMismatch(targetPath, os.FileMode(mountPermissionsUint)); err != nil {
|
||||
return nil, status.Error(codes.Internal, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
log.Debugf("NFS volume(%s) mount %s on %s succeeded", volumeId, source, targetPath)
|
||||
return &csi.NodePublishVolumeResponse{}, nil
|
||||
}
|
||||
|
||||
// iscsi & smb
|
||||
notMount, err := createTargetMountPath(ns.Mounter.Interface, targetPath, isBlock)
|
||||
if err != nil {
|
||||
return nil, status.Error(codes.Internal, err.Error())
|
||||
@@ -470,10 +601,7 @@ func (ns *nodeServer) NodePublishVolume(ctx context.Context, req *csi.NodePublis
|
||||
return &csi.NodePublishVolumeResponse{}, nil
|
||||
}
|
||||
|
||||
options := []string{"bind"}
|
||||
if req.GetReadonly() {
|
||||
options = append(options, "ro")
|
||||
}
|
||||
options = append(options, "bind")
|
||||
|
||||
switch req.VolumeContext["protocol"] {
|
||||
case utils.ProtocolSmb:
|
||||
@@ -574,7 +702,7 @@ func (ns *nodeServer) NodeGetVolumeStats(ctx context.Context, req *csi.NodeGetVo
|
||||
fmt.Sprintf("Volume[%s] does not exist on the %s", volumeId, volumePath))
|
||||
}
|
||||
|
||||
if k8sVolume.Protocol == utils.ProtocolSmb {
|
||||
if k8sVolume.Protocol == utils.ProtocolSmb || k8sVolume.Protocol == utils.ProtocolNfs {
|
||||
return &csi.NodeGetVolumeStatsResponse{
|
||||
Usage: []*csi.VolumeUsage{
|
||||
&csi.VolumeUsage{
|
||||
@@ -635,7 +763,7 @@ func (ns *nodeServer) NodeExpandVolume(ctx context.Context, req *csi.NodeExpandV
|
||||
return nil, status.Error(codes.NotFound, fmt.Sprintf("Volume[%s] is not found", volumeId))
|
||||
}
|
||||
|
||||
if k8sVolume.Protocol == utils.ProtocolSmb {
|
||||
if k8sVolume.Protocol == utils.ProtocolSmb || k8sVolume.Protocol == utils.ProtocolNfs {
|
||||
return &csi.NodeExpandVolumeResponse{
|
||||
CapacityBytes: sizeInByte}, nil
|
||||
}
|
||||
|
||||
+75
-14
@@ -100,7 +100,7 @@ func (service *DsmService) ListDsmVolumes(ip string) ([]webapi.VolInfo, error) {
|
||||
return allVolInfos, nil
|
||||
}
|
||||
|
||||
func (service *DsmService) getFirstAvailableVolume(dsm *webapi.DSM, sizeInBytes int64) (webapi.VolInfo, error) {
|
||||
func (service *DsmService) getFirstAvailableVolume(dsm *webapi.DSM, sizeInBytes int64, protocol string) (webapi.VolInfo, error) {
|
||||
volInfos, err := dsm.VolumeList()
|
||||
if err != nil {
|
||||
return webapi.VolInfo{}, err
|
||||
@@ -121,6 +121,10 @@ func (service *DsmService) getFirstAvailableVolume(dsm *webapi.DSM, sizeInBytes
|
||||
if volInfo.Container == "external" && volInfo.Location == "sata" {
|
||||
continue
|
||||
}
|
||||
|
||||
if volInfo.FsType == models.FsTypeExt4 && (protocol == utils.ProtocolSmb || protocol == utils.ProtocolNfs) {
|
||||
continue
|
||||
}
|
||||
return volInfo, nil
|
||||
}
|
||||
return webapi.VolInfo{}, fmt.Errorf("Cannot find any available volume")
|
||||
@@ -201,7 +205,7 @@ func (service *DsmService) createMappingTarget(dsm *webapi.DSM, spec *models.Cre
|
||||
func (service *DsmService) createVolumeByDsm(dsm *webapi.DSM, spec *models.CreateK8sVolumeSpec) (*models.K8sVolumeRespSpec, error) {
|
||||
// 1. Find a available location
|
||||
if spec.Location == "" {
|
||||
vol, err := service.getFirstAvailableVolume(dsm, spec.Size)
|
||||
vol, err := service.getFirstAvailableVolume(dsm, spec.Size, spec.Protocol)
|
||||
if err != nil {
|
||||
return nil,
|
||||
status.Errorf(codes.Internal, fmt.Sprintf("Failed to get available location, err: %v", err))
|
||||
@@ -372,16 +376,25 @@ func (service *DsmService) createVolumeByVolume(dsm *webapi.DSM, spec *models.Cr
|
||||
return DsmLunToK8sVolume(dsm.Ip, lunInfo, targetInfo), nil
|
||||
}
|
||||
|
||||
func DsmShareToK8sVolume(dsmIp string, info webapi.ShareInfo) *models.K8sVolumeRespSpec {
|
||||
func DsmShareToK8sVolume(dsmIp string, info webapi.ShareInfo, protocol string) *models.K8sVolumeRespSpec {
|
||||
var source, baseDir string
|
||||
if protocol == utils.ProtocolSmb {
|
||||
source = "//" + dsmIp + "/" + info.Name
|
||||
} else if protocol == utils.ProtocolNfs {
|
||||
source = "//" + dsmIp + "/" + info.Name
|
||||
baseDir = info.VolPath + "/" + info.Name
|
||||
}
|
||||
|
||||
return &models.K8sVolumeRespSpec{
|
||||
DsmIp: dsmIp,
|
||||
VolumeId: info.Uuid,
|
||||
SizeInBytes: utils.MBToBytes(info.QuotaValueInMB),
|
||||
Location: info.VolPath,
|
||||
Name: info.Name,
|
||||
Source: "//" + dsmIp + "/" + info.Name,
|
||||
Protocol: utils.ProtocolSmb,
|
||||
Source: source,
|
||||
Protocol: protocol,
|
||||
Share: info,
|
||||
BaseDir: baseDir,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -399,6 +412,45 @@ func DsmLunToK8sVolume(dsmIp string, info webapi.LunInfo, targetInfo webapi.Targ
|
||||
}
|
||||
}
|
||||
|
||||
func isNfsVersionSupport(dsm *webapi.DSM, nfsVersion string) bool {
|
||||
major := 0
|
||||
minor := 0
|
||||
|
||||
info, err := dsm.NfsGet()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if nfsVersion == "" {
|
||||
major = info.SupportMajorVer
|
||||
minor = info.SupportMinorVer
|
||||
} else if nfsVersion == "3" {
|
||||
major = 3
|
||||
} else if nfsVersion == "4" || nfsVersion == "4.0" || nfsVersion == "4.1" {
|
||||
major = 4
|
||||
if nfsVersion == "4.1" {
|
||||
minor = 1
|
||||
}
|
||||
} else {
|
||||
log.Infof("Input nfsVersion = %s, not supported!", nfsVersion)
|
||||
return false
|
||||
}
|
||||
|
||||
if major > info.SupportMajorVer || (major == info.SupportMajorVer && minor > info.SupportMinorVer) {
|
||||
log.Infof("Dsm NFS version not supported")
|
||||
return false
|
||||
}
|
||||
|
||||
// enable the highest NFS version the DSM supports
|
||||
if err := dsm.NfsSet(true, (info.SupportMajorVer == 4), info.SupportMinorVer); err != nil {
|
||||
log.Errorf("[%s] Failed to enable nfs: %v\n", dsm.Ip, err)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
|
||||
func (service *DsmService) CreateVolume(spec *models.CreateK8sVolumeSpec) (*models.K8sVolumeRespSpec, error) {
|
||||
if spec.SourceVolumeId != "" {
|
||||
/* Create volume by exists volume (Clone) */
|
||||
@@ -414,7 +466,7 @@ func (service *DsmService) CreateVolume(spec *models.CreateK8sVolumeSpec) (*mode
|
||||
|
||||
if spec.Protocol == utils.ProtocolIscsi {
|
||||
return service.createVolumeByVolume(dsm, spec, k8sVolume.Lun)
|
||||
} else if spec.Protocol == utils.ProtocolSmb {
|
||||
} else if spec.Protocol == utils.ProtocolSmb || spec.Protocol == utils.ProtocolNfs {
|
||||
return service.createSMBVolumeByVolume(dsm, spec, k8sVolume.Share)
|
||||
}
|
||||
return nil, status.Error(codes.InvalidArgument, "Unknown protocol")
|
||||
@@ -438,7 +490,10 @@ func (service *DsmService) CreateVolume(spec *models.CreateK8sVolumeSpec) (*mode
|
||||
snapshot.RootPath, spec.Location)
|
||||
return nil, status.Errorf(codes.InvalidArgument, msg)
|
||||
}
|
||||
if spec.Protocol != snapshot.Protocol {
|
||||
|
||||
log.Debugf("The source PVC protocol [%s] and the destination PVC protocol [%s]", snapshot.Protocol, spec.Protocol)
|
||||
if (spec.Protocol == utils.ProtocolIscsi || snapshot.Protocol == utils.ProtocolIscsi) &&
|
||||
spec.Protocol != snapshot.Protocol {
|
||||
msg := fmt.Sprintf("The source PVC and destination PVCs shouldn't have different protocols. Source is %s, but new PVC is %s",
|
||||
snapshot.Protocol, spec.Protocol)
|
||||
return nil, status.Errorf(codes.InvalidArgument, msg)
|
||||
@@ -451,7 +506,7 @@ func (service *DsmService) CreateVolume(spec *models.CreateK8sVolumeSpec) (*mode
|
||||
|
||||
if spec.Protocol == utils.ProtocolIscsi {
|
||||
return service.createVolumeBySnapshot(dsm, spec, snapshot)
|
||||
} else if spec.Protocol == utils.ProtocolSmb {
|
||||
} else if spec.Protocol == utils.ProtocolSmb || spec.Protocol == utils.ProtocolNfs {
|
||||
return service.createSMBVolumeBySnapshot(dsm, spec, snapshot)
|
||||
}
|
||||
return nil, status.Error(codes.InvalidArgument, "Unknown protocol")
|
||||
@@ -468,7 +523,12 @@ func (service *DsmService) CreateVolume(spec *models.CreateK8sVolumeSpec) (*mode
|
||||
if spec.Protocol == utils.ProtocolIscsi {
|
||||
k8sVolume, err = service.createVolumeByDsm(dsm, spec)
|
||||
} else if spec.Protocol == utils.ProtocolSmb {
|
||||
k8sVolume, err = service.createSMBVolumeByDsm(dsm, spec)
|
||||
k8sVolume, err = service.createSMBorNFSVolumeByDsm(dsm, spec, utils.ProtocolSmb)
|
||||
} else if spec.Protocol == utils.ProtocolNfs {
|
||||
if !isNfsVersionSupport(dsm, spec.NfsVersion) {
|
||||
continue
|
||||
}
|
||||
k8sVolume, err = service.createSMBorNFSVolumeByDsm(dsm, spec, utils.ProtocolNfs)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
@@ -494,7 +554,7 @@ func (service *DsmService) DeleteVolume(volId string) error {
|
||||
return status.Errorf(codes.Internal, fmt.Sprintf("Failed to get DSM[%s]", k8sVolume.DsmIp))
|
||||
}
|
||||
|
||||
if k8sVolume.Protocol == utils.ProtocolSmb {
|
||||
if k8sVolume.Protocol == utils.ProtocolSmb || k8sVolume.Protocol == utils.ProtocolNfs {
|
||||
if err := dsm.ShareDelete(k8sVolume.Share.Name); err != nil {
|
||||
log.Errorf("[%s] Failed to delete Share(%s): %v", dsm.Ip, k8sVolume.Share.Name, err)
|
||||
return err
|
||||
@@ -616,7 +676,7 @@ func (service *DsmService) ExpandVolume(volId string, newSize int64) (*models.K8
|
||||
return nil, status.Errorf(codes.Internal, fmt.Sprintf("Failed to get DSM[%s]", k8sVolume.DsmIp))
|
||||
}
|
||||
|
||||
if k8sVolume.Protocol == utils.ProtocolSmb {
|
||||
if k8sVolume.Protocol == utils.ProtocolSmb || k8sVolume.Protocol == utils.ProtocolNfs {
|
||||
newSizeInMB := utils.BytesToMBCeil(newSize) // round up to MB
|
||||
if err := dsm.SetShareQuota(k8sVolume.Share, newSizeInMB); err != nil {
|
||||
log.Errorf("[%s] Failed to set quota [%d (MB)] to Share [%s]: %v",
|
||||
@@ -674,7 +734,7 @@ func (service *DsmService) CreateSnapshot(spec *models.CreateK8sVolumeSnapshotSp
|
||||
}
|
||||
|
||||
return nil, status.Errorf(codes.NotFound, fmt.Sprintf("Failed to get iscsi snapshot (%s). Not found", snapshotUuid))
|
||||
} else if k8sVolume.Protocol == utils.ProtocolSmb {
|
||||
} else if k8sVolume.Protocol == utils.ProtocolSmb || k8sVolume.Protocol == utils.ProtocolNfs {
|
||||
snapshotSpec := webapi.ShareSnapshotCreateSpec{
|
||||
ShareName: k8sVolume.Share.Name,
|
||||
Desc: models.ShareSnapshotDescPrefix + spec.SnapshotName, // limitations: don't change the desc by DSM
|
||||
@@ -692,7 +752,8 @@ func (service *DsmService) CreateSnapshot(spec *models.CreateK8sVolumeSnapshotSp
|
||||
return snapshot, nil
|
||||
}
|
||||
}
|
||||
return nil, status.Errorf(codes.NotFound, fmt.Sprintf("Failed to get smb snapshot (%s, %s). Not found", snapshotTime, srcVolId))
|
||||
return nil, status.Errorf(codes.NotFound, fmt.Sprintf("Failed to get %s snapshot (%s, %s). Not found",
|
||||
k8sVolume.Protocol, snapshotTime, srcVolId))
|
||||
}
|
||||
|
||||
return nil, status.Error(codes.InvalidArgument, "Unsupported volume protocol")
|
||||
@@ -718,7 +779,7 @@ func (service *DsmService) DeleteSnapshot(snapshotUuid string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if snapshot.Protocol == utils.ProtocolSmb {
|
||||
if snapshot.Protocol == utils.ProtocolSmb || snapshot.Protocol == utils.ProtocolNfs {
|
||||
if err := dsm.ShareSnapshotDelete(snapshot.Time, snapshot.ParentName); err != nil {
|
||||
if snapshot := service.getSMBSnapshot(snapshotUuid); snapshot == nil { // idempotency
|
||||
return nil
|
||||
|
||||
@@ -77,7 +77,7 @@ func (service *DsmService) createSMBVolumeBySnapshot(dsm *webapi.DSM, spec *mode
|
||||
|
||||
log.Debugf("[%s] createSMBVolumeBySnapshot Successfully. VolumeId: %s", dsm.Ip, shareInfo.Uuid);
|
||||
|
||||
return DsmShareToK8sVolume(dsm.Ip, shareInfo), nil
|
||||
return DsmShareToK8sVolume(dsm.Ip, shareInfo, spec.Protocol), nil
|
||||
}
|
||||
|
||||
func (service *DsmService) createSMBVolumeByVolume(dsm *webapi.DSM, spec *models.CreateK8sVolumeSpec, srcShareInfo webapi.ShareInfo) (*models.K8sVolumeRespSpec, error) {
|
||||
@@ -124,15 +124,15 @@ func (service *DsmService) createSMBVolumeByVolume(dsm *webapi.DSM, spec *models
|
||||
|
||||
log.Debugf("[%s] createSMBVolumeByVolume Successfully. VolumeId: %s", dsm.Ip, shareInfo.Uuid);
|
||||
|
||||
return DsmShareToK8sVolume(dsm.Ip, shareInfo), nil
|
||||
return DsmShareToK8sVolume(dsm.Ip, shareInfo, spec.Protocol), nil
|
||||
}
|
||||
|
||||
func (service *DsmService) createSMBVolumeByDsm(dsm *webapi.DSM, spec *models.CreateK8sVolumeSpec) (*models.K8sVolumeRespSpec, error) {
|
||||
func (service *DsmService) createSMBorNFSVolumeByDsm(dsm *webapi.DSM, spec *models.CreateK8sVolumeSpec, protocol string) (*models.K8sVolumeRespSpec, error) {
|
||||
// TODO: Check if share name is allowable
|
||||
|
||||
// 1. Find a available location
|
||||
if spec.Location == "" {
|
||||
vol, err := service.getFirstAvailableVolume(dsm, spec.Size)
|
||||
vol, err := service.getFirstAvailableVolume(dsm, spec.Size, spec.Protocol)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Internal, fmt.Sprintf("Failed to get available location, err: %v", err))
|
||||
}
|
||||
@@ -140,11 +140,15 @@ func (service *DsmService) createSMBVolumeByDsm(dsm *webapi.DSM, spec *models.Cr
|
||||
}
|
||||
|
||||
// 2. Check if location exists
|
||||
_, err := dsm.VolumeGet(spec.Location)
|
||||
dsmVolInfo, err := dsm.VolumeGet(spec.Location)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.InvalidArgument, fmt.Sprintf("Unable to find location %s", spec.Location))
|
||||
}
|
||||
|
||||
if dsmVolInfo.FsType == models.FsTypeExt4 {
|
||||
return nil, status.Errorf(codes.InvalidArgument, fmt.Sprintf("Location: %s with ext4 fstype was not supported for creating smb/nfs protocol's K8s volume", spec.Location))
|
||||
}
|
||||
|
||||
// 3. Create Share
|
||||
sizeInMB := utils.BytesToMBCeil(spec.Size)
|
||||
shareSpec := webapi.ShareCreateSpec{
|
||||
@@ -173,9 +177,9 @@ func (service *DsmService) createSMBVolumeByDsm(dsm *webapi.DSM, spec *models.Cr
|
||||
status.Errorf(codes.Internal, fmt.Sprintf("Failed to get existed Share with name: %s, err: %v", spec.ShareName, err))
|
||||
}
|
||||
|
||||
log.Debugf("[%s] createSMBVolumeByDsm Successfully. VolumeId: %s", dsm.Ip, shareInfo.Uuid)
|
||||
log.Debugf("[%s] createSMBorNFSVolumeByDsm Successfully. VolumeId: %s", dsm.Ip, shareInfo.Uuid)
|
||||
|
||||
return DsmShareToK8sVolume(dsm.Ip, shareInfo), nil
|
||||
return DsmShareToK8sVolume(dsm.Ip, shareInfo, protocol), nil
|
||||
}
|
||||
|
||||
func (service *DsmService) listSMBVolumes(dsmIp string) (infos []*models.K8sVolumeRespSpec) {
|
||||
@@ -198,7 +202,8 @@ func (service *DsmService) listSMBVolumes(dsmIp string) (infos []*models.K8sVolu
|
||||
if !strings.HasPrefix(share.Name, models.SharePrefix) {
|
||||
continue
|
||||
}
|
||||
infos = append(infos, DsmShareToK8sVolume(dsm.Ip, share))
|
||||
//NFSTODO, if share has set nfs rule, deal it as NFS
|
||||
infos = append(infos, DsmShareToK8sVolume(dsm.Ip, share, utils.ProtocolSmb))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -76,6 +76,18 @@ type SharePermission struct {
|
||||
IsAdmin bool `json:"is_admin,omitempty"` // field for list
|
||||
}
|
||||
|
||||
type NfsInfo struct {
|
||||
EnableNfs bool `json:"enable_nfs"`
|
||||
EnableNfsV4 bool `json:"enable_nfs_v4"`
|
||||
NfsV4Domain string `json:"nfs_v4_domain"`
|
||||
ReadSize int `json:"read_size"`
|
||||
SupportEncryptShare int `json:"support_encrypt_share"`
|
||||
SupportMajorVer int `json:"support_major_ver"`
|
||||
SupportMinorVer int `json:"support_minor_ver"`
|
||||
UnixPriEnable bool `json:"unix_pri_enable"`
|
||||
WriteSize int `json:"write_size"`
|
||||
}
|
||||
|
||||
func shareErrCodeMapping(errCode int, oriErr error) error {
|
||||
switch errCode {
|
||||
case 402: // No such share
|
||||
@@ -361,3 +373,80 @@ func (dsm *DSM) SharePermissionList(shareName string, userGroupType string) ([]S
|
||||
return infos.Permissions, nil
|
||||
}
|
||||
|
||||
// ----------------------- FileServ NFS SharePrivilege APIs -----------------------
|
||||
type SecurityFlavor struct {
|
||||
Kerbros bool `json:"kerberos"`
|
||||
KerbrosIntegrity bool `json:"kerberos_integrity"`
|
||||
KerbrosPrivacy bool `json:"kerberos_privacy"`
|
||||
Sys bool `json:"sys"`
|
||||
}
|
||||
|
||||
type PrivilegeRule struct {
|
||||
Async bool `json:"async"`
|
||||
Client string `json:"client"`
|
||||
Crossmnt bool `json:"crossmnt"`
|
||||
Insecure bool `json:"insecure"`
|
||||
Privilege string `json:"privilege"`
|
||||
RootSquash string `json:"root_squash"`
|
||||
SecurityFlavor SecurityFlavor `json:"security_flavor"`
|
||||
}
|
||||
|
||||
type SharePrivilege struct {
|
||||
ShareName string `json:"share_name"`
|
||||
Rule []PrivilegeRule `json:"rule"`
|
||||
}
|
||||
|
||||
func (dsm *DSM) ShareNfsPrivilegeSave(privilege SharePrivilege) error {
|
||||
params := url.Values{}
|
||||
params.Add("api", "SYNO.Core.FileServ.NFS.SharePrivilege")
|
||||
params.Add("method", "save")
|
||||
params.Add("share_name", strconv.Quote(privilege.ShareName))
|
||||
params.Add("version", "1")
|
||||
|
||||
js, err := json.Marshal(privilege.Rule)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
params.Add("rule", string(js))
|
||||
|
||||
_, err = dsm.sendRequest("", &struct{}{}, params, "webapi/entry.cgi")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dsm *DSM) NfsGet() (NfsInfo, error) {
|
||||
params := url.Values{}
|
||||
params.Add("api", "SYNO.Core.FileServ.NFS")
|
||||
params.Add("method", "get")
|
||||
params.Add("version", "2")
|
||||
|
||||
info := NfsInfo{}
|
||||
_, err := dsm.sendRequest("", &info, params, "webapi/entry.cgi")
|
||||
if err != nil {
|
||||
return NfsInfo{}, err
|
||||
}
|
||||
|
||||
return info, nil
|
||||
}
|
||||
|
||||
func (dsm *DSM) NfsSet(enableV3 bool, enableV4 bool, enabledMinorVer int) error {
|
||||
params := url.Values{}
|
||||
params.Add("api", "SYNO.Core.FileServ.NFS")
|
||||
params.Add("method", "set")
|
||||
params.Add("version", "2")
|
||||
|
||||
params.Add("enable_nfs", strconv.FormatBool(enableV3))
|
||||
params.Add("enable_nfs_v4", strconv.FormatBool(enableV4))
|
||||
params.Add("enabled_minor_ver", strconv.Itoa(enabledMinorVer))
|
||||
|
||||
_, err := dsm.sendRequest("", &struct{}{}, params, "webapi/entry.cgi")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ type CreateK8sVolumeSpec struct {
|
||||
SourceSnapshotId string
|
||||
SourceVolumeId string
|
||||
Protocol string
|
||||
NfsVersion string
|
||||
}
|
||||
|
||||
type K8sVolumeRespSpec struct {
|
||||
@@ -36,6 +37,7 @@ type K8sVolumeRespSpec struct {
|
||||
Target webapi.TargetInfo
|
||||
Share webapi.ShareInfo
|
||||
Protocol string
|
||||
BaseDir string
|
||||
}
|
||||
|
||||
type K8sSnapshotRespSpec struct {
|
||||
|
||||
@@ -16,6 +16,7 @@ const (
|
||||
|
||||
ProtocolSmb = "smb"
|
||||
ProtocolIscsi = "iscsi"
|
||||
ProtocolNfs = "nfs"
|
||||
ProtocolDefault = ProtocolIscsi
|
||||
|
||||
AuthTypeReadWrite AuthType = "rw"
|
||||
|
||||
Reference in New Issue
Block a user