From 12217ad780cf9f909abd002189f3cc0f9f89f5b2 Mon Sep 17 00:00:00 2001 From: VamsiSiddu-7 <103578883+VamsiSiddu-7@users.noreply.github.com> Date: Tue, 25 Jul 2023 12:37:00 +0530 Subject: [PATCH] Add RestoreFileSystemFromSnapshot functionality for csi-powerflex nfs-support (#74) * add support for restore fs from snapshot. * resolved the conflicts. * add debug logs. * add debug logs. * added unit test and int tests. --- fs.go | 29 ++++++++++ fs_test.go | 138 ++++++++++++++++++++++++++++++++++++++++++++ inttests/fs_test.go | 84 +++++++++++++++++++++++++++ types/v1/types.go | 11 ++++ 4 files changed, 262 insertions(+) diff --git a/fs.go b/fs.go index a3fd6b7..43421bb 100644 --- a/fs.go +++ b/fs.go @@ -116,6 +116,35 @@ func (s *System) CreateFileSystemSnapshot(createSnapParam *types.CreateFileSyste return &snapResponse, nil } +// RestoreFileSystemFromSnapshot restores the filesystem from a given snapshot using filesytem id +func (s *System) RestoreFileSystemFromSnapshot(restoreSnapParam *types.RestoreFsSnapParam, fsID string) (*types.RestoreFsSnapResponse, error) { + defer TimeSpent("CreateFileSystemSnapshot", time.Now()) + + path := fmt.Sprintf("/rest/v1/file-systems/%v/restore", fsID) + + restoreFsResponse := types.RestoreFsSnapResponse{} + var err error + if restoreSnapParam.CopyName == "" { + err = s.client.getJSONWithRetry( + http.MethodPost, path, restoreSnapParam, nil) + if err == nil { + return nil, nil + } + } else { + err = s.client.getJSONWithRetry( + http.MethodPost, path, restoreSnapParam, &restoreFsResponse) + if err == nil { + return &restoreFsResponse, nil + } + } + + if err != nil { + return nil, err + } + + return nil, nil +} + // DeleteFileSystem deletes a file system func (s *System) DeleteFileSystem(name string) error { defer TimeSpent("DeleteFileSystem", time.Now()) diff --git a/fs_test.go b/fs_test.go index c4d06da..8876dad 100644 --- a/fs_test.go +++ b/fs_test.go @@ -412,6 +412,144 @@ func TestCreateFileSystemSnapshot(t *testing.T) { } } +func TestRestoreFileSystemFromSnapshot(t *testing.T) { + type checkFn func(*testing.T, *types.RestoreFsSnapResponse, error) + check := func(fns ...checkFn) []checkFn { return fns } + + hasNoError := func(t *testing.T, resp *types.RestoreFsSnapResponse, err error) { + if err != nil { + t.Fatalf("expected no error") + } + } + + hasError := func(t *testing.T, resp *types.RestoreFsSnapResponse, err error) { + if err == nil { + t.Fatalf("expected error") + } + } + + checkResp := func(snapId string) func(t *testing.T, resp *types.RestoreFsSnapResponse, err error) { + return func(t *testing.T, resp *types.RestoreFsSnapResponse, err error) { + assert.Equal(t, snapId, resp.ID) + } + } + + tests := map[string]func(t *testing.T) (*httptest.Server, []checkFn){ + "successNoContent": func(t *testing.T) (*httptest.Server, []checkFn) { + + href := fmt.Sprintf("/rest/v1/file-systems/%v/restore", "64366a19-54e8-1544-f3d7-2a50fb1ccff3") + + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + t.Fatal(fmt.Errorf("wrong method. Expected %s; but got %s", http.MethodGet, r.Method)) + } + + if r.URL.Path != href { + t.Fatal(fmt.Errorf("wrong path. Expected %s; but got %s", href, r.URL.Path)) + } + + w.WriteHeader(http.StatusNoContent) + })) + return ts, check(hasNoError) + }, + + "successWithContent": func(t *testing.T) (*httptest.Server, []checkFn) { + + href := fmt.Sprintf("/rest/v1/file-systems/%v/restore", "64366a19-54e8-1544-f3d7-2a50fb1ccff3") + + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + t.Fatal(fmt.Errorf("wrong method. Expected %s; but got %s", http.MethodGet, r.Method)) + } + + if r.URL.Path != href { + t.Fatal(fmt.Errorf("wrong path. Expected %s; but got %s", href, r.URL.Path)) + } + + resp := types.RestoreFsSnapResponse{ + ID: "64366a19-54e8-1544-f3d7-2a50fb1cckk3", + } + + respData, err := json.Marshal(resp) + if err != nil { + t.Fatal(err) + } + fmt.Fprintln(w, string(respData)) + })) + return ts, check(hasNoError, checkResp("64366a19-54e8-1544-f3d7-2a50fb1cckk3")) + }, + "not-found": func(t *testing.T) (*httptest.Server, []checkFn) { + href := fmt.Sprintf("/rest/v1/file-systems/%v/restore", "64366a19-54e8-1544-f3d7-2a50fb1ccff3") + + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + t.Fatal(fmt.Errorf("wrong method. Expected %s; but got %s", http.MethodGet, r.Method)) + } + + if r.URL.Path != href { + t.Fatal(fmt.Errorf("wrong path. Expected %s; but got %s", href, r.URL.Path)) + } + + http.Error(w, "not found", http.StatusNotFound) + })) + return ts, check(hasError) + }, + + "operation-failed": func(t *testing.T) (*httptest.Server, []checkFn) { + href := fmt.Sprintf("/rest/v1/file-systems/%v/restore", "64366a19-54e8-1544-f3d7-2a50fb1ccff3") + + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + t.Fatal(fmt.Errorf("wrong method. Expected %s; but got %s", http.MethodGet, r.Method)) + } + + if r.URL.Path != href { + t.Fatal(fmt.Errorf("wrong path. Expected %s; but got %s", href, r.URL.Path)) + } + + http.Error(w, "operation failed", http.StatusUnprocessableEntity) + })) + return ts, check(hasError) + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + ts, checkFns := tc(t) + defer ts.Close() + + client, err := NewClientWithArgs(ts.URL, "", math.MaxInt64, true, false) + client.configConnect.Version = "4.0" + if err != nil { + t.Fatal(err) + } + + s := System{ + client: client, + } + + fsID := "64366a19-54e8-1544-f3d7-2a50fb1ccff3" + var restoreSnapshotRequest = new(types.RestoreFsSnapParam) + if name == "successWithContent" { + restoreSnapshotRequest = &types.RestoreFsSnapParam{ + SnapshotID: "64366a19-54e8-1544-f3d7-2a50fb1ccdd3", + CopyName: "test-name", + } + } else { + restoreSnapshotRequest = &types.RestoreFsSnapParam{ + SnapshotID: "64366a19-54e8-1544-f3d7-2a50fb1ccdd3", + } + + } + + resp, err := s.RestoreFileSystemFromSnapshot(restoreSnapshotRequest, fsID) + for _, checkFn := range checkFns { + checkFn(t, resp, err) + } + + }) + } +} func TestDeleteFileSystem(t *testing.T) { name := "new-fs" diff --git a/inttests/fs_test.go b/inttests/fs_test.go index 9e6c5aa..70f7b73 100644 --- a/inttests/fs_test.go +++ b/inttests/fs_test.go @@ -126,6 +126,90 @@ func TestCreateFileSystemSnapshot(t *testing.T) { } +func TestRestoreFileSystemSnapshot(t *testing.T) { + system := getSystem() + assert.NotNil(t, system) + + fsName := fmt.Sprintf("%s-%s", "FS", testPrefix) + + // get protection domain + pd := getProtectionDomain(t) + assert.NotNil(t, pd) + + // get storage pool + pool := getStoragePool(t) + assert.NotNil(t, pool) + var spID string + if pd != nil && pool != nil { + sp, _ := pd.FindStoragePool(pool.StoragePool.ID, "", "") + assert.NotNil(t, sp) + spID = sp.ID + } + + // get NAS server ID + var nasServerName string + if os.Getenv("GOSCALEIO_NASSERVER") != "" { + nasServerName = os.Getenv("GOSCALEIO_NASSERVER") + } + nasServer, err := system.GetNASByIDName("", nasServerName) + assert.NotNil(t, nasServer) + assert.Nil(t, err) + + fs := &types.FsCreate{ + Name: fsName, + SizeTotal: 16106127360, + StoragePoolID: spID, + NasServerID: nasServer.ID, + } + + // create the file system + filesystem, err := system.CreateFileSystem(fs) + fsID := filesystem.ID + assert.Nil(t, err) + assert.NotNil(t, fsID) + snapName := fmt.Sprintf("%s-%s", "SNAP", testPrefix) + + snapResp, err := system.CreateFileSystemSnapshot(&types.CreateFileSystemSnapshotParam{ + Name: snapName, + }, fsID) + + assert.NotNil(t, snapResp) + assert.Nil(t, err) + + snap, err := system.GetFileSystemByIDName(snapResp.ID, "") + assert.NotNil(t, snap) + assert.Nil(t, err) + + restoreSnap, err := system.RestoreFileSystemFromSnapshot(&types.RestoreFsSnapParam{ + SnapshotID: snap.ID, + }, fsID) + + assert.Nil(t, restoreSnap) + assert.Nil(t, err) + + restoreSnap, err = system.RestoreFileSystemFromSnapshot(&types.RestoreFsSnapParam{ + SnapshotID: snap.ID, + CopyName: "copy" + snapName, + }, fsID) + + assert.NotNil(t, restoreSnap) + assert.Nil(t, err) + + rSnap, err := system.GetFileSystemByIDName(restoreSnap.ID, "") + + assert.NotNil(t, rSnap) + assert.Nil(t, err) + + err = system.DeleteFileSystem(rSnap.Name) + assert.Nil(t, err) + + err = system.DeleteFileSystem(snap.Name) + assert.Nil(t, err) + err = system.DeleteFileSystem(fs.Name) + assert.Nil(t, err) + +} + // TestGetFileSystemByIDName will return specific filesystem by name or ID func TestGetFileSystemByIDName(t *testing.T) { diff --git a/types/v1/types.go b/types/v1/types.go index adb53c8..8f1e1cd 100644 --- a/types/v1/types.go +++ b/types/v1/types.go @@ -1365,6 +1365,17 @@ type FileSystemResp struct { ID string `json:"id"` } +// RestoreFsSnapParam defines struct for Restoring filesytem from snapshot +type RestoreFsSnapParam struct { + SnapshotID string `json:"snapshot_id"` + CopyName string `json:"copy_name,omitempty"` +} + +// RestoreFsSnapResponse defines struct for Restore Filesystem snapshot response +type RestoreFsSnapResponse struct { + ID string `json:"id"` +} + // NFSExportDefaultAccessEnum defines default access type NFSExportDefaultAccessEnum string