Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Changes to support Sync replication #132

Merged
merged 1 commit into from
Sep 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
}
rajendraindukuri marked this conversation as resolved.
Show resolved Hide resolved

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'.
rajendraindukuri marked this conversation as resolved.
Show resolved Hide resolved
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)
}
})
}
}