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

Adds unit test scenarios for Auto SRDF Group creation #38

Merged
merged 1 commit into from
Jan 12, 2023
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
3 changes: 2 additions & 1 deletion .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
# Prasanna Muthukumaraswamy (prablr79)
# Yamunadevi Shanmugam (shanmydell)
# Sushma T S (tssushma)
# Nida Taranum (nidtara)

# for all files
* @bharadwajrs-dell @boyamurthy @delldubey @mjsdell @prablr79 @shanmydell @tssushma
* @bharadwajrs-dell @boyamurthy @delldubey @mjsdell @prablr79 @shanmydell @tssushma @nidtara
27 changes: 11 additions & 16 deletions VolumeReplication.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,8 +221,8 @@ func (c *Client) GetRDFGroupList(ctx context.Context, symID string, queryParams
URL += "?"
for key, val := range queryParams {
switch val := val.(type) {
case bool:
URL += fmt.Sprintf("%s=%s", key, strconv.FormatBool(val))
case int:
URL += fmt.Sprintf("%s=%s", key, strconv.Itoa(val))
case string:
URL += fmt.Sprintf("%s=%s", key, val)
}
Expand Down Expand Up @@ -276,26 +276,21 @@ func (c *Client) GetProtectedStorageGroup(ctx context.Context, symID, storageGro
}

// ExecuteCreateRDFGroup creates the RDF Group
func (c *Client) ExecuteCreateRDFGroup(ctx context.Context, symID string, CreateRDFPayload *types.RDFGroupCreate) bool {
func (c *Client) ExecuteCreateRDFGroup(ctx context.Context, symID string, CreateRDFPayload *types.RDFGroupCreate) error {
defer c.TimeSpent("ExecuteCreateRDFGroup", time.Now())
if _, err := c.IsAllowedArray(symID); err != nil {
return false
return err
}
URL := c.urlPrefix() + ReplicationX + SymmetrixX + symID + XRDFGroup
ctx, cancel := c.GetTimeoutContext(ctx)
defer cancel()
resp, err := c.api.DoAndGetResponseBody(
ctx, http.MethodPost, URL, c.getDefaultHeaders(), CreateRDFPayload)
if err = c.checkResponse(resp); err != nil {
return false
}
// Is there any CSI spec to say we need to parse the output and send it instead of just bool
//defer resp.Body.Close()
//decoder := json.NewDecoder(resp.Body)
//if err = decoder.Decode(rdfSG); err != nil {
// return nil, err
//}
return true
err := c.api.Post(ctx, URL, c.getDefaultHeaders(), CreateRDFPayload, nil)
if err != nil {
log.Error("Error in ExecuteCreateRDFGroup: " + err.Error())
return err
}
log.Debugf("sucessfully created RDF group")
return nil
}

// ExecuteReplicationActionOnSG executes supported replication based actions on the protected SG
Expand Down
8 changes: 4 additions & 4 deletions interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -319,13 +319,13 @@ type Pmax interface {
//GetFreeLocalAndRemoteRDFg returns list of Local and Remote Free RDFg in the array
GetFreeLocalAndRemoteRDFg(ctx context.Context, localSymmID string, remoteSymmID string) (*types.NextFreeRDFGroup, error)

// ExecuteCreateRDFGroup fetches Next Free RDFGroup across the arrays. Returns Free RDFGroup from R1 and R2
ExecuteCreateRDFGroup(ctx context.Context, symID string, CreateRDFPayload *types.RDFGroupCreate) bool
// ExecuteCreateRDFGroup creates a new RDF group based on payload
ExecuteCreateRDFGroup(ctx context.Context, symID string, CreateRDFPayload *types.RDFGroupCreate) error

//GetLocalOnlineRDFDirs returs a List of ONLINE RDF Directors for a given array
//GetLocalOnlineRDFDirs returns a List of ONLINE RDF Directors for a given array
GetLocalOnlineRDFDirs(ctx context.Context, localSymID string) (*types.RDFDirList, error)

//GetLocalOnlineRDFPorts returs List of ONLINE RDF Ports associated for a given ONLINE RDF Director
//GetLocalOnlineRDFPorts returns List of ONLINE RDF Ports associated for a given ONLINE RDF Director
GetLocalOnlineRDFPorts(ctx context.Context, rdfDir string, localSymID string) (*types.RDFPortList, error)

//GetRemoteRDFPortOnSAN returns an array of Remote RDF Ports on the SAN that are connected to given local RDF Dir:Port
Expand Down
180 changes: 167 additions & 13 deletions mock/mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ const (
PREFIX = "/univmax/restapi/" + APIVersion
PREFIXNOVERSION = "/univmax/restapi"
PRIVATEPREFIX = "/univmax/restapi/private/" + APIVersion
INTERNALPREFIX = "/univmax/restapi/internal/100"
defaultUsername = "username"
defaultPassword = "password"
Debug = false
Expand All @@ -47,12 +48,15 @@ const (
DefaultProtectedStorageGroup = "CSI-no-srp-async-test-13"
DefaultSymmetrixID = "000197900046"
DefaultRemoteSymID = "000000000013"
DefaultRDFDir = "OR-1C"
DefaultRDFPort = 3
PostELMSRSymmetrixID = "000197900047"
DefaultStoragePool = "SRP_1"
DefaultServiceLevel = "Optimized"
DefaultFcStoragePortWWN = "5000000000000001"
DefaultRDFGNo = 13
DefaultRemoteRDFGNo = 13
DefaultRDFLabel = "csi-mock-test"
RemoteArrayHeaderKey = "RemoteArray"
RemoteArrayHeaderValue = "true"
)
Expand Down Expand Up @@ -198,6 +202,12 @@ var InducedErrors struct {
GetVolumesMetricsError bool
GetStorageGroupPerfKeyError bool
GetArrayPerfKeyError bool
GetFreeRDFGError bool
GetLocalOnlineRDFDirsError bool
GetRemoteRDFPortOnSANError bool
GetLocalOnlineRDFPortsError bool
GetLocalRDFPortDetailsError bool
CreateRDFGroupError bool
}

// hasError checks to see if the specified error (via pointer)
Expand Down Expand Up @@ -305,6 +315,12 @@ func Reset() {
InducedErrors.GetVolumesMetricsError = false
InducedErrors.GetStorageGroupPerfKeyError = false
InducedErrors.GetArrayPerfKeyError = false
InducedErrors.GetFreeRDFGError = false
InducedErrors.GetLocalOnlineRDFDirsError = false
InducedErrors.GetRemoteRDFPortOnSANError = false
InducedErrors.GetLocalOnlineRDFPortsError = false
InducedErrors.GetLocalRDFPortDetailsError = false
InducedErrors.CreateRDFGroupError = false
Data.JSONDir = "mock"
Data.VolumeIDToIdentifier = make(map[string]string)
Data.VolumeIDToSize = make(map[string]int)
Expand Down Expand Up @@ -332,13 +348,13 @@ func Reset() {
Data.HostGroupIDToHostGroup = make(map[string]*types.HostGroup)
Data.RDFGroup = &types.RDFGroup{
RdfgNumber: DefaultRDFGNo,
Label: "RG_13",
Label: DefaultRDFLabel,
RemoteRdfgNumber: DefaultRDFGNo,
RemoteSymmetrix: DefaultRemoteSymID,
NumDevices: 0,
TotalDeviceCapacity: 0.0,
Modes: []string{"Asynchronous"},
Type: "VASA_ASYNC",
Type: "Dynamic",
Async: true,
}
Data.SGRDFInfo = &types.SGRDFInfo{
Expand Down Expand Up @@ -481,12 +497,18 @@ func getRouter() http.Handler {
router.HandleFunc(PREFIX+"/replication/capabilities/symmetrix", handleCapabilities)
router.HandleFunc(PREFIX+"/replication/symmetrix/{symID}/snapshot_policy/{snapshotPolicyID}/storagegroup/{storageGroupID}", handleStorageGroupSnapshotPolicy)

// SRDF
//SRDF
router.HandleFunc(PREFIX+"/replication/symmetrix/{symid}/rdf_group", handleRDFGroup)
router.HandleFunc(PREFIX+"/replication/symmetrix/{symid}/rdf_group/{rdf_no}", handleRDFGroup)
router.HandleFunc(PREFIX+"/replication/symmetrix/{symid}/storagegroup/{id}", handleRDFStorageGroup)
router.HandleFunc(PREFIX+"/replication/symmetrix/{symid}/storagegroup/{id}/rdf_group", handleRDFStorageGroup)
router.HandleFunc(PREFIX+"/replication/symmetrix/{symid}/storagegroup/{id}/rdf_group/{rdf_no}", handleSGRDFInfo)
router.HandleFunc(PREFIX+"/replication/symmetrix/{symid}/storagegroup/{id}/rdf_group/{rdf_no}", handleSGRDF)
router.HandleFunc(PREFIX+"/replication/symmetrix/{symid}/rdf_group/{rdf_no}/volume/{volume_id}", handleRDFDevicePair)
router.HandleFunc(INTERNALPREFIX+"/file/symmetrix/{symID}/rdf_group_numbers_free", handleFreeRDF)
router.HandleFunc(PREFIX+"/replication/symmetrix/{symID}/rdf_director", handleRDFDirector)
router.HandleFunc(PREFIX+"/replication/symmetrix/{symID}/rdf_director/{dir}/port", handleRDFPort)
router.HandleFunc(PREFIX+"/replication/symmetrix/{symID}/rdf_director/{dir}/port/{port}", handleRDFPort)
router.HandleFunc(PREFIX+"/replication/symmetrix/{symID}/rdf_director/{dir}/port/{port}/remote_port", handleRDFRemotePort)

// Performance Metrics
router.HandleFunc(PREFIXNOVERSION+"/performance/StorageGroup/metrics", handleStorageGroupMetrics)
Expand All @@ -500,6 +522,103 @@ func getRouter() http.Handler {
return router
}

// GET univmax/restapi/100/replication/symmetrix/{symID}/rdf_director/{dir}/port?online=true
// GET univmax/restapi/100/replication/symmetrix/{symID}/rdf_director/{dir}/port/{port}
func handleRDFPort(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
writeError(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
if InducedErrors.GetLocalOnlineRDFPortsError {
writeError(w, "Could not retrieve local online RDF ports: induced error", http.StatusBadRequest)
return
}
if InducedErrors.GetLocalRDFPortDetailsError {
writeError(w, "Could not retrieve local RDF port: induced error", http.StatusBadRequest)
return
}
routeParams := mux.Vars(r)
portID := routeParams["port"]
if portID != "" {
rdfPorts := &types.RDFPortDetails{
SymmID: DefaultSymmetrixID,
DirNum: 33,
DirID: DefaultRDFDir,
PortNum: DefaultRDFPort,
PortOnline: true,
PortWWN: "5000097200007003",
}
writeJSON(w, rdfPorts)
} else {
rdfPorts := &types.RDFPortList{RdfPorts: []string{"3"}}
writeJSON(w, rdfPorts)
}
}

// GET univmax/restapi/100/replication/symmetrix/{symID}/rdf_director/{dir}/port/{port}/remote_port
func handleRDFRemotePort(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
writeError(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
if InducedErrors.GetRemoteRDFPortOnSANError {
writeError(w, "Could not retrieve remote RDF port: induced error", http.StatusBadRequest)
return
}
routeParams := mux.Vars(r)
portID := routeParams["port"]
if portID == "" {
writeError(w, "portID is nil in request, can not retrieve remote port details", http.StatusBadRequest)
return
}
remotePorts := &types.RemoteRDFPortDetails{
RemotePorts: []types.RDFPortDetails{
{
SymmID: DefaultRemoteSymID,
DirNum: 33,
DirID: DefaultRDFDir,
PortNum: DefaultRDFPort,
PortOnline: true,
PortWWN: "5000097200007003",
},
},
}
writeJSON(w, remotePorts)
}

// GET univmax/restapi/100/replication/symmetrix/{symID}/rdf_director?online=true
func handleRDFDirector(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
writeError(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
if InducedErrors.GetLocalOnlineRDFDirsError {
writeError(w, "Could not retrieve RDF director: induced error", http.StatusBadRequest)
return
}
dir := &types.RDFDirList{
RdfDirs: []string{DefaultRDFDir, "OR-2C"},
}
writeJSON(w, dir)
}

// GET univmax/restapi/internal/100/file/symmetrix/{symID}/rdf_group_numbers_free?remote_symmetrix_id={remoteSymID}
func handleFreeRDF(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
writeError(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
if InducedErrors.GetFreeRDFGError {
writeError(w, "Could not retrieve free RDF group: induced error", http.StatusBadRequest)
return
}
nxtFreeRDFG := &types.NextFreeRDFGroup{
LocalRdfGroup: []int{DefaultRDFGNo},
RemoteRdfGroup: []int{DefaultRDFGNo},
}
writeJSON(w, nxtFreeRDFG)
}

// NewVolume creates a new mock volume with the specified characteristics.
func NewVolume(volumeID, volumeIdentifier string, size int, sgList []string) {
mockCacheMutex.Lock()
Expand Down Expand Up @@ -576,25 +695,60 @@ func handleRDFDevicePairInfo(w http.ResponseWriter, r *http.Request) {
writeJSON(w, rdfDevicePairInfo)
}

// GET, POST /univmax/restapi/APIVersion/replication/symmetrix/{symID}/rdf_group/
// GET /univmax/restapi/APIVersion/replication/symmetrix/{symID}/rdf_group/{rdf_no}
func handleRDFGroup(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
writeError(w, "Method not allowed", http.StatusMethodNotAllowed)
if InducedErrors.CreateRDFGroupError {
writeError(w, "error creating RDF group: induced error", http.StatusNotFound)
return
}
if InducedErrors.GetRDFGroupError {
writeError(w, "the specified RA group does not exist: induced error", http.StatusNotFound)
return
}
routeParams := mux.Vars(r)
rdfGroupNumber := routeParams["rdf_no"]
if rdfGroupNumber != fmt.Sprintf("%d", Data.RDFGroup.RdfgNumber) {
writeError(w, "The specified RA group is not valid", http.StatusNotFound)
switch r.Method {
case http.MethodGet:
routeParams := mux.Vars(r)
rdfGroupNumber := routeParams["rdf_no"]
ReturnRDFGroup(w, rdfGroupNumber)
case http.MethodPost:
writeJSON(w, Data.RDFGroup)
default:
writeError(w, "Method["+r.Method+"] not allowed", http.StatusMethodNotAllowed)
}

}

// ReturnRDFGroup - Returns RDF group information from mock cache
func ReturnRDFGroup(w http.ResponseWriter, rdfg string) {
mockCacheMutex.Lock()
defer mockCacheMutex.Unlock()
returnRDFGroup(w, rdfg)
}

func returnRDFGroup(w http.ResponseWriter, rdfg string) {
if rdfg != "" {
if rdfg != fmt.Sprintf("%d", Data.RDFGroup.RdfgNumber) {
writeError(w, "The specified RA group is not valid", http.StatusNotFound)
} else {
if InducedErrors.RDFGroupHasPairError {
Data.RDFGroup.NumDevices = 1
}
writeJSON(w, Data.RDFGroup)
}
} else {
if InducedErrors.RDFGroupHasPairError {
Data.RDFGroup.NumDevices = 1
rdflist := &types.RDFGroupList{
RDFGroupCount: 1,
RDFGroupIDs: []types.RDFGroupIDL{
{
RDFGNumber: 1,
Label: "mock",
RemoteSymID: DefaultRemoteSymID,
GroupType: "Dynamic",
},
},
}
writeJSON(w, Data.RDFGroup)
writeJSON(w, rdflist)
}
}

Expand Down
Loading