Skip to content

Commit 5a39b5b

Browse files
Snap Mirror support for NVMe
Co-authored-by: Vinay Kumar H S <[email protected]>
1 parent c46e6a9 commit 5a39b5b

File tree

5 files changed

+147
-31
lines changed

5 files changed

+147
-31
lines changed

storage_drivers/ontap/api/abstraction_rest.go

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1975,14 +1975,12 @@ func (d OntapAPIREST) SnapmirrorPolicyGet(ctx context.Context, replicationPolicy
19751975
func (d OntapAPIREST) isTransferInProgressError(
19761976
ctx context.Context, err error,
19771977
) bool {
1978-
if restErr, err := ExtractErrorResponse(ctx, err); err == nil {
1979-
if restErr.Error != nil && restErr.Error.Code != nil {
1980-
switch *restErr.Error.Code {
1981-
case SNAPMIRROR_TRANSFER_IN_PROGRESS, SNAPMIRROR_TRANSFER_IN_PROGRESS_BROKEN_OFF:
1982-
return true
1983-
default:
1984-
return false
1985-
}
1978+
if _, _, code := ExtractError(err); code != "" {
1979+
switch code {
1980+
case SNAPMIRROR_TRANSFER_IN_PROGRESS, SNAPMIRROR_TRANSFER_IN_PROGRESS_BROKEN_OFF:
1981+
return true
1982+
default:
1983+
return false
19861984
}
19871985
}
19881986
return false

storage_drivers/ontap/api/ontap_rest.go

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5839,8 +5839,8 @@ func (c *RestClient) SnapmirrorQuiesce(
58395839
func (c *RestClient) SnapmirrorAbort(
58405840
ctx context.Context, localFlexvolName, localSVMName, remoteFlexvolName, remoteSVMName string,
58415841
) error {
5842-
// first, find the relationship so we can then use the UUID to modify it
5843-
fields := []string{"destination.path", "source.path"}
5842+
// find the existing transfer in a relationship, so we can then use the UUID to abort the transfer.
5843+
fields := []string{"destination.path", "source.path", "transfer.uuid"}
58445844
relationship, err := c.SnapmirrorGet(ctx, localFlexvolName, c.SVMName(), remoteFlexvolName, remoteSVMName, fields)
58455845
if err != nil {
58465846
return err
@@ -5851,26 +5851,32 @@ func (c *RestClient) SnapmirrorAbort(
58515851
if relationship.UUID == nil {
58525852
return fmt.Errorf("unexpected response from snapmirror relationship lookup")
58535853
}
5854+
if relationship.Transfer == nil || relationship.Transfer.UUID == nil {
5855+
// no transfer in progress, nothing to abort
5856+
return nil
5857+
}
58545858

5855-
params := snapmirror.NewSnapmirrorRelationshipModifyParamsWithTimeout(c.httpClient.Timeout)
5859+
// Abort the transfer in the relationship
5860+
params := snapmirror.NewSnapmirrorRelationshipTransferModifyParamsWithTimeout(c.httpClient.Timeout)
58565861
params.SetContext(ctx)
58575862
params.SetHTTPClient(c.httpClient)
5858-
params.SetUUID(string(*relationship.UUID))
5863+
params.SetUUID(string(*relationship.Transfer.UUID))
5864+
params.WithRelationshipUUID(string(*relationship.UUID))
58595865

5860-
params.Info = &models.SnapmirrorRelationship{
5866+
params.Info = &models.SnapmirrorTransfer{
58615867
State: convert.ToPtr(models.SnapmirrorRelationshipInlineTransferStateAborted),
58625868
}
58635869

5864-
_, snapmirrorRelationshipModifyAccepted, err := c.api.Snapmirror.SnapmirrorRelationshipModify(params, c.authInfo)
5865-
if err != nil {
5870+
if _, err := c.api.Snapmirror.SnapmirrorRelationshipTransferModify(params, c.authInfo); err != nil {
5871+
if _, _, code := ExtractError(err); code == ENTRY_DOESNT_EXIST {
5872+
// Transfer does not exist, ignore the error
5873+
return nil
5874+
}
5875+
58665876
return err
58675877
}
5868-
if snapmirrorRelationshipModifyAccepted == nil {
5869-
return fmt.Errorf("unexpected response from snapmirror relationship modify")
5870-
}
58715878

5872-
jobLink := getGenericJobLinkFromSMRJobLink(snapmirrorRelationshipModifyAccepted.Payload)
5873-
return c.PollJobStatus(ctx, jobLink)
5879+
return nil
58745880
}
58755881

58765882
// SnapmirrorRelease removes all local snapmirror relationship metadata from the source vserver

storage_drivers/ontap/api/ontap_rest_test.go

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7106,6 +7106,43 @@ func mockSnapMirrorRelationshipResponseFailure(w http.ResponseWriter, r *http.Re
71067106
}
71077107
}
71087108

7109+
func mockSnapMirrorRelationshipTransferResponseFailure(w http.ResponseWriter, r *http.Request) {
7110+
snapmirrorTransferResponse := &models.SnapmirrorTransferResponse{
7111+
SnapmirrorTransferResponseInlineRecords: []*models.SnapmirrorTransfer{
7112+
{
7113+
UUID: convert.ToPtr(strfmt.UUID("1234567899")),
7114+
},
7115+
},
7116+
}
7117+
7118+
if r.URL.Path == "/api/cluster/jobs/1234" {
7119+
mockQtreeJobResponse(w, r)
7120+
} else {
7121+
switch r.Method {
7122+
case "PATCH", "DELETE":
7123+
jobId := strfmt.UUID("1234")
7124+
jobLink := models.JobLink{UUID: &jobId}
7125+
jobResponse := models.JobLinkResponse{Job: &jobLink}
7126+
setHTTPResponseHeader(w, http.StatusInternalServerError)
7127+
json.NewEncoder(w).Encode(jobResponse)
7128+
case "POST":
7129+
if r.URL.Path == "/api/snapmirror/relationships/1234/transfer" {
7130+
jobId := strfmt.UUID("1234")
7131+
jobLink := models.JobLink{UUID: &jobId}
7132+
jobResponse := models.JobLinkResponse{Job: &jobLink}
7133+
setHTTPResponseHeader(w, http.StatusInternalServerError)
7134+
json.NewEncoder(w).Encode(jobResponse)
7135+
} else {
7136+
setHTTPResponseHeader(w, http.StatusInternalServerError)
7137+
json.NewEncoder(w).Encode(snapmirrorTransferResponse)
7138+
}
7139+
default:
7140+
setHTTPResponseHeader(w, http.StatusOK)
7141+
json.NewEncoder(w).Encode(snapmirrorTransferResponse)
7142+
}
7143+
}
7144+
}
7145+
71097146
func mockSnapMirrorRelationshipResponseEmptyValue(w http.ResponseWriter, r *http.Request) {
71107147
snapMirrorRelationshipResponse := &models.SnapmirrorRelationshipResponse{
71117148
SnapmirrorRelationshipResponseInlineRecords: []*models.SnapmirrorRelationship{
@@ -7323,7 +7360,7 @@ func TestOntapRest_SnapmirrorAbort(t *testing.T) {
73237360
}{
73247361
{"PositiveTest", mockSnapMirrorRelationshipResponse, false},
73257362
{"UUIDNilInResponse", mockSnapMirrorRelationshipResponseUUIDNil, true},
7326-
{"SnapMirrorRelationshipFailed", mockSnapMirrorRelationshipResponseFailure, true},
7363+
{"SnapMirrorRelationshipTransferFailed", mockSnapMirrorRelationshipTransferResponseFailure, true},
73277364
{"NegativeTest_BackendReturnError", mockResourceNotFound, true},
73287365
}
73297366
for _, test := range tests {

storage_drivers/ontap/ontap_san_nvme.go

Lines changed: 45 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -795,6 +795,13 @@ func (d *NVMeStorageDriver) Destroy(ctx context.Context, volConfig *storage.Volu
795795
}
796796
}
797797

798+
// If flexvol has been a snapmirror source
799+
if err = d.API.SnapmirrorRelease(ctx, name, d.API.SVMName()); err != nil {
800+
if !api.IsNotFoundError(err) {
801+
return err
802+
}
803+
}
804+
798805
// Delete the FlexVol and Namespace.
799806
err = d.API.VolumeDestroy(ctx, name, true, skipRecoveryQueue)
800807
if err != nil {
@@ -840,6 +847,17 @@ func (d *NVMeStorageDriver) Publish(
840847
publishInfo.HostNQN = nqn
841848
}
842849

850+
if volConfig.IsMirrorDestination {
851+
// In this case, InternalID and NVMeNamespaceUUID would be empty
852+
volConfig.InternalID = createNamespacePath(volConfig.InternalName, extractNamespaceName(""))
853+
ns, err := d.API.NVMeNamespaceGetByName(ctx, volConfig.InternalID)
854+
if err != nil {
855+
return fmt.Errorf("problem fetching namespace %v. Error:%v", volConfig.InternalID, err)
856+
}
857+
858+
volConfig.AccessInfo.NVMeNamespaceUUID = ns.UUID
859+
}
860+
843861
nsPath := volConfig.InternalID
844862
nsUUID := volConfig.AccessInfo.NVMeNamespaceUUID
845863
// For docker context, some of the attributes like fsType, luks needs to be
@@ -1522,7 +1540,8 @@ func (d *NVMeStorageDriver) EstablishMirror(
15221540
replicationSchedule = ""
15231541
}
15241542

1525-
return establishMirror(ctx, localInternalVolumeName, remoteVolumeHandle, replicationPolicy, replicationSchedule, d.API)
1543+
return establishMirror(ctx, localInternalVolumeName, remoteVolumeHandle, replicationPolicy, replicationSchedule,
1544+
d.API)
15261545
}
15271546

15281547
// ReestablishMirror will attempt to resync a snapmirror relationship,
@@ -1562,7 +1581,8 @@ func (d *NVMeStorageDriver) ReestablishMirror(
15621581
replicationSchedule = ""
15631582
}
15641583

1565-
return reestablishMirror(ctx, localInternalVolumeName, remoteVolumeHandle, replicationPolicy, replicationSchedule, d.API)
1584+
return reestablishMirror(ctx, localInternalVolumeName, remoteVolumeHandle, replicationPolicy, replicationSchedule,
1585+
d.API)
15661586
}
15671587

15681588
// PromoteMirror will break the snapmirror and make the destination volume RW,
@@ -1587,10 +1607,32 @@ func (d *NVMeStorageDriver) ReleaseMirror(ctx context.Context, localInternalVolu
15871607
}
15881608

15891609
// GetReplicationDetails returns the replication policy and schedule of a snapmirror relationship.
1590-
func (d *NVMeStorageDriver) GetReplicationDetails(ctx context.Context, localInternalVolumeName, remoteVolumeHandle string) (string, string, string, error) {
1610+
func (d *NVMeStorageDriver) GetReplicationDetails(
1611+
ctx context.Context, localInternalVolumeName, remoteVolumeHandle string,
1612+
) (string, string, string, error) {
15911613
return getReplicationDetails(ctx, localInternalVolumeName, remoteVolumeHandle, d.API)
15921614
}
15931615

1616+
// UpdateMirror will attempt a mirror update for the given destination volume
1617+
func (d *NVMeStorageDriver) UpdateMirror(ctx context.Context, localInternalVolumeName, snapshotName string) error {
1618+
return mirrorUpdate(ctx, localInternalVolumeName, snapshotName, d.API)
1619+
}
1620+
1621+
// CheckMirrorTransferState will look at the transfer state of the mirror relationship to determine if it is failed,
1622+
// succeeded or in progress
1623+
func (d *NVMeStorageDriver) CheckMirrorTransferState(
1624+
ctx context.Context, localInternalVolumeName string,
1625+
) (*time.Time, error) {
1626+
return checkMirrorTransferState(ctx, localInternalVolumeName, d.API)
1627+
}
1628+
1629+
// GetMirrorTransferTime will return the transfer time of the mirror relationship
1630+
func (d *NVMeStorageDriver) GetMirrorTransferTime(
1631+
ctx context.Context, localInternalVolumeName string,
1632+
) (*time.Time, error) {
1633+
return getMirrorTransferTime(ctx, localInternalVolumeName, d.API)
1634+
}
1635+
15941636
// CreateNVMeNamespaceCommentString returns the string that needs to be stored in namespace comment field.
15951637
func (d *NVMeStorageDriver) createNVMeNamespaceCommentString(ctx context.Context, nsAttributeMap map[string]string, maxCommentLength int) (string, error) {
15961638
nsCommentMap := map[string]map[string]string{}

storage_drivers/ontap/ontap_san_nvme_test.go

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -777,6 +777,32 @@ func TestNVMeGetReplicationDetails_Error(t *testing.T) {
777777
assert.ErrorContains(t, err, "invalid volume name")
778778
}
779779

780+
func TestNVMeUpdateMirror_Error(t *testing.T) {
781+
d := newNVMeDriver(nil, nil, nil)
782+
783+
err := d.UpdateMirror(ctx, "", "")
784+
785+
assert.ErrorContains(t, err, "invalid volume name")
786+
}
787+
788+
func TestNVMeCheckMirrorTransferState_Error(t *testing.T) {
789+
d, mAPI := newNVMeDriverAndMockApi(t)
790+
mAPI.EXPECT().SVMName().Return("svm")
791+
792+
_, err := d.CheckMirrorTransferState(ctx, "")
793+
794+
assert.ErrorContains(t, err, "invalid volume name")
795+
}
796+
797+
func TestNVMeGetMirrorTransferTime_Error(t *testing.T) {
798+
d, mAPI := newNVMeDriverAndMockApi(t)
799+
mAPI.EXPECT().SVMName().Return("svm")
800+
801+
_, err := d.GetMirrorTransferTime(ctx, "")
802+
803+
assert.ErrorContains(t, err, "invalid volume name")
804+
}
805+
780806
func getNVMeCreateArgs(d *NVMeStorageDriver) (storage.Pool, *storage.VolumeConfig, map[string]sa.Request) {
781807
pool1 := d.virtualPools["pool1"]
782808
volConfig := &storage.VolumeConfig{InternalName: "vol1", Size: "200000000"}
@@ -1177,8 +1203,9 @@ func TestNVMeDestroy_VolumeDestroy(t *testing.T) {
11771203
"Volume destroy API call return error": {
11781204
configureMockOntapAPI: func(mockAPI *mockapi.MockOntapAPI) {
11791205
mockAPI.EXPECT().VolumeExists(ctx, gomock.Any()).Return(true, nil)
1180-
mockAPI.EXPECT().SVMName().Return("svm")
1206+
mockAPI.EXPECT().SVMName().Return("svm").AnyTimes()
11811207
mockAPI.EXPECT().SnapmirrorDeleteViaDestination(ctx, gomock.Any(), gomock.Any()).Return(nil)
1208+
mockAPI.EXPECT().SnapmirrorRelease(ctx, gomock.Any(), gomock.Any()).Return(nil)
11821209
mockAPI.EXPECT().VolumeDestroy(ctx, gomock.Any(), true,
11831210
false).Return(fmt.Errorf("destroy volume failed"))
11841211
},
@@ -1191,8 +1218,9 @@ func TestNVMeDestroy_VolumeDestroy(t *testing.T) {
11911218
"volume Destroy error: volume config with internal snapshot": {
11921219
configureMockOntapAPI: func(mockAPI *mockapi.MockOntapAPI) {
11931220
mockAPI.EXPECT().VolumeExists(ctx, gomock.Any()).Return(true, nil)
1194-
mockAPI.EXPECT().SVMName().Return("svm")
1221+
mockAPI.EXPECT().SVMName().Return("svm").AnyTimes()
11951222
mockAPI.EXPECT().SnapmirrorDeleteViaDestination(ctx, gomock.Any(), gomock.Any()).Return(nil)
1223+
mockAPI.EXPECT().SnapmirrorRelease(ctx, gomock.Any(), gomock.Any()).Return(nil)
11961224
mockAPI.EXPECT().VolumeDestroy(ctx, gomock.Any(), true,
11971225
false).Return(fmt.Errorf("destroy volume failed"))
11981226
},
@@ -1207,8 +1235,9 @@ func TestNVMeDestroy_VolumeDestroy(t *testing.T) {
12071235
"happy path": {
12081236
configureMockOntapAPI: func(mockAPI *mockapi.MockOntapAPI) {
12091237
mockAPI.EXPECT().VolumeExists(ctx, gomock.Any()).Return(true, nil)
1210-
mockAPI.EXPECT().SVMName().Return("svm")
1238+
mockAPI.EXPECT().SVMName().Return("svm").AnyTimes()
12111239
mockAPI.EXPECT().SnapmirrorDeleteViaDestination(ctx, gomock.Any(), gomock.Any()).Return(nil)
1240+
mockAPI.EXPECT().SnapmirrorRelease(ctx, gomock.Any(), gomock.Any()).Return(nil)
12121241
mockAPI.EXPECT().VolumeDestroy(ctx, gomock.Any(), true, false).Return(nil)
12131242
},
12141243
getVolumeConfig: func(driver *NVMeStorageDriver) storage.VolumeConfig {
@@ -1220,8 +1249,9 @@ func TestNVMeDestroy_VolumeDestroy(t *testing.T) {
12201249
"happy path: volume config with internal snapshot": {
12211250
configureMockOntapAPI: func(mockAPI *mockapi.MockOntapAPI) {
12221251
mockAPI.EXPECT().VolumeExists(ctx, gomock.Any()).Return(true, nil)
1223-
mockAPI.EXPECT().SVMName().Return("svm")
1252+
mockAPI.EXPECT().SVMName().Return("svm").AnyTimes()
12241253
mockAPI.EXPECT().SnapmirrorDeleteViaDestination(ctx, gomock.Any(), gomock.Any()).Return(nil)
1254+
mockAPI.EXPECT().SnapmirrorRelease(ctx, gomock.Any(), gomock.Any()).Return(nil)
12251255
mockAPI.EXPECT().VolumeDestroy(ctx, gomock.Any(), true, false).Return(nil)
12261256
mockAPI.EXPECT().VolumeSnapshotDelete(ctx, cloneSourceSnapshot, "").Return(nil)
12271257
},
@@ -1236,8 +1266,9 @@ func TestNVMeDestroy_VolumeDestroy(t *testing.T) {
12361266
"volume config with internal snapshot: snapshot delete error": {
12371267
configureMockOntapAPI: func(mockAPI *mockapi.MockOntapAPI) {
12381268
mockAPI.EXPECT().VolumeExists(ctx, gomock.Any()).Return(true, nil)
1239-
mockAPI.EXPECT().SVMName().Return("svm")
1269+
mockAPI.EXPECT().SVMName().Return("svm").AnyTimes()
12401270
mockAPI.EXPECT().SnapmirrorDeleteViaDestination(ctx, gomock.Any(), gomock.Any()).Return(nil)
1271+
mockAPI.EXPECT().SnapmirrorRelease(ctx, gomock.Any(), gomock.Any()).Return(nil)
12411272
mockAPI.EXPECT().VolumeDestroy(ctx, gomock.Any(), true, false).Return(nil)
12421273
mockAPI.EXPECT().VolumeSnapshotDelete(ctx, cloneSourceSnapshot, "").
12431274
Return(fmt.Errorf("failed to delete snapshot"))
@@ -1253,8 +1284,9 @@ func TestNVMeDestroy_VolumeDestroy(t *testing.T) {
12531284
"volume config with internal snapshot: snapshot delete not found error": {
12541285
configureMockOntapAPI: func(mockAPI *mockapi.MockOntapAPI) {
12551286
mockAPI.EXPECT().VolumeExists(ctx, gomock.Any()).Return(true, nil)
1256-
mockAPI.EXPECT().SVMName().Return("svm")
1287+
mockAPI.EXPECT().SVMName().Return("svm").AnyTimes()
12571288
mockAPI.EXPECT().SnapmirrorDeleteViaDestination(ctx, gomock.Any(), gomock.Any()).Return(nil)
1289+
mockAPI.EXPECT().SnapmirrorRelease(ctx, gomock.Any(), gomock.Any()).Return(nil)
12581290
mockAPI.EXPECT().VolumeDestroy(ctx, gomock.Any(), true, false).Return(nil)
12591291
mockAPI.EXPECT().VolumeSnapshotDelete(ctx, cloneSourceSnapshot, "").
12601292
Return(api.NotFoundError("snapshot not found"))
@@ -1270,8 +1302,9 @@ func TestNVMeDestroy_VolumeDestroy(t *testing.T) {
12701302
"skipRecoveryQueue volume": {
12711303
configureMockOntapAPI: func(mockAPI *mockapi.MockOntapAPI) {
12721304
mockAPI.EXPECT().VolumeExists(ctx, gomock.Any()).Return(true, nil)
1273-
mockAPI.EXPECT().SVMName().Return("svm")
1305+
mockAPI.EXPECT().SVMName().Return("svm").AnyTimes()
12741306
mockAPI.EXPECT().SnapmirrorDeleteViaDestination(ctx, gomock.Any(), gomock.Any()).Return(nil)
1307+
mockAPI.EXPECT().SnapmirrorRelease(ctx, gomock.Any(), gomock.Any()).Return(nil)
12751308
mockAPI.EXPECT().VolumeDestroy(ctx, gomock.Any(), true, true).Return(nil)
12761309
},
12771310
getVolumeConfig: func(driver *NVMeStorageDriver) storage.VolumeConfig {

0 commit comments

Comments
 (0)