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/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/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: 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/main.go b/main.go index c47d610..a5ce94d 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,147 @@ 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{ + PrettyPrint: true, + DisableTimestamp: false, + TimestampFormat: "2006-01-02 15:04:05", + }) + 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 747cf86..99e5ea1 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(fip string) { + defer wg.Done() + + // check if rpcinfo gives a response + ok, err := common.CheckNFSExports(fip) + if err != nil { + 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 = fip // Update the floating IP + mutex.Unlock() + log.Infof("Found floating IP data-portal %s", floatingip) + } + }(p.Address) } + + wg.Wait() return floatingip, nil } @@ -136,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 { @@ -143,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 @@ -262,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) @@ -278,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) @@ -295,11 +308,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 { @@ -307,7 +324,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 @@ -329,7 +346,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 @@ -338,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 } @@ -352,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 } @@ -434,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 @@ -485,7 +504,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) @@ -502,7 +521,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, @@ -527,7 +546,8 @@ func (client *HammerspaceClient) CreateShare(name string, ExtendedInfo: extendedInfo, Comment: comment, } - if size > 0 { + i, _ := strconv.Atoi(size) + if i > 0 { share.Size = size } @@ -581,7 +601,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, @@ -607,7 +627,8 @@ func (client *HammerspaceClient) CreateShareFromSnapshot(name string, ExtendedInfo: extendedInfo, Comment: comment, } - if size > 0 { + i, _ := strconv.ParseInt(size, 10, 64) + if i > 0 { share.Size = size } @@ -670,7 +691,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) @@ -719,16 +740,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) @@ -990,10 +1009,12 @@ 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()) } + // 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/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 96% rename from pkg/client/fakes_test.go rename to pkg/client/test/fakes_test.go index 9ef89f2..27925fb 100644 --- a/pkg/client/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/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/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/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/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..0b57c75 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,462 @@ 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 - } - - return nil + log.Infof("deleting file '%s'", pathname) + + // 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 + } + + 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 { + output1, err := ExecCommand("hs", + "-v", "tag", + "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.Errorf("Failed to set tag %v. Output - %v", err.Error(), output1) + break + } + log.Debugf("HS command output: %s", output1) + } + + 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/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_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/common/hs_types.go b/pkg/common/hs_types.go index 1dd3aa9..4ce9d54 100644 --- a/pkg/common/hs_types.go +++ b/pkg/common/hs_types.go @@ -27,6 +27,8 @@ type HSVolumeParameters struct { FSType string Comment string AdditionalMetadataTags map[string]string + CacheEnabled bool + FQDN string } type HSVolume struct { @@ -35,7 +37,7 @@ type HSVolume struct { Objectives []string BlockBackingShareName string MountBackingShareName string - Size int64 + Size string Name string Path string VolumeMode string @@ -44,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 @@ -51,7 +54,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 +62,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 +78,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"` @@ -83,17 +86,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 int64 `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 int64 `json:"percent"` } type ShareExportOptions struct { @@ -130,7 +133,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 515e80d..89b7340 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" ) @@ -44,11 +45,12 @@ 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 { 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) @@ -89,7 +91,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 @@ -144,19 +146,34 @@ 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 + } + + 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 } -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 != nil { // It exists! + if share != nil { if share.Size != hsVolume.Size { return status.Errorf( codes.AlreadyExists, @@ -164,14 +181,30 @@ func (d *CSIDriver) ensureShareBackedVolumeExists( share.Size, hsVolume.Size) } + if share.ShareState == "REMOVED" { return status.Errorf(codes.Aborted, common.VolumeBeingDeleted) } - // FIXME: Check that it's objectives, export options, deleteDelay(extended info), - // etc match (optional functionality with CSI 1.0) + } - return nil + if share == nil && err == nil { + // 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()) + } } + if hsVolume.SourceSnapPath != "" { // Create from snapshot sourceShare, err := d.hsclient.GetShare(hsVolume.SourceSnapShareName) @@ -204,31 +237,17 @@ func (d *CSIDriver) ensureShareBackedVolumeExists( 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) + err = d.publishShareBackedVolume(hsVolume.Path, targetPath, []string{}, false, hsVolume.FQDN) 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) @@ -240,15 +259,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, @@ -262,9 +278,12 @@ 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) + 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) + } err = common.SetMetadataTags(targetPath+"/", hsVolume.AdditionalMetadataTags) if err != nil { log.Warnf("failed to set additional metadata on share %v", err) @@ -295,13 +314,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 := backingShare.Space.Available - if hsVolume.Size > available { - return status.Errorf(codes.OutOfRange, common.OutOfCapacity, hsVolume.Size, available) + available, _ := strconv.ParseInt(backingShare.Space.Available, 10, 64) + if hsVolumeSize > available { + return status.Errorf(codes.OutOfRange, common.OutOfCapacity, hsVolumeSize, available) } backingDir := common.ShareStagingDir + backingShare.ExportPath @@ -319,7 +338,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 @@ -327,7 +346,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 @@ -344,21 +363,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 +391,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 +430,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) @@ -418,7 +442,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 } @@ -432,7 +456,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: @@ -441,9 +465,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 } } @@ -465,7 +487,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 +496,88 @@ 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, + FQDN: vParams.FQDN, + } + + // if it's file backed, we should check capacity of backing share + var backingShareName string + if blockRequested { + backingShareName = vParams.BlockBackingShareName + } else if filesystemRequested { + backingShareName = vParams.MountBackingShareName + if backingShareName == "" && fsType == "nfs" { + backingShareName = volumeName + } } else { - requestedSize = 0 + backingShareName = volumeName } 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 = backingShare.Space.Available + 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 +589,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: requestedSize, - 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 +604,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: @@ -575,7 +616,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 { @@ -585,19 +625,24 @@ 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" { volContext["blockBackingShareName"] = hsVolume.BlockBackingShareName - } else if volumeMode == "Filesystem" && fsType != "nfs" { + } else if volumeMode == "Filesystem" { volContext["mountBackingShareName"] = hsVolume.MountBackingShareName volContext["fsType"] = fsType } + 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: hsVolume.Size, + CapacityBytes: hsVolumeSize, VolumeId: hsVolume.Path, VolumeContext: volContext, }, @@ -605,9 +650,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 @@ -616,17 +663,17 @@ 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 { + 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 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()) @@ -639,7 +686,7 @@ func (d *CSIDriver) deleteFileBackedVolume(filepath string) error { } } - return nil + return err } func (d *CSIDriver) deleteShareBackedVolume(share *common.ShareResponse) error { @@ -678,21 +725,29 @@ func (d *CSIDriver) DeleteVolume( if volumeId == "" { return nil, status.Error(codes.InvalidArgument, common.EmptyVolumeId) } - defer d.releaseVolumeLock(volumeId) 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 } @@ -756,25 +811,24 @@ 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 - if err != nil { - available = 0 - } else { - available = backingShare.Space.Available + backingShare, _ := d.hsclient.GetShare(backingShareName) + var available int64 = 0 + if backingShare != nil { + available, _ = strconv.ParseInt(backingShare.Space.Available, 10, 64) } if available-sizeDiff < 0 { @@ -804,11 +858,11 @@ 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 { - 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 +900,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 +1021,7 @@ func (d *CSIDriver) GetCapacity( }, nil } - vParams, err := parseVolParams(req.Parameters) + vParams, err := ParseVolParams(req.Parameters) if err != nil { return nil, err } @@ -985,7 +1039,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/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.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/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/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 c2b260f..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) } @@ -461,17 +461,17 @@ 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) } - 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( 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.go b/pkg/driver/utils.go index 35398ef..5e6f2b3 100644 --- a/pkg/driver/utils.go +++ b/pkg/driver/utils.go @@ -17,199 +17,219 @@ 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" + "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 "", fmt.Errorf(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 "", fmt.Errorf(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 +func (d *CSIDriver) EnsureBackingShareMounted(backingShareName, fqdn 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, fqdn) + 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") +func (d *CSIDriver) MountShareAtBestDataportal(shareExportPath, targetPath string, mountFlags []string, fqdn string) error { + var err error + var fipaddr string = "" + + 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) + return err + } + + 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(extracted_endpoint) + if err != nil { + log.Warnf("Could not get exports for fqdn ip at %s. Error: %v", extracted_endpoint, err) + } + if ok { + fipaddr = extracted_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 { + 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 fmt.Errorf("could not mount to any data-portals") } 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 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 +}