From eeb60ae68197a4692659de070f7b4450fccb7283 Mon Sep 17 00:00:00 2001 From: Ravi Kumar Date: Wed, 17 Apr 2024 10:41:52 +0000 Subject: [PATCH 01/10] v1.2.4 - Added the fix for string args and the selection of dsx ip. --- VERSION | 2 +- pkg/client/hsclient.go | 48 +++++++++++++++++++++++----------------- pkg/common/hs_types.go | 24 ++++++++++---------- pkg/driver/controller.go | 10 ++++----- pkg/driver/node.go | 14 +++++------- 5 files changed, 52 insertions(+), 46 deletions(-) diff --git a/VERSION b/VERSION index 212c3bf..c7cd5b2 100755 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -v1.2.5 +v1.2.4 diff --git a/pkg/client/hsclient.go b/pkg/client/hsclient.go index 747cf86..61f9a49 100755 --- a/pkg/client/hsclient.go +++ b/pkg/client/hsclient.go @@ -31,6 +31,7 @@ import ( "path" "strconv" "strings" + "sync" "time" log "github.com/sirupsen/logrus" @@ -48,6 +49,9 @@ const ( taskPollIntervalCap = 30 * time.Second //Seconds, The maximum duration between calls when polling task objects ) +var wg sync.WaitGroup +var mutex sync.Mutex + type HammerspaceClient struct { username string password string @@ -109,26 +113,30 @@ func (client *HammerspaceClient) GetPortalFloatingIp() (string, error) { log.Error("Error parsing JSON response: " + err.Error()) return "", err } - // Local random function - random_select := func() bool { - r := make(chan struct{}) - close(r) - select { - case <-r: - return false - case <-r: - return true - } - } floatingip := "" for _, p := range clusters.PortalFloatingIps { - floatingip = p.Address - // If there are more than 1 floating IPs configured, randomly select one - rr := random_select() - if rr == true { - break - } + wg.Add(1) + go func(floatingip string) { + defer wg.Done() + + // check if showmount gives a response + exports, err := common.GetNFSExports(floatingip) + if err != nil { + log.Infof("Could not get exports for data-portal at %s. Error: %v", floatingip, err) + return + } + + // Check if exports has any values + if len(exports) > 0 { + mutex.Lock() + floatingip = p.Address // Update the floating IP + mutex.Unlock() + log.Infof("Found floating IP data-portal %s.", floatingip) + } + }(p.Address) } + + wg.Wait() return floatingip, nil } @@ -528,7 +536,7 @@ func (client *HammerspaceClient) CreateShare(name string, Comment: comment, } if size > 0 { - share.Size = size + share.Size = strconv.FormatInt(size, 10) } shareString := new(bytes.Buffer) @@ -608,7 +616,7 @@ func (client *HammerspaceClient) CreateShareFromSnapshot(name string, Comment: comment, } if size > 0 { - share.Size = size + share.Size = strconv.FormatInt(size, 10) } shareString := new(bytes.Buffer) @@ -990,7 +998,7 @@ func (client *HammerspaceClient) GetClusterAvailableCapacity() (int64, error) { if err != nil { log.Error("Error parsing JSON response: " + err.Error()) } - free := cluster.Capacity["free"] + free, err := strconv.ParseInt(cluster.Capacity["free"], 10, 64) if err != nil { log.Error("Error parsing free cluster capacity: " + err.Error()) } diff --git a/pkg/common/hs_types.go b/pkg/common/hs_types.go index 1dd3aa9..b81daa6 100644 --- a/pkg/common/hs_types.go +++ b/pkg/common/hs_types.go @@ -51,7 +51,7 @@ type HSVolume struct { // We must create separate req and response objects since the API does not allow // specifying unused fields type ClusterResponse struct { - Capacity map[string]int64 `json:"capacity"` + Capacity map[string]string `json:"capacity"` } type ShareRequest struct { @@ -59,8 +59,8 @@ type ShareRequest struct { ExportPath string `json:"path"` Comment string `json:"comment"` ExtendedInfo map[string]string `json:"extendedInfo"` - Size int64 `json:"shareSizeLimit,omitifempty"` - ExportOptions []ShareExportOptions `json:"exportOptions,omitifempty"` + Size string `json:"shareSizeLimit,omitempty"` + ExportOptions []ShareExportOptions `json:"exportOptions,omitempty"` } type ShareUpdateRequest struct { @@ -75,7 +75,7 @@ type ShareResponse struct { Comment string `json:"comment"` ExtendedInfo map[string]string `json:"extendedInfo"` ShareState string `json:"shareState"` - Size int64 `json:"shareSizeLimit` + Size int64 `json:"shareSizeLimit"` ExportOptions []ShareExportOptions `json:"exportOptions"` Space ShareSpaceResponse `json:"space"` Inodes ShareInodesResponse `json:"inodes"` @@ -83,17 +83,17 @@ type ShareResponse struct { } type ShareSpaceResponse struct { - Used int64 `json:"used"` - Total int64 `json:"total"` - Available int64 `json:"available"` - Percent int64 `json:"percent"` + Used string `json:"used"` + Total string `json:"total"` + Available string `json:"available"` + Percent string `json:"percent"` } type ShareInodesResponse struct { - Used int64 `json:"used"` - Total int64 `json:"total"` - Available int64 `json:"available"` - Percent int64 `json:"percent"` + Used string `json:"used"` + Total string `json:"total"` + Available string `json:"available"` + Percent string `json:"percent"` } type ShareExportOptions struct { diff --git a/pkg/driver/controller.go b/pkg/driver/controller.go index 515e80d..93dc26d 100755 --- a/pkg/driver/controller.go +++ b/pkg/driver/controller.go @@ -299,7 +299,7 @@ func (d *CSIDriver) ensureDeviceFileExists( if hsVolume.Size <= 0 { return status.Error(codes.InvalidArgument, common.BlockVolumeSizeNotSpecified) } - available := backingShare.Space.Available + available, _ := strconv.ParseInt(backingShare.Space.Available, 10, 64) if hsVolume.Size > available { return status.Errorf(codes.OutOfRange, common.OutOfCapacity, hsVolume.Size, available) } @@ -495,7 +495,7 @@ func (d *CSIDriver) CreateVolume( return nil, status.Error(codes.Internal, err.Error()) } } else { - available = backingShare.Space.Available + available, _ = strconv.ParseInt(backingShare.Space.Available, 10, 64) } } else { available, err = d.hsclient.GetClusterAvailableCapacity() @@ -774,7 +774,7 @@ func (d *CSIDriver) ControllerExpandVolume( if err != nil { available = 0 } else { - available = backingShare.Space.Available + available, _ = strconv.ParseInt(backingShare.Space.Available, 10, 64) } if available-sizeDiff < 0 { @@ -804,7 +804,7 @@ func (d *CSIDriver) ControllerExpandVolume( if err != nil { currentSize = 0 } else { - currentSize = share.Space.Available + currentSize, _ = strconv.ParseInt(share.Space.Available, 10, 64) } if currentSize < requestedSize { @@ -985,7 +985,7 @@ func (d *CSIDriver) GetCapacity( if err != nil { available = 0 } else { - available = backingShare.Space.Available + available, _ = strconv.ParseInt(backingShare.Space.Available, 10, 64) } } else { diff --git a/pkg/driver/node.go b/pkg/driver/node.go index c2b260f..706f499 100644 --- a/pkg/driver/node.go +++ b/pkg/driver/node.go @@ -465,13 +465,13 @@ func (d *CSIDriver) NodeGetVolumeStats(ctx context.Context, return nil, status.Error(codes.NotFound, common.ShareNotFound) } - available := share.Space.Available - used := share.Space.Used - total := share.Space.Total + available, _ := strconv.ParseInt(share.Space.Available, 10, 64) + used, _ := strconv.ParseInt(share.Space.Used, 10, 64) + total, _ := strconv.ParseInt(share.Space.Total, 10, 64) - inodes_available := share.Inodes.Available - inodes_used := share.Inodes.Used - inodes_total := share.Inodes.Total + inodes_available, _ := strconv.ParseInt(share.Inodes.Available, 10, 64) + inodes_used, _ := strconv.ParseInt(share.Inodes.Used, 10, 64) + inodes_total, _ := strconv.ParseInt(share.Inodes.Total, 10, 64) return &csi.NodeGetVolumeStatsResponse{ Usage: []*csi.VolumeUsage{ @@ -490,8 +490,6 @@ func (d *CSIDriver) NodeGetVolumeStats(ctx context.Context, }, }, nil } - - return nil, status.Error(codes.NotFound, common.VolumeNotFound) } func (d *CSIDriver) NodeExpandVolume( From c4c7edbc362497c7014fdd74b9e3bb1049eff462 Mon Sep 17 00:00:00 2001 From: Ravi Kumar Date: Thu, 18 Apr 2024 11:28:19 +0000 Subject: [PATCH 02/10] v1.2.4 - added the fix for sharespaceResponse Precent check, it int value. --- pkg/common/hs_types.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/common/hs_types.go b/pkg/common/hs_types.go index b81daa6..a1ffec1 100644 --- a/pkg/common/hs_types.go +++ b/pkg/common/hs_types.go @@ -86,14 +86,14 @@ type ShareSpaceResponse struct { Used string `json:"used"` Total string `json:"total"` Available string `json:"available"` - Percent string `json:"percent"` + Percent int64 `json:"percent"` } type ShareInodesResponse struct { Used string `json:"used"` Total string `json:"total"` Available string `json:"available"` - Percent string `json:"percent"` + Percent int64 `json:"percent"` } type ShareExportOptions struct { From 1451ab50cad6117f611ad0ff6e1bc2e581629ef2 Mon Sep 17 00:00:00 2001 From: Ravi Kumar Date: Fri, 3 May 2024 19:16:47 +0000 Subject: [PATCH 03/10] V1.2.4 - 1. Moved test case to differnt package 2. Added go routine while selecting floating ip address. 3. Added timeout to check rpc over floating ip. 4. Rpc info is used before using showmount to get all exports. --- Dockerfile | 2 +- Makefile | 4 +- main.go | 245 +++++---- pkg/client/hsclient.go | 44 +- pkg/client/hsclient_test.go | 313 ----------- pkg/client/{ => test}/fakes_test.go | 0 pkg/client/test/hsclient_test.go | 304 +++++++++++ pkg/common/error_text.go | 84 +-- pkg/common/host_utils.go | 752 +++++++++++++++----------- pkg/common/host_utils_test.go | 103 ---- pkg/common/hs_types.go | 6 +- pkg/common/test/host_utils_test.go | 104 ++++ pkg/driver/controller.go | 38 +- pkg/driver/controller_test.go | 187 ------- pkg/driver/driver_csi_v0_test.go | 73 --- pkg/driver/test/controller_test.go | 188 +++++++ pkg/driver/test/driver_csi_v0_test.go | 75 +++ pkg/driver/test/utils_test.go | 76 +++ pkg/driver/utils_test.go | 75 --- 19 files changed, 1396 insertions(+), 1277 deletions(-) delete mode 100644 pkg/client/hsclient_test.go rename pkg/client/{ => test}/fakes_test.go (100%) create mode 100644 pkg/client/test/hsclient_test.go delete mode 100644 pkg/common/host_utils_test.go create mode 100644 pkg/common/test/host_utils_test.go delete mode 100644 pkg/driver/controller_test.go delete mode 100644 pkg/driver/driver_csi_v0_test.go create mode 100644 pkg/driver/test/controller_test.go create mode 100644 pkg/driver/test/driver_csi_v0_test.go create mode 100644 pkg/driver/test/utils_test.go delete mode 100644 pkg/driver/utils_test.go diff --git a/Dockerfile b/Dockerfile index 34f4af4..ce3cc76 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,7 +13,7 @@ FROM registry.access.redhat.com/ubi8/ubi:8.4 ADD ubi/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo ADD ubi/CentOS-AppStream.repo /etc/yum.repos.d/CentOS-AppStream.repo ADD ubi/RPM-GPG-KEY-centosofficial /etc/pki/rpm-gpg/RPM-GPG-KEY-centosofficial -RUN dnf --disableplugin=subscription-manager -y install python2-pip libcom_err-devel \ +RUN dnf --disableplugin=subscription-manager -y --nobest install python2-pip libcom_err-devel \ ca-certificates-2021.2.50-80.0.el8_4.noarch \ e2fsprogs-1.45.6-2.el8.x86_64 \ #-1.45.6-1.el8.x86_64 \ diff --git a/Makefile b/Makefile index c7d59ca..bda2305 100755 --- a/Makefile +++ b/Makefile @@ -26,11 +26,11 @@ build-dev: build: @echo "==> Building Docker Image Latest" - @docker build -t "hammerspaceinc/csi-plugin-ubi:latest" . -f Dockerfile --no-cache + @docker build -t "hammerspaceinc/csi-plugin:latest" . -f Dockerfile --no-cache build-release: @echo "==> Building Docker Image ${VERSION} ${GITHASH}" - @docker build --build-arg version=${VERSION} -t "hammerspaceinc/csi-plugin-ubi:${VERSION}" . -f Dockerfile + @docker build --build-arg version=${VERSION} -t "hammerspaceinc/csi-plugin:${VERSION}" . -f Dockerfile build-alpine: @echo "==> Building Alpine Docker Image Latest" diff --git a/main.go b/main.go index c47d610..314b09e 100644 --- a/main.go +++ b/main.go @@ -5,7 +5,7 @@ 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 + 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, @@ -16,136 +16,141 @@ limitations under the License. package main import ( - "github.com/hammer-space/csi-plugin/pkg/common" - "net" - "net/url" - "os" - "os/signal" - "strconv" - "strings" - "syscall" - - log "github.com/sirupsen/logrus" - "github.com/hammer-space/csi-plugin/pkg/driver" + "context" + "net" + "net/url" + "os" + "os/signal" + "strconv" + "strings" + "syscall" + + "github.com/hammer-space/csi-plugin/pkg/common" + + "github.com/hammer-space/csi-plugin/pkg/driver" + log "github.com/sirupsen/logrus" ) -var () - func init() { - // Setup logging - log.SetFormatter(&log.JSONFormatter{}) - log.SetOutput(os.Stdout) - log.SetLevel(log.DebugLevel) - log.SetReportCaller(true) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + // Setup logging + log.SetFormatter(&log.JSONFormatter{ + DisableTimestamp: false, + }) + log.SetOutput(os.Stdout) + log.SetLevel(log.DebugLevel) + log.SetReportCaller(false) + log.WithContext(ctx) } func validateEnvironmentVars() { - endpoint := os.Getenv("CSI_ENDPOINT") - if len(endpoint) == 0 { - log.Error("CSI_ENDPOINT must be defined and must be a path") - os.Exit(1) - } - if strings.Contains(endpoint, ":") { - log.Error("CSI_ENDPOINT must be a unix path") - os.Exit(1) - } - - hsEndpoint := os.Getenv("HS_ENDPOINT") - if len(hsEndpoint) == 0 { - log.Error("HS_ENDPOINT must be defined") - os.Exit(1) - } - - endpointUrl, err := url.Parse(hsEndpoint) - if err != nil || endpointUrl.Scheme != "https" || endpointUrl.Host == "" { - log.Error("HS_ENDPOINT must be a valid HTTPS URL") - os.Exit(1) - } - - username := os.Getenv("HS_USERNAME") - if len(username) == 0 { - log.Error("HS_USERNAME must be defined") - os.Exit(1) - } - password := os.Getenv("HS_PASSWORD") - if len(password) == 0 { - log.Error("HS_PASSWORD must be defined") - os.Exit(1) - } - if os.Getenv("HS_TLS_VERIFY") != "" { - _, err = strconv.ParseBool(os.Getenv("HS_TLS_VERIFY")) - if err != nil { - log.Error("HS_TLS_VERIFY must be a bool") - os.Exit(1) - } - } - if os.Getenv("CSI_MAJOR_VERSION") != "0" || os.Getenv("CSI_MAJOR_VERSION") != "1" { - if err != nil { - log.Error("CSI_MAJOR_VERSION must be set to \"0\" or \"1\"") - os.Exit(1) - } - } - common.DataPortalMountPrefix = os.Getenv("HS_DATA_PORTAL_MOUNT_PREFIX") + endpoint := os.Getenv("CSI_ENDPOINT") + if len(endpoint) == 0 { + log.Error("CSI_ENDPOINT must be defined and must be a path") + os.Exit(1) + } + if strings.Contains(endpoint, ":") { + log.Error("CSI_ENDPOINT must be a unix path") + os.Exit(1) + } + + hsEndpoint := os.Getenv("HS_ENDPOINT") + if len(hsEndpoint) == 0 { + log.Error("HS_ENDPOINT must be defined") + os.Exit(1) + } + + endpointUrl, err := url.Parse(hsEndpoint) + if err != nil || endpointUrl.Scheme != "https" || endpointUrl.Host == "" { + log.Error("HS_ENDPOINT must be a valid HTTPS URL") + os.Exit(1) + } + + username := os.Getenv("HS_USERNAME") + if len(username) == 0 { + log.Error("HS_USERNAME must be defined") + os.Exit(1) + } + password := os.Getenv("HS_PASSWORD") + if len(password) == 0 { + log.Error("HS_PASSWORD must be defined") + os.Exit(1) + } + if os.Getenv("HS_TLS_VERIFY") != "" { + _, err = strconv.ParseBool(os.Getenv("HS_TLS_VERIFY")) + if err != nil { + log.Error("HS_TLS_VERIFY must be a bool") + os.Exit(1) + } + } + if os.Getenv("CSI_MAJOR_VERSION") != "0" || os.Getenv("CSI_MAJOR_VERSION") != "1" { + if err != nil { + log.Error("CSI_MAJOR_VERSION must be set to \"0\" or \"1\"") + os.Exit(1) + } + } + common.DataPortalMountPrefix = os.Getenv("HS_DATA_PORTAL_MOUNT_PREFIX") } type Server interface { - Start(net.Listener) error - Stop() + Start(net.Listener) error + Stop() } func main() { - validateEnvironmentVars() - - var server Server - - CSI_version := os.Getenv("CSI_MAJOR_VERSION") - - endpoint := os.Getenv("CSI_ENDPOINT") - csiDriver := driver.NewCSIDriver( - os.Getenv("HS_ENDPOINT"), - os.Getenv("HS_USERNAME"), - os.Getenv("HS_PASSWORD"), - os.Getenv("HS_TLS_VERIFY"), - ) - - if CSI_version == "0" { - server = driver.NewCSIDriver_v0Support(csiDriver) - common.CsiVersion = "0" - } else { - server = csiDriver - } - - // Listen - os.Remove(endpoint) - l, err := net.Listen("unix", endpoint) - if err != nil { - log.Errorf("Error: Unable to listen on %s socket: %v\n", - endpoint, - err) - os.Exit(1) - } - defer os.Remove(endpoint) - - // Start server - if err := server.Start(l); err != nil { - log.Errorf("Error: Unable to start CSI server: %v\n", - err) - os.Exit(1) - } - log.Info("hammerspace driver started") - - // Wait for signal - sigc := make(chan os.Signal, 1) - sigs := []os.Signal{ - syscall.SIGTERM, - syscall.SIGHUP, - syscall.SIGINT, - syscall.SIGQUIT, - } - signal.Notify(sigc, sigs...) - - <-sigc - server.Stop() - log.Info("hammerspace driver stopped") + validateEnvironmentVars() + + var server Server + + CSI_version := os.Getenv("CSI_MAJOR_VERSION") + + endpoint := os.Getenv("CSI_ENDPOINT") + csiDriver := driver.NewCSIDriver( + os.Getenv("HS_ENDPOINT"), + os.Getenv("HS_USERNAME"), + os.Getenv("HS_PASSWORD"), + os.Getenv("HS_TLS_VERIFY"), + ) + + if CSI_version == "0" { + server = driver.NewCSIDriver_v0Support(csiDriver) + common.CsiVersion = "0" + } else { + server = csiDriver + } + + // Listen + os.Remove(endpoint) + l, err := net.Listen("unix", endpoint) + if err != nil { + log.Errorf("Error: Unable to listen on %s socket: %v\n", + endpoint, + err) + os.Exit(1) + } + defer os.Remove(endpoint) + + // Start server + if err := server.Start(l); err != nil { + log.Errorf("Error: Unable to start CSI server: %v\n", + err) + os.Exit(1) + } + log.Info("hammerspace driver started") + + // Wait for signal + sigc := make(chan os.Signal, 1) + sigs := []os.Signal{ + syscall.SIGTERM, + syscall.SIGHUP, + syscall.SIGINT, + syscall.SIGQUIT, + } + signal.Notify(sigc, sigs...) + + <-sigc + server.Stop() + log.Info("hammerspace driver stopped") } diff --git a/pkg/client/hsclient.go b/pkg/client/hsclient.go index 61f9a49..366aa7b 100755 --- a/pkg/client/hsclient.go +++ b/pkg/client/hsclient.go @@ -119,19 +119,19 @@ func (client *HammerspaceClient) GetPortalFloatingIp() (string, error) { go func(floatingip string) { defer wg.Done() - // check if showmount gives a response - exports, err := common.GetNFSExports(floatingip) + // check if rpcinfo gives a response + ok, err := common.CheckNFSExports(floatingip) if err != nil { - log.Infof("Could not get exports for data-portal at %s. Error: %v", floatingip, err) + log.Warnf("Could not get exports for data-portal at %s. Error: %v", floatingip, err) return } // Check if exports has any values - if len(exports) > 0 { + if ok { mutex.Lock() floatingip = p.Address // Update the floating IP mutex.Unlock() - log.Infof("Found floating IP data-portal %s.", floatingip) + log.Infof("Found floating IP data-portal %s", floatingip) } }(p.Address) } @@ -303,11 +303,15 @@ func (client *HammerspaceClient) WaitForTaskCompletion(taskLocation string) (boo } } } - return false, errors.New(fmt.Sprintf("Task %s, of type %s, failed to complete within time limit. Current status is %s", task.Uuid, task.Action, task.Status)) + return false, fmt.Errorf("task %s, of type %s, failed to complete within time limit. Current status is %s", task.Uuid, task.Action, task.Status) } func (client *HammerspaceClient) ListShares() ([]common.ShareResponse, error) { req, err := client.generateRequest("GET", "/shares", "") + if err != nil { + log.Error(err) + return nil, err + } statusCode, respBody, _, err := client.doRequest(*req) if err != nil { @@ -315,7 +319,7 @@ func (client *HammerspaceClient) ListShares() ([]common.ShareResponse, error) { return nil, err } if statusCode != 200 { - return nil, errors.New(fmt.Sprintf(common.UnexpectedHSStatusCode, statusCode, 200)) + return nil, fmt.Errorf(common.UnexpectedHSStatusCode, statusCode, 200) } var shares []common.ShareResponse @@ -337,7 +341,7 @@ func (client *HammerspaceClient) ListObjectives() ([]common.ClusterObjectiveResp return nil, err } if statusCode != 200 { - return nil, errors.New(fmt.Sprintf(common.UnexpectedHSStatusCode, statusCode, 200)) + return nil, fmt.Errorf(common.UnexpectedHSStatusCode, statusCode, 200) } var objs []common.ClusterObjectiveResponse @@ -493,7 +497,7 @@ func (client *HammerspaceClient) GetFile(path string) (*common.File, error) { return nil, nil } if statusCode != 200 { - return nil, errors.New(fmt.Sprintf(common.UnexpectedHSStatusCode, statusCode, 200)) + return nil, fmt.Errorf(common.UnexpectedHSStatusCode, statusCode, 200) } var file common.File err = json.Unmarshal([]byte(respBody), &file) @@ -510,7 +514,7 @@ func (client *HammerspaceClient) DoesFileExist(path string) (bool, error) { func (client *HammerspaceClient) CreateShare(name string, exportPath string, - size int64, //size in bytes + size string, //size in bytes objectives []string, exportOptions []common.ShareExportOptions, deleteDelay int64, @@ -535,8 +539,9 @@ func (client *HammerspaceClient) CreateShare(name string, ExtendedInfo: extendedInfo, Comment: comment, } - if size > 0 { - share.Size = strconv.FormatInt(size, 10) + i, _ := strconv.Atoi(size) + if i > 0 { + share.Size = size } shareString := new(bytes.Buffer) @@ -589,7 +594,7 @@ func (client *HammerspaceClient) CreateShare(name string, func (client *HammerspaceClient) CreateShareFromSnapshot(name string, exportPath string, - size int64, //size in bytes + size string, //size in bytes objectives []string, exportOptions []common.ShareExportOptions, deleteDelay int64, @@ -615,8 +620,9 @@ func (client *HammerspaceClient) CreateShareFromSnapshot(name string, ExtendedInfo: extendedInfo, Comment: comment, } - if size > 0 { - share.Size = strconv.FormatInt(size, 10) + i, _ := strconv.ParseInt(size, 10, 64) + if i > 0 { + share.Size = size } shareString := new(bytes.Buffer) @@ -678,7 +684,7 @@ func (client *HammerspaceClient) CheckIfShareCreateTaskIsRunning(shareName strin return false, err } if statusCode != 200 { - return false, errors.New(fmt.Sprintf(common.UnexpectedHSStatusCode, statusCode, 200)) + return false, fmt.Errorf(common.UnexpectedHSStatusCode, statusCode, 200) } var tasks []common.Task err = json.Unmarshal([]byte(respBody), &tasks) @@ -727,16 +733,14 @@ func (client *HammerspaceClient) SetObjectives(shareName string, //FIXME: err is not set here log.Errorf("Failed to set objective %s on share %s at path %s, %v", objectiveName, shareName, path, err) - return errors.New(fmt.Sprint("failed to set objective")) + return errors.New("failed to set objective") } } return nil } -func (client *HammerspaceClient) UpdateShareSize(name string, - size int64, //size in bytes -) error { +func (client *HammerspaceClient) UpdateShareSize(name string, size string) error { log.Debugf("Update share size : %s to %v", name, size) diff --git a/pkg/client/hsclient_test.go b/pkg/client/hsclient_test.go deleted file mode 100644 index 990f407..0000000 --- a/pkg/client/hsclient_test.go +++ /dev/null @@ -1,313 +0,0 @@ -/* -Copyright 2019 Hammerspace - -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 client - -import ( - "fmt" - "io/ioutil" - "net/http" - "net/http/httptest" - "reflect" - "testing" - - //log "github.com/sirupsen/logrus" - - common "github.com/hammer-space/csi-plugin/pkg/common" - testutils "github.com/hammer-space/csi-plugin/test/utils" -) - -var ( - Mux *http.ServeMux - Server *httptest.Server - hsclient *HammerspaceClient -) - -func setupHTTP() { - Mux = http.NewServeMux() - Server = httptest.NewServer(Mux) - - httpclient := http.DefaultClient - hsclient = &HammerspaceClient{ - username: "test_user", - password: "test_password", - endpoint: Server.URL, - httpclient: httpclient, - } -} - -func tearDownHTTP() { - Server.Close() -} - -func TestListShares(t *testing.T) { - //log.SetLevel(log.DebugLevel) - setupHTTP() - defer tearDownHTTP() - - fakeResponse := "[]" - fakeResponseCode := 200 - - Mux.HandleFunc(BasePath+"/shares", func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintf(w, fakeResponse) - w.WriteHeader(fakeResponseCode) - }) - shares, err := hsclient.ListShares() - if err != nil { - t.Error(err) - } else if len(shares) != 0 { - t.Logf("List shares not empty") - t.FailNow() - } - - fakeResponse = fmt.Sprintf("[%s,%s]", FakeShareRoot, FakeShare1) - - shares, err = hsclient.ListShares() - if err != nil { - t.Error(err) - } else if len(shares) != 2 { - t.Logf("Incorrect number of shares") - t.FailNow() - } - - expectedShares := []common.ShareResponse{ - common.ShareResponse{ - Name: "root", - ExportPath: "/", - ExtendedInfo: map[string]string{}, - ShareState: "PUBLISHED", - ExportOptions: []common.ShareExportOptions{ - common.ShareExportOptions{ - Subnet: "*", - AccessPermissions: "RW", - RootSquash: false, - }, - }, - Space: common.ShareSpaceResponse{ - Total: "64393052160", - Used: "0", - Available: "63909851136", - }, - }, - common.ShareResponse{ - Name: "test-client-code", - ExportPath: "/test-client-code", - ExtendedInfo: map[string]string{ - "csi_created_by_plugin_version": "test_version", - "csi_created_by_plugin_name": "test_plugin", - "csi_delayed_delete": "0", - "csi_created_by_plugin_git_hash": "", - "csi_created_by_csi_version": "1", - }, - Size: 1073741824, - ShareState: "PUBLISHED", - ExportOptions: []common.ShareExportOptions{ - common.ShareExportOptions{ - Subnet: "*", - AccessPermissions: "RW", - RootSquash: false, - }, - }, - Space: common.ShareSpaceResponse{ - Total: "1073741824", - Used: "0", - Available: "1073741824", - }, - }, - } - - if !reflect.DeepEqual(shares, expectedShares) { - t.Logf("Shares not equal") - t.Logf("Expected: %v", expectedShares) - t.Logf("Actual: %v", shares) - t.FailNow() - } - - fakeResponseCode = 500 - _, err = hsclient.ListShares() - if err != nil { - t.Logf("Expected error") - t.Fail() - } -} - -func TestCreateShare(t *testing.T) { - setupHTTP() - defer tearDownHTTP() - - fakeResponseCode := 202 - expectedCreateShareBody := "" - - // Fake create share - Mux.HandleFunc(BasePath+"/shares", func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Location", "http://fake_location/tasks/99184048-9390-4e68-92b8-d3ce6413372d") - w.WriteHeader(fakeResponseCode) - bodyString, _ := ioutil.ReadAll(r.Body) - equal, err := testutils.AreEqualJSON(string(bodyString), expectedCreateShareBody) - if err != nil { - t.Error(err) - } - if !equal { - t.Fail() - } - }) - - fakeTaskResponse := fmt.Sprintf("%s", FakeTaskCompleted) - fakeTaskResponseCode := 200 - Mux.HandleFunc(BasePath+"/tasks/", func(w http.ResponseWriter, r *http.Request) { - fmt.Fprintf(w, fakeTaskResponse) - w.WriteHeader(fakeTaskResponseCode) - }) - - // test basic - expectedCreateShareBody = fmt.Sprintf(` - {"name":"test", - "path":"/test", - "extendedInfo":{ - "csi_created_by_plugin_version": "%s", - "csi_created_by_plugin_name": "%s", - "csi_delete_delay": "0", - "csi_created_by_plugin_git_hash": "%s", - "csi_created_by_csi_version": "%s" - }, - "shareSizeLimit":0, - "exportOptions":[]} - `, common.Version, common.CsiPluginName, common.Githash, common.CsiVersion) - err := hsclient.CreateShare("test", - "/test", -1, - []string{}, []common.ShareExportOptions{}, 0, "") - if err != nil { - t.Error(err) - } - - // test multiple objectives - - t.Log("Test Multiple Objectives") - Mux.HandleFunc(BasePath+"/shares/test/objective-set", func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(200) - if r.Method != "POST" { - t.Logf("Fail: this should be a POST method") - t.Fail() - } - if !((r.URL.Query()["objective-identifier"][0] == "test-obj") || (r.URL.Query()["objective-identifier"][0] == "test-obj2")) { - t.Logf("Fail: Incorrect Objective %s", r.URL.Query()["objective-identifier"][0]) - t.Fail() - } - }) - - err = hsclient.CreateShare("test", - "/test", - -1, []string{"test-obj", "test-obj2"}, - []common.ShareExportOptions{}, - 0, "") - if err != nil { - t.Error(err) - } - - // test share size - t.Log("Test Share Size") - expectedCreateShareBody = fmt.Sprintf(` - {"name":"test", - "path":"/test", - "extendedInfo":{ - "csi_created_by_plugin_version": "%s", - "csi_created_by_plugin_name": "%s", - "csi_created_by_plugin_git_hash": "%s", - "csi_created_by_csi_version": "%s" - }, - "shareSizeLimit":100, - "exportOptions":[]} - `, common.Version, common.CsiPluginName, common.Githash, common.CsiVersion) - err = hsclient.CreateShare("test", - "/test", - 100, - []string{}, - []common.ShareExportOptions{}, - -1, "") - if err != nil { - t.Error(err) - } - - // test multiple export options - t.Log("Test Multiple export options") - expectedCreateShareBody = fmt.Sprintf(` - {"name":"test", - "path":"/test", - "extendedInfo":{ - "csi_created_by_plugin_version": "%s", - "csi_created_by_plugin_name": "%s", - "csi_delete_delay": "0", - "csi_created_by_plugin_git_hash": "%s", - "csi_created_by_csi_version": "%s" - }, - "shareSizeLimit":100, - "exportOptions":[ - { - "subnet": "172.168.0.0/24", - "accessPermissions": "RW", - "rootSquash": false - }, - { - "subnet": "*", - "accessPermissions": "RO", - "rootSquash": true - } - ]} - `, common.Version, common.CsiPluginName, common.Githash, common.CsiVersion) - exportOptions := []common.ShareExportOptions{ - common.ShareExportOptions{ - Subnet: "172.168.0.0/24", - AccessPermissions: "RW", - RootSquash: false, - }, - common.ShareExportOptions{ - Subnet: "*", - AccessPermissions: "RO", - RootSquash: true, - }, - } - err = hsclient.CreateShare("test", - "/test", - 100, - []string{}, - exportOptions, - 0, "") - if err != nil { - t.Error(err) - } - - // test share creation fails on backend - t.Log("Test Share Creation Fails") - fakeTaskResponse = fmt.Sprintf("%s", FakeTaskFailed) - expectedCreateShareBody = fmt.Sprintf(` - {"name":"test", - "path":"/test", - "extendedInfo":{ - "csi_created_by_plugin_version": "%s", - "csi_created_by_plugin_name": "%s", - "csi_delete_delay": "0", - "csi_created_by_plugin_git_hash": "%s", - "csi_created_by_csi_version": "%s" - }, - "shareSizeLimit":0, - "exportOptions":[]} - `, common.Version, common.CsiPluginName, common.Githash, common.CsiVersion) - err = hsclient.CreateShare("test", "/test", -1, []string{}, []common.ShareExportOptions{}, 0, "") - if err == nil { - t.Logf("Expected error") - t.Fail() - } -} diff --git a/pkg/client/fakes_test.go b/pkg/client/test/fakes_test.go similarity index 100% rename from pkg/client/fakes_test.go rename to pkg/client/test/fakes_test.go diff --git a/pkg/client/test/hsclient_test.go b/pkg/client/test/hsclient_test.go new file mode 100644 index 0000000..5fbba70 --- /dev/null +++ b/pkg/client/test/hsclient_test.go @@ -0,0 +1,304 @@ +/* +Copyright 2019 Hammerspace + +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 client + +import ( + "fmt" + "io/ioutil" + "net/http" + "net/http/httptest" + "reflect" + "testing" + + "github.com/hammer-space/csi-plugin/pkg/client" + common "github.com/hammer-space/csi-plugin/pkg/common" + testutils "github.com/hammer-space/csi-plugin/test/utils" +) + +var ( + Mux *http.ServeMux + Server *httptest.Server + hsclient *client.HammerspaceClient +) + +func setupHTTP() { + Mux = http.NewServeMux() + Server = httptest.NewServer(Mux) +} + +func tearDownHTTP() { + Server.Close() +} + +func TestListShares(t *testing.T) { + //log.SetLevel(log.DebugLevel) + setupHTTP() + defer tearDownHTTP() + + fakeResponse := "[]" + fakeResponseCode := 200 + + Mux.HandleFunc(client.BasePath+"/shares", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, fakeResponse) + w.WriteHeader(fakeResponseCode) + }) + shares, err := hsclient.ListShares() + if err != nil { + t.Error(err) + } else if len(shares) != 0 { + t.Logf("List shares not empty") + t.FailNow() + } + + fakeResponse = fmt.Sprintf("[%s,%s]", FakeShareRoot, FakeShare1) + + shares, err = hsclient.ListShares() + if err != nil { + t.Error(err) + } else if len(shares) != 2 { + t.Logf("Incorrect number of shares") + t.FailNow() + } + + expectedShares := []common.ShareResponse{ + { + Name: "root", + ExportPath: "/", + ExtendedInfo: map[string]string{}, + ShareState: "PUBLISHED", + ExportOptions: []common.ShareExportOptions{ + { + Subnet: "*", + AccessPermissions: "RW", + RootSquash: false, + }, + }, + Space: common.ShareSpaceResponse{ + Total: "64393052160", + Used: "0", + Available: "63909851136", + }, + }, + { + Name: "test-client-code", + ExportPath: "/test-client-code", + ExtendedInfo: map[string]string{ + "csi_created_by_plugin_version": "test_version", + "csi_created_by_plugin_name": "test_plugin", + "csi_delayed_delete": "0", + "csi_created_by_plugin_git_hash": "", + "csi_created_by_csi_version": "1", + }, + Size: "1073741824", + ShareState: "PUBLISHED", + ExportOptions: []common.ShareExportOptions{ + { + Subnet: "*", + AccessPermissions: "RW", + RootSquash: false, + }, + }, + Space: common.ShareSpaceResponse{ + Total: "1073741824", + Used: "0", + Available: "1073741824", + }, + }, + } + + if !reflect.DeepEqual(shares, expectedShares) { + t.Logf("Shares not equal") + t.Logf("Expected: %v", expectedShares) + t.Logf("Actual: %v", shares) + t.FailNow() + } + + fakeResponseCode = 500 + _, err = hsclient.ListShares() + if err != nil { + t.Logf("Expected error") + t.Fail() + } +} + +func TestCreateShare(t *testing.T) { + setupHTTP() + defer tearDownHTTP() + + fakeResponseCode := 202 + expectedCreateShareBody := "" + + // Fake create share + Mux.HandleFunc(client.BasePath+"/shares", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Location", "http://fake_location/tasks/99184048-9390-4e68-92b8-d3ce6413372d") + w.WriteHeader(fakeResponseCode) + bodyString, _ := ioutil.ReadAll(r.Body) + equal, err := testutils.AreEqualJSON(string(bodyString), expectedCreateShareBody) + if err != nil { + t.Error(err) + } + if !equal { + t.Fail() + } + }) + + fakeTaskResponse := fmt.Sprintf("%s", FakeTaskCompleted) + fakeTaskResponseCode := 200 + Mux.HandleFunc(client.BasePath+"/tasks/", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, fakeTaskResponse) + w.WriteHeader(fakeTaskResponseCode) + }) + + // test basic + expectedCreateShareBody = fmt.Sprintf(` + {"name":"test", + "path":"/test", + "extendedInfo":{ + "csi_created_by_plugin_version": "%s", + "csi_created_by_plugin_name": "%s", + "csi_delete_delay": "0", + "csi_created_by_plugin_git_hash": "%s", + "csi_created_by_csi_version": "%s" + }, + "shareSizeLimit":0, + "exportOptions":[]} + `, common.Version, common.CsiPluginName, common.Githash, common.CsiVersion) + err := hsclient.CreateShare("test", + "/test", "-1", + []string{}, []common.ShareExportOptions{}, 0, "") + if err != nil { + t.Error(err) + } + + // test multiple objectives + + t.Log("Test Multiple Objectives") + Mux.HandleFunc(client.BasePath+"/shares/test/objective-set", func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(200) + if r.Method != "POST" { + t.Logf("Fail: this should be a POST method") + t.Fail() + } + if !((r.URL.Query()["objective-identifier"][0] == "test-obj") || (r.URL.Query()["objective-identifier"][0] == "test-obj2")) { + t.Logf("Fail: Incorrect Objective %s", r.URL.Query()["objective-identifier"][0]) + t.Fail() + } + }) + + err = hsclient.CreateShare("test", + "/test", + "-1", []string{"test-obj", "test-obj2"}, + []common.ShareExportOptions{}, + 0, "") + if err != nil { + t.Error(err) + } + + // test share size + t.Log("Test Share Size") + expectedCreateShareBody = fmt.Sprintf(` + {"name":"test", + "path":"/test", + "extendedInfo":{ + "csi_created_by_plugin_version": "%s", + "csi_created_by_plugin_name": "%s", + "csi_created_by_plugin_git_hash": "%s", + "csi_created_by_csi_version": "%s" + }, + "shareSizeLimit":"100", + "exportOptions":[]} + `, common.Version, common.CsiPluginName, common.Githash, common.CsiVersion) + err = hsclient.CreateShare("test", + "/test", + "100", + []string{}, + []common.ShareExportOptions{}, + -1, "") + if err != nil { + t.Error(err) + } + + // test multiple export options + t.Log("Test Multiple export options") + expectedCreateShareBody = fmt.Sprintf(` + {"name":"test", + "path":"/test", + "extendedInfo":{ + "csi_created_by_plugin_version": "%s", + "csi_created_by_plugin_name": "%s", + "csi_delete_delay": "0", + "csi_created_by_plugin_git_hash": "%s", + "csi_created_by_csi_version": "%s" + }, + "shareSizeLimit":100, + "exportOptions":[ + { + "subnet": "172.168.0.0/24", + "accessPermissions": "RW", + "rootSquash": false + }, + { + "subnet": "*", + "accessPermissions": "RO", + "rootSquash": true + } + ]} + `, common.Version, common.CsiPluginName, common.Githash, common.CsiVersion) + exportOptions := []common.ShareExportOptions{ + { + Subnet: "172.168.0.0/24", + AccessPermissions: "RW", + RootSquash: false, + }, + { + Subnet: "*", + AccessPermissions: "RO", + RootSquash: true, + }, + } + err = hsclient.CreateShare("test", + "/test", + "100", + []string{}, + exportOptions, + 0, "") + if err != nil { + t.Error(err) + } + + // test share creation fails on backend + t.Log("Test Share Creation Fails") + fakeTaskResponse = FakeTaskFailed + expectedCreateShareBody = fmt.Sprintf(` + {"name":"test", + "path":"/test", + "extendedInfo":{ + "csi_created_by_plugin_version": "%s", + "csi_created_by_plugin_name": "%s", + "csi_delete_delay": "0", + "csi_created_by_plugin_git_hash": "%s", + "csi_created_by_csi_version": "%s" + }, + "shareSizeLimit":0, + "exportOptions":[]} + `, common.Version, common.CsiPluginName, common.Githash, common.CsiVersion) + err = hsclient.CreateShare("test", "/test", "-1", []string{}, []common.ShareExportOptions{}, 0, "") + if err == nil { + t.Logf("Expected error") + t.Fail() + } +} diff --git a/pkg/common/error_text.go b/pkg/common/error_text.go index b54c941..845e1f8 100644 --- a/pkg/common/error_text.go +++ b/pkg/common/error_text.go @@ -19,52 +19,52 @@ package common // These are error messages that may be returned as responses via the gRPC API // Convention is to be lowercase with no ending punctuation const ( - // Validation errors - EmptyVolumeId = "Volume ID cannot be empty" - VolumeIdTooLong = "Volume ID cannot be longer than %d characters" - SnapshotIdTooLong = "Shapshot ID cannot be longer than %d characters" - ImproperlyFormattedSnapshotId = "Shapshot ID should be of the format |, received %s" - EmptyTargetPath = "Target path cannot be empty" - EmptyStagingTargetPath = "Staging target path cannot be empty" - EmptyVolumePath = "Volume Path cannot be empty" - NoCapabilitiesSupplied = "No capabilities supplied for volume %s" // volume id - ConflictingCapabilities = "Cannot request a volume to be both raw and a filesystem" - InvalidDeleteDelay = "deleteDelay parameter must be an Integer. Value received '%s'" - InvalidComment = "Failed to set comment, invalid value" - InvalidShareNameSize = "Share name cannot be longer than 80 characters" - InvalidCommentSize = "Share comment cannot be longer than 255 characters" - EmptySnapshotId = "Snapshot ID cannot be empty" - MissingSnapshotSourceVolumeId = "Snapshot SourceVolumeId cannot be empty" - MissingBlockBackingShareName = "blockBackingShareName must be provided when creating BlockVolumes" - MissingMountBackingShareName = "mountBackingShareName must be provided when creating Filesystem volumes other than 'nfs'" - BlockVolumeSizeNotSpecified = "Capacity must be specified for block volumes" - ShareNotMounted = "Share is not in mounted state." + // Validation errors + EmptyVolumeId = "Volume ID cannot be empty" + VolumeIdTooLong = "Volume ID cannot be longer than %d characters" + SnapshotIdTooLong = "Shapshot ID cannot be longer than %d characters" + ImproperlyFormattedSnapshotId = "Shapshot ID should be of the format |, received %s" + EmptyTargetPath = "Target path cannot be empty" + EmptyStagingTargetPath = "Staging target path cannot be empty" + EmptyVolumePath = "Volume Path cannot be empty" + NoCapabilitiesSupplied = "No capabilities supplied for volume %s" // volume id + ConflictingCapabilities = "Cannot request a volume to be both raw and a filesystem" + InvalidDeleteDelay = "deleteDelay parameter must be an Integer. Value received '%s'" + InvalidComment = "Failed to set comment, invalid value" + InvalidShareNameSize = "Share name cannot be longer than 80 characters" + InvalidCommentSize = "Share comment cannot be longer than 255 characters" + EmptySnapshotId = "Snapshot ID cannot be empty" + MissingSnapshotSourceVolumeId = "Snapshot SourceVolumeId cannot be empty" + MissingBlockBackingShareName = "blockBackingShareName must be provided when creating BlockVolumes" + MissingMountBackingShareName = "mountBackingShareName must be provided when creating Filesystem volumes other than 'nfs'" + BlockVolumeSizeNotSpecified = "Capacity must be specified for block volumes" + ShareNotMounted = "Share is not in mounted state." - InvalidExportOptions = "Export options must consist of 3 values: subnet,access,rootSquash, received '%s'" - InvalidRootSquash = "rootSquash must be a bool. Value received '%s'" - InvalidAdditionalMetadataTags = "Extended Info must be of format key=value, received '%s'" - InvalidObjectiveNameDoesNotExist = "Cannot find objective with the name %s" + InvalidExportOptions = "Export options must consist of 3 values: subnet,access,rootSquash, received '%s'" + InvalidRootSquash = "rootSquash must be a bool. Value received '%s'" + InvalidAdditionalMetadataTags = "Extended Info must be of format key=value, received '%s'" + InvalidObjectiveNameDoesNotExist = "Cannot find objective with the name %s" - VolumeExistsSizeMismatch = "Requested volume exists, but has a different size. Existing: %d, Requested: %d" + VolumeExistsSizeMismatch = "Requested volume exists, but has a different size. Existing: %s, Requested: %s" - VolumeDeleteHasSnapshots = "Volumes with snapshots cannot be deleted, delete snapshots first" - VolumeBeingDeleted = "The specified volume is currently being deleted" + VolumeDeleteHasSnapshots = "Volumes with snapshots cannot be deleted, delete snapshots first" + VolumeBeingDeleted = "The specified volume is currently being deleted" - // Not Found errors - VolumeNotFound = "Volume does not exist" - FileNotFound = "File does not exist" - ShareNotFound = "Share does not exist" - BackingShareNotFound = "Could not find specified backing share" - SourceSnapshotNotFound = "Could not find source snapshots" - SourceSnapshotShareNotFound = "Could not find the share for the source snapshot" + // Not Found errors + VolumeNotFound = "Volume does not exist" + FileNotFound = "File does not exist" + ShareNotFound = "Share does not exist" + BackingShareNotFound = "Could not find specified backing share" + SourceSnapshotNotFound = "Could not find source snapshots" + SourceSnapshotShareNotFound = "Could not find the share for the source snapshot" - // Internal errors - UnexpectedHSStatusCode = "Unexpected HTTP response from Hammerspace API: recieved status code %d, expected %d" - OutOfCapacity = "Requested capacity %d exceeds available %d" - LoopDeviceAttachFailed = "Failed setting up loop device: device=%s, filePath=%s" - TargetPathUnknownFiletype = "Target path exists but is not a block device nor directory" - UnknownError = "Unknown internal error" + // Internal errors + UnexpectedHSStatusCode = "Unexpected HTTP response from Hammerspace API: recieved status code %d, expected %d" + OutOfCapacity = "Requested capacity %d exceeds available %d" + LoopDeviceAttachFailed = "Failed setting up loop device: device=%s, filePath=%s" + TargetPathUnknownFiletype = "Target path exists but is not a block device nor directory" + UnknownError = "Unknown internal error" - // CSI v0 - BlockVolumesUnsupported = "Block volumes are unsupported in CSI v0.3" + // CSI v0 + BlockVolumesUnsupported = "Block volumes are unsupported in CSI v0.3" ) diff --git a/pkg/common/host_utils.go b/pkg/common/host_utils.go index 8f35798..8033329 100644 --- a/pkg/common/host_utils.go +++ b/pkg/common/host_utils.go @@ -17,53 +17,56 @@ limitations under the License. package common import ( - "bytes" - "fmt" - "os" - "os/exec" - "path/filepath" - "strconv" - "strings" - "time" - - log "github.com/sirupsen/logrus" - unix "golang.org/x/sys/unix" - - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" - "k8s.io/kubernetes/pkg/util/mount" + "bytes" + "context" + "errors" + "fmt" + "net" + "os" + "os/exec" + "path/filepath" + "strconv" + "strings" + "time" + + log "github.com/sirupsen/logrus" + unix "golang.org/x/sys/unix" + + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "k8s.io/kubernetes/pkg/util/mount" ) func execCommandHelper(command string, args ...string) ([]byte, error) { - cmd := exec.Command(command, args...) - log.Debugf("Executing command: %v", cmd) - var b bytes.Buffer - cmd.Stdout = &b - cmd.Stderr = &b - if err := cmd.Start(); err != nil { - log.Error(err) - return nil, err - } - // Wait for the process to finish or kill it after a timeout (whichever happens first): - done := make(chan error, 1) - go func() { - done <- cmd.Wait() - }() - select { - case <-time.After(CommandExecTimeout): - log.Warnf("Command '%s' with args '%v' did not completed after %d seconds", - command, args, CommandExecTimeout) - if err := cmd.Process.Kill(); err != nil { - log.Error("failed to kill process: ", err) - } - return nil, fmt.Errorf("process killed as timeout reached") - case err := <-done: - if err != nil { - log.Errorf("process finished with error = %v", err) - return nil, err - } - } - return b.Bytes(), nil + cmd := exec.Command(command, args...) + log.Debugf("Executing command: %v", cmd) + var b bytes.Buffer + cmd.Stdout = &b + cmd.Stderr = &b + if err := cmd.Start(); err != nil { + log.Error(err) + return nil, err + } + // Wait for the process to finish or kill it after a timeout (whichever happens first): + done := make(chan error, 1) + go func() { + done <- cmd.Wait() + }() + select { + case <-time.After(CommandExecTimeout): + log.Warnf("Command '%s' with args '%v' did not completed after %d seconds", + command, args, CommandExecTimeout) + if err := cmd.Process.Kill(); err != nil { + log.Error("failed to kill process: ", err) + } + return nil, fmt.Errorf("process killed as timeout reached") + case err := <-done: + if err != nil { + log.Errorf("process finished with error = %v", err) + return nil, err + } + } + return b.Bytes(), nil } var ExecCommand = execCommandHelper @@ -71,326 +74,437 @@ var ExecCommand = execCommandHelper // EnsureFreeLoopbackDeviceFile finds the next available loop device under /dev/loop* // If no free loop devices exist, a new one is created func EnsureFreeLoopbackDeviceFile() (uint64, error) { - LOOP_CTL_GET_FREE := uintptr(0x4C82) - LoopControlPath := "/dev/loop-control" - ctrl, err := os.OpenFile(LoopControlPath, os.O_RDWR, 0660) - if err != nil { - return 0, fmt.Errorf("could not open %s: %v", LoopControlPath, err) - } - defer ctrl.Close() - dev, _, errno := unix.Syscall(unix.SYS_IOCTL, ctrl.Fd(), LOOP_CTL_GET_FREE, 0) - if dev < 0 { - return 0, fmt.Errorf("could not get free device: %v", errno) - } - return uint64(dev), nil + LOOP_CTL_GET_FREE := uintptr(0x4C82) + LoopControlPath := "/dev/loop-control" + ctrl, err := os.OpenFile(LoopControlPath, os.O_RDWR, 0660) + if err != nil { + return 0, fmt.Errorf("could not open %s: %v", LoopControlPath, err) + } + defer ctrl.Close() + dev, _, errno := unix.Syscall(unix.SYS_IOCTL, ctrl.Fd(), LOOP_CTL_GET_FREE, 0) + if dev < 0 { + return 0, fmt.Errorf("could not get free device: %v", errno) + } + return uint64(dev), nil } func MountFilesystem(sourcefile, destfile, fsType string, mountFlags []string) error { - mounter := mount.New("") - if exists, _ := mounter.ExistsPath(destfile); !exists { - err := os.MkdirAll(filepath.Dir(destfile), os.FileMode(0644)) - if err == nil { - err = mounter.MakeFile(destfile) - } - if err != nil { - log.Errorf("could not make destination path for mount, %v", err) - return status.Error(codes.Internal, err.Error()) - } - } - - err := mounter.Mount(sourcefile, destfile, fsType, mountFlags) - if err != nil { - if os.IsPermission(err) { - return status.Error(codes.PermissionDenied, err.Error()) - } - if strings.Contains(err.Error(), "Invalid argument") { - return status.Error(codes.InvalidArgument, err.Error()) - } - return status.Error(codes.Internal, err.Error()) - } - return nil + mounter := mount.New("") + if exists, _ := mounter.ExistsPath(destfile); !exists { + err := os.MkdirAll(filepath.Dir(destfile), os.FileMode(0644)) + if err == nil { + err = mounter.MakeFile(destfile) + } + if err != nil { + log.Errorf("could not make destination path for mount, %v", err) + return status.Error(codes.Internal, err.Error()) + } + } + + err := mounter.Mount(sourcefile, destfile, fsType, mountFlags) + if err != nil { + if os.IsPermission(err) { + return status.Error(codes.PermissionDenied, err.Error()) + } + if strings.Contains(err.Error(), "Invalid argument") { + return status.Error(codes.InvalidArgument, err.Error()) + } + return status.Error(codes.Internal, err.Error()) + } + return nil } func ExpandFilesystem(device, fsType string) error { - log.Infof("Resizing filesystem on file '%s' with '%s' filesystem", device, fsType) - - var command string - if fsType == "xfs" { - command = "xfs_growfs" - } else { - command = "resize2fs" - } - output, err := ExecCommand(command, device) - if err != nil { - log.Errorf("Could not expand filesystem on device %s: %s: %s", device, err.Error(), output) - return err - } - return nil + log.Infof("Resizing filesystem on file '%s' with '%s' filesystem", device, fsType) + + var command string + if fsType == "xfs" { + command = "xfs_growfs" + } else { + command = "resize2fs" + } + output, err := ExecCommand(command, device) + if err != nil { + log.Errorf("Could not expand filesystem on device %s: %s: %s", device, err.Error(), output) + return err + } + return nil } func BindMountDevice(sourcefile, destfile string) error { - mounter := mount.New("") - if exists, _ := mounter.ExistsPath(destfile); !exists { - err := os.MkdirAll(filepath.Dir(destfile), os.FileMode(0644)) - if err == nil { - err = mounter.MakeFile(destfile) - } - if err != nil { - log.Errorf("could not make destination path for bind mount, %v", err) - return status.Error(codes.Internal, err.Error()) - } - } - - err := mounter.Mount(sourcefile, destfile, "", []string{"bind"}) - if err != nil { - if os.IsPermission(err) { - return status.Error(codes.PermissionDenied, err.Error()) - } - if strings.Contains(err.Error(), "invalid argument") { - return status.Error(codes.InvalidArgument, err.Error()) - } - return status.Error(codes.Internal, err.Error()) - } - return nil + mounter := mount.New("") + if exists, _ := mounter.ExistsPath(destfile); !exists { + err := os.MkdirAll(filepath.Dir(destfile), os.FileMode(0644)) + if err == nil { + err = mounter.MakeFile(destfile) + } + if err != nil { + log.Errorf("could not make destination path for bind mount, %v", err) + return status.Error(codes.Internal, err.Error()) + } + } + + err := mounter.Mount(sourcefile, destfile, "", []string{"bind"}) + if err != nil { + if os.IsPermission(err) { + return status.Error(codes.PermissionDenied, err.Error()) + } + if strings.Contains(err.Error(), "invalid argument") { + return status.Error(codes.InvalidArgument, err.Error()) + } + return status.Error(codes.Internal, err.Error()) + } + return nil } func GetDeviceMinorNumber(device string) (uint32, error) { - s := unix.Stat_t{} - if err := unix.Stat(device, &s); err != nil { - return 0, err - } - dev := uint64(s.Rdev) - return unix.Minor(dev), nil + s := unix.Stat_t{} + if err := unix.Stat(device, &s); err != nil { + return 0, err + } + dev := uint64(s.Rdev) + return unix.Minor(dev), nil } func MakeEmptyRawFile(pathname string, size int64) error { - log.Infof("creating file '%s'", pathname) - sizeStr := strconv.FormatInt(size, 10) - output, err := ExecCommand("qemu-img", "create", "-fraw", pathname, sizeStr) - if err != nil { - log.Errorf("%s, %v", output, err.Error()) - return err - } - return nil + log.Infof("creating file '%s'", pathname) + sizeStr := strconv.FormatInt(size, 10) + output, err := ExecCommand("qemu-img", "create", "-fraw", pathname, sizeStr) + if err != nil { + log.Errorf("%s, %v", output, err.Error()) + return err + } + return nil } func ExpandDeviceFileSize(pathname string, size int64) error { - log.Infof("resizing device file '%s'", pathname) - sizeStr := strconv.FormatInt(size, 10) - loopdev, err := determineLoopDeviceFromBackingFile(pathname) - if err != nil { - // log.Errorf("DFERR: loopdev: '%s', error: '%v'", loopdev, err.Error()) - return err - } - // Refresh the loop device size with losetup -c - // Requires UBI image - loresize, err := ExecCommand("losetup", "-c", loopdev) - if err != nil { - log.Errorf("Resizing loop device '%s' failed with output '%s': '%v'", loopdev, loresize, err.Error()) - return err - } - output, err := ExecCommand("qemu-img", "resize", "-fraw", pathname, sizeStr) - if err != nil { - log.Errorf("%s, %v", output, err.Error()) - return err - } - return nil + log.Infof("resizing device file '%s'", pathname) + sizeStr := strconv.FormatInt(size, 10) + loopdev, err := determineLoopDeviceFromBackingFile(pathname) + if err != nil { + // log.Errorf("DFERR: loopdev: '%s', error: '%v'", loopdev, err.Error()) + return err + } + // Refresh the loop device size with losetup -c + // Requires UBI image + loresize, err := ExecCommand("losetup", "-c", loopdev) + if err != nil { + log.Errorf("Resizing loop device '%s' failed with output '%s': '%v'", loopdev, loresize, err.Error()) + return err + } + output, err := ExecCommand("qemu-img", "resize", "-fraw", pathname, sizeStr) + if err != nil { + log.Errorf("%s, %v", output, err.Error()) + return err + } + return nil } func FormatDevice(device, fsType string) error { - log.Infof("formatting file '%s' with '%s' filesystem", device, fsType) - args := []string{device} - if fsType == "xfs" { - args = []string{"-m", "reflink=0", device} - } - output, err := ExecCommand(fmt.Sprintf("mkfs.%s", fsType), args...) - if err != nil { - log.Info(err) - if output != nil && strings.Contains(string(output), "will not make a filesystem here") { - log.Warningf("Device %s is already mounted", device) - return err - } - log.Errorf("Could not format device %s: %s", device, err.Error()) - return err - } - return nil + log.Infof("formatting file '%s' with '%s' filesystem", device, fsType) + args := []string{device} + if fsType == "xfs" { + args = []string{"-m", "reflink=0", device} + } + output, err := ExecCommand(fmt.Sprintf("mkfs.%s", fsType), args...) + if err != nil { + log.Info(err) + if output != nil && strings.Contains(string(output), "will not make a filesystem here") { + log.Warningf("Device %s is already mounted", device) + return err + } + log.Errorf("Could not format device %s: %s", device, err.Error()) + return err + } + return nil } func DeleteFile(pathname string) error { - log.Infof("deleting file '%s'", pathname) - err := os.Remove(pathname) - if err != nil { - return err - } + log.Infof("deleting file '%s'", pathname) + err := os.Remove(pathname) + if err != nil { + return err + } - return nil + return nil } func MountShare(sourcePath, targetPath string, mountFlags []string) error { - log.Infof("mounting %s to %s, with options %v", sourcePath, targetPath, mountFlags) - notMnt, err := mount.New("").IsLikelyNotMountPoint(targetPath) - if err != nil { - if os.IsNotExist(err) { - if err := os.MkdirAll(targetPath, 0750); err != nil { - return status.Error(codes.Internal, err.Error()) - } - notMnt = true - } else { - return status.Error(codes.Internal, err.Error()) - } - } - - if !notMnt { - return nil - } - - mo := mountFlags - - mounter := mount.New("") - err = mounter.Mount(sourcePath, targetPath, "nfs", mo) - if err != nil { - if os.IsPermission(err) { - return status.Error(codes.PermissionDenied, err.Error()) - } - if strings.Contains(err.Error(), "invalid argument") { - return status.Error(codes.InvalidArgument, err.Error()) - } - return status.Error(codes.Internal, err.Error()) - } - - return nil + log.Infof("mounting %s to %s, with options %v", sourcePath, targetPath, mountFlags) + notMnt, err := mount.New("").IsLikelyNotMountPoint(targetPath) + if err != nil { + if os.IsNotExist(err) { + if err := os.MkdirAll(targetPath, 0750); err != nil { + return status.Error(codes.Internal, err.Error()) + } + notMnt = true + } else { + return status.Error(codes.Internal, err.Error()) + } + } + + if !notMnt { + return nil + } + + mo := mountFlags + + mounter := mount.New("") + err = mounter.Mount(sourcePath, targetPath, "nfs", mo) + if err != nil { + if os.IsPermission(err) { + return status.Error(codes.PermissionDenied, err.Error()) + } + if strings.Contains(err.Error(), "invalid argument") { + return status.Error(codes.InvalidArgument, err.Error()) + } + return status.Error(codes.Internal, err.Error()) + } + + return nil } -func determineBackingFileFromLoopDevice(lodevice string) (string, error) { - output, err := ExecCommand("losetup", "-a") - if err != nil { - return "", status.Errorf(codes.Internal, - "could not determine backing file for loop device, %v", err) - } - devices := strings.Split(string(output), "\n") - for _, d := range devices { - if d != "" { - device := strings.Split(d, " ") - if lodevice == strings.Trim(device[0], ":()") { - return strings.Trim(device[len(device)-1], ":()"), nil - } - } - } - return "", status.Errorf(codes.Internal, - "could not determine backing file for loop device") +func DetermineBackingFileFromLoopDevice(lodevice string) (string, error) { + output, err := ExecCommand("losetup", "-a") + if err != nil { + return "", status.Errorf(codes.Internal, + "could not determine backing file for loop device, %v", err) + } + devices := strings.Split(string(output), "\n") + for _, d := range devices { + if d != "" { + device := strings.Split(d, " ") + if lodevice == strings.Trim(device[0], ":()") { + return strings.Trim(device[len(device)-1], ":()"), nil + } + } + } + return "", status.Errorf(codes.Internal, + "could not determine backing file for loop device") } // Note that this function does not work in Alpine image due to // losetup cutting the output off at 79 characters func determineLoopDeviceFromBackingFile(backingfile string) (string, error) { - log.Infof("determine loop device from backing file: '%s'", backingfile) - output, err := ExecCommand("losetup", "-a") - if err != nil { - return "", status.Errorf(codes.Internal, - "could not determine loop device for backing file, %v", err) - } - devices := strings.Split(string(output), "\n") - for _, d := range devices { - if d != "" { - device := strings.Split(d, " ") - if backingfile == strings.Trim(device[2], ":()") { - log.Infof("matched loop dev: '%s'", strings.Trim(device[0], ":()")) - return strings.Trim(device[0], ":()"), nil - } - } - } - return "", status.Errorf(codes.Internal, - "could not determine loop device for backing file") + log.Infof("determine loop device from backing file: '%s'", backingfile) + output, err := ExecCommand("losetup", "-a") + if err != nil { + return "", status.Errorf(codes.Internal, + "could not determine loop device for backing file, %v", err) + } + devices := strings.Split(string(output), "\n") + for _, d := range devices { + if d != "" { + device := strings.Split(d, " ") + if backingfile == strings.Trim(device[2], ":()") { + log.Infof("matched loop dev: '%s'", strings.Trim(device[0], ":()")) + return strings.Trim(device[0], ":()"), nil + } + } + } + return "", status.Errorf(codes.Internal, + "could not determine loop device for backing file") } func GetNFSExports(address string) ([]string, error) { - output, err := ExecCommand("showmount", "--no-headers", "-e", address) - if err != nil { - return nil, status.Errorf(codes.Internal, - "could not determine nfs exports, %v: %s", err, output) - } - exports := strings.Split(string(output), "\n") - toReturn := []string{} - for _, export := range exports { - exportTokens := strings.Fields(export) - if len(exportTokens) > 0 { - toReturn = append(toReturn, exportTokens[0]) - } - } - if len(toReturn) == 0 { - return nil, status.Errorf(codes.Internal, - "could not determine nfs exports, command output: %s", output) - } - return toReturn, nil + // Create a context with timeout of 30 seconds + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + // Execute the command within the context + outputChan := make(chan []byte) + errChan := make(chan error) + go func() { + output, err := ExecCommand("showmount", "--no-headers", "-e", address) + if err != nil { + errChan <- err + return + } + outputChan <- output + }() + + select { + case <-ctx.Done(): + // Timeout exceeded + return nil, status.Errorf(codes.DeadlineExceeded, "command execution timed out") + case err := <-errChan: + return nil, status.Errorf(codes.Internal, "could not determine nfs exports: %v", err) + case output := <-outputChan: + exports := strings.Split(string(output), "\n") + toReturn := []string{} + for _, export := range exports { + exportTokens := strings.Fields(export) + if len(exportTokens) > 0 { + toReturn = append(toReturn, exportTokens[0]) + } + } + if len(toReturn) == 0 { + return nil, status.Errorf(codes.Internal, "could not determine nfs exports") + } + return toReturn, nil + } +} + +func computeUaddr(ipAddress string, port int) (string, string, error) { + ipType, err := checkIPType(ipAddress) + if err != nil { + return "", "", err + } + + switch ipType { + case "IPv4": + return computeIPv4Uaddr(ipAddress, port), "tcp", nil + case "IPv6": + return computeIPv6Uaddr(ipAddress, port), "tcp", nil + default: + return "", "", errors.New("unsupported IP type") + } +} + +func computeIPv4Uaddr(ipAddress string, port int) string { + // Split the IPv4 address into octets + octets := strings.Split(ipAddress, ".") + if len(octets) != 4 { + return "" + } + + // Convert port to hexadecimal and get the last two digits + portHex := strconv.FormatInt(int64(port), 16) + portHex = fmt.Sprintf("%04s", portHex) // pad with zeros if necessary + portHigh, _ := strconv.ParseInt(portHex[:2], 16, 0) + portLow, _ := strconv.ParseInt(portHex[2:], 16, 0) + + // Compute the final uaddr string for IPv4 + uaddr := fmt.Sprintf("%s.%d.%d", ipAddress, portHigh, portLow) + return uaddr +} + +func computeIPv6Uaddr(ipAddress string, port int) string { + // Convert port to hexadecimal and format it + portHex := fmt.Sprintf("%04x", port) + + // Compute the final uaddr string for IPv6 + uaddr := fmt.Sprintf("[%s]:%s", ipAddress, portHex) + return uaddr +} + +func checkIPType(ipAddress string) (string, error) { + ip := net.ParseIP(ipAddress) + if ip == nil { + return "", errors.New("invalid IP address") + } + if ip.To4() != nil { + return "IPv4", nil + } else if ip.To16() != nil { + return "IPv6", nil + } + return "", errors.New("unknown IP type") +} + +func CheckNFSExports(address string) (bool, error) { + // Create a context with timeout of 30 seconds + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + log.Infof("Checking floating ip %s", address) + + uaddr, protocol, err := computeUaddr(address, 2049) + if err != nil { + log.Errorf("Error while computing uaddr: %v", err) + } + + // Execute the command within the context + outputChan := make(chan []byte) + errChan := make(chan error) + go func() { + output, err := ExecCommand("rpcinfo", "-a", uaddr, "-T", protocol, "100003", "3") + if err != nil { + errChan <- err + return + } + log.Infof("Check was success on uaddr %s, with protocol %s.", uaddr, protocol) + outputChan <- output + }() + + select { + case <-ctx.Done(): + // Timeout exceeded + return false, status.Errorf(codes.DeadlineExceeded, "command execution timed out while checking nfs exports with rpcinfo") + case err := <-errChan: + return false, status.Errorf(codes.Internal, "could not determine nfs exports: %v", err) + case output := <-outputChan: + log.Infof(string(output)) + return true, nil + } } func IsShareMounted(targetPath string) (bool, error) { - notMnt, err := mount.IsNotMountPoint(mount.New(""), targetPath) - - if err != nil { - if os.IsNotExist(err) { - return false, status.Error(codes.NotFound, EmptyTargetPath) - } else { - return false, status.Error(codes.Internal, err.Error()) - } - } - if notMnt { - return false, nil - } - return true, nil + notMnt, err := mount.IsNotMountPoint(mount.New(""), targetPath) + + if err != nil { + if os.IsNotExist(err) { + return false, status.Error(codes.NotFound, EmptyTargetPath) + } else { + return false, status.Error(codes.Internal, err.Error()) + } + } + if notMnt { + return false, nil + } + return true, nil } func UnmountFilesystem(targetPath string) error { - mounter := mount.New("") - - isMounted, err := IsShareMounted(targetPath) - - if err != nil { - log.Error(err.Error()) - return status.Error(codes.Internal, err.Error()) - } - if !isMounted { - return nil - } - - err = mounter.Unmount(targetPath) - if err != nil { - log.Error(err.Error()) - return status.Error(codes.Internal, err.Error()) - } - // delete target path - err = os.Remove(targetPath) - if err != nil { - log.Errorf("could not remove target path, %v", err) - return status.Error(codes.Internal, err.Error()) - } - return nil + mounter := mount.New("") + + isMounted, err := IsShareMounted(targetPath) + + if err != nil { + log.Error(err.Error()) + return status.Error(codes.Internal, err.Error()) + } + if !isMounted { + return nil + } + + err = mounter.Unmount(targetPath) + if err != nil { + log.Error(err.Error()) + return status.Error(codes.Internal, err.Error()) + } + // delete target path + err = os.Remove(targetPath) + if err != nil { + log.Errorf("could not remove target path, %v", err) + return status.Error(codes.Internal, err.Error()) + } + return nil } func SetMetadataTags(localPath string, tags map[string]string) error { - // hs attribute set localpath -e "CSI_DETAILS_TABLE{'','','',''}" - _, err := ExecCommand("hs", - "attribute", - "set", "CSI_DETAILS", - fmt.Sprintf("-e \"CSI_DETAILS_TABLE{'%s','%s','%s','%s'}\"", CsiVersion, CsiPluginName, Version, Githash), - localPath) - if err != nil { - log.Warn("Failed to set CSI_DETAILS metadata " + err.Error()) - } - - for tag_key, tag_value := range tags { - output, err := ExecCommand("hs", - "-v", "tag", - "set", "-e", fmt.Sprintf("'%s'", tag_value), tag_key, localPath, - ) - - // FIXME: The HS client returns exit code 0 even on failure, so we can't detect errors - if err != nil { - log.Error("Failed to set tag " + err.Error()) - break - } - log.Debugf("HS command output: %s", output) - } - - return err + // hs attribute set localpath -e "CSI_DETAILS_TABLE{'','','',''}" + _, err := ExecCommand("hs", + "attribute", + "set", "CSI_DETAILS", + fmt.Sprintf("-e \"CSI_DETAILS_TABLE{'%s','%s','%s','%s'}\"", CsiVersion, CsiPluginName, Version, Githash), + localPath) + if err != nil { + log.Warn("Failed to set CSI_DETAILS metadata " + err.Error()) + } + + for tag_key, tag_value := range tags { + output, err := ExecCommand("hs", + "-v", "tag", + "set", "-e", fmt.Sprintf("'%s'", tag_value), tag_key, localPath, + ) + + // FIXME: The HS client returns exit code 0 even on failure, so we can't detect errors + if err != nil { + log.Error("Failed to set tag " + err.Error()) + break + } + log.Debugf("HS command output: %s", output) + } + + return err } diff --git a/pkg/common/host_utils_test.go b/pkg/common/host_utils_test.go deleted file mode 100644 index 8823215..0000000 --- a/pkg/common/host_utils_test.go +++ /dev/null @@ -1,103 +0,0 @@ -package common - -import ( - "testing" - "reflect" -) - -func TestGetNFSExports(t *testing.T) { - ExecCommand = func(command string, args...string) ([]byte, error) { - return []byte(""), nil - } - expected := []string{} - actual, err := GetNFSExports("127.0.0.1") - if err != nil { - t.Logf("Unexpected error, %v", err) - t.FailNow() - } - if !reflect.DeepEqual(actual, expected) { - t.Logf("Expected: %v", expected) - t.Logf("Actual: %v", actual) - t.FailNow() - } - - ExecCommand = func(command string, args...string) ([]byte, error) { - return []byte(` - - -`), nil - } - expected = []string{} - actual, err = GetNFSExports("127.0.0.1") - if err != nil { - t.Logf("Unexpected error, %v", err) - t.FailNow() - } - if !reflect.DeepEqual(actual, expected) { - t.Logf("Expected: %v", expected) - t.Logf("Actual: %v", actual) - t.FailNow() - } - - ExecCommand = func(command string, args...string) ([]byte, error) { - return []byte(`/test * -/mnt/data-portal/test * -/hs/test * -`), nil - } - expected = []string{"/test", "/mnt/data-portal/test", "/hs/test"} - actual, err = GetNFSExports("127.0.0.1") - if err != nil { - t.Logf("Unexpected error, %v", err) - t.FailNow() - } - if !reflect.DeepEqual(actual, expected) { - t.Logf("Expected: %v", expected) - t.Logf("Actual: %v", actual) - t.FailNow() - } -} - - -func TestDetermineBackingFileFromLoopDevice(t *testing.T) { - ExecCommand = func(command string, args ...string) ([]byte, error) { - return []byte(` -/dev/loop0: 0 /tmp/test -/dev/loop1: 0 /tmp/test -/dev/loop2: 0 /tmp//test-csi-block/sanity-node-full-E067A84C-D67CAA8E -`), nil - } - expected := "/tmp/test" - actual, err := determineBackingFileFromLoopDevice("/dev/loop0") - if err != nil { - t.Logf("Unexpected error, %v", err) - t.FailNow() - } - if !reflect.DeepEqual(actual, expected) { - t.Logf("Expected: %v", expected) - t.Logf("Actual: %v", actual) - t.FailNow() - } -} - -func TestExecCommandHelper(t *testing.T) { - expected := []byte("test\n") - actual, err := execCommandHelper("echo", "test") - if err != nil { - t.Logf("Unexpected error, %v", err) - t.FailNow() - } - if !reflect.DeepEqual(actual, expected) { - t.Logf("Expected: %v", expected) - t.Logf("Actual: %v", actual) - t.FailNow() - } - - CommandExecTimeout = 1 - _, err = execCommandHelper("sleep", "5") - if err == nil { - t.Logf("Expected error") - t.FailNow() - } - -} \ No newline at end of file diff --git a/pkg/common/hs_types.go b/pkg/common/hs_types.go index a1ffec1..bd1fc29 100644 --- a/pkg/common/hs_types.go +++ b/pkg/common/hs_types.go @@ -35,7 +35,7 @@ type HSVolume struct { Objectives []string BlockBackingShareName string MountBackingShareName string - Size int64 + Size string Name string Path string VolumeMode string @@ -75,7 +75,7 @@ type ShareResponse struct { Comment string `json:"comment"` ExtendedInfo map[string]string `json:"extendedInfo"` ShareState string `json:"shareState"` - Size int64 `json:"shareSizeLimit"` + Size string `json:"shareSizeLimit"` ExportOptions []ShareExportOptions `json:"exportOptions"` Space ShareSpaceResponse `json:"space"` Inodes ShareInodesResponse `json:"inodes"` @@ -130,7 +130,7 @@ type TaskParamsMap struct { type File struct { Name string `json:"name"` Path string `json:"path"` - Size int64 `json:"size,string"` + Size string `json:"size"` } type FileSnapshot struct { diff --git a/pkg/common/test/host_utils_test.go b/pkg/common/test/host_utils_test.go new file mode 100644 index 0000000..699cb8f --- /dev/null +++ b/pkg/common/test/host_utils_test.go @@ -0,0 +1,104 @@ +package common + +import ( + "reflect" + "testing" + + "github.com/hammer-space/csi-plugin/pkg/common" +) + +func TestGetNFSExports(t *testing.T) { + common.ExecCommand = func(command string, args ...string) ([]byte, error) { + return []byte(""), nil + } + expected := []string{} + actual, err := common.GetNFSExports("127.0.0.1") + if err != nil { + t.Logf("Unexpected error, %v", err) + t.FailNow() + } + if !reflect.DeepEqual(actual, expected) { + t.Logf("Expected: %v", expected) + t.Logf("Actual: %v", actual) + t.FailNow() + } + + common.ExecCommand = func(command string, args ...string) ([]byte, error) { + return []byte(` + + +`), nil + } + expected = []string{} + actual, err = common.GetNFSExports("127.0.0.1") + if err != nil { + t.Logf("Unexpected error, %v", err) + t.FailNow() + } + if !reflect.DeepEqual(actual, expected) { + t.Logf("Expected: %v", expected) + t.Logf("Actual: %v", actual) + t.FailNow() + } + + common.ExecCommand = func(command string, args ...string) ([]byte, error) { + return []byte(`/test * +/mnt/data-portal/test * +/hs/test * +`), nil + } + expected = []string{"/test", "/mnt/data-portal/test", "/hs/test"} + actual, err = common.GetNFSExports("127.0.0.1") + if err != nil { + t.Logf("Unexpected error, %v", err) + t.FailNow() + } + if !reflect.DeepEqual(actual, expected) { + t.Logf("Expected: %v", expected) + t.Logf("Actual: %v", actual) + t.FailNow() + } +} + +func TestDetermineBackingFileFromLoopDevice(t *testing.T) { + common.ExecCommand = func(command string, args ...string) ([]byte, error) { + return []byte(` +/dev/loop0: 0 /tmp/test +/dev/loop1: 0 /tmp/test +/dev/loop2: 0 /tmp//test-csi-block/sanity-node-full-E067A84C-D67CAA8E +`), nil + } + expected := "/tmp/test" + actual, err := common.DetermineBackingFileFromLoopDevice("/dev/loop0") + if err != nil { + t.Logf("Unexpected error, %v", err) + t.FailNow() + } + if !reflect.DeepEqual(actual, expected) { + t.Logf("Expected: %v", expected) + t.Logf("Actual: %v", actual) + t.FailNow() + } +} + +func TestExecCommandHelper(t *testing.T) { + expected := []byte("test\n") + actual, err := common.ExecCommand("echo", "test") + if err != nil { + t.Logf("Unexpected error, %v", err) + t.FailNow() + } + if !reflect.DeepEqual(actual, expected) { + t.Log(string(actual)) + t.Logf("Expected: %v", expected) + t.Logf("Actual: %v", actual) + t.FailNow() + } + + _, err = common.ExecCommand("sleep", "5") + if err == nil { + t.Logf("Expected error") + t.FailNow() + } + +} diff --git a/pkg/driver/controller.go b/pkg/driver/controller.go index 93dc26d..84b71f4 100755 --- a/pkg/driver/controller.go +++ b/pkg/driver/controller.go @@ -44,7 +44,7 @@ var ( recentlyCreatedSnapshots = map[string]*csi.Snapshot{} ) -func parseVolParams(params map[string]string) (common.HSVolumeParameters, error) { +func ParseVolParams(params map[string]string) (common.HSVolumeParameters, error) { vParams := common.HSVolumeParameters{} if deleteDelayParam, exists := params["deleteDelay"]; exists { @@ -247,7 +247,7 @@ func (d *CSIDriver) ensureBackingShareExists(backingShareName string, hsVolume * err = d.hsclient.CreateShare( backingShareName, "/"+backingShareName, - -1, + "-1", []string{}, hsVolume.ExportOptions, hsVolume.DeleteDelay, @@ -295,13 +295,13 @@ func (d *CSIDriver) ensureDeviceFileExists( } return nil } - - if hsVolume.Size <= 0 { + hsVolumeSize, _ := strconv.ParseInt(hsVolume.Size, 10, 64) + if hsVolumeSize <= 0 { return status.Error(codes.InvalidArgument, common.BlockVolumeSizeNotSpecified) } available, _ := strconv.ParseInt(backingShare.Space.Available, 10, 64) - if hsVolume.Size > available { - return status.Errorf(codes.OutOfRange, common.OutOfCapacity, hsVolume.Size, available) + if hsVolumeSize > available { + return status.Errorf(codes.OutOfRange, common.OutOfCapacity, hsVolumeSize, available) } backingDir := common.ShareStagingDir + backingShare.ExportPath @@ -327,7 +327,7 @@ func (d *CSIDriver) ensureDeviceFileExists( //// Create an empty file of the correct size - err = common.MakeEmptyRawFile(deviceFile, hsVolume.Size) + err = common.MakeEmptyRawFile(deviceFile, hsVolumeSize) if err != nil { log.Errorf("failed to create backing file for volume, %v", err) return err @@ -418,7 +418,7 @@ func (d *CSIDriver) CreateVolume( return nil, status.Errorf(codes.InvalidArgument, common.NoCapabilitiesSupplied, req.Name) } - vParams, err := parseVolParams(req.Parameters) + vParams, err := ParseVolParams(req.Parameters) if err != nil { return nil, err } @@ -530,7 +530,7 @@ func (d *CSIDriver) CreateVolume( Objectives: vParams.Objectives, BlockBackingShareName: vParams.BlockBackingShareName, MountBackingShareName: vParams.MountBackingShareName, - Size: requestedSize, + Size: strconv.FormatInt(requestedSize, 10), Name: volumeName, VolumeMode: volumeMode, FSType: fsType, @@ -585,7 +585,7 @@ func (d *CSIDriver) CreateVolume( // Create Response volContext := make(map[string]string) - volContext["size"] = strconv.FormatInt(hsVolume.Size, 10) + volContext["size"] = hsVolume.Size volContext["mode"] = volumeMode if volumeMode == "Block" { @@ -594,10 +594,10 @@ func (d *CSIDriver) CreateVolume( volContext["mountBackingShareName"] = hsVolume.MountBackingShareName volContext["fsType"] = fsType } - + hsVolumeSize, _ := strconv.ParseInt(hsVolume.Size, 10, 64) return &csi.CreateVolumeResponse{ Volume: &csi.Volume{ - CapacityBytes: hsVolume.Size, + CapacityBytes: hsVolumeSize, VolumeId: hsVolume.Path, VolumeContext: volContext, }, @@ -678,7 +678,6 @@ func (d *CSIDriver) DeleteVolume( if volumeId == "" { return nil, status.Error(codes.InvalidArgument, common.EmptyVolumeId) } - defer d.releaseVolumeLock(volumeId) d.getVolumeLock(volumeId) @@ -756,18 +755,19 @@ func (d *CSIDriver) ControllerExpandVolume( if file == nil || err != nil { return nil, status.Error(codes.NotFound, common.VolumeNotFound) } else { + fileSize, _ := strconv.ParseInt(file.Size, 10, 64) log.Debugf("found file-backed volume to resize, %s", req.GetVolumeId()) // Check backing share size to determine if we can handle new size (look at create volume for how we do this) // && check the size of the file only resize if requested is larger than what we have // if we are good, then return saying we need a resize on next mount - if file.Size >= requestedSize { + if fileSize > requestedSize { return &csi.ControllerExpandVolumeResponse{ - CapacityBytes: file.Size, + CapacityBytes: fileSize, NodeExpansionRequired: false, }, nil } else { // if required - current > available on backend share - sizeDiff := requestedSize - file.Size + sizeDiff := requestedSize - fileSize backingShareName := path.Base(path.Dir(req.GetVolumeId())) backingShare, err := d.hsclient.GetShare(backingShareName) var available int64 @@ -808,7 +808,7 @@ func (d *CSIDriver) ControllerExpandVolume( } if currentSize < requestedSize { - err = d.hsclient.UpdateShareSize(shareName, requestedSize) + err = d.hsclient.UpdateShareSize(shareName, strconv.FormatInt(requestedSize, 10)) if err != nil { return nil, status.Error(codes.Internal, common.UnknownError) } @@ -846,7 +846,7 @@ func (d *CSIDriver) ValidateVolumeCapabilities( typeMount = true } - vParams, err := parseVolParams(req.Parameters) + vParams, err := ParseVolParams(req.Parameters) if err != nil { return nil, err } @@ -967,7 +967,7 @@ func (d *CSIDriver) GetCapacity( }, nil } - vParams, err := parseVolParams(req.Parameters) + vParams, err := ParseVolParams(req.Parameters) if err != nil { return nil, err } diff --git a/pkg/driver/controller_test.go b/pkg/driver/controller_test.go deleted file mode 100644 index 1f32b51..0000000 --- a/pkg/driver/controller_test.go +++ /dev/null @@ -1,187 +0,0 @@ -package driver - -import ( - "reflect" - "testing" - - common "github.com/hammer-space/csi-plugin/pkg/common" -) - -func TestParseParams(t *testing.T) { - - // Test defaults - expectedParams := common.HSVolumeParameters{ - VolumeNameFormat: common.DefaultVolumeNameFormat, - DeleteDelay: -1, - Comment: "Created by CSI driver", - } - stringParams := map[string]string{} - actualParams, _ := parseVolParams(stringParams) - if !reflect.DeepEqual(actualParams, expectedParams) { - t.Logf("Params not equal") - t.Logf("Expected: %v", expectedParams) - t.Logf("Actual: %v", actualParams) - t.FailNow() - } - - // Test valid name format - expectedParams = common.HSVolumeParameters{ - VolumeNameFormat: "my-csi-volume-%s-hammerspace", - DeleteDelay: -1, - Comment: "Created by CSI driver", - } - stringParams = map[string]string{ - "volumeNameFormat": "my-csi-volume-%s-hammerspace", - } - actualParams, err := parseVolParams(stringParams) - if !reflect.DeepEqual(actualParams, expectedParams) { - t.Logf("Params not equal") - t.Logf("Expected: %v", expectedParams) - t.Logf("Actual: %v", actualParams) - t.FailNow() - } - - // Test invalid name format - expectedParams = common.HSVolumeParameters{ - DeleteDelay: -1, - } - stringParams = map[string]string{ - "volumeNameFormat": "blah%s/", - } - actualParams, err = parseVolParams(stringParams) - if err == nil { - t.Logf("expected error") - t.FailNow() - } - stringParams = map[string]string{ - "volumeNameFormat": "blah", - } - actualParams, err = parseVolParams(stringParams) - if err == nil { - t.Logf("expected error") - t.FailNow() - } - - // Test delete delay - expectedParams = common.HSVolumeParameters{ - DeleteDelay: 30, - VolumeNameFormat: common.DefaultVolumeNameFormat, - Comment: "Created by CSI driver", - } - stringParams = map[string]string{ - "deleteDelay": "30", - } - actualParams, err = parseVolParams(stringParams) - if !reflect.DeepEqual(actualParams, expectedParams) { - t.Logf("Params not equal") - t.Logf("Expected: %v", expectedParams) - t.Logf("Actual: %v", actualParams) - t.FailNow() - } - - stringParams = map[string]string{ - "deleteDelay": "notanumber", - } - _, err = parseVolParams(stringParams) - if err == nil { - t.Logf("expected error") - t.FailNow() - } - - // Test objectives - expectedObjectives := []string{ - "obj1", "obj2", "obj3", - } - stringParams = map[string]string{ - "objectives": "obj1, obj2 ,obj3,,", - } - actualParams, err = parseVolParams(stringParams) - if !reflect.DeepEqual(actualParams.Objectives, expectedObjectives) { - t.Logf("Objectives not equal") - t.Logf("Expected: %v", expectedObjectives) - t.Logf("Actual: %v", actualParams) - t.FailNow() - } - - // Test export options - expectedOptions := []common.ShareExportOptions{ - { - Subnet: "*", - AccessPermissions: "RO", - RootSquash: false, - }, - { - Subnet: "10.2.0.0/24", - AccessPermissions: "RW", - RootSquash: true, - }, - } - stringParams = map[string]string{ - "exportOptions": "*,RO, false; 10.2.0.0/24,RW,true", - } - actualParams, err = parseVolParams(stringParams) - if !reflect.DeepEqual(actualParams.ExportOptions, expectedOptions) { - t.Logf("Export options not equal") - t.Logf("Expected: %v", expectedObjectives) - t.Logf("Actual: %v", actualParams) - t.FailNow() - } - - // Test invalid export options - - stringParams = map[string]string{ - "exportOptions": ";;", - } - _, err = parseVolParams(stringParams) - if err == nil { - t.Logf("expected error") - t.FailNow() - } - - stringParams = map[string]string{ - "exportOptions": "*,RO, blah", - } - _, err = parseVolParams(stringParams) - if err == nil { - t.Logf("expected error") - t.FailNow() - } - - stringParams = map[string]string{ - "exportOptions": "*,RO", - } - _, err = parseVolParams(stringParams) - if err == nil { - t.Logf("expected error") - t.FailNow() - } - - // Test extended info - expectedParams = common.HSVolumeParameters{ - AdditionalMetadataTags: map[string]string{ - "test_key": "test_value", - "test_quote": "\"test\"", - }, - } - stringParams = map[string]string{ - "additionalMetadataTags": "test_key=test_value,test_quote=\"test\"", - } - actualParams, err = parseVolParams(stringParams) - if !reflect.DeepEqual(actualParams.AdditionalMetadataTags, expectedParams.AdditionalMetadataTags) { - t.Logf("Params not equal") - t.Logf("Expected: %v", expectedParams.AdditionalMetadataTags) - t.Logf("Actual: %v", actualParams.AdditionalMetadataTags) - t.FailNow() - } - - // Test invalid - stringParams = map[string]string{ - "additionalMetadataTags": "test_keyest_value,test_quote=\"test\"", - } - actualParams, err = parseVolParams(stringParams) - if err == nil { - t.Logf("expected error") - t.FailNow() - } - -} diff --git a/pkg/driver/driver_csi_v0_test.go b/pkg/driver/driver_csi_v0_test.go deleted file mode 100644 index 1ecf624..0000000 --- a/pkg/driver/driver_csi_v0_test.go +++ /dev/null @@ -1,73 +0,0 @@ -package driver - -import ( - "fmt" - csi_v0 "github.com/ameade/spec/lib/go/csi/v0" - "github.com/container-storage-interface/spec/lib/go/csi" - "github.com/hammer-space/csi-plugin/pkg/common" - "reflect" - "strings" - "testing" -) - -func TestConvertVolumeCapablityfromv0tov1(t *testing.T) { - - // Test basic conversion - capv0 := &csi_v0.VolumeCapability{ - AccessType: &csi_v0.VolumeCapability_Mount{ - Mount: &csi_v0.VolumeCapability_MountVolume{ - FsType: "NFS", - MountFlags: []string{"nfsvers=4.2"}, - }, - }, - AccessMode: &csi_v0.VolumeCapability_AccessMode{ - Mode: csi_v0.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER, - }, - } - - capv1 := &csi.VolumeCapability{ - AccessType: &csi.VolumeCapability_Mount{ - Mount: &csi.VolumeCapability_MountVolume{ - FsType: "NFS", - MountFlags: []string{"nfsvers=4.2"}, - }, - }, - AccessMode: &csi.VolumeCapability_AccessMode{ - Mode: csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER, - }, - } - - actualcpv1, err := ConvertVolumeCapabilityFromv0Tov1(capv0) - if err != nil { - t.Logf("unexpected error") - t.FailNow() - } - - if !reflect.DeepEqual(actualcpv1, capv1) { - t.Logf("Expected: %v", capv1) - t.Logf("Actual: %v", actualcpv1) - t.FailNow() - } - - // Test that Raw volumes are not supported - capv0 = &csi_v0.VolumeCapability{ - AccessType: &csi_v0.VolumeCapability_Block{ - Block: &csi_v0.VolumeCapability_BlockVolume{}, - }, - AccessMode: &csi_v0.VolumeCapability_AccessMode{ - Mode: csi_v0.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY, - }, - } - - _, err = ConvertVolumeCapabilityFromv0Tov1(capv0) - if err == nil { - t.Logf("expected error") - t.FailNow() - } else { - errString := fmt.Sprintf("%s", err) - if !strings.Contains(errString, common.BlockVolumesUnsupported) { - t.Logf("unexpected error, %s", err) - t.FailNow() - } - } -} diff --git a/pkg/driver/test/controller_test.go b/pkg/driver/test/controller_test.go new file mode 100644 index 0000000..61fe124 --- /dev/null +++ b/pkg/driver/test/controller_test.go @@ -0,0 +1,188 @@ +package driver + +import ( + "reflect" + "testing" + + common "github.com/hammer-space/csi-plugin/pkg/common" + "github.com/hammer-space/csi-plugin/pkg/driver" +) + +func TestParseParams(t *testing.T) { + + // Test defaults + expectedParams := common.HSVolumeParameters{ + VolumeNameFormat: common.DefaultVolumeNameFormat, + DeleteDelay: -1, + Comment: "Created by CSI driver", + } + stringParams := map[string]string{} + actualParams, _ := driver.ParseVolParams(stringParams) + if !reflect.DeepEqual(actualParams, expectedParams) { + t.Logf("Params not equal") + t.Logf("Expected: %v", expectedParams) + t.Logf("Actual: %v", actualParams) + t.FailNow() + } + + // Test valid name format + expectedParams = common.HSVolumeParameters{ + VolumeNameFormat: "my-csi-volume-%s-hammerspace", + DeleteDelay: -1, + Comment: "Created by CSI driver", + } + stringParams = map[string]string{ + "volumeNameFormat": "my-csi-volume-%s-hammerspace", + } + actualParams, err := driver.ParseVolParams(stringParams) + if !reflect.DeepEqual(actualParams, expectedParams) { + t.Logf("Params not equal") + t.Logf("Expected: %v", expectedParams) + t.Logf("Actual: %v", actualParams) + t.FailNow() + } + + // Test invalid name format + expectedParams = common.HSVolumeParameters{ + DeleteDelay: -1, + } + stringParams = map[string]string{ + "volumeNameFormat": "blah%s/", + } + actualParams, err = driver.ParseVolParams(stringParams) + if err == nil { + t.Logf("expected error") + t.FailNow() + } + stringParams = map[string]string{ + "volumeNameFormat": "blah", + } + actualParams, err = driver.ParseVolParams(stringParams) + if err == nil { + t.Logf("expected error") + t.FailNow() + } + + // Test delete delay + expectedParams = common.HSVolumeParameters{ + DeleteDelay: 30, + VolumeNameFormat: common.DefaultVolumeNameFormat, + Comment: "Created by CSI driver", + } + stringParams = map[string]string{ + "deleteDelay": "30", + } + actualParams, err = driver.ParseVolParams(stringParams) + if !reflect.DeepEqual(actualParams, expectedParams) { + t.Logf("Params not equal") + t.Logf("Expected: %v", expectedParams) + t.Logf("Actual: %v", actualParams) + t.FailNow() + } + + stringParams = map[string]string{ + "deleteDelay": "notanumber", + } + _, err = driver.ParseVolParams(stringParams) + if err == nil { + t.Logf("expected error") + t.FailNow() + } + + // Test objectives + expectedObjectives := []string{ + "obj1", "obj2", "obj3", + } + stringParams = map[string]string{ + "objectives": "obj1, obj2 ,obj3,,", + } + actualParams, err = driver.ParseVolParams(stringParams) + if !reflect.DeepEqual(actualParams.Objectives, expectedObjectives) { + t.Logf("Objectives not equal") + t.Logf("Expected: %v", expectedObjectives) + t.Logf("Actual: %v", actualParams) + t.FailNow() + } + + // Test export options + expectedOptions := []common.ShareExportOptions{ + { + Subnet: "*", + AccessPermissions: "RO", + RootSquash: false, + }, + { + Subnet: "10.2.0.0/24", + AccessPermissions: "RW", + RootSquash: true, + }, + } + stringParams = map[string]string{ + "exportOptions": "*,RO, false; 10.2.0.0/24,RW,true", + } + actualParams, err = driver.ParseVolParams(stringParams) + if !reflect.DeepEqual(actualParams.ExportOptions, expectedOptions) { + t.Logf("Export options not equal") + t.Logf("Expected: %v", expectedObjectives) + t.Logf("Actual: %v", actualParams) + t.FailNow() + } + + // Test invalid export options + + stringParams = map[string]string{ + "exportOptions": ";;", + } + _, err = driver.ParseVolParams(stringParams) + if err == nil { + t.Logf("expected error") + t.FailNow() + } + + stringParams = map[string]string{ + "exportOptions": "*,RO, blah", + } + _, err = driver.ParseVolParams(stringParams) + if err == nil { + t.Logf("expected error") + t.FailNow() + } + + stringParams = map[string]string{ + "exportOptions": "*,RO", + } + _, err = driver.ParseVolParams(stringParams) + if err == nil { + t.Logf("expected error") + t.FailNow() + } + + // Test extended info + expectedParams = common.HSVolumeParameters{ + AdditionalMetadataTags: map[string]string{ + "test_key": "test_value", + "test_quote": "\"test\"", + }, + } + stringParams = map[string]string{ + "additionalMetadataTags": "test_key=test_value,test_quote=\"test\"", + } + actualParams, err = driver.ParseVolParams(stringParams) + if !reflect.DeepEqual(actualParams.AdditionalMetadataTags, expectedParams.AdditionalMetadataTags) { + t.Logf("Params not equal") + t.Logf("Expected: %v", expectedParams.AdditionalMetadataTags) + t.Logf("Actual: %v", actualParams.AdditionalMetadataTags) + t.FailNow() + } + + // Test invalid + stringParams = map[string]string{ + "additionalMetadataTags": "test_keyest_value,test_quote=\"test\"", + } + actualParams, err = driver.ParseVolParams(stringParams) + if err == nil { + t.Logf("expected error") + t.FailNow() + } + +} diff --git a/pkg/driver/test/driver_csi_v0_test.go b/pkg/driver/test/driver_csi_v0_test.go new file mode 100644 index 0000000..91b48dd --- /dev/null +++ b/pkg/driver/test/driver_csi_v0_test.go @@ -0,0 +1,75 @@ +package driver + +import ( + "fmt" + "reflect" + "strings" + "testing" + + csi_v0 "github.com/ameade/spec/lib/go/csi/v0" + "github.com/container-storage-interface/spec/lib/go/csi" + "github.com/hammer-space/csi-plugin/pkg/common" + "github.com/hammer-space/csi-plugin/pkg/driver" +) + +func TestConvertVolumeCapablityfromv0tov1(t *testing.T) { + + // Test basic conversion + capv0 := &csi_v0.VolumeCapability{ + AccessType: &csi_v0.VolumeCapability_Mount{ + Mount: &csi_v0.VolumeCapability_MountVolume{ + FsType: "NFS", + MountFlags: []string{"nfsvers=4.2"}, + }, + }, + AccessMode: &csi_v0.VolumeCapability_AccessMode{ + Mode: csi_v0.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER, + }, + } + + capv1 := &csi.VolumeCapability{ + AccessType: &csi.VolumeCapability_Mount{ + Mount: &csi.VolumeCapability_MountVolume{ + FsType: "NFS", + MountFlags: []string{"nfsvers=4.2"}, + }, + }, + AccessMode: &csi.VolumeCapability_AccessMode{ + Mode: csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER, + }, + } + + actualcpv1, err := driver.ConvertVolumeCapabilityFromv0Tov1(capv0) + if err != nil { + t.Logf("unexpected error") + t.FailNow() + } + + if !reflect.DeepEqual(actualcpv1, capv1) { + t.Logf("Expected: %v", capv1) + t.Logf("Actual: %v", actualcpv1) + t.FailNow() + } + + // Test that Raw volumes are not supported + capv0 = &csi_v0.VolumeCapability{ + AccessType: &csi_v0.VolumeCapability_Block{ + Block: &csi_v0.VolumeCapability_BlockVolume{}, + }, + AccessMode: &csi_v0.VolumeCapability_AccessMode{ + Mode: csi_v0.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY, + }, + } + + _, err = driver.ConvertVolumeCapabilityFromv0Tov1(capv0) + if err == nil { + t.Logf("expected error") + t.FailNow() + } else { + errString := fmt.Sprintf("%s", err) + if !strings.Contains(errString, common.BlockVolumesUnsupported) { + t.Logf("unexpected error, %s", err) + t.FailNow() + } + } +} diff --git a/pkg/driver/test/utils_test.go b/pkg/driver/test/utils_test.go new file mode 100644 index 0000000..f4c015e --- /dev/null +++ b/pkg/driver/test/utils_test.go @@ -0,0 +1,76 @@ +package driver + +import ( + "reflect" + "testing" + + "github.com/hammer-space/csi-plugin/pkg/driver" +) + +func TestGetSnapshotNameFromSnapshotId(t *testing.T) { + + snapshotId := "2019-05-24T15-26-57-0|/sanity-controller-source-vol-859F8B9B-35BBFB36" + expected := "2019-05-24T15-26-57-0" + actual, err := driver.GetSnapshotNameFromSnapshotId(snapshotId) + if err != nil { + t.Logf("Unexpected error, %v", err) + t.FailNow() + } + if !reflect.DeepEqual(actual, expected) { + t.Logf("Expected: %v", expected) + t.Logf("Actual: %v", actual) + t.FailNow() + } + + snapshotId = "2019-05-24T15-26-57-0" + _, err = driver.GetSnapshotNameFromSnapshotId(snapshotId) + if err == nil { + t.Logf("Expected error") + t.FailNow() + } + +} + +func TestGetShareNameFromSnapshotId(t *testing.T) { + + snapshotId := "2019-05-24T15-26-57-0|/sanity-controller-source-vol-859F8B9B-35BBFB36" + expected := "sanity-controller-source-vol-859F8B9B-35BBFB36" + actual, err := driver.GetShareNameFromSnapshotId(snapshotId) + if err != nil { + t.Logf("Unexpected error, %v", err) + t.FailNow() + } + if !reflect.DeepEqual(actual, expected) { + t.Logf("Expected: %v", expected) + t.Logf("Actual: %v", actual) + t.FailNow() + } + + snapshotId = "2019-05-24T15-26-57-0" + _, err = driver.GetShareNameFromSnapshotId(snapshotId) + if err == nil { + t.Logf("Expected error") + t.FailNow() + } +} + +func TestGetSnapshotIDFromSnapshotName(t *testing.T) { + expected := "2019-05-24T15-26-57-0|/sanity-controller-source-vol-859F8B9B-35BBFB36" + actual := driver.GetSnapshotIDFromSnapshotName("2019-05-24T15-26-57-0", + "/sanity-controller-source-vol-859F8B9B-35BBFB36") + if !reflect.DeepEqual(actual, expected) { + t.Logf("Expected: %v", expected) + t.Logf("Actual: %v", actual) + t.FailNow() + } +} + +func TestGetVolumeNameFromPath(t *testing.T) { + expected := "test-volume" + actual := driver.GetVolumeNameFromPath("/test-backing-share/test-volume") + if !reflect.DeepEqual(actual, expected) { + t.Logf("Expected: %v", expected) + t.Logf("Actual: %v", actual) + t.FailNow() + } +} diff --git a/pkg/driver/utils_test.go b/pkg/driver/utils_test.go deleted file mode 100644 index 15958a2..0000000 --- a/pkg/driver/utils_test.go +++ /dev/null @@ -1,75 +0,0 @@ -package driver - -import ( - "reflect" - "testing" -) - -func TestGetSnapshotNameFromSnapshotId(t *testing.T) { - - snapshotId := "2019-05-24T15-26-57-0|/sanity-controller-source-vol-859F8B9B-35BBFB36" - expected := "2019-05-24T15-26-57-0" - actual, err := GetSnapshotNameFromSnapshotId(snapshotId) - if err != nil { - t.Logf("Unexpected error, %v", err) - t.FailNow() - } - if !reflect.DeepEqual(actual, expected) { - t.Logf("Expected: %v", expected) - t.Logf("Actual: %v", actual) - t.FailNow() - } - - - snapshotId = "2019-05-24T15-26-57-0" - _, err = GetSnapshotNameFromSnapshotId(snapshotId) - if err == nil { - t.Logf("Expected error") - t.FailNow() - } - -} - -func TestGetShareNameFromSnapshotId(t *testing.T) { - - snapshotId := "2019-05-24T15-26-57-0|/sanity-controller-source-vol-859F8B9B-35BBFB36" - expected := "sanity-controller-source-vol-859F8B9B-35BBFB36" - actual, err := GetShareNameFromSnapshotId(snapshotId) - if err != nil { - t.Logf("Unexpected error, %v", err) - t.FailNow() - } - if !reflect.DeepEqual(actual, expected) { - t.Logf("Expected: %v", expected) - t.Logf("Actual: %v", actual) - t.FailNow() - } - - snapshotId = "2019-05-24T15-26-57-0" - _, err = GetShareNameFromSnapshotId(snapshotId) - if err == nil { - t.Logf("Expected error") - t.FailNow() - } -} - -func TestGetSnapshotIDFromSnapshotName(t *testing.T) { - expected := "2019-05-24T15-26-57-0|/sanity-controller-source-vol-859F8B9B-35BBFB36" - actual := GetSnapshotIDFromSnapshotName("2019-05-24T15-26-57-0", - "/sanity-controller-source-vol-859F8B9B-35BBFB36") - if !reflect.DeepEqual(actual, expected) { - t.Logf("Expected: %v", expected) - t.Logf("Actual: %v", actual) - t.FailNow() - } -} - -func TestGetVolumeNameFromPath(t *testing.T) { - expected := "test-volume" - actual := GetVolumeNameFromPath("/test-backing-share/test-volume") - if !reflect.DeepEqual(actual, expected) { - t.Logf("Expected: %v", expected) - t.Logf("Actual: %v", actual) - t.FailNow() - } -} \ No newline at end of file From a34cbcd6329dc606bf7fa019776c5ba08afc6e11 Mon Sep 17 00:00:00 2001 From: Ravi Kumar Date: Wed, 8 May 2024 09:44:31 +0000 Subject: [PATCH 04/10] HS-25174: 1. Added cache to driver. 2. Removed unwamted check for file create from /files api. 3. Added fix for delete volume. 4. Removed duplicate api call for getting share. --- main.go | 2 + pkg/client/hsclient.go | 27 +++++--- pkg/client/utils.go | 23 +++++++ pkg/common/host_utils.go | 26 ++++++-- pkg/common/hs_cache.go | 46 +++++++++++++ pkg/driver/controller.go | 140 ++++++++++++++++++++++++--------------- 6 files changed, 195 insertions(+), 69 deletions(-) create mode 100644 pkg/client/utils.go create mode 100644 pkg/common/hs_cache.go diff --git a/main.go b/main.go index 314b09e..498d9a9 100644 --- a/main.go +++ b/main.go @@ -36,7 +36,9 @@ func init() { defer cancel() // Setup logging log.SetFormatter(&log.JSONFormatter{ + PrettyPrint: true, DisableTimestamp: false, + TimestampFormat: "2006-01-02 15:04:05", }) log.SetOutput(os.Stdout) log.SetLevel(log.DebugLevel) diff --git a/pkg/client/hsclient.go b/pkg/client/hsclient.go index 366aa7b..d282e86 100755 --- a/pkg/client/hsclient.go +++ b/pkg/client/hsclient.go @@ -116,20 +116,20 @@ func (client *HammerspaceClient) GetPortalFloatingIp() (string, error) { floatingip := "" for _, p := range clusters.PortalFloatingIps { wg.Add(1) - go func(floatingip string) { + go func(fip string) { defer wg.Done() // check if rpcinfo gives a response - ok, err := common.CheckNFSExports(floatingip) + ok, err := common.CheckNFSExports(fip) if err != nil { - log.Warnf("Could not get exports for data-portal at %s. Error: %v", floatingip, err) + log.Warnf("Could not get exports for data-portal at %s. Error: %v", fip, err) return } // Check if exports has any values if ok { mutex.Lock() - floatingip = p.Address // Update the floating IP + floatingip = fip // Update the floating IP mutex.Unlock() log.Infof("Found floating IP data-portal %s", floatingip) } @@ -144,6 +144,11 @@ func (client *HammerspaceClient) GetPortalFloatingIp() (string, error) { // those with a matching nodeID are put at the top of the list func (client *HammerspaceClient) GetDataPortals(nodeID string) ([]common.DataPortal, error) { req, err := client.generateRequest("GET", "/data-portals/", "") + if err != nil { + log.Error(err) + return nil, err + } + statusCode, respBody, _, err := client.doRequest(*req) if err != nil { @@ -151,7 +156,7 @@ func (client *HammerspaceClient) GetDataPortals(nodeID string) ([]common.DataPor return nil, err } if statusCode != 200 { - return nil, errors.New(fmt.Sprintf(common.UnexpectedHSStatusCode, statusCode, 200)) + return nil, fmt.Errorf(common.UnexpectedHSStatusCode, statusCode, 200) } var portals []common.DataPortal @@ -270,7 +275,7 @@ func (client *HammerspaceClient) WaitForTaskCompletion(taskLocation string) (boo startTime := time.Now() var task common.Task - for time.Now().Sub(startTime) < taskPollTimeout { + for time.Since(startTime) < taskPollTimeout { d := b.Duration() time.Sleep(d) @@ -286,7 +291,7 @@ func (client *HammerspaceClient) WaitForTaskCompletion(taskLocation string) (boo return false, err } if statusCode != 200 { - return false, errors.New(fmt.Sprintf(common.UnexpectedHSStatusCode, statusCode, 200)) + return false, fmt.Errorf(common.UnexpectedHSStatusCode, statusCode, 200) } err = json.Unmarshal([]byte(respBody), &task) @@ -350,7 +355,8 @@ func (client *HammerspaceClient) ListObjectives() ([]common.ClusterObjectiveResp log.Error("Error parsing JSON response: " + err.Error()) } log.Debug(fmt.Sprintf("Found %d objectives", len(objs))) - + // set free capacity to cache expire in 5 min + SetCacheData("OBJECTIVE_LIST", objs, 60*5) return objs, nil } @@ -364,7 +370,8 @@ func (client *HammerspaceClient) ListObjectiveNames() ([]string, error) { for i, o := range objectives { objectiveNames[i] = o.Name } - + // set free capacity to cache expire in 5 min + SetCacheData("OBJECTIVE_LIST_NAMES", objectiveNames, 60*5) return objectiveNames, nil } @@ -1007,5 +1014,7 @@ func (client *HammerspaceClient) GetClusterAvailableCapacity() (int64, error) { log.Error("Error parsing free cluster capacity: " + err.Error()) } + // set free capacity to cache expire in 5 min + SetCacheData("FREE_CAPACITY", cluster.Capacity["free"], 60*5) return free, nil } diff --git a/pkg/client/utils.go b/pkg/client/utils.go new file mode 100644 index 0000000..f7b4ae4 --- /dev/null +++ b/pkg/client/utils.go @@ -0,0 +1,23 @@ +package client + +import ( + "time" + + "github.com/hammer-space/csi-plugin/pkg/common" +) + +var cache = common.CsiCache() + +func GetCacheData(key string) (interface{}, error) { + if cachedData, ok := cache.Get(key); ok { + return cachedData, nil + } + return nil, nil +} + +func SetCacheData(key string, value interface{}, cacheExpireTime int) { + if cacheExpireTime != 0 { + cacheExpireTime = 60 // 1 min is default timeout + } + cache.Set(key, value, time.Duration(cacheExpireTime)*time.Second) +} diff --git a/pkg/common/host_utils.go b/pkg/common/host_utils.go index 8033329..7169ffb 100644 --- a/pkg/common/host_utils.go +++ b/pkg/common/host_utils.go @@ -221,8 +221,20 @@ func FormatDevice(device, fsType string) error { func DeleteFile(pathname string) error { log.Infof("deleting file '%s'", pathname) - err := os.Remove(pathname) - if err != nil { + + // Check if the file exists + if _, err := os.Stat(pathname); err != nil { + // If the file does not exist, return without an error + if os.IsNotExist(err) { + log.Errorf("file '%s' does not exist", pathname) + return nil + } + // If there was an error other than the file not existing, return it + return err + } + + // Delete the file + if err := os.Remove(pathname); err != nil { return err } @@ -486,24 +498,24 @@ func SetMetadataTags(localPath string, tags map[string]string) error { _, err := ExecCommand("hs", "attribute", "set", "CSI_DETAILS", - fmt.Sprintf("-e \"CSI_DETAILS_TABLE{'%s','%s','%s','%s'}\"", CsiVersion, CsiPluginName, Version, Githash), + fmt.Sprintf("-e CSI_DETAILS_TABLE{%s,%s,%s,%s}", CsiVersion, CsiPluginName, Version, Githash), localPath) if err != nil { log.Warn("Failed to set CSI_DETAILS metadata " + err.Error()) } for tag_key, tag_value := range tags { - output, err := ExecCommand("hs", + output1, err := ExecCommand("hs", "-v", "tag", - "set", "-e", fmt.Sprintf("'%s'", tag_value), tag_key, localPath, + "set", "-e", tag_value, tag_key, localPath, ) // FIXME: The HS client returns exit code 0 even on failure, so we can't detect errors if err != nil { - log.Error("Failed to set tag " + err.Error()) + log.Errorf("Failed to set tag %v. Output - %v", err.Error(), output1) break } - log.Debugf("HS command output: %s", output) + log.Debugf("HS command output: %s", output1) } return err diff --git a/pkg/common/hs_cache.go b/pkg/common/hs_cache.go new file mode 100644 index 0000000..f0c71b7 --- /dev/null +++ b/pkg/common/hs_cache.go @@ -0,0 +1,46 @@ +package common + +import ( + "sync" + "time" +) + +type Cache struct { + data map[string]cacheValue + lock sync.Mutex +} + +type cacheValue struct { + value interface{} + expiration time.Time +} + +func CsiCache() *Cache { + return &Cache{ + data: make(map[string]cacheValue), + } +} + +func (c *Cache) Set(key string, value interface{}, expiration time.Duration) { + c.lock.Lock() + defer c.lock.Unlock() + + expirationTime := time.Now().Add(expiration) + c.data[key] = cacheValue{ + value: value, + expiration: expirationTime, + } +} + +func (c *Cache) Get(key string) (interface{}, bool) { + c.lock.Lock() + defer c.lock.Unlock() + + value, ok := c.data[key] + if !ok || time.Now().After(value.expiration) { + delete(c.data, key) + return nil, false + } + + return value.value, true +} diff --git a/pkg/driver/controller.go b/pkg/driver/controller.go index 84b71f4..1d86304 100755 --- a/pkg/driver/controller.go +++ b/pkg/driver/controller.go @@ -33,6 +33,7 @@ import ( "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + client "github.com/hammer-space/csi-plugin/pkg/client" "github.com/hammer-space/csi-plugin/pkg/common" ) @@ -89,7 +90,7 @@ func ParseVolParams(params map[string]string) (common.HSVolumeParameters, error) if exportOptionsParam, exists := params["exportOptions"]; exists { if exists { exportOptionsList := strings.Split(exportOptionsParam, ";") - vParams.ExportOptions = make([]common.ShareExportOptions, len(exportOptionsList), len(exportOptionsList)) + vParams.ExportOptions = make([]common.ShareExportOptions, len(exportOptionsList)) for i, o := range exportOptionsList { options := strings.Split(o, ",") //assert options is len 3 @@ -156,7 +157,7 @@ func (d *CSIDriver) ensureShareBackedVolumeExists( if err != nil { return status.Errorf(codes.Internal, err.Error()) } - if share != nil { // It exists! + if share.Size != hsVolume.Size { if share.Size != hsVolume.Size { return status.Errorf( codes.AlreadyExists, @@ -228,7 +229,7 @@ func (d *CSIDriver) ensureShareBackedVolumeExists( defer common.UnmountFilesystem(targetPath) err = d.publishShareBackedVolume(hsVolume.Path, targetPath, []string{}, false) if err != nil { - log.Warnf("failed to set additional metadata on share %v", err) + log.Warnf("failed to get share backed volume on hsVolumePath %s targetPath %s. Err %v", hsVolume.Path, targetPath, err) } // The hs client expects a trailing slash for directories err = common.SetMetadataTags(targetPath+"/", hsVolume.AdditionalMetadataTags) @@ -265,6 +266,9 @@ func (d *CSIDriver) ensureBackingShareExists(backingShareName string, hsVolume * targetPath := common.ShareStagingDir + "metadata-mounts" + hsVolume.Path defer common.UnmountFilesystem(targetPath) err = d.publishShareBackedVolume(hsVolume.Path, targetPath, []string{}, false) + if err != nil { + log.Warnf("failed to get share backed volume on hsVolumePath %s targetPath %s. Err %v", hsVolume.Path, targetPath, err) + } err = common.SetMetadataTags(targetPath+"/", hsVolume.AdditionalMetadataTags) if err != nil { log.Warnf("failed to set additional metadata on share %v", err) @@ -344,21 +348,25 @@ func (d *CSIDriver) ensureDeviceFileExists( } b := &backoff.Backoff{ - Max: 10 * time.Second, + Max: 2 * time.Second, Factor: 1.5, Jitter: true, } startTime := time.Now() var backingFileExists bool - for time.Now().Sub(startTime) < (10 * time.Minute) { + for time.Since(startTime) < (10 * time.Minute) { dur := b.Duration() time.Sleep(dur) + output, err := common.ExecCommand("ls", deviceFile) //Wait for file to exists on metadata server - backingFileExists, err = d.hsclient.DoesFileExist(hsVolume.Path) - if !backingFileExists { + // backingFileExists, err = d.hsclient.DoesFileExist(hsVolume.Path) + if err != nil { + backingFileExists = false time.Sleep(time.Second) } else { + log.Infof("file exist -> %s", string(output)) + backingFileExists = true break } } @@ -368,7 +376,7 @@ func (d *CSIDriver) ensureDeviceFileExists( } if len(hsVolume.Objectives) > 0 { - err = d.hsclient.SetObjectives(backingShare.ExportPath, "/"+hsVolume.Name, hsVolume.Objectives, true) + err = d.hsclient.SetObjectives(backingShare.Name, "/"+hsVolume.Name, hsVolume.Objectives, true) if err != nil { log.Warnf("failed to set objectives on backing file for volume %v", err) } @@ -407,6 +415,7 @@ func (d *CSIDriver) CreateVolume( req *csi.CreateVolumeRequest) ( *csi.CreateVolumeResponse, error) { + startTime := time.Now() // Validate Parameters if req.Name == "" { return nil, status.Error(codes.InvalidArgument, common.EmptyVolumeId) @@ -465,7 +474,7 @@ func (d *CSIDriver) CreateVolume( // Check we have available capacity cr := req.CapacityRange - var requestedSize int64 + var requestedSize int64 = 0 if cr != nil { if cr.LimitBytes != 0 { requestedSize = cr.LimitBytes @@ -474,45 +483,90 @@ func (d *CSIDriver) CreateVolume( } } else if fileBacked { requestedSize = common.DefaultBackingFileSizeBytes + } + + hsVolume := &common.HSVolume{ + DeleteDelay: vParams.DeleteDelay, + ExportOptions: vParams.ExportOptions, + Objectives: vParams.Objectives, + BlockBackingShareName: vParams.BlockBackingShareName, + MountBackingShareName: vParams.MountBackingShareName, + Size: strconv.FormatInt(requestedSize, 10), + Name: volumeName, + VolumeMode: volumeMode, + FSType: fsType, + AdditionalMetadataTags: vParams.AdditionalMetadataTags, + Comment: vParams.Comment, + } + var backingShare *common.ShareResponse + // if it's file backed, we should check capacity of backing share + var backingShareName string + if blockRequested { + backingShareName = vParams.BlockBackingShareName } else { - requestedSize = 0 + backingShareName = vParams.MountBackingShareName + } + backingShare, err = d.hsclient.GetShare(backingShareName) + if err != nil { + log.Infof("share dosent exist ensuring share exist.") + backingShare, err = d.ensureBackingShareExists(backingShare.Name, hsVolume) + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } } if requestedSize > 0 { + freeCapacity, err := client.GetCacheData("FREE_CAPACITY") + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } var available int64 - if fileBacked { - // if it's file backed, we should check capacity of backing share - var backingShareName string - if blockRequested { - backingShareName = vParams.BlockBackingShareName - } else { - backingShareName = vParams.MountBackingShareName - } - backingShare, err := d.hsclient.GetShare(backingShareName) - if backingShare == nil || err != nil { - available, err = d.hsclient.GetClusterAvailableCapacity() + + if freeCapacity != nil { + switch v := freeCapacity.(type) { + case int64: + available = v + case string: + d, err := strconv.ParseInt(v, 10, 64) if err != nil { - return nil, status.Error(codes.Internal, err.Error()) + log.Errorf("error while parsing free capacity. Err %v", err) } - } else { - available, _ = strconv.ParseInt(backingShare.Space.Available, 10, 64) + available = d + default: + return nil, status.Error(codes.Internal, "unexpected type for free capacity") } } else { + log.Infof("getting free capacity from api response") + // Call your function to get the free capacity from the API response here available, err = d.hsclient.GetClusterAvailableCapacity() if err != nil { return nil, status.Error(codes.Internal, err.Error()) } } + if available < requestedSize { return nil, status.Errorf(codes.OutOfRange, common.OutOfCapacity, requestedSize, available) } } //// Check if objectives exist on the cluster - clusterObjectiveNames, err := d.hsclient.ListObjectiveNames() + var clusterObjectiveNames []string + cachedObjectiveList, err := client.GetCacheData("OBJECTIVE_LIST_NAMES") if err != nil { return nil, status.Error(codes.Internal, err.Error()) } + if cachedObjectiveList != nil { + if objectives, ok := cachedObjectiveList.([]string); ok && len(objectives) > 0 { + // If cached objective list is not nil and not empty, assign it to clusterObjectiveNames + clusterObjectiveNames = objectives + } + } else { + // If cached objective list is nil or empty, fetch it from the API + clusterObjectiveNames, err = d.hsclient.ListObjectiveNames() + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + } for _, o := range vParams.Objectives { if !IsValueInList(o, clusterObjectiveNames) { @@ -524,19 +578,6 @@ func (d *CSIDriver) CreateVolume( defer d.releaseVolumeLock(volumeName) d.getVolumeLock(volumeName) - hsVolume := &common.HSVolume{ - DeleteDelay: vParams.DeleteDelay, - ExportOptions: vParams.ExportOptions, - Objectives: vParams.Objectives, - BlockBackingShareName: vParams.BlockBackingShareName, - MountBackingShareName: vParams.MountBackingShareName, - Size: strconv.FormatInt(requestedSize, 10), - Name: volumeName, - VolumeMode: volumeMode, - FSType: fsType, - AdditionalMetadataTags: vParams.AdditionalMetadataTags, - Comment: vParams.Comment, - } if snap != nil { sourceSnapName, err := GetSnapshotNameFromSnapshotId(snap.GetSnapshotId()) if err != nil { @@ -552,22 +593,11 @@ func (d *CSIDriver) CreateVolume( } if fileBacked { - var backingShareName string - if blockRequested { - if hsVolume.BlockBackingShareName == "" { - return nil, status.Error(codes.InvalidArgument, common.MissingBlockBackingShareName) - } - backingShareName = hsVolume.BlockBackingShareName - } else { - if hsVolume.MountBackingShareName == "" { - return nil, status.Error(codes.InvalidArgument, common.MissingMountBackingShareName) - } - backingShareName = hsVolume.MountBackingShareName - } err = d.ensureFileBackedVolumeExists(ctx, hsVolume, backingShareName) if err != nil { return nil, err } + } else { // TODO/FIXME: create from snapshot // Workaround: @@ -594,7 +624,12 @@ func (d *CSIDriver) CreateVolume( volContext["mountBackingShareName"] = hsVolume.MountBackingShareName volContext["fsType"] = fsType } - hsVolumeSize, _ := strconv.ParseInt(hsVolume.Size, 10, 64) + + hsVolumeSize, err := strconv.ParseInt(hsVolume.Size, 10, 64) + if err != nil { + log.Errorf("uable to parse hsVolume.size to int64 %v", err) + } + log.Infof("Total time taken for create volume %v", time.Since(startTime)) return &csi.CreateVolumeResponse{ Volume: &csi.Volume{ CapacityBytes: hsVolumeSize, @@ -616,7 +651,6 @@ func (d *CSIDriver) deleteFileBackedVolume(filepath string) error { return status.Errorf(codes.FailedPrecondition, common.VolumeDeleteHasSnapshots) } - residingSharePath := path.Dir(filepath) residingShareName := path.Base(path.Dir(filepath)) if exists { @@ -626,7 +660,7 @@ func (d *CSIDriver) deleteFileBackedVolume(filepath string) error { defer d.releaseVolumeLock(residingShareName) d.getVolumeLock(residingShareName) defer d.UnmountBackingShareIfUnused(residingShareName) - err := d.EnsureBackingShareMounted(residingSharePath) // check if share is mounted + err := d.EnsureBackingShareMounted(residingShareName) // check if share is mounted if err != nil { log.Errorf("failed to ensure backing share is mounted, %v", err) return status.Errorf(codes.Internal, err.Error()) From 9c6d4dbca738d7894def9c8830046c6f6060b747 Mon Sep 17 00:00:00 2001 From: Ravi Kumar Date: Thu, 16 May 2024 06:26:08 +0000 Subject: [PATCH 05/10] Added nil pointer fix for create share with create volume and deletevolume. --- .gitignore | 5 +- .pre-commit-config.yaml | 8 + pkg/client/hsclient.go | 2 +- pkg/client/test/fakes_test.go | 10 +- pkg/common/config.go | 53 +- pkg/driver/controller.go | 99 ++- pkg/driver/driver.go | 234 +++--- pkg/driver/driver_csi_v0.go | 754 +++++++++---------- pkg/driver/identity.go | 116 +-- pkg/driver/node.go | 2 +- pkg/driver/utils.go | 347 ++++----- test/sanity/hammerspace_file_backed_block.go | 45 +- test/sanity/hammerspace_file_backed_mount.go | 42 +- test/sanity/hammerspace_negative.go | 5 +- test/sanity/hammerspace_nfs.go | 33 +- test/sanity/sanity_test.go | 1 - test/sanity/sanity_utils.go | 17 +- 17 files changed, 891 insertions(+), 882 deletions(-) create mode 100644 .pre-commit-config.yaml diff --git a/.gitignore b/.gitignore index a99fb63..70b5370 100755 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,9 @@ bin/ tmp/ -hammerspace-cs-driver +hammerspace-csi-driver csi-plugin - +deploy-test/ +vendor/ *.swp *.test *.tests diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..cb922e7 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,8 @@ +repos: + - repo: https://github.com/dnephin/pre-commit-golang + rev: v0.4.0 + hooks: + - id: go-fmt + - id: go-vet + - id: go-imports + - id: go-mod-tidy \ No newline at end of file diff --git a/pkg/client/hsclient.go b/pkg/client/hsclient.go index d282e86..99e5ea1 100755 --- a/pkg/client/hsclient.go +++ b/pkg/client/hsclient.go @@ -453,7 +453,7 @@ func (client *HammerspaceClient) GetShare(name string) (*common.ShareResponse, e return nil, nil } if statusCode != 200 { - return nil, errors.New(fmt.Sprintf(common.UnexpectedHSStatusCode, statusCode, 200)) + return nil, fmt.Errorf(common.UnexpectedHSStatusCode, statusCode, 200) } var share common.ShareResponse diff --git a/pkg/client/test/fakes_test.go b/pkg/client/test/fakes_test.go index 9ef89f2..27925fb 100644 --- a/pkg/client/test/fakes_test.go +++ b/pkg/client/test/fakes_test.go @@ -17,7 +17,7 @@ limitations under the License. package client const ( - FakeShareRoot = ` + FakeShareRoot = ` { "uoid": { "uuid": "acd90e88-ed23-3464-90ee-320e11de31ae", @@ -53,7 +53,7 @@ const ( "scheduledPurgeTime": null } ` - FakeShare1 = ` + FakeShare1 = ` { "uoid": { "uuid": "ac486652-6957-43cd-ac75-9885b3b3e9c9", @@ -102,7 +102,7 @@ const ( } ` - FakeTaskCompleted = ` + FakeTaskCompleted = ` { "uuid": "a59ad344-6f1a-4ef2-b1e2-1d232707978d", "name": "share-create", @@ -111,7 +111,7 @@ const ( } ` - FakeTaskFailed = ` + FakeTaskFailed = ` { "uuid": "b59ad344-6f1a-4ef2-b1e2-1d232707978d", "name": "share-create", @@ -120,7 +120,7 @@ const ( } ` - FakeTaskRunning = ` + FakeTaskRunning = ` { "uuid": "c59ad344-6f1a-4ef2-b1e2-1d232707978d", "name": "share-create", diff --git a/pkg/common/config.go b/pkg/common/config.go index e8863c5..91f1055 100644 --- a/pkg/common/config.go +++ b/pkg/common/config.go @@ -19,42 +19,41 @@ package common import "time" const ( - CsiPluginName = "com.hammerspace.csi" + CsiPluginName = "com.hammerspace.csi" - // Directory on hosts where backing shares for file-backed volumes will be mounted - // Must end with a "/" - ShareStagingDir = "/tmp" - SharePathPrefix = "/" - DefaultBackingFileSizeBytes = 1073741824 - DefaultVolumeNameFormat = "%s" + // Directory on hosts where backing shares for file-backed volumes will be mounted + // Must end with a "/" + ShareStagingDir = "/tmp" + SharePathPrefix = "/" + DefaultBackingFileSizeBytes = 1073741824 + DefaultVolumeNameFormat = "%s" - // Topology keys - TopologyKeyDataPortal = "topology.csi.hammerspace.com/is-data-portal" + // Topology keys + TopologyKeyDataPortal = "topology.csi.hammerspace.com/is-data-portal" ) var ( - // These should be set at compile time - Version = "NONE" - Githash = "NONE" + // These should be set at compile time + Version = "NONE" + Githash = "NONE" - CsiVersion = "1" + CsiVersion = "1" - // The list of export path prefixes to try to use, in order, when mounting to a data portal - DefaultDataPortalMountPrefixes = [...]string{"/", "/mnt/data-portal", ""} - DataPortalMountPrefix = "" - CommandExecTimeout = 300 * time.Second // Seconds + // The list of export path prefixes to try to use, in order, when mounting to a data portal + DefaultDataPortalMountPrefixes = [...]string{"/", "/mnt/data-portal", ""} + DataPortalMountPrefix = "" + CommandExecTimeout = 300 * time.Second // Seconds - - UseAnvil bool + UseAnvil bool ) // Extended info to be set on every share created by the driver -func GetCommonExtendedInfo() (map[string]string) { - extendedInfo := map[string]string{ - "csi_created_by_plugin_name": CsiPluginName, - "csi_created_by_plugin_version": Version, - "csi_created_by_plugin_git_hash": Githash, - "csi_created_by_csi_version": CsiVersion, - } - return extendedInfo +func GetCommonExtendedInfo() map[string]string { + extendedInfo := map[string]string{ + "csi_created_by_plugin_name": CsiPluginName, + "csi_created_by_plugin_version": Version, + "csi_created_by_plugin_git_hash": Githash, + "csi_created_by_csi_version": CsiVersion, + } + return extendedInfo } diff --git a/pkg/driver/controller.go b/pkg/driver/controller.go index 1d86304..7740cba 100755 --- a/pkg/driver/controller.go +++ b/pkg/driver/controller.go @@ -148,31 +148,44 @@ func ParseVolParams(params map[string]string) (common.HSVolumeParameters, error) return vParams, nil } -func (d *CSIDriver) ensureShareBackedVolumeExists( - ctx context.Context, - hsVolume *common.HSVolume) error { +func (d *CSIDriver) ensureShareBackedVolumeExists(ctx context.Context, hsVolume *common.HSVolume) error { - //// Check if Mount Volume Exists + // Check if the Mount Volume Exists share, err := d.hsclient.GetShare(hsVolume.Name) if err != nil { - return status.Errorf(codes.Internal, err.Error()) + return fmt.Errorf("failed to get share: %w", err) } - if share.Size != hsVolume.Size { - if share.Size != hsVolume.Size { - return status.Errorf( - codes.AlreadyExists, - common.VolumeExistsSizeMismatch, - share.Size, - hsVolume.Size) - } - if share.ShareState == "REMOVED" { - return status.Errorf(codes.Aborted, common.VolumeBeingDeleted) + if share == nil { + // Create the Mountvolume + err = d.hsclient.CreateShare( + hsVolume.Name, + hsVolume.Path, + hsVolume.Size, + hsVolume.Objectives, + hsVolume.ExportOptions, + hsVolume.DeleteDelay, + hsVolume.Comment, + ) + + if err != nil { + return status.Errorf(codes.Internal, err.Error()) } - // FIXME: Check that it's objectives, export options, deleteDelay(extended info), - // etc match (optional functionality with CSI 1.0) + } + if err != nil { + return fmt.Errorf("share not found") + } + if share.Size != hsVolume.Size { + return status.Errorf( + codes.AlreadyExists, + common.VolumeExistsSizeMismatch, + share.Size, + hsVolume.Size) + } - return nil + if share.ShareState == "REMOVED" { + return status.Errorf(codes.Aborted, common.VolumeBeingDeleted) } + if hsVolume.SourceSnapPath != "" { // Create from snapshot sourceShare, err := d.hsclient.GetShare(hsVolume.SourceSnapShareName) @@ -241,15 +254,12 @@ func (d *CSIDriver) ensureShareBackedVolumeExists( func (d *CSIDriver) ensureBackingShareExists(backingShareName string, hsVolume *common.HSVolume) (*common.ShareResponse, error) { share, err := d.hsclient.GetShare(backingShareName) - if err != nil { - return share, status.Errorf(codes.Internal, err.Error()) - } if share == nil { err = d.hsclient.CreateShare( backingShareName, - "/"+backingShareName, - "-1", - []string{}, + hsVolume.Path, + hsVolume.Size, + hsVolume.Objectives, hsVolume.ExportOptions, hsVolume.DeleteDelay, hsVolume.Comment, @@ -365,7 +375,7 @@ func (d *CSIDriver) ensureDeviceFileExists( backingFileExists = false time.Sleep(time.Second) } else { - log.Infof("file exist -> %s", string(output)) + log.Infof("file exist - %s", string(output)) backingFileExists = true break } @@ -441,7 +451,7 @@ func (d *CSIDriver) CreateVolume( var blockRequested bool var filesystemRequested bool var fileBacked bool - var fsType string + var fsType string = "nfs" for _, cap := range req.VolumeCapabilities { switch cap.AccessType.(type) { case *csi.VolumeCapability_Block: @@ -450,9 +460,7 @@ func (d *CSIDriver) CreateVolume( case *csi.VolumeCapability_Mount: filesystemRequested = true fsType = vParams.FSType - if fsType == "" { - fsType = "nfs" - } else if fsType != "nfs" { + if fsType != "nfs" { fileBacked = true } } @@ -507,7 +515,7 @@ func (d *CSIDriver) CreateVolume( backingShareName = vParams.MountBackingShareName } backingShare, err = d.hsclient.GetShare(backingShareName) - if err != nil { + if err != nil || backingShare == nil { log.Infof("share dosent exist ensuring share exist.") backingShare, err = d.ensureBackingShareExists(backingShare.Name, hsVolume) if err != nil { @@ -605,7 +613,6 @@ func (d *CSIDriver) CreateVolume( // restore snap to weird path // move weird path to proper location // NOTE: Expect this to change when we change restore from snapshot in the core product. - hsVolume.Path = common.SharePathPrefix + volumeName err = d.ensureShareBackedVolumeExists(ctx, hsVolume) if err != nil { @@ -620,7 +627,7 @@ func (d *CSIDriver) CreateVolume( if volumeMode == "Block" { volContext["blockBackingShareName"] = hsVolume.BlockBackingShareName - } else if volumeMode == "Filesystem" && fsType != "nfs" { + } else if volumeMode == "Filesystem" { volContext["mountBackingShareName"] = hsVolume.MountBackingShareName volContext["fsType"] = fsType } @@ -640,9 +647,11 @@ func (d *CSIDriver) CreateVolume( } func (d *CSIDriver) deleteFileBackedVolume(filepath string) error { + var err error var exists bool - if exists, _ = d.hsclient.DoesFileExist(filepath); exists { - log.Debugf("found file-backed volume to delete, %s", filepath) + exists, err = d.hsclient.DoesFileExist(filepath) + if err != nil { + return fmt.Errorf("file not found error while fetching file from file path %s. Error %v", filepath, err.Error()) } // Check if file has snapshots and fail @@ -654,6 +663,7 @@ func (d *CSIDriver) deleteFileBackedVolume(filepath string) error { residingShareName := path.Base(path.Dir(filepath)) if exists { + log.Debugf("found file-backed volume to delete, %s", filepath) // mount share and delete file destination := common.ShareStagingDir + path.Dir(filepath) // grab and defer a lock here for the backing share @@ -673,7 +683,7 @@ func (d *CSIDriver) deleteFileBackedVolume(filepath string) error { } } - return nil + return err } func (d *CSIDriver) deleteShareBackedVolume(share *common.ShareResponse) error { @@ -716,16 +726,25 @@ func (d *CSIDriver) DeleteVolume( d.getVolumeLock(volumeId) volumeName := GetVolumeNameFromPath(volumeId) + log.Infof("delete volume checking for share name %s. Volume Id %s", volumeName, volumeId) share, err := d.hsclient.GetShare(volumeName) if err != nil { - return nil, status.Errorf(codes.Internal, err.Error()) + return nil, fmt.Errorf("unable to get share, volume id %s and volume name %s. Err %v", volumeId, volumeName, err) } if share == nil { // Share does not exist, may be a file-backed volume + log.Infof("share is nil and Volume Id %s", volumeId) err = d.deleteFileBackedVolume(volumeId) + if err != nil { + return nil, fmt.Errorf("unable to delete file backed volume, volume id %s and volume name %s. Err %v", volumeId, volumeName, err) + } return &csi.DeleteVolumeResponse{}, err } else { // Share exists and is a Filesystem + log.Infof("share exist with name %s", share.Name) err = d.deleteShareBackedVolume(share) + if err != nil { + return nil, fmt.Errorf("unable to delete share backed volume, share %s and volume name %s. Err %v", share.Name, volumeName, err) + } return &csi.DeleteVolumeResponse{}, err } @@ -803,11 +822,9 @@ func (d *CSIDriver) ControllerExpandVolume( // if required - current > available on backend share sizeDiff := requestedSize - fileSize backingShareName := path.Base(path.Dir(req.GetVolumeId())) - backingShare, err := d.hsclient.GetShare(backingShareName) - var available int64 - if err != nil { - available = 0 - } else { + backingShare, _ := d.hsclient.GetShare(backingShareName) + var available int64 = 0 + if backingShare != nil { available, _ = strconv.ParseInt(backingShare.Space.Available, 10, 64) } diff --git a/pkg/driver/driver.go b/pkg/driver/driver.go index f71a1e3..a6d95bf 100644 --- a/pkg/driver/driver.go +++ b/pkg/driver/driver.go @@ -38,166 +38,166 @@ import ( ) type CSIDriver struct { - listener net.Listener - server *grpc.Server - wg sync.WaitGroup - running bool - lock sync.Mutex - volumeLocks map[string]*sync.Mutex //This only grows and may be a memory issue - snapshotLocks map[string]*sync.Mutex - hsclient *client.HammerspaceClient - NodeID string + listener net.Listener + server *grpc.Server + wg sync.WaitGroup + running bool + lock sync.Mutex + volumeLocks map[string]*sync.Mutex //This only grows and may be a memory issue + snapshotLocks map[string]*sync.Mutex + hsclient *client.HammerspaceClient + NodeID string } func NewCSIDriver(endpoint, username, password, tlsVerifyStr string) *CSIDriver { - tlsVerify := false - if os.Getenv("HS_TLS_VERIFY") != "" { - tlsVerify, _ = strconv.ParseBool(tlsVerifyStr) - } else { - tlsVerify = false - } - client, err := client.NewHammerspaceClient(endpoint, username, password, tlsVerify) - if err != nil { - log.Error(err) - os.Exit(1) - } - // We now require mounting through a DSX server - common.UseAnvil = false - - return &CSIDriver{ - hsclient: client, - volumeLocks: make(map[string]*sync.Mutex), - snapshotLocks: make(map[string]*sync.Mutex), - NodeID: os.Getenv("CSI_NODE_NAME"), - } + tlsVerify := false + if os.Getenv("HS_TLS_VERIFY") != "" { + tlsVerify, _ = strconv.ParseBool(tlsVerifyStr) + } else { + tlsVerify = false + } + client, err := client.NewHammerspaceClient(endpoint, username, password, tlsVerify) + if err != nil { + log.Error(err) + os.Exit(1) + } + // We now require mounting through a DSX server + common.UseAnvil = false + + return &CSIDriver{ + hsclient: client, + volumeLocks: make(map[string]*sync.Mutex), + snapshotLocks: make(map[string]*sync.Mutex), + NodeID: os.Getenv("CSI_NODE_NAME"), + } } func (c *CSIDriver) getVolumeLock(volName string) { - if _, exists := c.volumeLocks[volName]; !exists { - c.volumeLocks[volName] = &sync.Mutex{} - } - c.volumeLocks[volName].Lock() + if _, exists := c.volumeLocks[volName]; !exists { + c.volumeLocks[volName] = &sync.Mutex{} + } + c.volumeLocks[volName].Lock() } func (c *CSIDriver) releaseVolumeLock(volName string) { - if _, exists := c.volumeLocks[volName]; exists { - if exists { - c.volumeLocks[volName].Unlock() - } - } + if _, exists := c.volumeLocks[volName]; exists { + if exists { + c.volumeLocks[volName].Unlock() + } + } } func (c *CSIDriver) getSnapshotLock(volName string) { - if _, exists := c.snapshotLocks[volName]; !exists { - c.snapshotLocks[volName] = &sync.Mutex{} - } - c.snapshotLocks[volName].Lock() + if _, exists := c.snapshotLocks[volName]; !exists { + c.snapshotLocks[volName] = &sync.Mutex{} + } + c.snapshotLocks[volName].Lock() } func (c *CSIDriver) releaseSnapshotLock(volName string) { - if _, exists := c.snapshotLocks[volName]; exists { - if exists { - c.snapshotLocks[volName].Unlock() - } - } + if _, exists := c.snapshotLocks[volName]; exists { + if exists { + c.snapshotLocks[volName].Unlock() + } + } } func (c *CSIDriver) goServe(started chan<- bool) { - c.wg.Add(1) - go func() { - defer c.wg.Done() - started <- true - err := c.server.Serve(c.listener) - if err != nil { - panic(err.Error()) - } - }() + c.wg.Add(1) + go func() { + defer c.wg.Done() + started <- true + err := c.server.Serve(c.listener) + if err != nil { + panic(err.Error()) + } + }() } func (c *CSIDriver) Address() string { - return c.listener.Addr().String() + return c.listener.Addr().String() } func (c *CSIDriver) Start(l net.Listener) error { - c.lock.Lock() - defer c.lock.Unlock() - - // Set listener - c.listener = l - - // Create a new grpc server - c.server = grpc.NewServer( - grpc.UnaryInterceptor(c.callInterceptor), - grpc.KeepaliveParams(keepalive.ServerParameters{ - Time: 5 * time.Minute, - }), - ) - - csi.RegisterControllerServer(c.server, c) - csi.RegisterIdentityServer(c.server, c) - csi.RegisterNodeServer(c.server, c) - reflection.Register(c.server) - - // Start listening for requests - waitForServer := make(chan bool) - c.goServe(waitForServer) - <-waitForServer - c.running = true - return nil + c.lock.Lock() + defer c.lock.Unlock() + + // Set listener + c.listener = l + + // Create a new grpc server + c.server = grpc.NewServer( + grpc.UnaryInterceptor(c.callInterceptor), + grpc.KeepaliveParams(keepalive.ServerParameters{ + Time: 5 * time.Minute, + }), + ) + + csi.RegisterControllerServer(c.server, c) + csi.RegisterIdentityServer(c.server, c) + csi.RegisterNodeServer(c.server, c) + reflection.Register(c.server) + + // Start listening for requests + waitForServer := make(chan bool) + c.goServe(waitForServer) + <-waitForServer + c.running = true + return nil } func (c *CSIDriver) Stop() { - c.lock.Lock() - defer c.lock.Unlock() + c.lock.Lock() + defer c.lock.Unlock() - if !c.running { - return - } + if !c.running { + return + } - c.server.Stop() - c.wg.Wait() + c.server.Stop() + c.wg.Wait() } func (c *CSIDriver) Close() { - c.server.Stop() + c.server.Stop() } func (c *CSIDriver) IsRunning() bool { - c.lock.Lock() - defer c.lock.Unlock() + c.lock.Lock() + defer c.lock.Unlock() - return c.running + return c.running } func (c *CSIDriver) GetHammerspaceClient() *client.HammerspaceClient { - return c.hsclient + return c.hsclient } func (c *CSIDriver) callInterceptor( - ctx context.Context, - req interface{}, - info *grpc.UnaryServerInfo, - handler grpc.UnaryHandler) (interface{}, error) { - rsp, err := handler(ctx, req) - logGRPC(info.FullMethod, req, rsp, err) - return rsp, err + ctx context.Context, + req interface{}, + info *grpc.UnaryServerInfo, + handler grpc.UnaryHandler) (interface{}, error) { + rsp, err := handler(ctx, req) + logGRPC(info.FullMethod, req, rsp, err) + return rsp, err } func logGRPC(method string, request, reply interface{}, err error) { - // Log JSON with the request and response for easier parsing - logMessage := struct { - Method string - Request interface{} - Response interface{} - Error string - }{ - Method: method, - Request: request, - Response: reply, - } - if err != nil { - logMessage.Error = err.Error() - } - msg, _ := json.Marshal(logMessage) - fmt.Printf("gRPCCall: %s\n", msg) + // Log JSON with the request and response for easier parsing + logMessage := struct { + Method string + Request interface{} + Response interface{} + Error string + }{ + Method: method, + Request: request, + Response: reply, + } + if err != nil { + logMessage.Error = err.Error() + } + msg, _ := json.Marshal(logMessage) + fmt.Printf("gRPCCall: %s\n", msg) } diff --git a/pkg/driver/driver_csi_v0.go b/pkg/driver/driver_csi_v0.go index ae69ceb..bf2f38e 100644 --- a/pkg/driver/driver_csi_v0.go +++ b/pkg/driver/driver_csi_v0.go @@ -34,494 +34,480 @@ import ( ) type CSIDriver_v0Support struct { - listener net.Listener - server *grpc.Server - wg sync.WaitGroup - running bool - lock sync.Mutex - driver *CSIDriver + listener net.Listener + server *grpc.Server + wg sync.WaitGroup + running bool + lock sync.Mutex + driver *CSIDriver } func NewCSIDriver_v0Support(driver *CSIDriver) *CSIDriver_v0Support { - return &CSIDriver_v0Support{ - driver: driver, - } + return &CSIDriver_v0Support{ + driver: driver, + } } func (c *CSIDriver_v0Support) goServe(started chan<- bool) { - c.wg.Add(1) - go func() { - defer c.wg.Done() - started <- true - err := c.server.Serve(c.listener) - if err != nil { - panic(err.Error()) - } - }() + c.wg.Add(1) + go func() { + defer c.wg.Done() + started <- true + err := c.server.Serve(c.listener) + if err != nil { + panic(err.Error()) + } + }() } func (c *CSIDriver_v0Support) Address() string { - return c.listener.Addr().String() + return c.listener.Addr().String() } func (c *CSIDriver_v0Support) Start(l net.Listener) error { - c.lock.Lock() - defer c.lock.Unlock() - - log.Infof("Starting gRPC server with CSI v0 support") - - // Set listener - c.listener = l - - // Create a new grpc server - c.server = grpc.NewServer( - grpc.UnaryInterceptor(c.callInterceptor), - grpc.KeepaliveParams(keepalive.ServerParameters{ - Time: 5 * time.Minute, - }), - ) - - csi_v0.RegisterControllerServer(c.server, c) - csi_v0.RegisterIdentityServer(c.server, c) - csi_v0.RegisterNodeServer(c.server, c) - reflection.Register(c.server) - - // Start listening for requests - waitForServer := make(chan bool) - c.goServe(waitForServer) - <-waitForServer - c.running = true - return nil + c.lock.Lock() + defer c.lock.Unlock() + + log.Infof("Starting gRPC server with CSI v0 support") + + // Set listener + c.listener = l + + // Create a new grpc server + c.server = grpc.NewServer( + grpc.UnaryInterceptor(c.callInterceptor), + grpc.KeepaliveParams(keepalive.ServerParameters{ + Time: 5 * time.Minute, + }), + ) + + csi_v0.RegisterControllerServer(c.server, c) + csi_v0.RegisterIdentityServer(c.server, c) + csi_v0.RegisterNodeServer(c.server, c) + reflection.Register(c.server) + + // Start listening for requests + waitForServer := make(chan bool) + c.goServe(waitForServer) + <-waitForServer + c.running = true + return nil } func (c *CSIDriver_v0Support) Stop() { - c.lock.Lock() - defer c.lock.Unlock() + c.lock.Lock() + defer c.lock.Unlock() - if !c.running { - return - } + if !c.running { + return + } - c.server.Stop() - c.wg.Wait() + c.server.Stop() + c.wg.Wait() } func (c *CSIDriver_v0Support) Close() { - c.server.Stop() + c.server.Stop() } func (c *CSIDriver_v0Support) IsRunning() bool { - c.lock.Lock() - defer c.lock.Unlock() + c.lock.Lock() + defer c.lock.Unlock() - return c.running + return c.running } func (c *CSIDriver_v0Support) callInterceptor( - ctx context.Context, - req interface{}, - info *grpc.UnaryServerInfo, - handler grpc.UnaryHandler) (interface{}, error) { - rsp, err := handler(ctx, req) - logGRPC(info.FullMethod, req, rsp, err) - return rsp, err + ctx context.Context, + req interface{}, + info *grpc.UnaryServerInfo, + handler grpc.UnaryHandler) (interface{}, error) { + rsp, err := handler(ctx, req) + logGRPC(info.FullMethod, req, rsp, err) + return rsp, err } - func (d *CSIDriver_v0Support) CreateVolume( - ctx context.Context, - req *csi_v0.CreateVolumeRequest) ( - *csi_v0.CreateVolumeResponse, error) { - - // Change capabilities from v0 -> v1 - caps := []*csi.VolumeCapability{} - for _, cap := range req.GetVolumeCapabilities() { - capv1, err := ConvertVolumeCapabilityFromv0Tov1(cap) - - if err != nil { - return nil, err - } - caps = append(caps, capv1) - } - - // CapacityRange from v0 -> v1 - capacityRange := &csi.CapacityRange{ - RequiredBytes: req.GetCapacityRange().GetRequiredBytes(), - LimitBytes: req.GetCapacityRange().GetLimitBytes(), - } - - //call driver - res, err := d.driver.CreateVolume(ctx, &csi.CreateVolumeRequest{ - Name: req.GetName(), - CapacityRange: capacityRange, - VolumeCapabilities: caps, - Parameters: req.GetParameters(), - Secrets: req.ControllerCreateSecrets, - }) - if err != nil { - return nil, err - } - - return &csi_v0.CreateVolumeResponse{ - Volume: &csi_v0.Volume{ - CapacityBytes: res.Volume.CapacityBytes, - Id: res.Volume.GetVolumeId(), - Attributes: res.GetVolume().GetVolumeContext(), - }, - }, err + ctx context.Context, + req *csi_v0.CreateVolumeRequest) ( + *csi_v0.CreateVolumeResponse, error) { + + // Change capabilities from v0 -> v1 + caps := []*csi.VolumeCapability{} + for _, cap := range req.GetVolumeCapabilities() { + capv1, err := ConvertVolumeCapabilityFromv0Tov1(cap) + + if err != nil { + return nil, err + } + caps = append(caps, capv1) + } + + // CapacityRange from v0 -> v1 + capacityRange := &csi.CapacityRange{ + RequiredBytes: req.GetCapacityRange().GetRequiredBytes(), + LimitBytes: req.GetCapacityRange().GetLimitBytes(), + } + + //call driver + res, err := d.driver.CreateVolume(ctx, &csi.CreateVolumeRequest{ + Name: req.GetName(), + CapacityRange: capacityRange, + VolumeCapabilities: caps, + Parameters: req.GetParameters(), + Secrets: req.ControllerCreateSecrets, + }) + if err != nil { + return nil, err + } + + return &csi_v0.CreateVolumeResponse{ + Volume: &csi_v0.Volume{ + CapacityBytes: res.Volume.CapacityBytes, + Id: res.Volume.GetVolumeId(), + Attributes: res.GetVolume().GetVolumeContext(), + }, + }, err } func (d *CSIDriver_v0Support) DeleteVolume( - ctx context.Context, - req *csi_v0.DeleteVolumeRequest) ( - *csi_v0.DeleteVolumeResponse, error) { - - _, err := d.driver.DeleteVolume(ctx, &csi.DeleteVolumeRequest{ - VolumeId: req.GetVolumeId(), - Secrets: req.GetControllerDeleteSecrets(), - }) - return &csi_v0.DeleteVolumeResponse{}, err + ctx context.Context, + req *csi_v0.DeleteVolumeRequest) ( + *csi_v0.DeleteVolumeResponse, error) { + + _, err := d.driver.DeleteVolume(ctx, &csi.DeleteVolumeRequest{ + VolumeId: req.GetVolumeId(), + Secrets: req.GetControllerDeleteSecrets(), + }) + return &csi_v0.DeleteVolumeResponse{}, err } - - func (d *CSIDriver_v0Support) ControllerPublishVolume( - ctx context.Context, - req *csi_v0.ControllerPublishVolumeRequest) ( - *csi_v0.ControllerPublishVolumeResponse, error) { - return nil, status.Error(codes.Unimplemented, "ControllerPublishVolume not supported") + ctx context.Context, + req *csi_v0.ControllerPublishVolumeRequest) ( + *csi_v0.ControllerPublishVolumeResponse, error) { + return nil, status.Error(codes.Unimplemented, "ControllerPublishVolume not supported") } func (d *CSIDriver_v0Support) ControllerUnpublishVolume( - ctx context.Context, - req *csi_v0.ControllerUnpublishVolumeRequest) ( - *csi_v0.ControllerUnpublishVolumeResponse, error) { - return nil, status.Error(codes.Unimplemented, "ControllerUnpublishVolume not supported") + ctx context.Context, + req *csi_v0.ControllerUnpublishVolumeRequest) ( + *csi_v0.ControllerUnpublishVolumeResponse, error) { + return nil, status.Error(codes.Unimplemented, "ControllerUnpublishVolume not supported") } func (d *CSIDriver_v0Support) ValidateVolumeCapabilities( - ctx context.Context, - req *csi_v0.ValidateVolumeCapabilitiesRequest) ( - *csi_v0.ValidateVolumeCapabilitiesResponse, error) { - - // Change capabilities from v0 -> v1 - caps := []*csi.VolumeCapability{} - for _, cap := range req.GetVolumeCapabilities() { - capv1, err := ConvertVolumeCapabilityFromv0Tov1(cap) - - if err != nil { - return &csi_v0.ValidateVolumeCapabilitiesResponse{ - Supported: false, - }, nil - } - - caps = append(caps, capv1) - } - request := &csi.ValidateVolumeCapabilitiesRequest{ - VolumeId: req.GetVolumeId(), - VolumeContext: req.GetVolumeAttributes(), - VolumeCapabilities: caps, - } - _, err := d.driver.ValidateVolumeCapabilities(ctx, request) - - if err != nil { - return nil, err - } - - return &csi_v0.ValidateVolumeCapabilitiesResponse{ - Supported: true, - }, err + ctx context.Context, + req *csi_v0.ValidateVolumeCapabilitiesRequest) ( + *csi_v0.ValidateVolumeCapabilitiesResponse, error) { + + // Change capabilities from v0 -> v1 + caps := []*csi.VolumeCapability{} + for _, cap := range req.GetVolumeCapabilities() { + capv1, err := ConvertVolumeCapabilityFromv0Tov1(cap) + + if err != nil { + return &csi_v0.ValidateVolumeCapabilitiesResponse{ + Supported: false, + }, nil + } + + caps = append(caps, capv1) + } + request := &csi.ValidateVolumeCapabilitiesRequest{ + VolumeId: req.GetVolumeId(), + VolumeContext: req.GetVolumeAttributes(), + VolumeCapabilities: caps, + } + _, err := d.driver.ValidateVolumeCapabilities(ctx, request) + + if err != nil { + return nil, err + } + + return &csi_v0.ValidateVolumeCapabilitiesResponse{ + Supported: true, + }, err } - func (d *CSIDriver_v0Support) GetCapacity( - ctx context.Context, - req *csi_v0.GetCapacityRequest) ( - *csi_v0.GetCapacityResponse, error) { - - caps := []*csi.VolumeCapability{} - for _, cap := range req.GetVolumeCapabilities() { - - // convert accesstype - accessType := cap.GetMount() - - if accessType == nil { - return &csi_v0.GetCapacityResponse{ - AvailableCapacity: 0, - }, nil - } - - accessMode := csi.VolumeCapability_AccessMode_Mode(cap.GetAccessMode().GetMode()) - - caps = append(caps, &csi.VolumeCapability{ - AccessType: &csi.VolumeCapability_Mount{ - Mount: &csi.VolumeCapability_MountVolume{ - FsType: accessType.GetFsType(), - MountFlags: accessType.GetMountFlags(), - }, - }, - AccessMode: &csi.VolumeCapability_AccessMode{ - Mode: accessMode, - }, - }) - } - - - capacity, err := d.driver.GetCapacity(ctx, &csi.GetCapacityRequest{ - VolumeCapabilities: caps, - Parameters: req.GetParameters(), - }) - - return &csi_v0.GetCapacityResponse{ - AvailableCapacity: capacity.GetAvailableCapacity(), - }, err + ctx context.Context, + req *csi_v0.GetCapacityRequest) ( + *csi_v0.GetCapacityResponse, error) { + + caps := []*csi.VolumeCapability{} + for _, cap := range req.GetVolumeCapabilities() { + + // convert accesstype + accessType := cap.GetMount() + + if accessType == nil { + return &csi_v0.GetCapacityResponse{ + AvailableCapacity: 0, + }, nil + } + + accessMode := csi.VolumeCapability_AccessMode_Mode(cap.GetAccessMode().GetMode()) + + caps = append(caps, &csi.VolumeCapability{ + AccessType: &csi.VolumeCapability_Mount{ + Mount: &csi.VolumeCapability_MountVolume{ + FsType: accessType.GetFsType(), + MountFlags: accessType.GetMountFlags(), + }, + }, + AccessMode: &csi.VolumeCapability_AccessMode{ + Mode: accessMode, + }, + }) + } + + capacity, err := d.driver.GetCapacity(ctx, &csi.GetCapacityRequest{ + VolumeCapabilities: caps, + Parameters: req.GetParameters(), + }) + + return &csi_v0.GetCapacityResponse{ + AvailableCapacity: capacity.GetAvailableCapacity(), + }, err } func (d *CSIDriver_v0Support) ControllerGetCapabilities( - ctx context.Context, - req *csi_v0.ControllerGetCapabilitiesRequest) ( - *csi_v0.ControllerGetCapabilitiesResponse, error) { - - caps := []*csi_v0.ControllerServiceCapability{ - { - Type: &csi_v0.ControllerServiceCapability_Rpc{ - Rpc: &csi_v0.ControllerServiceCapability_RPC{ - Type: csi_v0.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME, - }, - }, - }, - { - Type: &csi_v0.ControllerServiceCapability_Rpc{ - Rpc: &csi_v0.ControllerServiceCapability_RPC{ - Type: csi_v0.ControllerServiceCapability_RPC_GET_CAPACITY, - }, - }, - }, - - } - - return &csi_v0.ControllerGetCapabilitiesResponse{ - Capabilities: caps, - }, nil + ctx context.Context, + req *csi_v0.ControllerGetCapabilitiesRequest) ( + *csi_v0.ControllerGetCapabilitiesResponse, error) { + + caps := []*csi_v0.ControllerServiceCapability{ + { + Type: &csi_v0.ControllerServiceCapability_Rpc{ + Rpc: &csi_v0.ControllerServiceCapability_RPC{ + Type: csi_v0.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME, + }, + }, + }, + { + Type: &csi_v0.ControllerServiceCapability_Rpc{ + Rpc: &csi_v0.ControllerServiceCapability_RPC{ + Type: csi_v0.ControllerServiceCapability_RPC_GET_CAPACITY, + }, + }, + }, + } + + return &csi_v0.ControllerGetCapabilitiesResponse{ + Capabilities: caps, + }, nil } - func (d *CSIDriver_v0Support) ListVolumes( - ctx context.Context, - req *csi_v0.ListVolumesRequest) ( - *csi_v0.ListVolumesResponse, error) { + ctx context.Context, + req *csi_v0.ListVolumesRequest) ( + *csi_v0.ListVolumesResponse, error) { - return nil, status.Error(codes.Unimplemented, "") + return nil, status.Error(codes.Unimplemented, "") } - func (d *CSIDriver_v0Support) CreateSnapshot(ctx context.Context, - req *csi_v0.CreateSnapshotRequest) (*csi_v0.CreateSnapshotResponse, error) { + req *csi_v0.CreateSnapshotRequest) (*csi_v0.CreateSnapshotResponse, error) { - return nil, status.Error(codes.Unimplemented, "") + return nil, status.Error(codes.Unimplemented, "") } func (d *CSIDriver_v0Support) DeleteSnapshot(ctx context.Context, - req *csi_v0.DeleteSnapshotRequest) (*csi_v0.DeleteSnapshotResponse, error) { + req *csi_v0.DeleteSnapshotRequest) (*csi_v0.DeleteSnapshotResponse, error) { - return nil, status.Error(codes.Unimplemented, "") + return nil, status.Error(codes.Unimplemented, "") } - func (d *CSIDriver_v0Support) ListSnapshots(ctx context.Context, - req *csi_v0.ListSnapshotsRequest) (*csi_v0.ListSnapshotsResponse, error) { + req *csi_v0.ListSnapshotsRequest) (*csi_v0.ListSnapshotsResponse, error) { - return nil, status.Error(codes.Unimplemented, "") + return nil, status.Error(codes.Unimplemented, "") } - - - - func (d *CSIDriver_v0Support) GetPluginInfo( - ctx context.Context, - req *csi_v0.GetPluginInfoRequest) ( - *csi_v0.GetPluginInfoResponse, error) { - pluginInfo, err := d.driver.GetPluginInfo(ctx, &csi.GetPluginInfoRequest{}) - - return &csi_v0.GetPluginInfoResponse{ - Name: pluginInfo.Name, - VendorVersion: pluginInfo.VendorVersion, - Manifest: pluginInfo.Manifest, - }, err + ctx context.Context, + req *csi_v0.GetPluginInfoRequest) ( + *csi_v0.GetPluginInfoResponse, error) { + pluginInfo, err := d.driver.GetPluginInfo(ctx, &csi.GetPluginInfoRequest{}) + + return &csi_v0.GetPluginInfoResponse{ + Name: pluginInfo.Name, + VendorVersion: pluginInfo.VendorVersion, + Manifest: pluginInfo.Manifest, + }, err } func (d *CSIDriver_v0Support) Probe( - ctx context.Context, - req *csi_v0.ProbeRequest) ( - *csi_v0.ProbeResponse, error) { + ctx context.Context, + req *csi_v0.ProbeRequest) ( + *csi_v0.ProbeResponse, error) { - res, err := d.driver.Probe(ctx, &csi.ProbeRequest{}) + res, err := d.driver.Probe(ctx, &csi.ProbeRequest{}) - return &csi_v0.ProbeResponse{ - Ready: res.Ready, - }, err + return &csi_v0.ProbeResponse{ + Ready: res.Ready, + }, err } func (d *CSIDriver_v0Support) GetPluginCapabilities( - ctx context.Context, - req *csi_v0.GetPluginCapabilitiesRequest) ( - *csi_v0.GetPluginCapabilitiesResponse, error) { - - return &csi_v0.GetPluginCapabilitiesResponse{ - Capabilities: []*csi_v0.PluginCapability{ - { - Type: &csi_v0.PluginCapability_Service_{ - Service: &csi_v0.PluginCapability_Service{ - Type: csi_v0.PluginCapability_Service_CONTROLLER_SERVICE, - }, - }, - }, - }, - }, nil + ctx context.Context, + req *csi_v0.GetPluginCapabilitiesRequest) ( + *csi_v0.GetPluginCapabilitiesResponse, error) { + + return &csi_v0.GetPluginCapabilitiesResponse{ + Capabilities: []*csi_v0.PluginCapability{ + { + Type: &csi_v0.PluginCapability_Service_{ + Service: &csi_v0.PluginCapability_Service{ + Type: csi_v0.PluginCapability_Service_CONTROLLER_SERVICE, + }, + }, + }, + }, + }, nil } - func (d *CSIDriver_v0Support) NodeStageVolume( - ctx context.Context, - req *csi_v0.NodeStageVolumeRequest) ( - *csi_v0.NodeStageVolumeResponse, error) { + ctx context.Context, + req *csi_v0.NodeStageVolumeRequest) ( + *csi_v0.NodeStageVolumeResponse, error) { - capv1, err := ConvertVolumeCapabilityFromv0Tov1(req.GetVolumeCapability()) + capv1, err := ConvertVolumeCapabilityFromv0Tov1(req.GetVolumeCapability()) - if err != nil { - return nil, err - } + if err != nil { + return nil, err + } - _, err = d.driver.NodeStageVolume(ctx, &csi.NodeStageVolumeRequest{ - StagingTargetPath: req.GetStagingTargetPath(), - VolumeId: req.GetVolumeId(), - VolumeCapability: capv1, - PublishContext: req.GetVolumeAttributes(), - Secrets: req.GetNodeStageSecrets(), - }) + _, err = d.driver.NodeStageVolume(ctx, &csi.NodeStageVolumeRequest{ + StagingTargetPath: req.GetStagingTargetPath(), + VolumeId: req.GetVolumeId(), + VolumeCapability: capv1, + PublishContext: req.GetVolumeAttributes(), + Secrets: req.GetNodeStageSecrets(), + }) - return &csi_v0.NodeStageVolumeResponse{}, err + return &csi_v0.NodeStageVolumeResponse{}, err } func (d *CSIDriver_v0Support) NodeUnstageVolume( - ctx context.Context, - req *csi_v0.NodeUnstageVolumeRequest) ( - *csi_v0.NodeUnstageVolumeResponse, error) { + ctx context.Context, + req *csi_v0.NodeUnstageVolumeRequest) ( + *csi_v0.NodeUnstageVolumeResponse, error) { - _, err := d.driver.NodeUnstageVolume(ctx, &csi.NodeUnstageVolumeRequest{ - StagingTargetPath: req.GetStagingTargetPath(), - VolumeId: req.GetVolumeId(), - }) + _, err := d.driver.NodeUnstageVolume(ctx, &csi.NodeUnstageVolumeRequest{ + StagingTargetPath: req.GetStagingTargetPath(), + VolumeId: req.GetVolumeId(), + }) - return &csi_v0.NodeUnstageVolumeResponse{}, err + return &csi_v0.NodeUnstageVolumeResponse{}, err } func ConvertVolumeCapabilityFromv0Tov1(capability *csi_v0.VolumeCapability) (*csi.VolumeCapability, error) { - // convert accesstype - accessType := capability.GetMount() - - if accessType == nil { - return &csi.VolumeCapability{}, status.Error(codes.InvalidArgument, common.BlockVolumesUnsupported) - } - - accessMode := csi.VolumeCapability_AccessMode_Mode(capability.AccessMode.GetMode()) - - return &csi.VolumeCapability{ - AccessType: &csi.VolumeCapability_Mount{ - Mount: &csi.VolumeCapability_MountVolume{ - FsType: accessType.GetFsType(), - MountFlags: accessType.GetMountFlags(), - }, - }, - AccessMode: &csi.VolumeCapability_AccessMode{ - Mode: accessMode, - }, - }, nil + // convert accesstype + accessType := capability.GetMount() + + if accessType == nil { + return &csi.VolumeCapability{}, status.Error(codes.InvalidArgument, common.BlockVolumesUnsupported) + } + + accessMode := csi.VolumeCapability_AccessMode_Mode(capability.AccessMode.GetMode()) + + return &csi.VolumeCapability{ + AccessType: &csi.VolumeCapability_Mount{ + Mount: &csi.VolumeCapability_MountVolume{ + FsType: accessType.GetFsType(), + MountFlags: accessType.GetMountFlags(), + }, + }, + AccessMode: &csi.VolumeCapability_AccessMode{ + Mode: accessMode, + }, + }, nil } func (d *CSIDriver_v0Support) NodePublishVolume( - ctx context.Context, - req *csi_v0.NodePublishVolumeRequest) ( - *csi_v0.NodePublishVolumeResponse, error) { - - capv1, err := ConvertVolumeCapabilityFromv0Tov1(req.GetVolumeCapability()) - - if err != nil { - return nil, err - } - - request := &csi.NodePublishVolumeRequest{ - TargetPath: req.TargetPath, - VolumeId: req.VolumeId, - PublishContext: req.PublishInfo, - StagingTargetPath: req.StagingTargetPath, - VolumeCapability: capv1, - Readonly: req.Readonly, - Secrets: req.NodePublishSecrets, - VolumeContext: req.VolumeAttributes, - } - _, err = d.driver.NodePublishVolume(ctx, request) - if err != nil { - return nil, err - } - - return &csi_v0.NodePublishVolumeResponse{}, nil + ctx context.Context, + req *csi_v0.NodePublishVolumeRequest) ( + *csi_v0.NodePublishVolumeResponse, error) { + + capv1, err := ConvertVolumeCapabilityFromv0Tov1(req.GetVolumeCapability()) + + if err != nil { + return nil, err + } + + request := &csi.NodePublishVolumeRequest{ + TargetPath: req.TargetPath, + VolumeId: req.VolumeId, + PublishContext: req.PublishInfo, + StagingTargetPath: req.StagingTargetPath, + VolumeCapability: capv1, + Readonly: req.Readonly, + Secrets: req.NodePublishSecrets, + VolumeContext: req.VolumeAttributes, + } + _, err = d.driver.NodePublishVolume(ctx, request) + if err != nil { + return nil, err + } + + return &csi_v0.NodePublishVolumeResponse{}, nil } func (d *CSIDriver_v0Support) NodeUnpublishVolume( - ctx context.Context, - req *csi_v0.NodeUnpublishVolumeRequest) ( - *csi_v0.NodeUnpublishVolumeResponse, error) { - request := &csi.NodeUnpublishVolumeRequest{ - TargetPath: req.TargetPath, - VolumeId: req.VolumeId, - } - _, err := d.driver.NodeUnpublishVolume(ctx, request) - if err != nil { - return nil, err - } - return &csi_v0.NodeUnpublishVolumeResponse{}, nil + ctx context.Context, + req *csi_v0.NodeUnpublishVolumeRequest) ( + *csi_v0.NodeUnpublishVolumeResponse, error) { + request := &csi.NodeUnpublishVolumeRequest{ + TargetPath: req.TargetPath, + VolumeId: req.VolumeId, + } + _, err := d.driver.NodeUnpublishVolume(ctx, request) + if err != nil { + return nil, err + } + return &csi_v0.NodeUnpublishVolumeResponse{}, nil } func (d *CSIDriver_v0Support) NodeGetCapabilities( - ctx context.Context, - req *csi_v0.NodeGetCapabilitiesRequest) ( - *csi_v0.NodeGetCapabilitiesResponse, error) { - - return &csi_v0.NodeGetCapabilitiesResponse{ - Capabilities: []*csi_v0.NodeServiceCapability{ - { - Type: &csi_v0.NodeServiceCapability_Rpc{ - Rpc: &csi_v0.NodeServiceCapability_RPC{ - Type: csi_v0.NodeServiceCapability_RPC_UNKNOWN, - }, - }, - }, - { - Type: &csi_v0.NodeServiceCapability_Rpc{ - Rpc: &csi_v0.NodeServiceCapability_RPC{ - Type: csi_v0.NodeServiceCapability_RPC_STAGE_UNSTAGE_VOLUME, - }, - }, - }, - }, - }, nil + ctx context.Context, + req *csi_v0.NodeGetCapabilitiesRequest) ( + *csi_v0.NodeGetCapabilitiesResponse, error) { + + return &csi_v0.NodeGetCapabilitiesResponse{ + Capabilities: []*csi_v0.NodeServiceCapability{ + { + Type: &csi_v0.NodeServiceCapability_Rpc{ + Rpc: &csi_v0.NodeServiceCapability_RPC{ + Type: csi_v0.NodeServiceCapability_RPC_UNKNOWN, + }, + }, + }, + { + Type: &csi_v0.NodeServiceCapability_Rpc{ + Rpc: &csi_v0.NodeServiceCapability_RPC{ + Type: csi_v0.NodeServiceCapability_RPC_STAGE_UNSTAGE_VOLUME, + }, + }, + }, + }, + }, nil } func (d *CSIDriver_v0Support) NodeGetInfo(ctx context.Context, - req *csi_v0.NodeGetInfoRequest) (*csi_v0.NodeGetInfoResponse, error) { - csiNodeResponse := &csi_v0.NodeGetInfoResponse{ - NodeId: d.driver.NodeID, - } - return csiNodeResponse, nil + req *csi_v0.NodeGetInfoRequest) (*csi_v0.NodeGetInfoResponse, error) { + csiNodeResponse := &csi_v0.NodeGetInfoResponse{ + NodeId: d.driver.NodeID, + } + return csiNodeResponse, nil } func (d *CSIDriver_v0Support) NodeGetId(ctx context.Context, - req *csi_v0.NodeGetIdRequest) (*csi_v0.NodeGetIdResponse, error) { - csiNodeResponse := &csi_v0.NodeGetIdResponse{ - NodeId: d.driver.NodeID, - } - return csiNodeResponse, nil + req *csi_v0.NodeGetIdRequest) (*csi_v0.NodeGetIdResponse, error) { + csiNodeResponse := &csi_v0.NodeGetIdResponse{ + NodeId: d.driver.NodeID, + } + return csiNodeResponse, nil } diff --git a/pkg/driver/identity.go b/pkg/driver/identity.go index 2b9115f..aefb1b9 100644 --- a/pkg/driver/identity.go +++ b/pkg/driver/identity.go @@ -27,73 +27,73 @@ import ( ) func (d *CSIDriver) GetPluginInfo( - ctx context.Context, - req *csi.GetPluginInfoRequest) ( - *csi.GetPluginInfoResponse, error) { + ctx context.Context, + req *csi.GetPluginInfoRequest) ( + *csi.GetPluginInfoResponse, error) { - manifest := map[string]string{} - manifest["githash"] = common.Githash + manifest := map[string]string{} + manifest["githash"] = common.Githash - return &csi.GetPluginInfoResponse{ - Name: common.CsiPluginName, - VendorVersion: common.Version, - Manifest: manifest, - }, nil + return &csi.GetPluginInfoResponse{ + Name: common.CsiPluginName, + VendorVersion: common.Version, + Manifest: manifest, + }, nil } func (d *CSIDriver) Probe( - ctx context.Context, - req *csi.ProbeRequest) ( - *csi.ProbeResponse, error) { + ctx context.Context, + req *csi.ProbeRequest) ( + *csi.ProbeResponse, error) { - // Make sure the client and backend can communicate - err := d.hsclient.EnsureLogin() - if err != nil { - return &csi.ProbeResponse{ - Ready: &wrappers.BoolValue{Value: false}, - }, status.Errorf(codes.Unavailable, err.Error()) - } + // Make sure the client and backend can communicate + err := d.hsclient.EnsureLogin() + if err != nil { + return &csi.ProbeResponse{ + Ready: &wrappers.BoolValue{Value: false}, + }, status.Errorf(codes.Unavailable, err.Error()) + } - return &csi.ProbeResponse{ - Ready: &wrappers.BoolValue{Value: true}, - }, nil + return &csi.ProbeResponse{ + Ready: &wrappers.BoolValue{Value: true}, + }, nil } func (d *CSIDriver) GetPluginCapabilities( - ctx context.Context, - req *csi.GetPluginCapabilitiesRequest) ( - *csi.GetPluginCapabilitiesResponse, error) { + ctx context.Context, + req *csi.GetPluginCapabilitiesRequest) ( + *csi.GetPluginCapabilitiesResponse, error) { - return &csi.GetPluginCapabilitiesResponse{ - Capabilities: []*csi.PluginCapability{ - { - Type: &csi.PluginCapability_Service_{ - Service: &csi.PluginCapability_Service{ - Type: csi.PluginCapability_Service_CONTROLLER_SERVICE, - }, - }, - }, - { - Type: &csi.PluginCapability_Service_{ - Service: &csi.PluginCapability_Service{ - Type: csi.PluginCapability_Service_VOLUME_ACCESSIBILITY_CONSTRAINTS, - }, - }, - }, - { - Type: &csi.PluginCapability_VolumeExpansion_{ - VolumeExpansion: &csi.PluginCapability_VolumeExpansion{ - Type: csi.PluginCapability_VolumeExpansion_ONLINE, - }, - }, - }, - { - Type: &csi.PluginCapability_VolumeExpansion_{ - VolumeExpansion: &csi.PluginCapability_VolumeExpansion{ - Type: csi.PluginCapability_VolumeExpansion_OFFLINE, - }, - }, - }, - }, - }, nil + return &csi.GetPluginCapabilitiesResponse{ + Capabilities: []*csi.PluginCapability{ + { + Type: &csi.PluginCapability_Service_{ + Service: &csi.PluginCapability_Service{ + Type: csi.PluginCapability_Service_CONTROLLER_SERVICE, + }, + }, + }, + { + Type: &csi.PluginCapability_Service_{ + Service: &csi.PluginCapability_Service{ + Type: csi.PluginCapability_Service_VOLUME_ACCESSIBILITY_CONSTRAINTS, + }, + }, + }, + { + Type: &csi.PluginCapability_VolumeExpansion_{ + VolumeExpansion: &csi.PluginCapability_VolumeExpansion{ + Type: csi.PluginCapability_VolumeExpansion_ONLINE, + }, + }, + }, + { + Type: &csi.PluginCapability_VolumeExpansion_{ + VolumeExpansion: &csi.PluginCapability_VolumeExpansion{ + Type: csi.PluginCapability_VolumeExpansion_OFFLINE, + }, + }, + }, + }, + }, nil } diff --git a/pkg/driver/node.go b/pkg/driver/node.go index 706f499..5a5b7be 100644 --- a/pkg/driver/node.go +++ b/pkg/driver/node.go @@ -461,7 +461,7 @@ func (d *CSIDriver) NodeGetVolumeStats(ctx context.Context, // NFS backend volumeName := GetVolumeNameFromPath(req.GetVolumeId()) share, err := d.hsclient.GetShare(volumeName) - if err != nil { + if err != nil || share == nil { return nil, status.Error(codes.NotFound, common.ShareNotFound) } diff --git a/pkg/driver/utils.go b/pkg/driver/utils.go index 35398ef..502d2f6 100644 --- a/pkg/driver/utils.go +++ b/pkg/driver/utils.go @@ -17,199 +17,206 @@ limitations under the License. package driver import ( - "errors" - "fmt" - "os/exec" - "path" - "path/filepath" - "strings" - - log "github.com/sirupsen/logrus" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" - - common "github.com/hammer-space/csi-plugin/pkg/common" + "errors" + "fmt" + "os/exec" + "path" + "path/filepath" + "strings" + + log "github.com/sirupsen/logrus" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + common "github.com/hammer-space/csi-plugin/pkg/common" ) func IsValueInList(value string, list []string) bool { - for _, v := range list { - if v == value { - return true - } - } - return false + for _, v := range list { + if v == value { + return true + } + } + return false } func GetVolumeNameFromPath(path string) string { - return filepath.Base(path) + return filepath.Base(path) } func GetSnapshotNameFromSnapshotId(snapshotId string) (string, error) { - tokens := strings.SplitN(snapshotId, "|", 2) - if len(tokens) != 2 { - return "", errors.New(fmt.Sprintf(common.ImproperlyFormattedSnapshotId, snapshotId)) - } - return tokens[0], nil + tokens := strings.SplitN(snapshotId, "|", 2) + if len(tokens) != 2 { + return "", errors.New(fmt.Sprintf(common.ImproperlyFormattedSnapshotId, snapshotId)) + } + return tokens[0], nil } func GetShareNameFromSnapshotId(snapshotId string) (string, error) { - tokens := strings.SplitN(snapshotId, "|", 2) - if len(tokens) != 2 { - return "", errors.New(fmt.Sprintf(common.ImproperlyFormattedSnapshotId, snapshotId)) - } - return path.Base(tokens[1]), nil + tokens := strings.SplitN(snapshotId, "|", 2) + if len(tokens) != 2 { + return "", errors.New(fmt.Sprintf(common.ImproperlyFormattedSnapshotId, snapshotId)) + } + return path.Base(tokens[1]), nil } // generate snapshot ID to be stored by the CO // | -func GetSnapshotIDFromSnapshotName(hsSnapName, sourceVolumeID string) (string) { - return fmt.Sprintf("%s|%s", hsSnapName, sourceVolumeID) +func GetSnapshotIDFromSnapshotName(hsSnapName, sourceVolumeID string) string { + return fmt.Sprintf("%s|%s", hsSnapName, sourceVolumeID) } func (d *CSIDriver) EnsureBackingShareMounted(backingShareName string) error { - backingShare, err := d.hsclient.GetShare(backingShareName) - if err != nil { - return status.Errorf(codes.NotFound, err.Error()) - } - if backingShare != nil { - backingDir := common.ShareStagingDir + backingShare.ExportPath - // Mount backing share - if isMounted, _ := common.IsShareMounted(backingDir); !isMounted { - mo := []string{} - err := d.MountShareAtBestDataportal(backingShare.ExportPath, backingDir, mo) - if err != nil { - log.Errorf("failed to mount backing share, %v", err) - return err - } - - log.Infof("mounted backing share, %s", backingDir) - } else { - log.Infof("backing share already mounted, %s", backingDir) - } - return nil - } - return nil + backingShare, err := d.hsclient.GetShare(backingShareName) + if err != nil { + return status.Errorf(codes.NotFound, err.Error()) + } + if backingShare != nil { + backingDir := common.ShareStagingDir + backingShare.ExportPath + // Mount backing share + if isMounted, _ := common.IsShareMounted(backingDir); !isMounted { + mo := []string{} + err := d.MountShareAtBestDataportal(backingShare.ExportPath, backingDir, mo) + if err != nil { + log.Errorf("failed to mount backing share, %v", err) + return err + } + + log.Infof("mounted backing share, %s", backingDir) + } else { + log.Infof("backing share already mounted, %s", backingDir) + } + return nil + } + return nil } func (d *CSIDriver) UnmountBackingShareIfUnused(backingShareName string) (bool, error) { - backingShare, err := d.hsclient.GetShare(backingShareName) - mountPath := common.ShareStagingDir + backingShare.ExportPath - if isMounted, _ := common.IsShareMounted(mountPath); !isMounted { - return true, nil - } - // If any loopback devices are using the mount - output, err := exec.Command("losetup", "-a").CombinedOutput() - if err != nil { - return false, status.Errorf(codes.Internal, - "could not list backing files for loop devices, %v", err) - } - devices := strings.Split(string(output), "\n") - for _, d := range devices { - if d != "" { - device := strings.Split(d, " ") - backingFile := strings.Trim(device[len(device)-1], ":()") - if strings.Index(backingFile, mountPath) == 0 { - log.Infof("backing share, %s, still in use by, %s", mountPath, devices[0]) - return false, nil - } - } - } - - log.Infof("unmounting backing share %s", mountPath) - err = common.UnmountFilesystem(mountPath) - if err != nil { - log.Errorf("failed to unmount backing share %s", mountPath) - return false, err - } - - return true, err + backingShare, err := d.hsclient.GetShare(backingShareName) + if err != nil || backingShare == nil { + if backingShare == nil { + log.Infof("backing share %s, dosent exist", backingShareName) + } + return false, err + } + mountPath := common.ShareStagingDir + backingShare.ExportPath + if isMounted, _ := common.IsShareMounted(mountPath); !isMounted { + return true, nil + } + // If any loopback devices are using the mount + output, err := exec.Command("losetup", "-a").CombinedOutput() + if err != nil { + return false, status.Errorf(codes.Internal, + "could not list backing files for loop devices, %v", err) + } + devices := strings.Split(string(output), "\n") + for _, d := range devices { + if d != "" { + device := strings.Split(d, " ") + backingFile := strings.Trim(device[len(device)-1], ":()") + if strings.Index(backingFile, mountPath) == 0 { + log.Infof("backing share, %s, still in use by, %s", mountPath, devices[0]) + return false, nil + } + } + } + + log.Infof("unmounting backing share %s", mountPath) + err = common.UnmountFilesystem(mountPath) + if err != nil { + log.Errorf("failed to unmount backing share %s", mountPath) + return false, err + } + + return true, err } func (d *CSIDriver) MountShareAtBestDataportal(shareExportPath, targetPath string, mountFlags []string) error { - var err error - - log.Infof("Finding best host exporting %s", shareExportPath) - - portals, err := d.hsclient.GetDataPortals(d.NodeID) - if err != nil { - log.Errorf("Could not create list of data-portals, %v", err) - } - // Always look for floating data portal IPs - fipaddr, err := d.hsclient.GetPortalFloatingIp() - if err != nil { - log.Errorf("Could not contact Anvil for floating IPs, %v", err) - } - - MountToDataPortal := func(portal common.DataPortal, mount_options []string) (bool){ - addr := "" - if len(fipaddr) > 0 { - addr = fipaddr - log.Infof("Floating IP address detected: %s", fipaddr) - } else { - addr = portal.Node.MgmtIpAddress.Address - } - export := "" - // Use configured prefix if specified - if common.DataPortalMountPrefix != "" { - export = fmt.Sprintf("%s:%s%s", addr, common.DataPortalMountPrefix, shareExportPath) - } else { - // grab exports with showmount - exports, err := common.GetNFSExports(addr) - if err != nil { - log.Infof("Could not get exports for data-portal at %s, %s. Error: %v", addr, portal.Uoid["uuid"], err) - return false - } - log.Infof("Found exports for data-portal %s, %v", addr, exports) - - // Check configured prefix - // Check the default prefixes - for _, mountPrefix := range common.DefaultDataPortalMountPrefixes { - for _, e := range exports { - if e == fmt.Sprintf("%s%s", mountPrefix, shareExportPath) { - export = fmt.Sprintf("%s:%s%s", addr, mountPrefix, shareExportPath) - log.Infof("Found export %s", export) - break - } - } - if export != "" { - break - } - } - if export == "" { - log.Infof("Could not find any matching export on data-portal, %s.", portal.Uoid["uuid"]) - return false - } - } - mo := append(mountFlags, mount_options...) - err = common.MountShare(export, targetPath, mo) - if err != nil { - log.Infof("Could not mount via data-portal, %s. Error: %v", portal.Uoid["uuid"], err) - } else { - log.Infof("Mounted via data-portal, %s.", portal.Uoid["uuid"]) - return true - } - return false - } - - log.Infof("Attempting to mount via NFS 4.2.") - mounted := false - for _, p := range portals { - mounted = MountToDataPortal(p, append(mountFlags, "nfsvers=4.2")) - if mounted { - break - } - } - if !mounted { - log.Infof("Could not mount via NFS 4.2, falling back to NFS 3.") - for _, p := range portals { - mounted = MountToDataPortal(p, append(mountFlags, "nfsvers=3,nolock")) - if mounted { - break - } - } - } - if mounted { - return nil - } - return errors.New("Could not mount to any data-portals") + var err error + + log.Infof("Finding best host exporting %s", shareExportPath) + + portals, err := d.hsclient.GetDataPortals(d.NodeID) + if err != nil { + log.Errorf("Could not create list of data-portals, %v", err) + } + // Always look for floating data portal IPs + fipaddr, err := d.hsclient.GetPortalFloatingIp() + if err != nil { + log.Errorf("Could not contact Anvil for floating IPs, %v", err) + } + + MountToDataPortal := func(portal common.DataPortal, mount_options []string) bool { + addr := "" + if len(fipaddr) > 0 { + addr = fipaddr + log.Infof("Floating IP address detected: %s", fipaddr) + } else { + addr = portal.Node.MgmtIpAddress.Address + } + export := "" + // Use configured prefix if specified + if common.DataPortalMountPrefix != "" { + export = fmt.Sprintf("%s:%s%s", addr, common.DataPortalMountPrefix, shareExportPath) + } else { + // grab exports with showmount + exports, err := common.GetNFSExports(addr) + if err != nil { + log.Infof("Could not get exports for data-portal at %s, %s. Error: %v", addr, portal.Uoid["uuid"], err) + return false + } + log.Infof("Found exports for data-portal %s, %v", addr, exports) + + // Check configured prefix + // Check the default prefixes + for _, mountPrefix := range common.DefaultDataPortalMountPrefixes { + for _, e := range exports { + log.Debugf("checking exports for %s%s with %s", mountPrefix, shareExportPath, e) + if e == fmt.Sprintf("%s%s", mountPrefix, shareExportPath) { + export = fmt.Sprintf("%s:%s%s", addr, mountPrefix, shareExportPath) + log.Infof("Found export %s", export) + break + } + } + if export != "" { + break + } + } + if export == "" { + log.Infof("Could not find any matching export on data-portal, %s.", portal.Uoid["uuid"]) + return false + } + } + mo := append(mountFlags, mount_options...) + err = common.MountShare(export, targetPath, mo) + if err != nil { + log.Infof("Could not mount via data-portal, %s. Error: %v", portal.Uoid["uuid"], err) + } else { + log.Infof("Mounted via data-portal, %s.", portal.Uoid["uuid"]) + return true + } + return false + } + + log.Infof("Attempting to mount via NFS 4.2.") + mounted := false + for _, p := range portals { + mounted = MountToDataPortal(p, append(mountFlags, "nfsvers=4.2")) + if mounted { + break + } + } + if !mounted { + log.Infof("Could not mount via NFS 4.2, falling back to NFS 3.") + for _, p := range portals { + mounted = MountToDataPortal(p, append(mountFlags, "nfsvers=3,nolock")) + if mounted { + break + } + } + } + if mounted { + return nil + } + return errors.New("Could not mount to any data-portals") } diff --git a/test/sanity/hammerspace_file_backed_block.go b/test/sanity/hammerspace_file_backed_block.go index 964cd59..1626b2b 100644 --- a/test/sanity/hammerspace_file_backed_block.go +++ b/test/sanity/hammerspace_file_backed_block.go @@ -104,8 +104,8 @@ var _ = sanity.DescribeSanity("Hammerspace - Block Volumes", func(sc *sanity.San context.Background(), &csi.NodePublishVolumeRequest{ VolumeId: vol.GetVolume().GetVolumeId(), - TargetPath: sc.Config.TargetPath+"/dev", - StagingTargetPath: sc.Config.StagingPath+"/dev", + TargetPath: sc.Config.TargetPath + "/dev", + StagingTargetPath: sc.Config.StagingPath + "/dev", VolumeCapability: &csi.VolumeCapability{ AccessType: &csi.VolumeCapability_Block{ Block: &csi.VolumeCapability_BlockVolume{}, @@ -128,13 +128,13 @@ var _ = sanity.DescribeSanity("Hammerspace - Block Volumes", func(sc *sanity.San additionalMetadataTags = parseMetadataTagsParam(tags) } for key, value := range additionalMetadataTags { - // Check the file exists - output, err := common.ExecCommand("cat", fmt.Sprintf("%s?.eval list_tags", common.ShareStagingDir + vol.GetVolume().GetVolumeId())) - if err != nil { - Expect(err).NotTo(HaveOccurred()) - } - log.Infof(string(output)) - output, err = common.ExecCommand("cat", fmt.Sprintf("%s?.eval get_tag(\"%s\")", common.ShareStagingDir + vol.GetVolume().GetVolumeId(), key)) + // Check the file exists + output, err := common.ExecCommand("cat", fmt.Sprintf("%s?.eval list_tags", common.ShareStagingDir+vol.GetVolume().GetVolumeId())) + if err != nil { + Expect(err).NotTo(HaveOccurred()) + } + log.Infof(string(output)) + output, err = common.ExecCommand("cat", fmt.Sprintf("%s?.eval get_tag(\"%s\")", common.ShareStagingDir+vol.GetVolume().GetVolumeId(), key)) if err != nil { Expect(err).NotTo(HaveOccurred()) } @@ -144,15 +144,15 @@ var _ = sanity.DescribeSanity("Hammerspace - Block Volumes", func(sc *sanity.San By("Write data to volume") //sc.Config.TargetPath testData := []byte("test_data") - err = ioutil.WriteFile(sc.Config.TargetPath + "/dev", testData, 0644) + err = ioutil.WriteFile(sc.Config.TargetPath+"/dev", testData, 0644) Expect(err).NotTo(HaveOccurred()) By("expand the volume") _, err = c.NodeExpandVolume( context.Background(), &csi.NodeExpandVolumeRequest{ - VolumeId: vol.GetVolume().GetVolumeId(), - VolumePath: sc.Config.TargetPath + "/dev", + VolumeId: vol.GetVolume().GetVolumeId(), + VolumePath: sc.Config.TargetPath + "/dev", CapacityRange: &csi.CapacityRange{ RequiredBytes: TestVolumeSize(sc) * 2, }, @@ -161,7 +161,7 @@ var _ = sanity.DescribeSanity("Hammerspace - Block Volumes", func(sc *sanity.San Expect(err).NotTo(HaveOccurred()) - output, err := common.ExecCommand("blockdev", "--getsize64", sc.Config.TargetPath + "/dev") + output, err := common.ExecCommand("blockdev", "--getsize64", sc.Config.TargetPath+"/dev") if err != nil { Expect(err).NotTo(HaveOccurred()) } @@ -171,8 +171,8 @@ var _ = sanity.DescribeSanity("Hammerspace - Block Volumes", func(sc *sanity.San _, err = c.NodeUnpublishVolume( context.Background(), &csi.NodeUnpublishVolumeRequest{ - VolumeId: vol.GetVolume().GetVolumeId(), - TargetPath: sc.Config.TargetPath + "/dev", + VolumeId: vol.GetVolume().GetVolumeId(), + TargetPath: sc.Config.TargetPath + "/dev", }, ) @@ -183,16 +183,15 @@ var _ = sanity.DescribeSanity("Hammerspace - Block Volumes", func(sc *sanity.San context.Background(), &csi.NodePublishVolumeRequest{ VolumeId: vol.GetVolume().GetVolumeId(), - TargetPath: sc.Config.TargetPath+ "/dev", - StagingTargetPath: sc.Config.StagingPath+ "/dev", - Readonly: true, + TargetPath: sc.Config.TargetPath + "/dev", + StagingTargetPath: sc.Config.StagingPath + "/dev", + Readonly: true, VolumeCapability: &csi.VolumeCapability{ AccessType: &csi.VolumeCapability_Block{ Block: &csi.VolumeCapability_BlockVolume{}, }, AccessMode: &csi.VolumeCapability_AccessMode{ Mode: csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER, - }, }, VolumeContext: vol.GetVolume().GetVolumeContext(), @@ -217,15 +216,15 @@ var _ = sanity.DescribeSanity("Hammerspace - Block Volumes", func(sc *sanity.San Expect(output).To(Equal(testData)) By("Ensure write data to volume fails") - err = ioutil.WriteFile(sc.Config.TargetPath + "/dev", testData, 0644) + err = ioutil.WriteFile(sc.Config.TargetPath+"/dev", testData, 0644) Expect(err).To(HaveOccurred()) By("unpublish the volume from alt location") _, err = c.NodeUnpublishVolume( context.Background(), &csi.NodeUnpublishVolumeRequest{ - VolumeId: vol.GetVolume().GetVolumeId(), - TargetPath: sc.Config.TargetPath + "/dev", + VolumeId: vol.GetVolume().GetVolumeId(), + TargetPath: sc.Config.TargetPath + "/dev", }, ) @@ -233,4 +232,4 @@ var _ = sanity.DescribeSanity("Hammerspace - Block Volumes", func(sc *sanity.San }) }) -}) \ No newline at end of file +}) diff --git a/test/sanity/hammerspace_file_backed_mount.go b/test/sanity/hammerspace_file_backed_mount.go index d4eed0a..904e4c4 100644 --- a/test/sanity/hammerspace_file_backed_mount.go +++ b/test/sanity/hammerspace_file_backed_mount.go @@ -19,23 +19,22 @@ limitations under the License. package sanitytest import ( + "context" "fmt" + "os" + "strconv" + "strings" + "github.com/container-storage-interface/spec/lib/go/csi" "github.com/hammer-space/csi-plugin/pkg/common" "github.com/kubernetes-csi/csi-test/pkg/sanity" - "io/ioutil" "k8s.io/kubernetes/pkg/kubelet/kubeletconfig/util/log" - "strconv" - "strings" -) -import ( - "context" . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" ) - var _ = sanity.DescribeSanity("Hammerspace - File Backed Mount Volumes", func(sc *sanity.SanityContext) { var ( cl *sanity.Cleanup @@ -139,12 +138,12 @@ var _ = sanity.DescribeSanity("Hammerspace - File Backed Mount Volumes", func(sc } for key, value := range additionalMetadataTags { // Check the file exists - output, err := common.ExecCommand("cat", fmt.Sprintf("%s?.eval list_tags", common.ShareStagingDir + vol.GetVolume().GetVolumeId())) + output, err := common.ExecCommand("cat", fmt.Sprintf("%s?.eval list_tags", common.ShareStagingDir+vol.GetVolume().GetVolumeId())) if err != nil { Expect(err).NotTo(HaveOccurred()) } log.Infof(string(output)) - output, err = common.ExecCommand("cat", fmt.Sprintf("%s?.eval get_tag(\"%s\")",common.ShareStagingDir + vol.GetVolume().GetVolumeId(), key)) + output, err = common.ExecCommand("cat", fmt.Sprintf("%s?.eval get_tag(\"%s\")", common.ShareStagingDir+vol.GetVolume().GetVolumeId(), key)) if err != nil { Expect(err).NotTo(HaveOccurred()) } @@ -154,15 +153,15 @@ var _ = sanity.DescribeSanity("Hammerspace - File Backed Mount Volumes", func(sc By("Write data to volume") //sc.Config.TargetPath testData := []byte("test_data") - err = ioutil.WriteFile(sc.Config.TargetPath + "/testfile", testData, 0644) + err = os.WriteFile(sc.Config.TargetPath+"/testfile", testData, 0644) Expect(err).NotTo(HaveOccurred()) By("expand the volume") _, err = c.NodeExpandVolume( context.Background(), &csi.NodeExpandVolumeRequest{ - VolumeId: vol.GetVolume().GetVolumeId(), - VolumePath: sc.Config.TargetPath + "/dev", + VolumeId: vol.GetVolume().GetVolumeId(), + VolumePath: sc.Config.TargetPath + "/dev", CapacityRange: &csi.CapacityRange{ RequiredBytes: TestVolumeSize(sc) * 2, }, @@ -171,7 +170,7 @@ var _ = sanity.DescribeSanity("Hammerspace - File Backed Mount Volumes", func(sc Expect(err).NotTo(HaveOccurred()) - output, err := common.ExecCommand("blockdev", "--getsize64", sc.Config.TargetPath + "/dev") + output, err := common.ExecCommand("blockdev", "--getsize64", sc.Config.TargetPath+"/dev") if err != nil { Expect(err).NotTo(HaveOccurred()) } @@ -181,8 +180,8 @@ var _ = sanity.DescribeSanity("Hammerspace - File Backed Mount Volumes", func(sc _, err = c.NodeUnpublishVolume( context.Background(), &csi.NodeUnpublishVolumeRequest{ - VolumeId: vol.GetVolume().GetVolumeId(), - TargetPath: sc.Config.TargetPath, + VolumeId: vol.GetVolume().GetVolumeId(), + TargetPath: sc.Config.TargetPath, }, ) @@ -195,7 +194,7 @@ var _ = sanity.DescribeSanity("Hammerspace - File Backed Mount Volumes", func(sc VolumeId: vol.GetVolume().GetVolumeId(), TargetPath: sc.Config.TargetPath, StagingTargetPath: sc.Config.StagingPath, - Readonly: true, + Readonly: true, VolumeCapability: &csi.VolumeCapability{ AccessType: &csi.VolumeCapability_Mount{ Mount: &csi.VolumeCapability_MountVolume{ @@ -204,7 +203,6 @@ var _ = sanity.DescribeSanity("Hammerspace - File Backed Mount Volumes", func(sc }, AccessMode: &csi.VolumeCapability_AccessMode{ Mode: csi.VolumeCapability_AccessMode_SINGLE_NODE_WRITER, - }, }, VolumeContext: vol.GetVolume().GetVolumeContext(), @@ -215,20 +213,20 @@ var _ = sanity.DescribeSanity("Hammerspace - File Backed Mount Volumes", func(sc Expect(nodepubvol).NotTo(BeNil()) By("Read data from volume") - output, err = ioutil.ReadFile(sc.Config.TargetPath + "/testfile") + output, err = os.ReadFile(sc.Config.TargetPath + "/testfile") Expect(err).NotTo(HaveOccurred()) Expect(output).To(Equal(testData)) By("Ensure write data to volume fails") - err = ioutil.WriteFile(sc.Config.TargetPath + "/testfile", testData, 0644) + err = os.WriteFile(sc.Config.TargetPath+"/testfile", testData, 0644) Expect(err).To(HaveOccurred()) By("unpublish the volume") _, err = c.NodeUnpublishVolume( context.Background(), &csi.NodeUnpublishVolumeRequest{ - VolumeId: vol.GetVolume().GetVolumeId(), - TargetPath: sc.Config.TargetPath, + VolumeId: vol.GetVolume().GetVolumeId(), + TargetPath: sc.Config.TargetPath, }, ) @@ -236,4 +234,4 @@ var _ = sanity.DescribeSanity("Hammerspace - File Backed Mount Volumes", func(sc }) }) -}) \ No newline at end of file +}) diff --git a/test/sanity/hammerspace_negative.go b/test/sanity/hammerspace_negative.go index 3a9bcbb..ca7a39c 100644 --- a/test/sanity/hammerspace_negative.go +++ b/test/sanity/hammerspace_negative.go @@ -100,7 +100,6 @@ var _ = sanity.DescribeSanity("Hammerspace - Create Volume Negative Tests", func }) - // Create Volume with invalid metadata tags field It("should fail with invalid metadata", func() { name := uniqueString("sanity-node-full") @@ -137,7 +136,6 @@ var _ = sanity.DescribeSanity("Hammerspace - Create Volume Negative Tests", func }) - // Create Volume with invalid objectives field It("should fail with invalid objectives", func() { name := uniqueString("sanity-node-full") @@ -207,7 +205,6 @@ var _ = sanity.DescribeSanity("Hammerspace - Create Volume Negative Tests", func Expect(err).To(HaveOccurred()) }) - // Create Volume with invalid export options It("should fail with invalid objectives", func() { name := uniqueString("sanity-node-full") @@ -312,4 +309,4 @@ var _ = sanity.DescribeSanity("Hammerspace - Create Volume Negative Tests", func }) }) -}) \ No newline at end of file +}) diff --git a/test/sanity/hammerspace_nfs.go b/test/sanity/hammerspace_nfs.go index ac6db8d..ec9efe6 100644 --- a/test/sanity/hammerspace_nfs.go +++ b/test/sanity/hammerspace_nfs.go @@ -19,19 +19,19 @@ limitations under the License. package sanitytest import ( + "context" "fmt" + "os" + "strings" + "github.com/container-storage-interface/spec/lib/go/csi" "github.com/hammer-space/csi-plugin/pkg/common" "github.com/hammer-space/csi-plugin/pkg/driver" "github.com/kubernetes-csi/csi-test/pkg/sanity" - "io/ioutil" "k8s.io/kubernetes/pkg/kubelet/kubeletconfig/util/log" - "strings" -) -import ( - "context" . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" ) @@ -140,12 +140,12 @@ var _ = sanity.DescribeSanity("Hammerspace - NFS Volumes", func(sc *sanity.Sanit } for key, value := range additionalMetadataTags { // Check the file exists - output, err := common.ExecCommand("cat", fmt.Sprintf("%s?.eval list_tags", sc.Config.TargetPath + "/")) + output, err := common.ExecCommand("cat", fmt.Sprintf("%s?.eval list_tags", sc.Config.TargetPath+"/")) if err != nil { Expect(err).NotTo(HaveOccurred()) } log.Infof(string(output)) - output, err = common.ExecCommand("cat", fmt.Sprintf("%s?.eval get_tag(\"%s\")", sc.Config.TargetPath + "/", key)) + output, err = common.ExecCommand("cat", fmt.Sprintf("%s?.eval get_tag(\"%s\")", sc.Config.TargetPath+"/", key)) if err != nil { Expect(err).NotTo(HaveOccurred()) } @@ -164,7 +164,7 @@ var _ = sanity.DescribeSanity("Hammerspace - NFS Volumes", func(sc *sanity.Sanit } for _, obj := range objectives { - if ! driver.IsValueInList(obj, objectiveNames) { + if !driver.IsValueInList(obj, objectiveNames) { Fail(fmt.Sprintf("%s objective is not set on share, applied objectives: %s", obj, share.Objectives)) } } @@ -173,15 +173,15 @@ var _ = sanity.DescribeSanity("Hammerspace - NFS Volumes", func(sc *sanity.Sanit By("Write data to volume") //sc.Config.TargetPath testData := []byte("test_data") - err = ioutil.WriteFile(sc.Config.TargetPath + "/testfile", testData, 0644) + err = os.WriteFile(sc.Config.TargetPath+"/testfile", testData, 0644) Expect(err).NotTo(HaveOccurred()) By("unpublish the volume") _, err = c.NodeUnpublishVolume( context.Background(), &csi.NodeUnpublishVolumeRequest{ - VolumeId: vol.GetVolume().GetVolumeId(), - TargetPath: sc.Config.TargetPath, + VolumeId: vol.GetVolume().GetVolumeId(), + TargetPath: sc.Config.TargetPath, }, ) @@ -194,7 +194,7 @@ var _ = sanity.DescribeSanity("Hammerspace - NFS Volumes", func(sc *sanity.Sanit VolumeId: vol.GetVolume().GetVolumeId(), TargetPath: sc.Config.TargetPath, StagingTargetPath: sc.Config.StagingPath, - Readonly: true, + Readonly: true, VolumeCapability: &csi.VolumeCapability{ AccessType: &csi.VolumeCapability_Mount{ Mount: &csi.VolumeCapability_MountVolume{ @@ -203,7 +203,6 @@ var _ = sanity.DescribeSanity("Hammerspace - NFS Volumes", func(sc *sanity.Sanit }, AccessMode: &csi.VolumeCapability_AccessMode{ Mode: csi.VolumeCapability_AccessMode_MULTI_NODE_MULTI_WRITER, - }, }, VolumeContext: vol.GetVolume().GetVolumeContext(), @@ -214,20 +213,20 @@ var _ = sanity.DescribeSanity("Hammerspace - NFS Volumes", func(sc *sanity.Sanit Expect(nodepubvol).NotTo(BeNil()) By("Read data from volume") - output, err := ioutil.ReadFile(sc.Config.TargetPath + "/testfile") + output, err := os.ReadFile(sc.Config.TargetPath + "/testfile") Expect(err).NotTo(HaveOccurred()) Expect(output).To(Equal(testData)) By("Ensure write data to volume fails") - err = ioutil.WriteFile(sc.Config.TargetPath + "/testfile", testData, 0644) + err = os.WriteFile(sc.Config.TargetPath+"/testfile", testData, 0644) Expect(err).To(HaveOccurred()) By("unpublish the volume") _, err = c.NodeUnpublishVolume( context.Background(), &csi.NodeUnpublishVolumeRequest{ - VolumeId: vol.GetVolume().GetVolumeId(), - TargetPath: sc.Config.TargetPath, + VolumeId: vol.GetVolume().GetVolumeId(), + TargetPath: sc.Config.TargetPath, }, ) diff --git a/test/sanity/sanity_test.go b/test/sanity/sanity_test.go index c5ed153..a0e4975 100644 --- a/test/sanity/sanity_test.go +++ b/test/sanity/sanity_test.go @@ -30,7 +30,6 @@ var ( HSClient *client.HammerspaceClient ) - func Mkdir(targetPath string) (string, error) { os.Mkdir(targetPath, 0755) return targetPath, nil diff --git a/test/sanity/sanity_utils.go b/test/sanity/sanity_utils.go index 19963d6..421a506 100644 --- a/test/sanity/sanity_utils.go +++ b/test/sanity/sanity_utils.go @@ -3,15 +3,14 @@ package sanitytest import ( "crypto/rand" "fmt" - "github.com/hammer-space/csi-plugin/pkg/client" - "github.com/kubernetes-csi/csi-test/pkg/sanity" - "gopkg.in/yaml.v2" - "io/ioutil" "os" "strconv" "strings" -) + "github.com/hammer-space/csi-plugin/pkg/client" + "github.com/kubernetes-csi/csi-test/pkg/sanity" + "gopkg.in/yaml.v2" +) func copyStringMap(originalMap map[string]string) map[string]string { newMap := make(map[string]string) @@ -38,7 +37,7 @@ func createMountTargetLocation(targetPath string) error { func loadSecrets(path string) (*sanity.CSISecrets, error) { var creds sanity.CSISecrets - yamlFile, err := ioutil.ReadFile(path) + yamlFile, err := os.ReadFile(path) if err != nil { return &creds, fmt.Errorf("failed to read file %q: #%v", path, err) } @@ -71,7 +70,7 @@ func uniqueString(prefix string) string { return prefix + "-" + pseudoUUID() } -func GetHammerspaceClient() (*client.HammerspaceClient){ +func GetHammerspaceClient() *client.HammerspaceClient { tlsVerify, _ := strconv.ParseBool(os.Getenv("HS_TLS_VERIFY")) client, err := client.NewHammerspaceClient( @@ -85,7 +84,7 @@ func GetHammerspaceClient() (*client.HammerspaceClient){ return client } -func parseMetadataTagsParam(additionalMetadataTagsString string) (map[string]string){ +func parseMetadataTagsParam(additionalMetadataTagsString string) map[string]string { additionalMetadataTags := map[string]string{} tagsList := strings.Split(additionalMetadataTagsString, ",") @@ -99,4 +98,4 @@ func parseMetadataTagsParam(additionalMetadataTagsString string) (map[string]str } return additionalMetadataTags -} \ No newline at end of file +} From 3b236c473fe9836e346e12e7b1d80e3d82158be0 Mon Sep 17 00:00:00 2001 From: Ravi Kumar Date: Thu, 16 May 2024 07:19:05 +0000 Subject: [PATCH 06/10] Added pre-commit insturction to readme file. --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index a890d89..be6eb42 100755 --- a/README.md +++ b/README.md @@ -49,6 +49,12 @@ $ yum install nfs-utils The plugin container(s) must run as privileged containers +## Development Dependencies + +```bash +$ pip2 install pre-commit +``` + ## Installation Kubernetes specific deployment instructions are located at [here](https://github.com/hammer-space/csi-plugin/blob/master/deploy/kubernetes/README.md) @@ -90,6 +96,7 @@ Currently, only the ``topology.csi.hammerspace.com/is-data-portal`` key is suppo * Docker * Golang 1.12+ * nfs-utils +* pre-commit ### Building ##### Build a new docker image from local source: From c063835baa7047e76081559a6b195a5da0fd13ee Mon Sep 17 00:00:00 2001 From: Ravi Kumar Date: Wed, 22 May 2024 07:04:51 +0000 Subject: [PATCH 07/10] HS-25396: 1. Added option to specify fqdn and skip hs-endpoint. 2. Added option to enable data caching. 3. Resolve fqdn in pod, ensure hostNetwork in plugin is set to false to use fqdn from coredns. --- main.go | 24 ++++++++++++++++++++++-- pkg/common/host_utils.go | 13 +++++++++++++ pkg/common/hs_types.go | 1 + pkg/driver/controller.go | 8 ++++++++ pkg/driver/driver.go | 4 ++++ pkg/driver/utils.go | 28 ++++++++++++++++++++-------- 6 files changed, 68 insertions(+), 10 deletions(-) diff --git a/main.go b/main.go index 498d9a9..b91e788 100644 --- a/main.go +++ b/main.go @@ -58,8 +58,14 @@ func validateEnvironmentVars() { } hsEndpoint := os.Getenv("HS_ENDPOINT") + if os.Getenv("FQDN") != "" { + hsEndpoint = os.Getenv("FQDN") + if !strings.HasPrefix(hsEndpoint, "https://") { + hsEndpoint = "https://" + hsEndpoint + } + } if len(hsEndpoint) == 0 { - log.Error("HS_ENDPOINT must be defined") + log.Error("HS_ENDPOINT or FQDN must be defined") os.Exit(1) } @@ -109,8 +115,22 @@ func main() { CSI_version := os.Getenv("CSI_MAJOR_VERSION") endpoint := os.Getenv("CSI_ENDPOINT") + hs_endpoint := os.Getenv("HS_ENDPOINT") + if os.Getenv("FQDN") != "" { + log.Infof("FQDN - %v", os.Getenv("FQDN")) + extracted_endpoint, err := common.ResolveFQDN(os.Getenv("FQDN")) + if err != nil { + log.Errorf("Error - %v", err) + } else { + if !strings.HasPrefix(extracted_endpoint, "https://") { + hs_endpoint = "https://" + extracted_endpoint + } else { + hs_endpoint = extracted_endpoint + } + } + } csiDriver := driver.NewCSIDriver( - os.Getenv("HS_ENDPOINT"), + hs_endpoint, os.Getenv("HS_USERNAME"), os.Getenv("HS_PASSWORD"), os.Getenv("HS_TLS_VERIFY"), diff --git a/pkg/common/host_utils.go b/pkg/common/host_utils.go index 7169ffb..0b57c75 100644 --- a/pkg/common/host_utils.go +++ b/pkg/common/host_utils.go @@ -520,3 +520,16 @@ func SetMetadataTags(localPath string, tags map[string]string) error { return err } + +// resolveFQDN resolves the FQDN to an IP address +func ResolveFQDN(fqdn string) (string, error) { + ips, err := net.LookupIP(fqdn) + if err != nil { + return "", fmt.Errorf("failed to resolve FQDN %s: %v", fqdn, err) + } + if len(ips) == 0 { + return "", fmt.Errorf("no IP addresses found for FQDN %s", fqdn) + } + // Use the first resolved IP address + return ips[0].String(), nil +} diff --git a/pkg/common/hs_types.go b/pkg/common/hs_types.go index bd1fc29..2f74b81 100644 --- a/pkg/common/hs_types.go +++ b/pkg/common/hs_types.go @@ -27,6 +27,7 @@ type HSVolumeParameters struct { FSType string Comment string AdditionalMetadataTags map[string]string + CacheEnabled bool } type HSVolume struct { diff --git a/pkg/driver/controller.go b/pkg/driver/controller.go index 7740cba..a8ec0eb 100755 --- a/pkg/driver/controller.go +++ b/pkg/driver/controller.go @@ -145,6 +145,14 @@ func ParseVolParams(params map[string]string) (common.HSVolumeParameters, error) } } + if params["cacheEnabled"] != "" { + cacheEnabled, err := strconv.ParseBool(params["cacheEnabled"]) + if err != nil { + vParams.CacheEnabled = false + } + vParams.CacheEnabled = cacheEnabled + } + return vParams, nil } diff --git a/pkg/driver/driver.go b/pkg/driver/driver.go index a6d95bf..626244d 100644 --- a/pkg/driver/driver.go +++ b/pkg/driver/driver.go @@ -47,6 +47,8 @@ type CSIDriver struct { snapshotLocks map[string]*sync.Mutex hsclient *client.HammerspaceClient NodeID string + fqdn string + endpoint string } func NewCSIDriver(endpoint, username, password, tlsVerifyStr string) *CSIDriver { @@ -69,6 +71,8 @@ func NewCSIDriver(endpoint, username, password, tlsVerifyStr string) *CSIDriver volumeLocks: make(map[string]*sync.Mutex), snapshotLocks: make(map[string]*sync.Mutex), NodeID: os.Getenv("CSI_NODE_NAME"), + fqdn: os.Getenv("FQDN"), + endpoint: endpoint, } } diff --git a/pkg/driver/utils.go b/pkg/driver/utils.go index 502d2f6..b333c17 100644 --- a/pkg/driver/utils.go +++ b/pkg/driver/utils.go @@ -17,7 +17,6 @@ limitations under the License. package driver import ( - "errors" "fmt" "os/exec" "path" @@ -47,7 +46,7 @@ func GetVolumeNameFromPath(path string) string { func GetSnapshotNameFromSnapshotId(snapshotId string) (string, error) { tokens := strings.SplitN(snapshotId, "|", 2) if len(tokens) != 2 { - return "", errors.New(fmt.Sprintf(common.ImproperlyFormattedSnapshotId, snapshotId)) + return "", fmt.Errorf(common.ImproperlyFormattedSnapshotId, snapshotId) } return tokens[0], nil } @@ -55,7 +54,7 @@ func GetSnapshotNameFromSnapshotId(snapshotId string) (string, error) { func GetShareNameFromSnapshotId(snapshotId string) (string, error) { tokens := strings.SplitN(snapshotId, "|", 2) if len(tokens) != 2 { - return "", errors.New(fmt.Sprintf(common.ImproperlyFormattedSnapshotId, snapshotId)) + return "", fmt.Errorf(common.ImproperlyFormattedSnapshotId, snapshotId) } return path.Base(tokens[1]), nil } @@ -133,6 +132,7 @@ func (d *CSIDriver) UnmountBackingShareIfUnused(backingShareName string) (bool, func (d *CSIDriver) MountShareAtBestDataportal(shareExportPath, targetPath string, mountFlags []string) error { var err error + var fipaddr string = "" log.Infof("Finding best host exporting %s", shareExportPath) @@ -140,10 +140,22 @@ func (d *CSIDriver) MountShareAtBestDataportal(shareExportPath, targetPath strin if err != nil { log.Errorf("Could not create list of data-portals, %v", err) } - // Always look for floating data portal IPs - fipaddr, err := d.hsclient.GetPortalFloatingIp() - if err != nil { - log.Errorf("Could not contact Anvil for floating IPs, %v", err) + + if d.fqdn != "" { // if fqdn is provided use that ip for all Communication + // check if rpcinfo gives a response + ok, err := common.CheckNFSExports(d.endpoint) + if err != nil { + log.Warnf("Could not get exports for fqdn ip at %s. Error: %v", d.endpoint, err) + } + if ok { + fipaddr = d.endpoint + } + } else { + // Always look for floating data portal IPs + fipaddr, err = d.hsclient.GetPortalFloatingIp() + if err != nil { + log.Errorf("Could not contact Anvil for floating IPs, %v", err) + } } MountToDataPortal := func(portal common.DataPortal, mount_options []string) bool { @@ -218,5 +230,5 @@ func (d *CSIDriver) MountShareAtBestDataportal(shareExportPath, targetPath strin if mounted { return nil } - return errors.New("Could not mount to any data-portals") + return fmt.Errorf("could not mount to any data-portals") } From 65e45e6f9bcd25faab8f27744dd608f27ab167a3 Mon Sep 17 00:00:00 2001 From: Ravi Kumar Date: Thu, 23 May 2024 12:36:27 +0000 Subject: [PATCH 08/10] HS-25396: 1. Removed previous implimentation and added fqdn via storage class. 2. The fqdn will only be consumed when for mounting. --- main.go | 28 ++++++---------------------- pkg/common/hs_types.go | 2 ++ pkg/driver/controller.go | 19 +++++++++++++++---- pkg/driver/driver.go | 4 ---- pkg/driver/node.go | 14 +++++++------- pkg/driver/utils.go | 15 ++++++++------- 6 files changed, 38 insertions(+), 44 deletions(-) diff --git a/main.go b/main.go index b91e788..a5ce94d 100644 --- a/main.go +++ b/main.go @@ -58,14 +58,8 @@ func validateEnvironmentVars() { } hsEndpoint := os.Getenv("HS_ENDPOINT") - if os.Getenv("FQDN") != "" { - hsEndpoint = os.Getenv("FQDN") - if !strings.HasPrefix(hsEndpoint, "https://") { - hsEndpoint = "https://" + hsEndpoint - } - } if len(hsEndpoint) == 0 { - log.Error("HS_ENDPOINT or FQDN must be defined") + log.Error("HS_ENDPOINT must be defined") os.Exit(1) } @@ -80,11 +74,13 @@ func validateEnvironmentVars() { log.Error("HS_USERNAME must be defined") os.Exit(1) } + password := os.Getenv("HS_PASSWORD") if len(password) == 0 { log.Error("HS_PASSWORD must be defined") os.Exit(1) } + if os.Getenv("HS_TLS_VERIFY") != "" { _, err = strconv.ParseBool(os.Getenv("HS_TLS_VERIFY")) if err != nil { @@ -92,12 +88,14 @@ func validateEnvironmentVars() { os.Exit(1) } } + if os.Getenv("CSI_MAJOR_VERSION") != "0" || os.Getenv("CSI_MAJOR_VERSION") != "1" { if err != nil { log.Error("CSI_MAJOR_VERSION must be set to \"0\" or \"1\"") os.Exit(1) } } + common.DataPortalMountPrefix = os.Getenv("HS_DATA_PORTAL_MOUNT_PREFIX") } @@ -115,22 +113,8 @@ func main() { CSI_version := os.Getenv("CSI_MAJOR_VERSION") endpoint := os.Getenv("CSI_ENDPOINT") - hs_endpoint := os.Getenv("HS_ENDPOINT") - if os.Getenv("FQDN") != "" { - log.Infof("FQDN - %v", os.Getenv("FQDN")) - extracted_endpoint, err := common.ResolveFQDN(os.Getenv("FQDN")) - if err != nil { - log.Errorf("Error - %v", err) - } else { - if !strings.HasPrefix(extracted_endpoint, "https://") { - hs_endpoint = "https://" + extracted_endpoint - } else { - hs_endpoint = extracted_endpoint - } - } - } csiDriver := driver.NewCSIDriver( - hs_endpoint, + os.Getenv("HS_ENDPOINT"), os.Getenv("HS_USERNAME"), os.Getenv("HS_PASSWORD"), os.Getenv("HS_TLS_VERIFY"), diff --git a/pkg/common/hs_types.go b/pkg/common/hs_types.go index 2f74b81..4ce9d54 100644 --- a/pkg/common/hs_types.go +++ b/pkg/common/hs_types.go @@ -28,6 +28,7 @@ type HSVolumeParameters struct { Comment string AdditionalMetadataTags map[string]string CacheEnabled bool + FQDN string } type HSVolume struct { @@ -45,6 +46,7 @@ type HSVolume struct { Comment string SourceSnapShareName string AdditionalMetadataTags map[string]string + FQDN string } ///// Request and Response objects for interacting with the HS API diff --git a/pkg/driver/controller.go b/pkg/driver/controller.go index a8ec0eb..726094d 100755 --- a/pkg/driver/controller.go +++ b/pkg/driver/controller.go @@ -50,6 +50,7 @@ func ParseVolParams(params map[string]string) (common.HSVolumeParameters, error) if deleteDelayParam, exists := params["deleteDelay"]; exists { var err error + log.Infof("share delete delay %s", deleteDelayParam) vParams.DeleteDelay, err = strconv.ParseInt(deleteDelayParam, 10, 64) if err != nil { return vParams, status.Errorf(codes.InvalidArgument, common.InvalidDeleteDelay, deleteDelayParam) @@ -153,6 +154,15 @@ func ParseVolParams(params map[string]string) (common.HSVolumeParameters, error) vParams.CacheEnabled = cacheEnabled } + if params["fqdn"] != "" { + FQDN, err := common.ResolveFQDN(params["fqdn"]) + if err != nil { + log.Warnf("fully qualified domain name not specified. Err %v", err.Error()) + vParams.FQDN = "" + } + vParams.FQDN = FQDN + } + return vParams, nil } @@ -248,7 +258,7 @@ func (d *CSIDriver) ensureShareBackedVolumeExists(ctx context.Context, hsVolume // generate unique target path on host for setting file metadata targetPath := common.ShareStagingDir + "metadata-mounts" + hsVolume.Path defer common.UnmountFilesystem(targetPath) - err = d.publishShareBackedVolume(hsVolume.Path, targetPath, []string{}, false) + err = d.publishShareBackedVolume(hsVolume.Path, targetPath, []string{}, false, hsVolume.FQDN) if err != nil { log.Warnf("failed to get share backed volume on hsVolumePath %s targetPath %s. Err %v", hsVolume.Path, targetPath, err) } @@ -283,7 +293,7 @@ func (d *CSIDriver) ensureBackingShareExists(backingShareName string, hsVolume * // generate unique target path on host for setting file metadata targetPath := common.ShareStagingDir + "metadata-mounts" + hsVolume.Path defer common.UnmountFilesystem(targetPath) - err = d.publishShareBackedVolume(hsVolume.Path, targetPath, []string{}, false) + err = d.publishShareBackedVolume(hsVolume.Path, targetPath, []string{}, false, hsVolume.FQDN) if err != nil { log.Warnf("failed to get share backed volume on hsVolumePath %s targetPath %s. Err %v", hsVolume.Path, targetPath, err) } @@ -341,7 +351,7 @@ func (d *CSIDriver) ensureDeviceFileExists( //// Mount Backing Share defer d.UnmountBackingShareIfUnused(backingShare.Name) - err = d.EnsureBackingShareMounted(backingShare.Name) // check if share is mounted + err = d.EnsureBackingShareMounted(backingShare.Name, hsVolume.FQDN) // check if share is mounted if err != nil { log.Errorf("failed to ensure backing share is mounted, %v", err) return err @@ -513,6 +523,7 @@ func (d *CSIDriver) CreateVolume( FSType: fsType, AdditionalMetadataTags: vParams.AdditionalMetadataTags, Comment: vParams.Comment, + FQDN: vParams.FQDN, } var backingShare *common.ShareResponse // if it's file backed, we should check capacity of backing share @@ -678,7 +689,7 @@ func (d *CSIDriver) deleteFileBackedVolume(filepath string) error { defer d.releaseVolumeLock(residingShareName) d.getVolumeLock(residingShareName) defer d.UnmountBackingShareIfUnused(residingShareName) - err := d.EnsureBackingShareMounted(residingShareName) // check if share is mounted + err := d.EnsureBackingShareMounted(residingShareName, "") // check if share is mounted if err != nil { log.Errorf("failed to ensure backing share is mounted, %v", err) return status.Errorf(codes.Internal, err.Error()) diff --git a/pkg/driver/driver.go b/pkg/driver/driver.go index 626244d..a6d95bf 100644 --- a/pkg/driver/driver.go +++ b/pkg/driver/driver.go @@ -47,8 +47,6 @@ type CSIDriver struct { snapshotLocks map[string]*sync.Mutex hsclient *client.HammerspaceClient NodeID string - fqdn string - endpoint string } func NewCSIDriver(endpoint, username, password, tlsVerifyStr string) *CSIDriver { @@ -71,8 +69,6 @@ func NewCSIDriver(endpoint, username, password, tlsVerifyStr string) *CSIDriver volumeLocks: make(map[string]*sync.Mutex), snapshotLocks: make(map[string]*sync.Mutex), NodeID: os.Getenv("CSI_NODE_NAME"), - fqdn: os.Getenv("FQDN"), - endpoint: endpoint, } } diff --git a/pkg/driver/node.go b/pkg/driver/node.go index 5a5b7be..246c56e 100644 --- a/pkg/driver/node.go +++ b/pkg/driver/node.go @@ -71,7 +71,7 @@ func (d *CSIDriver) NodeUnstageVolume( func (d *CSIDriver) publishShareBackedVolume( exportPath, - targetPath string, mountFlags []string, readOnly bool) error { + targetPath string, mountFlags []string, readOnly bool, fqdn string) error { notMnt, err := mount.New("").IsLikelyNotMountPoint(targetPath) if err != nil { @@ -93,12 +93,12 @@ func (d *CSIDriver) publishShareBackedVolume( if readOnly { mountFlags = append(mountFlags, "ro") } - err = d.MountShareAtBestDataportal(exportPath, targetPath, mountFlags) + err = d.MountShareAtBestDataportal(exportPath, targetPath, mountFlags, fqdn) return err } func (d *CSIDriver) publishFileBackedVolume( - backingShareName, volumePath, targetPath, fsType string, mountFlags []string, readOnly bool) error { + backingShareName, volumePath, targetPath, fsType string, mountFlags []string, readOnly bool, fqdn string) error { defer d.releaseVolumeLock(backingShareName) d.getVolumeLock(backingShareName) @@ -125,7 +125,7 @@ func (d *CSIDriver) publishFileBackedVolume( } // Ensure the backing share is mounted - err = d.EnsureBackingShareMounted(backingShareName) + err = d.EnsureBackingShareMounted(backingShareName, fqdn) if err != nil { return err } @@ -206,6 +206,7 @@ func (d *CSIDriver) NodePublishVolume( var volumeMode, fsType string var mountFlags []string + fqdn := req.GetVolumeContext()["fqdn"] cap := req.GetVolumeCapability() switch cap.GetAccessType().(type) { case *csi.VolumeCapability_Block: @@ -225,7 +226,7 @@ func (d *CSIDriver) NodePublishVolume( } var err error if fsType == "nfs" { - err = d.publishShareBackedVolume(req.GetVolumeId(), req.GetTargetPath(), mountFlags, req.GetReadonly()) + err = d.publishShareBackedVolume(req.GetVolumeId(), req.GetTargetPath(), mountFlags, req.GetReadonly(), fqdn) } else { var backingShareName string if volumeMode == "Block" { @@ -235,8 +236,7 @@ func (d *CSIDriver) NodePublishVolume( } log.Infof("Found backing share %s for volume %s", backingShareName, req.GetVolumeId()) - err = d.publishFileBackedVolume( - backingShareName, req.GetVolumeId(), req.GetTargetPath(), fsType, mountFlags, req.GetReadonly()) + err = d.publishFileBackedVolume(backingShareName, req.GetVolumeId(), req.GetTargetPath(), fsType, mountFlags, req.GetReadonly(), fqdn) } diff --git a/pkg/driver/utils.go b/pkg/driver/utils.go index b333c17..6622055 100644 --- a/pkg/driver/utils.go +++ b/pkg/driver/utils.go @@ -65,7 +65,7 @@ func GetSnapshotIDFromSnapshotName(hsSnapName, sourceVolumeID string) string { return fmt.Sprintf("%s|%s", hsSnapName, sourceVolumeID) } -func (d *CSIDriver) EnsureBackingShareMounted(backingShareName string) error { +func (d *CSIDriver) EnsureBackingShareMounted(backingShareName, fqdn string) error { backingShare, err := d.hsclient.GetShare(backingShareName) if err != nil { return status.Errorf(codes.NotFound, err.Error()) @@ -75,7 +75,7 @@ func (d *CSIDriver) EnsureBackingShareMounted(backingShareName string) error { // Mount backing share if isMounted, _ := common.IsShareMounted(backingDir); !isMounted { mo := []string{} - err := d.MountShareAtBestDataportal(backingShare.ExportPath, backingDir, mo) + err := d.MountShareAtBestDataportal(backingShare.ExportPath, backingDir, mo, fqdn) if err != nil { log.Errorf("failed to mount backing share, %v", err) return err @@ -130,7 +130,7 @@ func (d *CSIDriver) UnmountBackingShareIfUnused(backingShareName string) (bool, return true, err } -func (d *CSIDriver) MountShareAtBestDataportal(shareExportPath, targetPath string, mountFlags []string) error { +func (d *CSIDriver) MountShareAtBestDataportal(shareExportPath, targetPath string, mountFlags []string, fqdn string) error { var err error var fipaddr string = "" @@ -141,14 +141,15 @@ func (d *CSIDriver) MountShareAtBestDataportal(shareExportPath, targetPath strin log.Errorf("Could not create list of data-portals, %v", err) } - if d.fqdn != "" { // if fqdn is provided use that ip for all Communication + extracted_endpoint, err := common.ResolveFQDN(fqdn) + if extracted_endpoint != "" && err == nil { // if fqdn is provided use that ip instead of floatingips // check if rpcinfo gives a response - ok, err := common.CheckNFSExports(d.endpoint) + ok, err := common.CheckNFSExports(extracted_endpoint) if err != nil { - log.Warnf("Could not get exports for fqdn ip at %s. Error: %v", d.endpoint, err) + log.Warnf("Could not get exports for fqdn ip at %s. Error: %v", extracted_endpoint, err) } if ok { - fipaddr = d.endpoint + fipaddr = extracted_endpoint } } else { // Always look for floating data portal IPs From 7f5dea0b6d3e81acbebbb4d1a546904bfce87960 Mon Sep 17 00:00:00 2001 From: Ravi Kumar Date: Sun, 16 Jun 2024 16:12:48 +0000 Subject: [PATCH 09/10] HS-25784 - Added fix for nfs volume create. --- pkg/driver/controller.go | 15 ++++++--------- pkg/driver/utils.go | 1 - 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/pkg/driver/controller.go b/pkg/driver/controller.go index 726094d..efbfbe4 100755 --- a/pkg/driver/controller.go +++ b/pkg/driver/controller.go @@ -525,21 +525,18 @@ func (d *CSIDriver) CreateVolume( Comment: vParams.Comment, FQDN: vParams.FQDN, } - var backingShare *common.ShareResponse + // if it's file backed, we should check capacity of backing share var backingShareName string if blockRequested { backingShareName = vParams.BlockBackingShareName - } else { + } else if filesystemRequested { backingShareName = vParams.MountBackingShareName - } - backingShare, err = d.hsclient.GetShare(backingShareName) - if err != nil || backingShare == nil { - log.Infof("share dosent exist ensuring share exist.") - backingShare, err = d.ensureBackingShareExists(backingShare.Name, hsVolume) - if err != nil { - return nil, status.Error(codes.Internal, err.Error()) + if backingShareName == "" && fsType == "nfs" { + backingShareName = volumeName } + } else { + backingShareName = volumeName } if requestedSize > 0 { diff --git a/pkg/driver/utils.go b/pkg/driver/utils.go index 6622055..1c0b4aa 100644 --- a/pkg/driver/utils.go +++ b/pkg/driver/utils.go @@ -184,7 +184,6 @@ func (d *CSIDriver) MountShareAtBestDataportal(shareExportPath, targetPath strin // Check the default prefixes for _, mountPrefix := range common.DefaultDataPortalMountPrefixes { for _, e := range exports { - log.Debugf("checking exports for %s%s with %s", mountPrefix, shareExportPath, e) if e == fmt.Sprintf("%s%s", mountPrefix, shareExportPath) { export = fmt.Sprintf("%s:%s%s", addr, mountPrefix, shareExportPath) log.Infof("Found export %s", export) From fd27579c3de6339bc89b8860f08ac23e710b0fd8 Mon Sep 17 00:00:00 2001 From: Ravi Kumar Date: Mon, 1 Jul 2024 06:56:43 +0000 Subject: [PATCH 10/10] HS-25784: 1. Added fix for null pointer check. --- pkg/driver/controller.go | 51 +++++++++++++++------------------------- pkg/driver/utils.go | 1 + 2 files changed, 20 insertions(+), 32 deletions(-) diff --git a/pkg/driver/controller.go b/pkg/driver/controller.go index efbfbe4..89b7340 100755 --- a/pkg/driver/controller.go +++ b/pkg/driver/controller.go @@ -173,7 +173,22 @@ func (d *CSIDriver) ensureShareBackedVolumeExists(ctx context.Context, hsVolume if err != nil { return fmt.Errorf("failed to get share: %w", err) } - if share == nil { + if share != nil { + if share.Size != hsVolume.Size { + return status.Errorf( + codes.AlreadyExists, + common.VolumeExistsSizeMismatch, + share.Size, + hsVolume.Size) + } + + if share.ShareState == "REMOVED" { + return status.Errorf(codes.Aborted, common.VolumeBeingDeleted) + } + } + + if share == nil && err == nil { + // Create empty share // Create the Mountvolume err = d.hsclient.CreateShare( hsVolume.Name, @@ -189,20 +204,6 @@ func (d *CSIDriver) ensureShareBackedVolumeExists(ctx context.Context, hsVolume return status.Errorf(codes.Internal, err.Error()) } } - if err != nil { - return fmt.Errorf("share not found") - } - if share.Size != hsVolume.Size { - return status.Errorf( - codes.AlreadyExists, - common.VolumeExistsSizeMismatch, - share.Size, - hsVolume.Size) - } - - if share.ShareState == "REMOVED" { - return status.Errorf(codes.Aborted, common.VolumeBeingDeleted) - } if hsVolume.SourceSnapPath != "" { // Create from snapshot @@ -236,27 +237,13 @@ func (d *CSIDriver) ensureShareBackedVolumeExists(ctx context.Context, hsVolume hsVolume.SourceSnapPath, ) - if err != nil { - return status.Errorf(codes.Internal, err.Error()) - } - } else { // Create empty share - // Create the Mountvolume - err = d.hsclient.CreateShare( - hsVolume.Name, - hsVolume.Path, - hsVolume.Size, - hsVolume.Objectives, - hsVolume.ExportOptions, - hsVolume.DeleteDelay, - hsVolume.Comment, - ) - if err != nil { return status.Errorf(codes.Internal, err.Error()) } } + // generate unique target path on host for setting file metadata - targetPath := common.ShareStagingDir + "metadata-mounts" + hsVolume.Path + targetPath := common.ShareStagingDir + "/metadata-mounts" + hsVolume.Path defer common.UnmountFilesystem(targetPath) err = d.publishShareBackedVolume(hsVolume.Path, targetPath, []string{}, false, hsVolume.FQDN) if err != nil { @@ -291,7 +278,7 @@ func (d *CSIDriver) ensureBackingShareExists(backingShareName string, hsVolume * } // generate unique target path on host for setting file metadata - targetPath := common.ShareStagingDir + "metadata-mounts" + hsVolume.Path + targetPath := common.ShareStagingDir + "/metadata-mounts" + hsVolume.Path defer common.UnmountFilesystem(targetPath) err = d.publishShareBackedVolume(hsVolume.Path, targetPath, []string{}, false, hsVolume.FQDN) if err != nil { diff --git a/pkg/driver/utils.go b/pkg/driver/utils.go index 1c0b4aa..5e6f2b3 100644 --- a/pkg/driver/utils.go +++ b/pkg/driver/utils.go @@ -139,6 +139,7 @@ func (d *CSIDriver) MountShareAtBestDataportal(shareExportPath, targetPath strin portals, err := d.hsclient.GetDataPortals(d.NodeID) if err != nil { log.Errorf("Could not create list of data-portals, %v", err) + return err } extracted_endpoint, err := common.ResolveFQDN(fqdn)