From c4ca5e6eda2246b836bce6d9f64c824ebbd0a5fe Mon Sep 17 00:00:00 2001 From: Shendge Date: Tue, 25 Jul 2023 15:02:39 +0530 Subject: [PATCH 1/8] added rename functionality for MDM --- inttests/system_test.go | 39 +++++++++++++++++++++++++++ system.go | 13 +++++++++ system_test.go | 58 +++++++++++++++++++++++++++++++++++++++++ types/v1/types.go | 6 +++++ 4 files changed, 116 insertions(+) diff --git a/inttests/system_test.go b/inttests/system_test.go index 9196f02..e95b99c 100644 --- a/inttests/system_test.go +++ b/inttests/system_test.go @@ -179,6 +179,45 @@ func TestSwitchClusterModeInvalid(t *testing.T) { assert.NotNil(t, err) } +// TestRenameMdm modifies MDM name +func TestRenameMdm(t *testing.T) { + // first, get all of the systems + allSystems, err := C.GetSystems() + assert.Nil(t, err) + + // then try to get the first one returned, explicitly + system, err := C.FindSystem(allSystems[0].ID, "", "") + assert.Nil(t, err) + + mdmDetails, err1 := system.GetMDMClusterDetails() + assert.Nil(t, err1) + assert.NotNil(t, mdmDetails) + + payload := types.RenameMdm{ + ID: mdmDetails.TiebreakerMdm[0].ID, + NewName: "mdm_renamed", + } + + err = system.RenameMdm(&payload) + assert.Nil(t, err) + + payload = types.RenameMdm{ + ID: mdmDetails.TiebreakerMdm[0].ID, + NewName: "mdm_renamed", + } + + err = system.RenameMdm(&payload) + assert.NotNil(t, err) + + payload = types.RenameMdm{ + ID: mdmDetails.TiebreakerMdm[0].ID, + NewName: "tb_mdm", + } + + err = system.RenameMdm(&payload) + assert.Nil(t, err) +} + // TestChangeMDMOwnership modifies primary MDM func TestChangeMDMOwnership(t *testing.T) { // first, get all of the systems diff --git a/system.go b/system.go index 5934e84..7226a31 100644 --- a/system.go +++ b/system.go @@ -199,3 +199,16 @@ func (s *System) ChangeMdmOwnerShip(id string) error { } return nil } + +// RenameMdm modifies name of the MDM +func (s *System) RenameMdm(renameMdm *types.RenameMdm) error { + defer TimeSpent("ChangeMdmOwnerShip", time.Now()) + + path := "/api/instances/System/action/renameMdm" + err := s.client.getJSONWithRetry( + http.MethodPost, path, renameMdm, nil) + if err != nil { + return err + } + return nil +} diff --git a/system_test.go b/system_test.go index 6ff14b5..056dc79 100644 --- a/system_test.go +++ b/system_test.go @@ -292,3 +292,61 @@ func TestGetMDMClusterDetails(t *testing.T) { assert.NotNil(t, mdmDetails) assert.Nil(t, err1) } + +func TestRenameMdm(t *testing.T) { + type testCase struct { + id string + newName string + expected error + } + + cases := []testCase{ + { + "0e4f0a2f5978ae02", + "mdm_renamed", + nil, + }, + { + "FiveNodes", + "mdm_renamed", + errors.New("An MDM with the same name already exists"), + }, + } + + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNoContent) + })) + defer svr.Close() + + for _, tc := range cases { + tc := tc + t.Run("", func(ts *testing.T) { + + client, err := NewClientWithArgs(svr.URL, "", math.MaxInt64, true, false) + client.configConnect.Version = "3.6" + if err != nil { + t.Fatal(err) + } + + s := System{ + client: client, + } + + payload := types.RenameMdm{ + ID: tc.id, + NewName: tc.newName, + } + + err = s.RenameMdm(&payload) + if err != nil { + if tc.expected == nil { + t.Errorf("Renaming MDM did not work as expected, \n\tgot: %s \n\twant: %v", err, tc.expected) + } else { + if err.Error() != tc.expected.Error() { + t.Errorf("Renaming MDM did not work as expected, \n\tgot: %s \n\twant: %s", err, tc.expected) + } + } + } + }) + } +} diff --git a/types/v1/types.go b/types/v1/types.go index adb53c8..4091b36 100644 --- a/types/v1/types.go +++ b/types/v1/types.go @@ -167,6 +167,12 @@ type SwitchClusterMode struct { RemoveTBMdms []string `json:"removeTBIdList,omitempty"` } +// RenameMdm defines struct for modifying MDM name +type RenameMdm struct { + ID string `json:"id"` + NewName string `json:"newName"` +} + // Link defines struct of Link type Link struct { Rel string `json:"rel"` From c209b7a456478c9b41bcb61dc21a7d3d3b5de1bf Mon Sep 17 00:00:00 2001 From: Shendge Date: Tue, 25 Jul 2023 18:47:35 +0530 Subject: [PATCH 2/8] added constant for cluster mode --- types/v1/types.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/types/v1/types.go b/types/v1/types.go index 4091b36..9c3f2af 100644 --- a/types/v1/types.go +++ b/types/v1/types.go @@ -104,6 +104,12 @@ type System struct { PerformanceProfile string `json:"perfProfile"` } +// Constants representing cluster mode +const ( + FiveNodesClusterMode = "FiveNodes" + ThreeNodesClusterMode = "ThreeNodes" +) + // MdmCluster defines struct for MDM cluster type MdmCluster struct { ID string `json:"id"` From 633aa39d3bcf09477c72a682f4da509cbb15c11e Mon Sep 17 00:00:00 2001 From: Shendge Date: Thu, 27 Jul 2023 11:33:20 +0530 Subject: [PATCH 3/8] added constant for MDM role --- types/v1/types.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/types/v1/types.go b/types/v1/types.go index 9c3f2af..50c47dc 100644 --- a/types/v1/types.go +++ b/types/v1/types.go @@ -110,6 +110,12 @@ const ( ThreeNodesClusterMode = "ThreeNodes" ) +// Constants representing MDM role +const ( + Manager = "Manager" + TieBreaker = "TieBreaker" +) + // MdmCluster defines struct for MDM cluster type MdmCluster struct { ID string `json:"id"` From a73d7d47846cbd231ea9aef2a6b7afcd9d7ee207 Mon Sep 17 00:00:00 2001 From: Shendge Date: Tue, 1 Aug 2023 00:08:47 +0530 Subject: [PATCH 4/8] added get VTree functionality --- inttests/vtree_test.go | 41 +++++++++++++++ types/v1/vTreeTypes.go | 29 +++++++++++ vtree.go | 67 ++++++++++++++++++++++++ vtree_test.go | 115 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 252 insertions(+) create mode 100644 inttests/vtree_test.go create mode 100644 types/v1/vTreeTypes.go create mode 100644 vtree.go create mode 100644 vtree_test.go diff --git a/inttests/vtree_test.go b/inttests/vtree_test.go new file mode 100644 index 0000000..7389f69 --- /dev/null +++ b/inttests/vtree_test.go @@ -0,0 +1,41 @@ +package inttests + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestGetVTrees(t *testing.T) { + allVTrees, err := C.GetVTrees() + assert.Nil(t, err) + assert.NotNil(t, allVTrees) +} + +func TestGetVTreeByID(t *testing.T) { + allVTrees, err := C.GetVTrees() + assert.Nil(t, err) + assert.NotNil(t, allVTrees) + + vTree, err := C.GetVTreeByID(allVTrees[0].ID) + assert.Nil(t, err) + assert.NotNil(t, vTree) + + vTree, err = C.GetVTreeByID(invalidIdentifier) + assert.NotNil(t, err) + assert.Nil(t, vTree) +} + +func TestGetVTreeInstances(t *testing.T) { + allVTrees, err := C.GetVTrees() + assert.Nil(t, err) + assert.NotNil(t, allVTrees) + + allVTrees, err = C.GetVTreeInstances([]string{allVTrees[0].ID}) + assert.Nil(t, err) + assert.NotNil(t, allVTrees) + + allVTrees, err = C.GetVTreeInstances([]string{invalidIdentifier}) + assert.NotNil(t, err) + assert.Nil(t, allVTrees) +} diff --git a/types/v1/vTreeTypes.go b/types/v1/vTreeTypes.go new file mode 100644 index 0000000..9536556 --- /dev/null +++ b/types/v1/vTreeTypes.go @@ -0,0 +1,29 @@ +package goscaleio + +// VTreeDetails defines struct for VTrees +type VTreeDetails struct { + CompressionMethod string `json:"compressionMethod"` + DataLayout string `json:"dataLayout"` + ID string `json:"id"` + InDeletion bool `json:"inDeletion"` + Name string `json:"name"` + RootVolumes []string `json:"rootVolumes"` + StoragePoolID string `json:"storagePoolId"` + Links []*Link `json:"links"` + VtreeMigrationInfo VTreeMigrationInfo `json:"vtreeMigrationInfo"` +} + +// VTreeMigrationInfo defines struct for VTree migration +type VTreeMigrationInfo struct { + DestinationStoragePoolID string `json:"destinationStoragePoolId"` + MigrationPauseReason string `json:"migrationPauseReason"` + MigrationQueuePosition int64 `json:"migrationQueuePosition"` + MigrationStatus string `json:"migrationStatus"` + SourceStoragePoolID string `json:"sourceStoragePoolId"` + ThicknessConversionType string `json:"thicknessConversionType"` +} + +// VTreeQueryBySelectedIdsParam defines struct for specifying Vtree IDs +type VTreeQueryBySelectedIdsParam struct { + IDs []string `json:"ids"` +} diff --git a/vtree.go b/vtree.go new file mode 100644 index 0000000..78e14c8 --- /dev/null +++ b/vtree.go @@ -0,0 +1,67 @@ +// Copyright © 2019 - 2023 Dell Inc. or its subsidiaries. All Rights Reserved. +// +// 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 goscaleio + +import ( + "fmt" + "net/http" + "time" + + types "github.com/dell/goscaleio/types/v1" +) + +// GetVTrees returns vtrees present in the cluster +func (c *Client) GetVTrees() ([]types.VTreeDetails, error) { + defer TimeSpent("GetVTrees", time.Now()) + + path := "/api/types/VTree/instances" + + var vTree []types.VTreeDetails + err := c.getJSONWithRetry(http.MethodGet, path, nil, &vTree) + if err != nil { + return nil, err + } + + return vTree, nil +} + +// GetVTreeByID returns the VTree details for the given ID +func (c *Client) GetVTreeByID(id string) (*types.VTreeDetails, error) { + defer TimeSpent("GetVTreeByID", time.Now()) + + path := fmt.Sprintf("/api/instances/VTree::%v", id) + + var vTree types.VTreeDetails + err := c.getJSONWithRetry(http.MethodGet, path, nil, &vTree) + if err != nil { + return nil, err + } + return &vTree, nil +} + +// GetVTreeInstances returns the VTree details for the given IDs +func (c *Client) GetVTreeInstances(ids []string) ([]types.VTreeDetails, error) { + defer TimeSpent("GetVTrees", time.Now()) + + path := "/api/types/VTree/instances/action/queryBySelectedIds" + + payload := types.VTreeQueryBySelectedIdsParam{ + IDs: ids, + } + var vTree []types.VTreeDetails + err := c.getJSONWithRetry(http.MethodPost, path, payload, &vTree) + if err != nil { + return nil, err + } + return vTree, nil +} diff --git a/vtree_test.go b/vtree_test.go new file mode 100644 index 0000000..e359be3 --- /dev/null +++ b/vtree_test.go @@ -0,0 +1,115 @@ +package goscaleio + +import ( + "errors" + "math" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestGetVTrees(t *testing.T) { + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusNoContent) + })) + defer svr.Close() + + client, err := NewClientWithArgs(svr.URL, "", math.MaxInt64, true, false) + client.configConnect.Version = "3.6" + if err != nil { + t.Fatal(err) + } + + vTreeDetails, err := client.GetVTrees() + assert.Equal(t, len(vTreeDetails), 0) + assert.Nil(t, err) +} + +func TestGetVTreeByID(t *testing.T) { + type testCase struct { + id string + expected error + } + + cases := []testCase{ + { + id: "b21581e400000001", + expected: nil, + }, + { + id: "b21581e400000002", + expected: errors.New("The VTree was not found"), + }, + } + + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + })) + defer svr.Close() + + for _, tc := range cases { + tc := tc + t.Run("", func(ts *testing.T) { + client, err := NewClientWithArgs(svr.URL, "", math.MaxInt64, true, false) + if err != nil { + t.Fatal(err) + } + + _, err = client.GetVTreeByID(tc.id) + if err != nil { + if tc.expected == nil { + t.Errorf("Getting VTree by ID did not work as expected, \n\tgot: %s \n\twant: %v", err, tc.expected) + } else { + if err.Error() != tc.expected.Error() { + t.Errorf("Getting VTree by ID did not work as expected, \n\tgot: %s \n\twant: %s", err, tc.expected) + } + } + } + }) + } + +} + +func TestGetVTreeInstances(t *testing.T) { + type testCase struct { + ids []string + expected error + } + + cases := []testCase{ + { + ids: []string{"b21581e400000001"}, + expected: nil, + }, + { + ids: []string{"b21581e400000002"}, + expected: errors.New("Query selected Instances type: VTree - Got no statistics for id b21581e300000002. It doesn't exist."), + }, + } + + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + })) + defer svr.Close() + + for _, tc := range cases { + tc := tc + t.Run("", func(ts *testing.T) { + client, err := NewClientWithArgs(svr.URL, "", math.MaxInt64, true, false) + if err != nil { + t.Fatal(err) + } + + _, err = client.GetVTreeInstances(tc.ids) + if err != nil { + if tc.expected == nil { + t.Errorf("Getting VTree by ID did not work as expected, \n\tgot: %s \n\twant: %v", err, tc.expected) + } else { + if err.Error() != tc.expected.Error() { + t.Errorf("Getting VTree by ID did not work as expected, \n\tgot: %s \n\twant: %s", err, tc.expected) + } + } + } + }) + } +} From 1cda05ec90e084b451ecda3ae12576a6a9b5c317 Mon Sep 17 00:00:00 2001 From: Shendge Date: Tue, 1 Aug 2023 00:13:01 +0530 Subject: [PATCH 5/8] addressing linting issues --- vtree_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vtree_test.go b/vtree_test.go index e359be3..0bbab4f 100644 --- a/vtree_test.go +++ b/vtree_test.go @@ -84,7 +84,7 @@ func TestGetVTreeInstances(t *testing.T) { }, { ids: []string{"b21581e400000002"}, - expected: errors.New("Query selected Instances type: VTree - Got no statistics for id b21581e300000002. It doesn't exist."), + expected: errors.New("Query selected Instances type: VTree - Got no statistics for id b21581e300000002. It doesn't exist"), }, } From 068bd5eea8a81c6677ac658459fc4b122303506c Mon Sep 17 00:00:00 2001 From: Shendge Date: Wed, 2 Aug 2023 23:28:51 +0530 Subject: [PATCH 6/8] added get VTree using volume ID function --- inttests/vtree_test.go | 14 ++++++++++++++ vtree.go | 12 ++++++++++++ vtree_test.go | 44 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 70 insertions(+) diff --git a/inttests/vtree_test.go b/inttests/vtree_test.go index 7389f69..5d02821 100644 --- a/inttests/vtree_test.go +++ b/inttests/vtree_test.go @@ -39,3 +39,17 @@ func TestGetVTreeInstances(t *testing.T) { assert.NotNil(t, err) assert.Nil(t, allVTrees) } + +func TestGetVTreeByVolumeID(t *testing.T) { + allVTrees, err := C.GetVTrees() + assert.Nil(t, err) + assert.NotNil(t, allVTrees) + + vTree, err := C.GetVTreeByVolumeID(allVTrees[0].RootVolumes[0]) + assert.Nil(t, err) + assert.NotNil(t, vTree) + + vTree, err = C.GetVTreeByVolumeID(invalidIdentifier) + assert.NotNil(t, err) + assert.Nil(t, vTree) +} diff --git a/vtree.go b/vtree.go index 78e14c8..cf6b6ba 100644 --- a/vtree.go +++ b/vtree.go @@ -65,3 +65,15 @@ func (c *Client) GetVTreeInstances(ids []string) ([]types.VTreeDetails, error) { } return vTree, nil } + +// GetVTreeByVolumeID returns VTree details based on Volume ID +func (c *Client) GetVTreeByVolumeID(id string) (*types.VTreeDetails, error) { + defer TimeSpent("GetVTreeByVolumeID", time.Now()) + + volDetails, err := c.GetVolume("", id, "", "", false) + if err != nil { + return nil, err + } + + return c.GetVTreeByID(volDetails[0].VTreeID) +} diff --git a/vtree_test.go b/vtree_test.go index 0bbab4f..07e15c6 100644 --- a/vtree_test.go +++ b/vtree_test.go @@ -113,3 +113,47 @@ func TestGetVTreeInstances(t *testing.T) { }) } } + +func TestGetVTreeByVolumeID(t *testing.T) { + type testCase struct { + id string + expected error + } + + cases := []testCase{ + { + id: "3c855e2900000001", + expected: nil, + }, + { + id: "b21581e400000002", + expected: errors.New("Invalid volume"), + }, + } + + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + })) + defer svr.Close() + + for _, tc := range cases { + tc := tc + t.Run("", func(ts *testing.T) { + client, err := NewClientWithArgs(svr.URL, "", math.MaxInt64, true, false) + if err != nil { + t.Fatal(err) + } + + _, err = client.GetVTreeByVolumeID(tc.id) + if err != nil { + if tc.expected == nil { + t.Errorf("Getting VTree by Volume ID did not work as expected, \n\tgot: %s \n\twant: %v", err, tc.expected) + } else { + if err.Error() != tc.expected.Error() { + t.Errorf("Getting VTree by Volume ID did not work as expected, \n\tgot: %s \n\twant: %s", err, tc.expected) + } + } + } + }) + } + +} From 2c2a5d6b783a2bb6c338aadbcf05bb7bc7a8b697 Mon Sep 17 00:00:00 2001 From: Krunal Thakkar <121278477+Krunal-Thakkar@users.noreply.github.com> Date: Thu, 3 Aug 2023 15:13:07 +0530 Subject: [PATCH 7/8] PowerFlex Deployment Related Operation Implementation (#76) * Deployment Changes --- deploy.go | 282 +++++++++++++++++++++------- deploy_test.go | 165 ++++++++++++++++ inttests/GOSCALEIO_TEST.env_example | 2 + inttests/deploy_test.go | 44 ++++- types/v1/types.go | 169 ++++++++++++++++- 5 files changed, 585 insertions(+), 77 deletions(-) create mode 100644 deploy_test.go diff --git a/deploy.go b/deploy.go index df4ef24..4e9d182 100644 --- a/deploy.go +++ b/deploy.go @@ -122,10 +122,10 @@ func (gc *GatewayClient) UploadPackages(filePaths []string) (*types.GatewayRespo req.Header.Set("Content-Type", writer.FormDataContentType()) req.Header.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(gc.username+":"+gc.password))) client := gc.http - response, httpReqError := client.Do(req) + response, httpRespError := client.Do(req) - if httpReqError != nil { - return &gatewayResponse, httpReqError + if httpRespError != nil { + return &gatewayResponse, httpRespError } if response.StatusCode != 200 { @@ -184,10 +184,10 @@ func (gc *GatewayClient) ParseCSV(filePath string) (*types.GatewayResponse, erro req.Header.Set("Content-Type", writer.FormDataContentType()) req.Header.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(gc.username+":"+gc.password))) client := gc.http - response, httpReqError := client.Do(req) + response, httpRespError := client.Do(req) - if httpReqError != nil { - return &gatewayResponse, httpReqError + if httpRespError != nil { + return &gatewayResponse, httpRespError } responseString, _ := extractString(response) @@ -238,14 +238,17 @@ func (gc *GatewayClient) GetPackageDetails() ([]*types.PackageDetails, error) { req.Header.Set("Content-Type", "application/json") client := gc.http - httpRes, httpReqError := client.Do(req) - if httpReqError != nil { - return packageParam, httpReqError + httpResp, httpRespError := client.Do(req) + if httpRespError != nil { + return packageParam, httpRespError } - responseString, _ := extractString(httpRes) + responseString, error := extractString(httpResp) + if error != nil { + return packageParam, fmt.Errorf("Error Extracting Response: %s", error) + } - if httpRes.StatusCode == 200 { + if httpResp.StatusCode == 200 { err := json.Unmarshal([]byte(responseString), &packageParam) @@ -271,19 +274,22 @@ func (gc *GatewayClient) ValidateMDMDetails(mdmTopologyParam []byte) (*types.Gat req.Header.Set("Content-Type", "application/json") client := gc.http - httpRes, httpReqError := client.Do(req) - if httpReqError != nil { - return &gatewayResponse, httpReqError + httpResp, httpRespError := client.Do(req) + if httpRespError != nil { + return &gatewayResponse, httpRespError } - responseString, _ := extractString(httpRes) + responseString, error := extractString(httpResp) + if error != nil { + return &gatewayResponse, fmt.Errorf("Error Extracting Response: %s", error) + } - if httpRes.StatusCode != 200 { + if httpResp.StatusCode != 200 { err := json.Unmarshal([]byte(responseString), &gatewayResponse) if err != nil { - return &gatewayResponse, fmt.Errorf("Error For Validate MDM Details: %s", err) + return &gatewayResponse, fmt.Errorf("Error Validating MDM Details: %s", err) } return &gatewayResponse, nil @@ -294,7 +300,7 @@ func (gc *GatewayClient) ValidateMDMDetails(mdmTopologyParam []byte) (*types.Gat err := json.Unmarshal([]byte(responseString), &mdmTopologyDetails) if err != nil { - return &gatewayResponse, fmt.Errorf("Error For Validate MDM Details: %s", err) + return &gatewayResponse, fmt.Errorf("Error Validating MDM Details: %s", err) } gatewayResponse.StatusCode = 200 @@ -304,6 +310,66 @@ func (gc *GatewayClient) ValidateMDMDetails(mdmTopologyParam []byte) (*types.Gat return &gatewayResponse, nil } +// GetClusterDetails used for get MDM cluster details +func (gc *GatewayClient) GetClusterDetails(mdmTopologyParam []byte, requireJSONOutput bool) (*types.GatewayResponse, error) { + var gatewayResponse types.GatewayResponse + + req, httpError := http.NewRequest("POST", gc.host+"/im/types/Configuration/instances", bytes.NewBuffer(mdmTopologyParam)) + if httpError != nil { + return &gatewayResponse, httpError + } + req.Header.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(gc.username+":"+gc.password))) + req.Header.Set("Content-Type", "application/json") + + client := gc.http + httpResp, httpRespError := client.Do(req) + if httpRespError != nil { + return &gatewayResponse, httpRespError + } + + responseString, error := extractString(httpResp) + if error != nil { + return &gatewayResponse, fmt.Errorf("Error Extracting Response: %s", error) + } + + if httpResp.StatusCode != 200 { + + err := json.Unmarshal([]byte(responseString), &gatewayResponse) + + if err != nil { + return &gatewayResponse, fmt.Errorf("Error Validating MDM Details: %s", err) + } + + return &gatewayResponse, nil + } + + if responseString == "" { + return &gatewayResponse, fmt.Errorf("Error Getting Cluster Details") + } + + if requireJSONOutput { + gatewayResponse.StatusCode = 200 + + gatewayResponse.Data = responseString + + return &gatewayResponse, nil + } + + var mdmTopologyDetails types.MDMTopologyDetails + + err := json.Unmarshal([]byte(responseString), &mdmTopologyDetails) + + if err != nil { + return &gatewayResponse, fmt.Errorf("Error For Get Cluster Details: %s", err) + } + + gatewayResponse.StatusCode = 200 + + gatewayResponse.ClusterDetails = mdmTopologyDetails + + return &gatewayResponse, nil +} + // DeletePackage used for delete packages from gateway server func (gc *GatewayClient) DeletePackage(packageName string) (*types.GatewayResponse, error) { @@ -317,14 +383,17 @@ func (gc *GatewayClient) DeletePackage(packageName string) (*types.GatewayRespon req.Header.Set("Content-Type", "application/json") client := gc.http - httpRes, httpReqError := client.Do(req) - if httpReqError != nil { - return &gatewayResponse, httpReqError + httpResp, httpRespError := client.Do(req) + if httpRespError != nil { + return &gatewayResponse, httpRespError } - responseString, _ := extractString(httpRes) + responseString, error := extractString(httpResp) + if error != nil { + return &gatewayResponse, fmt.Errorf("Error Extracting Response: %s", error) + } - if httpRes.StatusCode != 200 { + if httpResp.StatusCode != 200 { err := json.Unmarshal([]byte(responseString), &gatewayResponse) @@ -341,7 +410,7 @@ func (gc *GatewayClient) DeletePackage(packageName string) (*types.GatewayRespon } // BeginInstallation used for start installation -func (gc *GatewayClient) BeginInstallation(jsonStr, mdmUsername, mdmPassword, liaPassword string, expansion bool) (*types.GatewayResponse, error) { +func (gc *GatewayClient) BeginInstallation(jsonStr, mdmUsername, mdmPassword, liaPassword string, allowNonSecureCommunicationWithMdm, allowNonSecureCommunicationWithLia, disableNonMgmtComponentsAuth, expansion bool) (*types.GatewayResponse, error) { var gatewayResponse types.GatewayResponse @@ -353,11 +422,12 @@ func (gc *GatewayClient) BeginInstallation(jsonStr, mdmUsername, mdmPassword, li mapData["mdmPassword"] = mdmPassword mapData["mdmUser"] = mdmUsername mapData["liaPassword"] = liaPassword + mapData["liaLdapInitialMode"] = "NATIVE_AUTHENTICATION" secureData := map[string]interface{}{ - "allowNonSecureCommunicationWithMdm": true, - "allowNonSecureCommunicationWithLia": true, - "disableNonMgmtComponentsAuth": false, + "allowNonSecureCommunicationWithMdm": allowNonSecureCommunicationWithMdm, + "allowNonSecureCommunicationWithLia": allowNonSecureCommunicationWithLia, + "disableNonMgmtComponentsAuth": disableNonMgmtComponentsAuth, } mapData["securityConfiguration"] = secureData @@ -370,7 +440,11 @@ func (gc *GatewayClient) BeginInstallation(jsonStr, mdmUsername, mdmPassword, li q.Set("noConfigure", "false") q.Set("noLinuxDevValidation", "false") q.Set("globalZeroPadPolicy", "false") - q.Set("extend", strconv.FormatBool(expansion)) + + if expansion { + q.Set("extend", strconv.FormatBool(expansion)) + } + u.RawQuery = q.Encode() req, httpError := http.NewRequest("POST", u.String(), bytes.NewBuffer(finalJSON)) @@ -382,14 +456,17 @@ func (gc *GatewayClient) BeginInstallation(jsonStr, mdmUsername, mdmPassword, li client := gc.http - httpRes, httpReqError := client.Do(req) - if httpReqError != nil { - return &gatewayResponse, httpReqError + httpResp, httpRespError := client.Do(req) + if httpRespError != nil { + return &gatewayResponse, httpRespError } - if httpRes.StatusCode != 202 { + if httpResp.StatusCode != 202 { - responseString, _ := extractString(httpRes) + responseString, error := extractString(httpResp) + if error != nil { + return &gatewayResponse, fmt.Errorf("Error Extracting Response: %s", error) + } err := json.Unmarshal([]byte(responseString), &gatewayResponse) @@ -419,14 +496,17 @@ func (gc *GatewayClient) MoveToNextPhase() (*types.GatewayResponse, error) { client := gc.http - httpRes, httpReqError := client.Do(req) - if httpReqError != nil { - return &gatewayResponse, httpReqError + httpResp, httpRespError := client.Do(req) + if httpRespError != nil { + return &gatewayResponse, httpRespError } - responseString, _ := extractString(httpRes) + responseString, error := extractString(httpResp) + if error != nil { + return &gatewayResponse, fmt.Errorf("Error Extracting Response: %s", error) + } - if httpRes.StatusCode != 200 { + if httpResp.StatusCode != 200 { err := json.Unmarshal([]byte(responseString), &gatewayResponse) @@ -456,14 +536,17 @@ func (gc *GatewayClient) RetryPhase() (*types.GatewayResponse, error) { client := gc.http - httpRes, httpReqError := client.Do(req) - if httpReqError != nil { - return &gatewayResponse, httpReqError + httpResp, httpRespError := client.Do(req) + if httpRespError != nil { + return &gatewayResponse, httpRespError } - responseString, _ := extractString(httpRes) + responseString, error := extractString(httpResp) + if error != nil { + return &gatewayResponse, fmt.Errorf("Error Extracting Response: %s", error) + } - if httpRes.StatusCode != 200 { + if httpResp.StatusCode != 200 { err := json.Unmarshal([]byte(responseString), &gatewayResponse) @@ -493,14 +576,17 @@ func (gc *GatewayClient) AbortOperation() (*types.GatewayResponse, error) { client := gc.http - httpRes, httpReqError := client.Do(req) - if httpReqError != nil { - return &gatewayResponse, httpReqError + httpResp, httpRespError := client.Do(req) + if httpRespError != nil { + return &gatewayResponse, httpRespError } - responseString, _ := extractString(httpRes) + responseString, error := extractString(httpResp) + if error != nil { + return &gatewayResponse, fmt.Errorf("Error Extracting Response: %s", error) + } - if httpRes.StatusCode != 200 { + if httpResp.StatusCode != 200 { err := json.Unmarshal([]byte(responseString), &gatewayResponse) @@ -530,14 +616,17 @@ func (gc *GatewayClient) ClearQueueCommand() (*types.GatewayResponse, error) { client := gc.http - httpRes, httpReqError := client.Do(req) - if httpReqError != nil { - return &gatewayResponse, httpReqError + httpResp, httpRespError := client.Do(req) + if httpRespError != nil { + return &gatewayResponse, httpRespError } - responseString, _ := extractString(httpRes) + responseString, error := extractString(httpResp) + if error != nil { + return &gatewayResponse, fmt.Errorf("Error Extracting Response: %s", error) + } - if httpRes.StatusCode != 200 { + if httpResp.StatusCode != 200 { err := json.Unmarshal([]byte(responseString), &gatewayResponse) @@ -567,14 +656,17 @@ func (gc *GatewayClient) MoveToIdlePhase() (*types.GatewayResponse, error) { client := gc.http - httpRes, httpReqError := client.Do(req) - if httpReqError != nil { - return &gatewayResponse, httpReqError + httpResp, httpRespError := client.Do(req) + if httpRespError != nil { + return &gatewayResponse, httpRespError } - responseString, _ := extractString(httpRes) + responseString, error := extractString(httpResp) + if error != nil { + return &gatewayResponse, fmt.Errorf("Error Extracting Response: %s", error) + } - if httpRes.StatusCode != 200 { + if httpResp.StatusCode != 200 { err := json.Unmarshal([]byte(responseString), &gatewayResponse) @@ -603,14 +695,17 @@ func (gc *GatewayClient) GetInQueueCommand() ([]types.MDMQueueCommandDetails, er req.Header.Set("Content-Type", "application/json") client := gc.http - httpRes, httpReqError := client.Do(req) - if httpReqError != nil { - return mdmQueueCommandDetails, httpReqError + httpResp, httpRespError := client.Do(req) + if httpRespError != nil { + return mdmQueueCommandDetails, httpRespError } - responseString, _ := extractString(httpRes) + responseString, error := extractString(httpResp) + if error != nil { + return mdmQueueCommandDetails, fmt.Errorf("Error Extracting Response: %s", error) + } - if httpRes.StatusCode == 200 { + if httpResp.StatusCode == 200 { var queueCommandDetails map[string][]interface{} @@ -676,6 +771,67 @@ func (gc *GatewayClient) CheckForCompletionQueueCommands(currentPhase string) (* return &gatewayResponse, nil } +// UninstallCluster used for uninstallation of cluster +func (gc *GatewayClient) UninstallCluster(jsonStr, mdmUsername, mdmPassword, liaPassword string, allowNonSecureCommunicationWithMdm, allowNonSecureCommunicationWithLia, disableNonMgmtComponentsAuth, expansion bool) (*types.GatewayResponse, error) { + + var gatewayResponse types.GatewayResponse + + clusterData, jsonParseError := jsonToMap(jsonStr) + if jsonParseError != nil { + return &gatewayResponse, jsonParseError + } + + clusterData["mdmPassword"] = mdmPassword + clusterData["mdmUser"] = mdmUsername + clusterData["liaPassword"] = liaPassword + clusterData["liaLdapInitialMode"] = "NATIVE_AUTHENTICATION" + + secureData := map[string]interface{}{ + "allowNonSecureCommunicationWithMdm": allowNonSecureCommunicationWithMdm, + "allowNonSecureCommunicationWithLia": allowNonSecureCommunicationWithLia, + "disableNonMgmtComponentsAuth": disableNonMgmtComponentsAuth, + } + clusterData["securityConfiguration"] = secureData + + finalJSON, _ := json.Marshal(clusterData) + + u, _ := url.Parse(gc.host + "/im/types/Configuration/actions/uninstall") + + req, httpError := http.NewRequest("POST", u.String(), bytes.NewBuffer(finalJSON)) + if httpError != nil { + return &gatewayResponse, httpError + } + req.Header.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(gc.username+":"+gc.password))) + req.Header.Set("Content-Type", "application/json") + + client := gc.http + + httpResp, httpRespError := client.Do(req) + if httpRespError != nil { + return &gatewayResponse, httpRespError + } + + if httpResp.StatusCode != 202 { + + responseString, error := extractString(httpResp) + if error != nil { + return &gatewayResponse, fmt.Errorf("Error Extracting Response: %s", error) + } + + err := json.Unmarshal([]byte(responseString), &gatewayResponse) + + if err != nil { + return &gatewayResponse, fmt.Errorf("Error For Uninstall Cluster: %s", err) + } + + return &gatewayResponse, nil + } + + gatewayResponse.StatusCode = 200 + + return &gatewayResponse, nil +} + // jsonToMap used for convert json to map func jsonToMap(jsonStr string) (map[string]interface{}, error) { result := make(map[string]interface{}) diff --git a/deploy_test.go b/deploy_test.go new file mode 100644 index 0000000..e0f86e1 --- /dev/null +++ b/deploy_test.go @@ -0,0 +1,165 @@ +// Copyright © 2021 - 2023 Dell Inc. or its subsidiaries. All Rights Reserved. +// +// 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 goscaleio + +import ( + "encoding/json" + "errors" + "net/http" + "net/http/httptest" + "testing" +) + +func TestMoveToNextPhase(t *testing.T) { + type testCase struct { + expected error + } + + cases := []testCase{ + { + nil, + }, + } + + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + })) + defer svr.Close() + + for _, tc := range cases { + tc := tc + t.Run("", func(ts *testing.T) { + + GC, err := NewGateway(svr.URL, "", "", true, true) + if err != nil { + t.Fatal(err) + } + + _, err = GC.MoveToNextPhase() + if err != nil { + if tc.expected == nil { + t.Errorf("Move to Next Phase did not work as expected, \n\tgot: %s \n\twant: %v", err, tc.expected) + } else { + if err.Error() != tc.expected.Error() { + t.Errorf("Move to Next Phase did not work as expected, \n\tgot: %s \n\twant: %s", err, tc.expected) + } + } + } + + }) + } +} + +func TestUninstallCluster(t *testing.T) { + type testCase struct { + jsonInput string + username string + mdmPassword string + liaPassword string + expected error + } + + cases := []testCase{ + { + "", + "test", + "123", + "123", + errors.New("unexpected end of JSON input"), + }, + } + + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + })) + defer svr.Close() + + for _, tc := range cases { + tc := tc + t.Run("", func(ts *testing.T) { + + GC, err := NewGateway(svr.URL, "", "", true, true) + if err != nil { + t.Fatal(err) + } + + _, err = GC.UninstallCluster(tc.jsonInput, tc.username, tc.mdmPassword, tc.liaPassword, true, true, false, true) + if err != nil { + if tc.expected == nil { + t.Errorf("Uninstalling Cluster did not work as expected, \n\tgot: %s \n\twant: %v", err, tc.expected) + } else { + if err.Error() != tc.expected.Error() { + t.Errorf("Uninstalling Cluster did not work as expected, \n\tgot: %s \n\twant: %s", err, tc.expected) + } + } + } + + }) + } +} + +func TestGetClusterDetails(t *testing.T) { + type testCase struct { + mdmIP string + mdmPassword string + expected error + } + + cases := []testCase{ + { + "", + "", + errors.New("Error Getting Cluster Details"), + }, + } + + svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + })) + defer svr.Close() + + for _, tc := range cases { + tc := tc + t.Run("", func(ts *testing.T) { + + GC, err := NewGateway(svr.URL, "", "", true, true) + if err != nil { + t.Fatal(err) + } + + clusterData := map[string]interface{}{ + "mdmUser": "admin", + "mdmPassword": tc.mdmPassword, + } + clusterData["mdmIps"] = []string{tc.mdmIP} + + secureData := map[string]interface{}{ + "allowNonSecureCommunicationWithMdm": true, + "allowNonSecureCommunicationWithLia": true, + "disableNonMgmtComponentsAuth": false, + } + clusterData["securityConfiguration"] = secureData + + jsonres, _ := json.Marshal(clusterData) + + _, err = GC.GetClusterDetails(jsonres, false) + if err != nil { + if tc.expected == nil { + t.Errorf("Uninstalling Cluster did not work as expected, \n\tgot: %s \n\twant: %v", err, tc.expected) + } else { + if err.Error() != tc.expected.Error() { + t.Errorf("Uninstalling Cluster did not work as expected, \n\tgot: %s \n\twant: %s", err, tc.expected) + } + } + } + + }) + } +} diff --git a/inttests/GOSCALEIO_TEST.env_example b/inttests/GOSCALEIO_TEST.env_example index 9933f0d..347e9a7 100644 --- a/inttests/GOSCALEIO_TEST.env_example +++ b/inttests/GOSCALEIO_TEST.env_example @@ -10,6 +10,8 @@ GOSCALEIO_STORAGEPOOL=pool1 GOSCALEIO_SYSTEMNAME=powerflex-gateway GOSCALEIO_INSTALLATIONID=08bhsd7f9cfy890g GOSCALEIO_SDSID=0db2c37100002311 +GOSCALEIO_MDMIP=1.2.3.4 +GOSCALEIO_MDMPASSWORD=Password USER_PASSWORD=Password # For filesystem operations, enable these values diff --git a/inttests/deploy_test.go b/inttests/deploy_test.go index 0ab0a8e..48452d6 100644 --- a/inttests/deploy_test.go +++ b/inttests/deploy_test.go @@ -2,6 +2,7 @@ package inttests import ( "encoding/json" + "os" "testing" "github.com/stretchr/testify/assert" @@ -30,20 +31,20 @@ func TestDeployGetPackgeDetails(t *testing.T) { // TestDeployValidateMDMDetails function to test Retrival of MDM Topology Function func TestDeployValidateMDMDetails(t *testing.T) { - mapData := map[string]interface{}{ + clusterData := map[string]interface{}{ "mdmUser": "admin", - "mdmPassword": "Password123", + "mdmPassword": string(os.Getenv("GOSCALEIO_MDMPASSWORD")), } - mapData["mdmIps"] = []string{"10.247.101.68"} + clusterData["mdmIps"] = []string{string(os.Getenv("GOSCALEIO_MDMIP"))} secureData := map[string]interface{}{ "allowNonSecureCommunicationWithMdm": true, "allowNonSecureCommunicationWithLia": true, "disableNonMgmtComponentsAuth": false, } - mapData["securityConfiguration"] = secureData + clusterData["securityConfiguration"] = secureData - jsonres, _ := json.Marshal(mapData) + jsonres, _ := json.Marshal(clusterData) res, err := GC.ValidateMDMDetails(jsonres) @@ -54,6 +55,31 @@ func TestDeployValidateMDMDetails(t *testing.T) { assert.Nil(t, err) } +func TestDeployGetClusterDetails(t *testing.T) { + clusterData := map[string]interface{}{ + "mdmUser": "admin", + "mdmPassword": string(os.Getenv("GOSCALEIO_MDMPASSWORD")), + } + clusterData["mdmIps"] = []string{string(os.Getenv("GOSCALEIO_MDMIP"))} + + secureData := map[string]interface{}{ + "allowNonSecureCommunicationWithMdm": true, + "allowNonSecureCommunicationWithLia": true, + "disableNonMgmtComponentsAuth": false, + } + clusterData["securityConfiguration"] = secureData + + jsonres, _ := json.Marshal(clusterData) + + res, err := GC.GetClusterDetails(jsonres, false) + + assert.NotNil(t, res) + + assert.EqualValues(t, res.StatusCode, 200) + + assert.Nil(t, err) +} + // TestDeployDeletePackge function to test Delete Functionality func TestDeployDeletePackge(t *testing.T) { res, err := GC.DeletePackage("ABC") @@ -64,7 +90,13 @@ func TestDeployDeletePackge(t *testing.T) { // TestDeployBeginInstallation function to test Begin Installation with Parsed CSV Data func TestDeployBeginInstallation(t *testing.T) { - _, err := GC.BeginInstallation("", "admin", "Password", "Password", true) + _, err := GC.BeginInstallation("", "admin", "Password", "Password", true, true, false, true) + assert.NotNil(t, err) +} + +// TestDeployUninstallCluster function to test Begin Installation with Parsed CSV Data +func TestDeployUninstallCluster(t *testing.T) { + _, err := GC.UninstallCluster("", "admin", "Password", "Password", true, true, false, true) assert.NotNil(t, err) } diff --git a/types/v1/types.go b/types/v1/types.go index 8f1e1cd..6c953b8 100644 --- a/types/v1/types.go +++ b/types/v1/types.go @@ -1503,10 +1503,11 @@ type PackageDetails struct { // GatewayResponse defines struct for Gateway API Response type GatewayResponse struct { - Message string `json:"message,omitempty"` - Data string `json:"data,omitempty"` - StatusCode int `json:"httpStatusCode,omitempty"` - ErrorCode int `json:"errorCode,omitempty"` + Message string `json:"message,omitempty"` + Data string `json:"data,omitempty"` + ClusterDetails MDMTopologyDetails `json:"clusterDetails,omitempty"` + StatusCode int `json:"httpStatusCode,omitempty"` + ErrorCode int `json:"errorCode,omitempty"` } // MDMTopologyParam defines struct for Validate MDM Topology @@ -1526,10 +1527,162 @@ type SecurityConfigurationDetails struct { // MDMTopologyDetails defines struct for Validated MDM Topology Details type MDMTopologyDetails struct { - MdmIPs []string `json:"mdmIPs,omitempty"` - SdsAndMdmIps []string `json:"sdsAndMdmIps,omitempty"` - SdcIps []string `json:"sdcIps,omitempty"` - SystemVersionName string `json:"systemVersionName,omitempty"` + MdmIPs []string `json:"mdmIPs,omitempty"` + SdsAndMdmIps []string `json:"sdsAndMdmIps,omitempty"` + SdcIps []string `json:"sdcIps,omitempty"` + SystemVersionName string `json:"systemVersionName,omitempty"` + SdsList []SdsList `json:"sdsList,omitempty"` + SdcList []SdcList `json:"sdcList,omitempty"` + ProtectionDomains []ProtectionDomains `json:"protectionDomains,omitempty"` + SdrList []SdrList `json:"sdrList,omitempty"` + VasaProviderList []any `json:"vasaProviderList,omitempty"` + MasterMdm MasterMdm `json:"masterMdm,omitempty"` + SlaveMdmSet []SlaveMdmSet `json:"slaveMdmSet,omitempty"` + TbSet []TbSet `json:"tbSet,omitempty"` + StandbyMdmSet []StandbyMdmSet `json:"standbyMdmSet,omitempty"` + StandbyTbSet []StandbyTbSet `json:"standbyTbSet,omitempty"` +} + +// MasterMdm defines struct for Mster MDM Details +type MasterMdm struct { + Node Node `json:"node,omitempty"` + MdmIPs []string `json:"mdmIPs,omitempty"` + Name string `json:"name,omitempty"` + ID string `json:"id,omitempty"` + IPForActor any `json:"ipForActor,omitempty"` + ManagementIPs []string `json:"managementIPs,omitempty"` + VirtIPIntfsList []string `json:"virtIpIntfsList,omitempty"` +} + +// SlaveMdmSet defines struct for Slave MDMs Details +type SlaveMdmSet struct { + Node Node `json:"node,omitempty"` + MdmIPs []string `json:"mdmIPs,omitempty"` + Name string `json:"name,omitempty"` + ID string `json:"id,omitempty"` + IPForActor any `json:"ipForActor,omitempty"` + ManagementIPs []string `json:"managementIPs,omitempty"` + VirtIPIntfsList []string `json:"virtIpIntfsList,omitempty"` +} + +// StandbyMdmSet defines struct for Standby MDMs Details +type StandbyMdmSet struct { + Node Node `json:"node,omitempty"` + MdmIPs []string `json:"mdmIPs,omitempty"` + Name string `json:"name,omitempty"` + ID string `json:"id,omitempty"` + IPForActor any `json:"ipForActor,omitempty"` + ManagementIPs []string `json:"managementIPs,omitempty"` + VirtIPIntfsList []string `json:"virtIpIntfsList,omitempty"` +} + +// TbSet defines struct for TB MDMs Details +type TbSet struct { + Node Node `json:"node,omitempty"` + MdmIPs []string `json:"mdmIPs,omitempty"` + Name string `json:"name,omitempty"` + ID string `json:"id,omitempty"` + TbIPs []string `json:"tbIPs,omitempty"` +} + +// StandbyTbSet defines struct for Standby TB MDMs Details +type StandbyTbSet struct { + Node Node `json:"node,omitempty"` + MdmIPs []string `json:"mdmIPs,omitempty"` + Name string `json:"name,omitempty"` + ID string `json:"id,omitempty"` + TbIPs []string `json:"tbIPs,omitempty"` +} + +// Node defines struct for Node Information +type Node struct { + Ostype string `json:"ostype,omitempty"` + NodeName string `json:"nodeName,omitempty"` + NodeIPs []string `json:"nodeIPs,omitempty"` +} + +// Devices defines struct for Device Details +type Devices struct { + DevicePath string `json:"devicePath,omitempty"` + StoragePool string `json:"storagePool,omitempty"` + DeviceName string `json:"deviceName,omitempty"` + MaxCapacityInKb int `json:"maxCapacityInKb,omitempty"` +} + +// SdsList defines struct for SDS Details +type SdsList struct { + Node Node `json:"node,omitempty"` + SdsName string `json:"sdsName,omitempty"` + ProtectionDomain string `json:"protectionDomain,omitempty"` + ProtectionDomainID string `json:"protectionDomainId,omitempty"` + FaultSet string `json:"faultSet,omitempty"` + FaultSetID string `json:"faultSetId,omitempty"` + AllIPs []string `json:"allIPs,omitempty"` + SdsOnlyIPs []string `json:"sdsOnlyIPs,omitempty"` + SdcOnlyIPs []string `json:"sdcOnlyIPs,omitempty"` + Devices []Devices `json:"devices,omitempty"` + RfCached bool `json:"rfCached,omitempty"` + RfCachedPools []any `json:"rfCachedPools,omitempty"` + RfCachedDevices []any `json:"rfCachedDevices,omitempty"` + RfCacheType any `json:"rfCacheType,omitempty"` + FlashAccDevices []any `json:"flashAccDevices,omitempty"` + NvdimmAccDevices []any `json:"nvdimmAccDevices,omitempty"` + UseRmCache bool `json:"useRmCache,omitempty"` + Optimized bool `json:"optimized,omitempty"` + OptimizedNumOfIOBufs int `json:"optimizedNumOfIOBufs,omitempty"` + Port int `json:"port,omitempty"` + ID string `json:"id,omitempty"` +} + +// SdcList defines struct for SDC Details +type SdcList struct { + Node Node `json:"node,omitempty"` + GUID string `json:"guid,omitempty"` + SdcName string `json:"sdcName,omitempty"` + Optimized bool `json:"optimized,omitempty"` + ID string `json:"id,omitempty"` +} + +// SdrList defines struct for SDR Details +type SdrList struct { + Node Node `json:"node,omitempty"` + ProtectionDomain string `json:"protectionDomain,omitempty"` + ProtectionDomainID string `json:"protectionDomainId,omitempty"` + ApplicationOnlyIPs []string `json:"applicationOnlyIPs,omitempty"` + StorageOnlyIPs []string `json:"storageOnlyIPs,omitempty"` + ExternalOnlyIPs []string `json:"externalOnlyIPs,omitempty"` + ApplicationAndStorageIPs []string `json:"applicationAndStorageIPs,omitempty"` + ApplicationAndExternalIPs []string `json:"applicationAndExternalIPs,omitempty"` + StorageAndExternalIPs []string `json:"storageAndExternalIPs,omitempty"` + AllIPs []string `json:"allIPs,omitempty"` + SupersetIPs any `json:"supersetIPs,omitempty"` + SdrName string `json:"sdrName,omitempty"` + SdrPort int `json:"sdrPort,omitempty"` + Optimized bool `json:"optimized,omitempty"` + ID string `json:"id,omitempty"` +} + +// ProtectionDomains defines struct for ProtectionDomains Details +type ProtectionDomains struct { + Name string `json:"name,omitempty"` + StoragePools []StoragePools `json:"storagePools,omitempty"` + AccelerationPools []any `json:"accelerationPools,omitempty"` +} + +// StoragePools defines struct for StoragePools Details +type StoragePools struct { + Name string `json:"name,omitempty"` + MediaType string `json:"mediaType,omitempty"` + ExternalAccelerationType string `json:"externalAccelerationType,omitempty"` + DataLayout string `json:"dataLayout,omitempty"` + CompressionMethod string `json:"compressionMethod,omitempty"` + SpefAccPoolName any `json:"spefAccPoolName,omitempty"` + ShouldApplyZeroPadding bool `json:"shouldApplyZeroPadding,omitempty"` + WriteAtomicitySize any `json:"writeAtomicitySize,omitempty"` + OverProvisioningFactor any `json:"overProvisioningFactor,omitempty"` + MaxCompressionRatio any `json:"maxCompressionRatio,omitempty"` + PerfProfile any `json:"perfProfile,omitempty"` + RplJournalCapacity int `json:"rplJournalCapacity,omitempty"` } // InstallerPhaseDetail defines struct for Current and Next Phase Details From 657fcb1e35a1463103178f15d4106e8168f3d1b4 Mon Sep 17 00:00:00 2001 From: VamsiSiddu-7 <103578883+VamsiSiddu-7@users.noreply.github.com> Date: Fri, 4 Aug 2023 10:44:31 +0530 Subject: [PATCH 8/8] add GetFsSnapshotByVolumeID functionality (#77) --- fs.go | 16 +++++++ fs_test.go | 102 ++++++++++++++++++++++++++++++++++++++++++++ inttests/fs_test.go | 63 +++++++++++++++++++++++++++ 3 files changed, 181 insertions(+) diff --git a/fs.go b/fs.go index 43421bb..fba250f 100644 --- a/fs.go +++ b/fs.go @@ -145,6 +145,22 @@ func (s *System) RestoreFileSystemFromSnapshot(restoreSnapParam *types.RestoreFs return nil, nil } +// GetFsSnapshotsByVolumeID gets list of snapshots associated with a filesystem +func (s *System) GetFsSnapshotsByVolumeID(fsID string) ([]types.FileSystem, error) { + defer TimeSpent("GetFsSnapshotsByVolumeID", time.Now()) + var snapshotList []types.FileSystem + fsList, err := s.GetAllFileSystems() + if err != nil { + return nil, err + } + for _, fs := range fsList { + if fs.ParentID == fsID { + snapshotList = append(snapshotList, fs) + } + } + return snapshotList, err +} + // 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 8876dad..c6bbc55 100644 --- a/fs_test.go +++ b/fs_test.go @@ -412,6 +412,108 @@ func TestCreateFileSystemSnapshot(t *testing.T) { } } +func TestGetFsSnapshotsByVolumeID(t *testing.T) { + type checkFn func(*testing.T, []types.FileSystem, error) + check := func(fns ...checkFn) []checkFn { return fns } + + hasNoError := func(t *testing.T, resp []types.FileSystem, err error) { + if err != nil { + t.Fatalf("expected no error") + } + } + + hasError := func(t *testing.T, resp []types.FileSystem, err error) { + if err == nil { + t.Fatalf("expected error") + } + } + + checkResp := func(snapLength int) func(t *testing.T, resp []types.FileSystem, err error) { + return func(t *testing.T, resp []types.FileSystem, err error) { + assert.Equal(t, snapLength, len(resp)) + } + } + + tests := map[string]func(t *testing.T) (*httptest.Server, []checkFn){ + "success": func(t *testing.T) (*httptest.Server, []checkFn) { + + href := "/rest/v1/file-systems" + var resp []types.FileSystem + + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + 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.FileSystem{ + { + ID: "64366a19-54e8-1544-f3d7-2a50fb1ccff3", + Name: "fs-test-1", + }, + { + ID: "6436aa58-e6a1-a4e2-de7b-2a50fb1ccff3", + Name: "fs-test-2", + }, + } + + respData, err := json.Marshal(resp) + if err != nil { + t.Fatal(err) + } + fmt.Fprintln(w, string(respData)) + + })) + return ts, check(hasNoError, checkResp(len(resp))) + }, + + "operation-failed": func(t *testing.T) (*httptest.Server, []checkFn) { + href := "/rest/v1/file-systems" + + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + 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" + + resp, err := s.GetFsSnapshotsByVolumeID(fsID) + for _, checkFn := range checkFns { + checkFn(t, resp, err) + } + + }) + } +} + func TestRestoreFileSystemFromSnapshot(t *testing.T) { type checkFn func(*testing.T, *types.RestoreFsSnapResponse, error) check := func(fns ...checkFn) []checkFn { return fns } diff --git a/inttests/fs_test.go b/inttests/fs_test.go index 70f7b73..009f0b3 100644 --- a/inttests/fs_test.go +++ b/inttests/fs_test.go @@ -210,6 +210,69 @@ func TestRestoreFileSystemSnapshot(t *testing.T) { } +func TestGetFsSnapshotsByVolumeID(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) + + snapResp, err := system.CreateFileSystemSnapshot(&types.CreateFileSystemSnapshotParam{ + Name: "test-snapshot", + }, fsID) + + assert.NotNil(t, snapResp) + assert.Nil(t, err) + + snap, err := system.GetFileSystemByIDName(snapResp.ID, "") + assert.NotNil(t, snap) + assert.Nil(t, err) + + snapList, err := system.GetFsSnapshotsByVolumeID(fsID) + assert.NotNil(t, snapList) + 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) {