Skip to content

Commit

Permalink
Merge pull request #132 from dell/sync-replication-changes-for-powers…
Browse files Browse the repository at this point in the history
…tore

Changes to support Sync replication
  • Loading branch information
rajendraindukuri authored Sep 17, 2024
2 parents 04a9e04 + a6e1ac2 commit 0ca5138
Show file tree
Hide file tree
Showing 5 changed files with 265 additions and 2 deletions.
File renamed without changes.
158 changes: 158 additions & 0 deletions inttests/replication_sync_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
/*
*
* Copyright © 2024 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 inttests

import (
"context"
"os"
"testing"

"github.com/dell/gopowerstore"
"github.com/joho/godotenv"
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
)

type ReplicationTestSuiteSync struct {
suite.Suite
remoteSystem string
remoteSystemMIP string
randomString string
remoteClient gopowerstore.Client
pp gopowerstore.CreateResponse
vg gopowerstore.CreateResponse
rr gopowerstore.CreateResponse
vol gopowerstore.CreateResponse
}

func (suite *ReplicationTestSuiteSync) SetupSuite() {
// instead of completely hardcoded/constant string, let's make it dynamic
// in case if TearDownSuite doesn't run at the end, we will not be blocked for next round of testing
suite.randomString = randString(8)
suite.remoteSystem, suite.remoteSystemMIP = getRemoteSystemSync(suite.T(), suite)
err := godotenv.Load("GOPOWERSTORE_TEST.env")
if err != nil {
return
}
user := os.Getenv("GOPOWERSTORE_USERNAME")
pass := os.Getenv("GOPOWERSTORE_PASSWORD")

clientOptions := &gopowerstore.ClientOptions{}
clientOptions.SetInsecure(true)
client, err := gopowerstore.NewClientWithArgs("https://"+suite.remoteSystemMIP+"/api/rest", user, pass, clientOptions)
if err != nil {
return
}
suite.remoteClient = client
}

func (suite *ReplicationTestSuiteSync) TearDownSuite() {
vg, err := C.GetVolumeGroup(context.Background(), suite.vg.ID)
assert.NoError(suite.T(), err)
pp, err := C.GetProtectionPolicyByName(context.Background(), "intcsi"+suite.randomString+"-pptst")
assert.NoError(suite.T(), err)
rr, err := C.GetReplicationRuleByName(context.Background(), "intcsi"+suite.randomString+"-ruletst")
assert.NoError(suite.T(), err)
if len(rr.ProtectionPolicies) != 1 || len(pp.ReplicationRules) != 1 || len(vg.Volumes) != 1 || len(pp.VolumeGroups) != 1 {
suite.T().Fail()
}
C.ModifyVolumeGroup(context.Background(), &gopowerstore.VolumeGroupModify{ProtectionPolicyID: ""}, suite.vg.ID)
C.RemoveMembersFromVolumeGroup(context.Background(), &gopowerstore.VolumeGroupMembers{VolumeIDs: []string{suite.vol.ID}}, suite.vg.ID)
C.ModifyVolume(context.Background(), &gopowerstore.VolumeModify{ProtectionPolicyID: ""}, suite.vol.ID)
C.DeleteProtectionPolicy(context.Background(), suite.pp.ID)
C.DeleteReplicationRule(context.Background(), suite.rr.ID)
C.DeleteVolumeGroup(context.Background(), suite.vg.ID)
vgid, err := suite.remoteClient.GetVolumeGroupByName(context.Background(), "intcsi"+suite.randomString+"-vgtst")
if err != nil {
logrus.Info(err)
}
suite.remoteClient.DeleteVolumeGroup(context.Background(), vgid.ID)
C.DeleteVolume(context.Background(), nil, suite.vol.ID)
}

func getRemoteSystemSync(t *testing.T, suite *ReplicationTestSuiteSync) (string, string) {
resp, err := C.GetAllRemoteSystems(context.Background())
skipTestOnError(t, err)
if len(resp) == 0 {
t.Skip("Skipping test as there are no remote systems configured on array.")
}
// try to find the working remote system from the list of all available/configured remoteSystems
for i := range resp {
rs, err := C.GetRemoteSystem(context.Background(), resp[i].ID)
assert.NoError(t, err)
assert.Equal(t, rs.ID, resp[i].ID)
// create replicationRule and Protection policy beforeHand to check if remote system is working fine or not
suite.rr, err = C.CreateReplicationRule(context.Background(), &gopowerstore.ReplicationRuleCreate{
Name: "intcsi" + suite.randomString + "-ruletst",
Rpo: gopowerstore.RpoZero,
RemoteSystemID: rs.ID,
})
assert.NoError(t, err)

suite.pp, err = C.CreateProtectionPolicy(context.Background(), &gopowerstore.ProtectionPolicyCreate{
Name: "intcsi" + suite.randomString + "-pptst",
ReplicationRuleIDs: []string{suite.rr.ID},
})

if err == nil {
return resp[i].ID, resp[i].ManagementAddress
}
// need to delete replication rule created earlier with the remoteIP not able to create Protection policy
C.DeleteReplicationRule(context.Background(), suite.rr.ID)
suite.rr.ID = ""
}
t.Skip("Skipping test as there are no working remote systems configured on array.")
return "", ""
}

func (suite *ReplicationTestSuiteSync) TestReplicationSync() {
t := suite.T()

// get the remote powerstore system
remoteSystem := suite.remoteSystem
rs, err := C.GetRemoteSystem(context.Background(), remoteSystem)
assert.NoError(t, err)
assert.Equal(t, rs.ID, remoteSystem)

// create a volume group with a protection policy
// A protection policy with a synchronous replication rule can only be applied to a write-order consistent volume group
suite.vg, err = C.CreateVolumeGroup(context.Background(), &gopowerstore.VolumeGroupCreate{
Name: "intcsi" + suite.randomString + "-vgtst",
ProtectionPolicyID: suite.pp.ID,
IsWriteOrderConsistent: true,
})
assert.NoError(t, err)

// create a volume within the volume group
volName := "intcsi" + suite.randomString + "-voltst"
size := int64(OneMB)
suite.vol, err = C.CreateVolume(context.Background(), &gopowerstore.VolumeCreate{
Name: &volName,
Size: &size,
VolumeGroupID: suite.vg.ID,
})
assert.NoError(t, err)

// get the volume group from the volume ID
volID := suite.vol.ID
_, err = C.GetVolumeGroupsByVolumeID(context.Background(), volID)
assert.NoError(t, err)
}

func TestReplicationSuiteSync(t *testing.T) {
suite.Run(t, new(ReplicationTestSuiteSync))
}
20 changes: 19 additions & 1 deletion replication_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
*
* Copyright © 2021-2023 Dell Inc. or its subsidiaries. All Rights Reserved.
* Copyright © 2021-2024 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.
Expand Down Expand Up @@ -73,6 +73,24 @@ func TestClientIMPL_CreateReplicationRule(t *testing.T) {
assert.Equal(t, volID, resp.ID)
}

func TestClientIMPL_CreateReplicationRuleSync(t *testing.T) {
httpmock.Activate()
defer httpmock.DeactivateAndReset()
respData := fmt.Sprintf(`{"id": "%s"}`, volID)
httpmock.RegisterResponder("POST", replicationRuleMockURL,
httpmock.NewStringResponder(201, respData))

createReq := ReplicationRuleCreate{
Name: "rr-test",
Rpo: RpoZero,
RemoteSystemID: "XX-0000X",
}

resp, err := C.CreateReplicationRule(context.Background(), &createReq)
assert.Nil(t, err)
assert.Equal(t, volID, resp.ID)
}

func TestClientIMPL_DeleteProtectionPolicy(t *testing.T) {
httpmock.Activate()
defer httpmock.DeactivateAndReset()
Expand Down
5 changes: 4 additions & 1 deletion replication_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const (
RpoSixHours RPOEnum = "Six_Hours"
RpoTwelveHours RPOEnum = "Twelve_Hours"
RpoOneDay RPOEnum = "One_Day"
RpoZero RPOEnum = "Zero"
RsStateInitializing RSStateEnum = "Initializing"
RsStateOk RSStateEnum = "OK"
RsStateSynchronizing RSStateEnum = "Synchronizing"
Expand All @@ -49,7 +50,7 @@ const (

func (rpo RPOEnum) IsValid() error {
switch rpo {
case RpoFiveMinutes, RpoFifteenMinutes, RpoThirtyMinutes, RpoOneHour, RpoSixHours, RpoTwelveHours, RpoOneDay:
case RpoFiveMinutes, RpoFifteenMinutes, RpoThirtyMinutes, RpoOneHour, RpoSixHours, RpoTwelveHours, RpoOneDay, RpoZero:
return nil
}
return errors.New("invalid rpo type")
Expand All @@ -60,6 +61,7 @@ type ReplicationRuleCreate struct {
// Name of the replication rule.
Name string `json:"name"`
// Recovery point objective (RPO), which is the acceptable amount of data, measured in units of time, that may be lost in case of a failure.
// If RPO is Zero, it indicates the replication_type is 'sync'.
Rpo RPOEnum `json:"rpo"`
// Unique identifier of the remote system to which this rule will replicate the associated resources
RemoteSystemID string `json:"remote_system_id"`
Expand All @@ -71,6 +73,7 @@ type ReplicationRule struct {
// Name of replication rule
Name string `json:"name"`
// Rpo (Recovery point objective), which is the acceptable amount of data, measured in units of time, that may be lost in case of a failure.
// If RPO is Zero, it indicates the replication_type is 'sync'.
Rpo RPOEnum `json:"rpo"`
// RemoteSystemID - unique identifier of the remote system to which this rule will replicate the associated resources.
RemoteSystemID string `json:"remote_system_id"`
Expand Down
84 changes: 84 additions & 0 deletions replication_types_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
*
* Copyright © 2024 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 gopowerstore

import "testing"

func TestRPOEnum_IsValid(t *testing.T) {
const (
RpoInvalid RPOEnum = "RPO_Invalid"
)

tests := []struct {
name string
rpo RPOEnum
wantErr bool
}{
{
name: "Rpo FiveMinutes",
rpo: RpoFiveMinutes,
wantErr: false,
},
{
name: "Rpo FifteenMinutes",
rpo: RpoFifteenMinutes,
wantErr: false,
},
{
name: "Rpo ThirtyMinutes",
rpo: RpoThirtyMinutes,
wantErr: false,
},
{
name: "Rpo OneHour",
rpo: RpoOneHour,
wantErr: false,
},
{
name: "Rpo SixHours",
rpo: RpoSixHours,
wantErr: false,
},
{
name: "Rpo TwelveHours",
rpo: RpoTwelveHours,
wantErr: false,
},
{
name: "Rpo OneDay",
rpo: RpoOneDay,
wantErr: false,
},
{
name: "Rpo Zero",
rpo: RpoZero,
wantErr: false,
},
{
name: "Rpo Invalid",
rpo: RpoInvalid,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := tt.rpo.IsValid(); (err != nil) != tt.wantErr {
t.Errorf("RPOEnum.IsValid() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}

0 comments on commit 0ca5138

Please sign in to comment.