diff --git a/README.md b/README.md index a7b9201..1776eb6 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,7 @@ Please check out `examples` for more information. | user | iRODS user id | "irods_user" | | password | iRODS user password | "password" in plain text | | url | URL | "https://data.cyverse.org/dav/iplant/home/irods_user" | +| config | additional config parameters in comma-separated kv pairs to be passed to 'davfs2.conf' | "key1=val1,key2=val2" | Mounts **url** diff --git a/pkg/client/client.go b/pkg/client/client.go index a66aa50..af9fe6e 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -7,6 +7,7 @@ import ( "google.golang.org/grpc/status" "k8s.io/klog" + "github.com/cyverse/irods-csi-driver/pkg/client/common" "github.com/cyverse/irods-csi-driver/pkg/client/irods" "github.com/cyverse/irods-csi-driver/pkg/client/nfs" "github.com/cyverse/irods-csi-driver/pkg/client/webdav" @@ -14,57 +15,11 @@ import ( "github.com/cyverse/irods-csi-driver/pkg/mounter" ) -// ClientType is a mount client type -type ClientType string - -// mount driver (iRODS Client) types -const ( - // IrodsFuseClientType is for iRODS FUSE - IrodsFuseClientType ClientType = "irodsfuse" - // WebdavClientType is for WebDav client (Davfs2) - WebdavClientType ClientType = "webdav" - // NfsClientType is for NFS client - NfsClientType ClientType = "nfs" -) - -// GetClientType returns iRODS Client value from param map -func GetClientType(params map[string]string) ClientType { - return GetValidClientType(params["client"]) -} - -// IsValidClientType checks if given client string is valid -func IsValidClientType(client string) bool { - switch client { - case string(IrodsFuseClientType): - return true - case string(WebdavClientType): - return true - case string(NfsClientType): - return true - default: - return false - } -} - -// GetValidClientType checks if given client string is valid -func GetValidClientType(client string) ClientType { - switch client { - case string(IrodsFuseClientType): - return IrodsFuseClientType - case string(WebdavClientType): - return WebdavClientType - case string(NfsClientType): - return NfsClientType - default: - return IrodsFuseClientType - } -} - // MountClient mounts a fs client func MountClient(mounter mounter.Mounter, volID string, configs map[string]string, mountOptions []string, targetPath string) error { - irodsClientType := GetClientType(configs) + irodsClientType := common.GetClientType(configs) switch irodsClientType { - case IrodsFuseClientType: + case common.IrodsFuseClientType: klog.V(5).Infof("mounting %s", irodsClientType) if err := irods.Mount(mounter, volID, configs, mountOptions, targetPath); err != nil { @@ -76,7 +31,7 @@ func MountClient(mounter mounter.Mounter, volID string, configs map[string]strin metrics.IncreaseCounterForVolumeMount() metrics.IncreaseCounterForActiveVolumeMount() return nil - case WebdavClientType: + case common.WebdavClientType: klog.V(5).Infof("mounting %s", irodsClientType) if err := webdav.Mount(mounter, volID, configs, mountOptions, targetPath); err != nil { @@ -88,7 +43,7 @@ func MountClient(mounter mounter.Mounter, volID string, configs map[string]strin metrics.IncreaseCounterForVolumeMount() metrics.IncreaseCounterForActiveVolumeMount() return nil - case NfsClientType: + case common.NfsClientType: klog.V(5).Infof("mounting %s", irodsClientType) if err := nfs.Mount(mounter, volID, configs, mountOptions, targetPath); err != nil { @@ -114,9 +69,9 @@ func ClearFailedMount(mounter mounter.Mounter, targetPath string) { } // UnmountClient unmounts a fs client -func UnmountClient(mounter mounter.Mounter, volID string, irodsClientType ClientType, configs map[string]string, targetPath string) error { +func UnmountClient(mounter mounter.Mounter, volID string, irodsClientType common.ClientType, configs map[string]string, targetPath string) error { switch irodsClientType { - case IrodsFuseClientType: + case common.IrodsFuseClientType: klog.V(5).Infof("unmounting %s", irodsClientType) if err := irods.Unmount(mounter, volID, configs, targetPath); err != nil { @@ -127,7 +82,7 @@ func UnmountClient(mounter mounter.Mounter, volID string, irodsClientType Client metrics.IncreaseCounterForVolumeUnmount() metrics.DecreaseCounterForActiveVolumeMount() return nil - case WebdavClientType: + case common.WebdavClientType: klog.V(5).Infof("unmounting %s", irodsClientType) if err := webdav.Unmount(mounter, volID, configs, targetPath); err != nil { @@ -138,7 +93,7 @@ func UnmountClient(mounter mounter.Mounter, volID string, irodsClientType Client metrics.IncreaseCounterForVolumeUnmount() metrics.DecreaseCounterForActiveVolumeMount() return nil - case NfsClientType: + case common.NfsClientType: klog.V(5).Infof("unmounting %s", irodsClientType) if err := nfs.Unmount(mounter, volID, configs, targetPath); err != nil { diff --git a/pkg/client/common/common.go b/pkg/client/common/common.go new file mode 100644 index 0000000..ccdfc39 --- /dev/null +++ b/pkg/client/common/common.go @@ -0,0 +1,81 @@ +package common + +import ( + "path/filepath" + "strconv" + "strings" +) + +// ClientType is a mount client type +type ClientType string + +// mount driver (iRODS Client) types +const ( + // IrodsFuseClientType is for iRODS FUSE + IrodsFuseClientType ClientType = "irodsfuse" + // WebdavClientType is for WebDav client (Davfs2) + WebdavClientType ClientType = "webdav" + // NfsClientType is for NFS client + NfsClientType ClientType = "nfs" +) + +// GetClientType returns iRODS Client value from param map +func GetClientType(params map[string]string) ClientType { + return GetValidClientType(params["client"]) +} + +// IsValidClientType checks if given client string is valid +func IsValidClientType(client string) bool { + switch client { + case string(IrodsFuseClientType): + return true + case string(WebdavClientType): + return true + case string(NfsClientType): + return true + default: + return false + } +} + +// GetValidClientType checks if given client string is valid +func GetValidClientType(client string) ClientType { + switch client { + case string(IrodsFuseClientType): + return IrodsFuseClientType + case string(WebdavClientType): + return WebdavClientType + case string(NfsClientType): + return NfsClientType + default: + return IrodsFuseClientType + } +} + +// GetConfigEnforceProxyAccess checks if proxy access is enforced via driver config +func GetConfigEnforceProxyAccess(configs map[string]string) bool { + enforce := configs["enforceproxyaccess"] + bEnforce, _ := strconv.ParseBool(enforce) + return bEnforce +} + +// GetConfigMountPathWhitelist returns a whitelist of collections that users can mount +func GetConfigMountPathWhitelist(configs map[string]string) []string { + whitelist := configs["mountpathwhitelist"] + + whitelistItems := strings.Split(whitelist, ",") + if len(whitelistItems) > 0 { + for idx := range whitelistItems { + whitelistItems[idx] = strings.TrimSpace(whitelistItems[idx]) + } + return whitelistItems + } + + return []string{"/"} +} + +// GetConfigDataRootPath returns a data root path +func GetConfigDataRootPath(configs map[string]string, volID string) string { + irodsClientType := GetClientType(configs) + return filepath.Join(configs["storagepath"], string(irodsClientType), volID) +} diff --git a/pkg/client/irods/config.go b/pkg/client/irods/config.go deleted file mode 100644 index 4749916..0000000 --- a/pkg/client/irods/config.go +++ /dev/null @@ -1,33 +0,0 @@ -package irods - -import ( - "path/filepath" - "strconv" - "strings" -) - -// getConfigEnforceProxyAccess checks if proxy access is enforced via driver config -func getConfigEnforceProxyAccess(configs map[string]string) bool { - enforce := configs["enforceproxyaccess"] - bEnforce, _ := strconv.ParseBool(enforce) - return bEnforce -} - -// getConfigMountPathWhitelist returns a whitelist of collections that users can mount -func getConfigMountPathWhitelist(configs map[string]string) []string { - whitelist := configs["mountpathwhitelist"] - - whitelistItems := strings.Split(whitelist, ",") - if len(whitelistItems) > 0 { - for idx := range whitelistItems { - whitelistItems[idx] = strings.TrimSpace(whitelistItems[idx]) - } - return whitelistItems - } - - return []string{"/"} -} - -func getConfigDataRootPath(configs map[string]string, volID string) string { - return filepath.Join(configs["storagepath"], "irodsfs", volID) -} diff --git a/pkg/client/irods/connection_info.go b/pkg/client/irods/connection_info.go index b27b8a8..2240ae7 100644 --- a/pkg/client/irods/connection_info.go +++ b/pkg/client/irods/connection_info.go @@ -6,6 +6,7 @@ import ( "path/filepath" "strconv" + client_common "github.com/cyverse/irods-csi-driver/pkg/client/common" "github.com/cyverse/irods-csi-driver/pkg/common" "github.com/cyverse/irods-csi-driver/pkg/mounter" "google.golang.org/grpc/codes" @@ -167,7 +168,7 @@ func GetConnectionInfo(configs map[string]string) (*IRODSFSConnectionInfo, error return nil, status.Error(codes.InvalidArgument, "Argument clientUser must be a non-anonymous user") } - if getConfigEnforceProxyAccess(configs) { + if client_common.GetConfigEnforceProxyAccess(configs) { // we don't allow anonymous user if connInfo.IsAnonymousUser() { return nil, status.Error(codes.InvalidArgument, "Argument user must be a non-anonymous user") @@ -225,7 +226,7 @@ func GetConnectionInfo(configs map[string]string) (*IRODSFSConnectionInfo, error return nil, status.Error(codes.InvalidArgument, "Argument path and path_mappings are empty, one must be given") } - whitelist := getConfigMountPathWhitelist(configs) + whitelist := client_common.GetConfigMountPathWhitelist(configs) for _, mapping := range connInfo.PathMappings { if !mounter.IsMountPathAllowed(whitelist, mapping.IRODSPath) { return nil, status.Errorf(codes.InvalidArgument, "Argument path %s is not allowed to mount", mapping.IRODSPath) diff --git a/pkg/client/irods/mount.go b/pkg/client/irods/mount.go index 9540907..8dc4ad0 100644 --- a/pkg/client/irods/mount.go +++ b/pkg/client/irods/mount.go @@ -4,6 +4,7 @@ import ( "fmt" "os" + client_common "github.com/cyverse/irods-csi-driver/pkg/client/common" "github.com/cyverse/irods-csi-driver/pkg/mounter" "golang.org/x/xerrors" "google.golang.org/grpc/codes" @@ -34,7 +35,7 @@ func Mount(mounter mounter.Mounter, volID string, configs map[string]string, mnt irodsFsConfig := NewDefaultIRODSFSConfig() // create irodsfs dataroot - dataRootPath := getConfigDataRootPath(configs, volID) + dataRootPath := client_common.GetConfigDataRootPath(configs, volID) err = makeIrodsFuseLiteDataRootPath(dataRootPath) if err != nil { return status.Error(codes.Internal, err.Error()) @@ -89,7 +90,7 @@ func Unmount(mounter mounter.Mounter, volID string, configs map[string]string, t } // manage logs - dataRootPath := getConfigDataRootPath(configs, volID) + dataRootPath := client_common.GetConfigDataRootPath(configs, volID) err = deleteIrodsFuseLiteData(dataRootPath) if err != nil { klog.V(5).Infof("Error deleting iRODS FUSE Lite data at %s - ignoring", dataRootPath) diff --git a/pkg/client/webdav/connection_info.go b/pkg/client/webdav/connection_info.go index 2e2935a..2af3dbe 100644 --- a/pkg/client/webdav/connection_info.go +++ b/pkg/client/webdav/connection_info.go @@ -2,6 +2,7 @@ package webdav import ( "net/url" + "strings" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" @@ -16,6 +17,7 @@ type WebDAVConnectionInfo struct { URL string User string Password string + Config map[string]string } // SetAnonymousUser sets anonymous user @@ -37,6 +39,15 @@ func getConnectionInfoFromMap(params map[string]string, connInfo *WebDAVConnecti connInfo.Password = v case "url": connInfo.URL = v + case "config": + connInfo.Config = map[string]string{} + configStrings := strings.Split(v, ",") + for _, configString := range configStrings { + configKV := strings.Split(strings.TrimSpace(configString), "=") + if len(configKV) == 2 { + connInfo.Config[strings.TrimSpace(configKV[0])] = strings.TrimSpace(configKV[1]) + } + } default: // ignore } diff --git a/pkg/client/webdav/davfs_config.go b/pkg/client/webdav/davfs_config.go new file mode 100644 index 0000000..de68fbe --- /dev/null +++ b/pkg/client/webdav/davfs_config.go @@ -0,0 +1,38 @@ +package webdav + +import ( + "fmt" + "os" + "strings" +) + +type DavFSConfig struct { + Params map[string]string +} + +// NewDefaultDavFSConfig creates default DavFS config map +func NewDefaultDavFSConfig() *DavFSConfig { + return &DavFSConfig{ + Params: map[string]string{}, + } +} + +func (config *DavFSConfig) AddParam(key string, value string) { + config.Params[key] = value +} + +func (config *DavFSConfig) AddParams(params map[string]string) { + for k, v := range params { + config.Params[k] = v + } +} + +func (config *DavFSConfig) SaveToFile(name string) error { + sb := strings.Builder{} + + for k, v := range config.Params { + sb.WriteString(fmt.Sprintf("%s %s\n", k, v)) + } + + return os.WriteFile(name, []byte(sb.String()), 0o664) +} diff --git a/pkg/client/webdav/mount.go b/pkg/client/webdav/mount.go index f27e9bb..0e027e1 100644 --- a/pkg/client/webdav/mount.go +++ b/pkg/client/webdav/mount.go @@ -2,8 +2,12 @@ package webdav import ( "fmt" + "os" + "path/filepath" + client_common "github.com/cyverse/irods-csi-driver/pkg/client/common" "github.com/cyverse/irods-csi-driver/pkg/mounter" + "golang.org/x/xerrors" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "k8s.io/klog" @@ -22,6 +26,32 @@ func Mount(mounter mounter.Mounter, volID string, configs map[string]string, mnt mountSensitiveOptions := []string{} stdinArgs := []string{} + // create davfs dataroot + dataRootPath := client_common.GetConfigDataRootPath(configs, volID) + err = makeDavFSDataRootPath(dataRootPath) + if err != nil { + return status.Error(codes.Internal, err.Error()) + } + + err = makeDavFSCachePath(dataRootPath) + if err != nil { + return status.Error(codes.Internal, err.Error()) + } + + davFsConfig := NewDefaultDavFSConfig() + davFsConfig.AddParams(irodsConnectionInfo.Config) + + cachePath := getDavFSCachePath(dataRootPath) + davFsConfig.AddParam("cache_dir", cachePath) + + configPath := filepath.Join(dataRootPath, "davfs2.conf") + err = davFsConfig.SaveToFile(configPath) + if err != nil { + return status.Error(codes.Internal, err.Error()) + } + configOption := fmt.Sprintf("-o conf=%s", configPath) + mountOptions = append(mountOptions, configOption) + mountOptions = append(mountOptions, mntOptions...) // if user == anonymous, password is empty, and doesn't need to pass user/password as arguments @@ -43,5 +73,59 @@ func Unmount(mounter mounter.Mounter, volID string, configs map[string]string, t if err != nil { return status.Errorf(codes.Internal, "Failed to unmount %q: %v", targetPath, err) } + + dataRootPath := client_common.GetConfigDataRootPath(configs, volID) + err = deleteDavFSData(dataRootPath) + if err != nil { + klog.V(5).Infof("Error deleting davfs data at %s - ignoring", dataRootPath) + } + return nil +} + +func makeDavFSDataRootPath(dataRootPath string) error { + // create davfs dataroot + _, err := os.Stat(dataRootPath) + if err != nil { + if os.IsNotExist(err) { + // not exist, make one + err = os.MkdirAll(dataRootPath, os.FileMode(0755)) + if err != nil { + return xerrors.Errorf("failed to create a davfs data root path %s: %w", dataRootPath, err) + } + } else { + return xerrors.Errorf("failed to access a davfs data root path %s: %w", dataRootPath, err) + } + } + return nil +} + +func getDavFSCachePath(dataRootPath string) string { + return filepath.Join(dataRootPath, "cache") +} + +func makeDavFSCachePath(dataRootPath string) error { + // create davfs cache + cachePath := getDavFSCachePath(dataRootPath) + _, err := os.Stat(cachePath) + if err != nil { + if os.IsNotExist(err) { + // not exist, make one + err = os.MkdirAll(cachePath, os.FileMode(0755)) + if err != nil { + return xerrors.Errorf("failed to create a davfs cache path %s: %w", cachePath, err) + } + } else { + return xerrors.Errorf("failed to access a davfs cache path %s: %w", cachePath, err) + } + } + return nil +} + +func deleteDavFSData(dataRootPath string) error { + // delete davfs data + err := os.RemoveAll(dataRootPath) + if err != nil { + return xerrors.Errorf("failed to delete a davfs data root path %s: %w", dataRootPath, err) + } return nil } diff --git a/pkg/driver/controller.go b/pkg/driver/controller.go index eb1947a..24fb28b 100644 --- a/pkg/driver/controller.go +++ b/pkg/driver/controller.go @@ -26,7 +26,7 @@ import ( "context" "github.com/container-storage-interface/spec/lib/go/csi" - "github.com/cyverse/irods-csi-driver/pkg/client" + client_common "github.com/cyverse/irods-csi-driver/pkg/client/common" "github.com/cyverse/irods-csi-driver/pkg/client/irods" "github.com/cyverse/irods-csi-driver/pkg/metrics" "github.com/cyverse/irods-csi-driver/pkg/volumeinfo" @@ -80,8 +80,8 @@ func (driver *Driver) CreateVolume(ctx context.Context, req *csi.CreateVolumeReq /////////////////////////////////////////////////////////// // We only support irodsfs for dynamic volume provisioning /////////////////////////////////////////////////////////// - irodsClientType := client.GetClientType(configs) - if irodsClientType != client.IrodsFuseClientType { + irodsClientType := client_common.GetClientType(configs) + if irodsClientType != client_common.IrodsFuseClientType { metrics.IncreaseCounterForVolumeMountFailures() return nil, status.Errorf(codes.InvalidArgument, "unsupported driver type - %v", irodsClientType) } diff --git a/pkg/driver/node.go b/pkg/driver/node.go index 9f97d61..9a8bba4 100644 --- a/pkg/driver/node.go +++ b/pkg/driver/node.go @@ -32,6 +32,7 @@ import ( "github.com/container-storage-interface/spec/lib/go/csi" "github.com/cyverse/irods-csi-driver/pkg/client" + client_common "github.com/cyverse/irods-csi-driver/pkg/client/common" "github.com/cyverse/irods-csi-driver/pkg/metrics" "github.com/cyverse/irods-csi-driver/pkg/mounter" "github.com/cyverse/irods-csi-driver/pkg/volumeinfo" @@ -146,7 +147,7 @@ func (driver *Driver) NodeStageVolume(ctx context.Context, req *csi.NodeStageVol MountPath: "", StagingMountOptions: mountOptions, MountOptions: []string{}, - ClientType: string(client.GetClientType(configs)), + ClientType: string(client_common.GetClientType(configs)), ClientConfig: redactConfig(configs), DynamicVolumeProvisioning: true, StageVolume: true, @@ -286,7 +287,7 @@ func (driver *Driver) NodePublishVolume(ctx context.Context, req *csi.NodePublis MountPath: targetPath, StagingMountOptions: []string{}, MountOptions: mountOptions, - ClientType: string(client.GetClientType(configs)), + ClientType: string(client_common.GetClientType(configs)), ClientConfig: redactConfig(configs), DynamicVolumeProvisioning: false, StageVolume: false, @@ -294,7 +295,7 @@ func (driver *Driver) NodePublishVolume(ctx context.Context, req *csi.NodePublis } else { nodeVolume.MountPath = targetPath nodeVolume.MountOptions = mountOptions - nodeVolume.ClientType = string(client.GetClientType(configs)) + nodeVolume.ClientType = string(client_common.GetClientType(configs)) nodeVolume.ClientConfig = redactConfig(configs) } @@ -382,7 +383,7 @@ func (driver *Driver) NodeUnpublishVolume(ctx context.Context, req *csi.NodeUnpu } else { // unmountClient klog.V(5).Infof("NodeUnpublishVolume: unmounting %s", targetPath) - err = client.UnmountClient(driver.mounter, volID, client.GetValidClientType(nodeVolume.ClientType), nodeVolume.ClientConfig, targetPath) + err = client.UnmountClient(driver.mounter, volID, client_common.GetValidClientType(nodeVolume.ClientType), nodeVolume.ClientConfig, targetPath) if err != nil { return nil, err } @@ -460,7 +461,7 @@ func (driver *Driver) NodeUnstageVolume(ctx context.Context, req *csi.NodeUnstag metrics.DecreaseCounterForActiveVolumeMount() } else { klog.V(5).Infof("NodeUnstageVolume: unmounting %s", targetPath) - err = client.UnmountClient(driver.mounter, volID, client.GetValidClientType(nodeVolume.ClientType), nodeVolume.ClientConfig, targetPath) + err = client.UnmountClient(driver.mounter, volID, client_common.GetValidClientType(nodeVolume.ClientType), nodeVolume.ClientConfig, targetPath) if err != nil { return nil, err }