Skip to content

Commit 53befce

Browse files
authored
Fix nil ptr exceptions in ontap rest layer
1 parent cdae514 commit 53befce

3 files changed

Lines changed: 88 additions & 17 deletions

File tree

storage_drivers/ontap/api/abstraction_rest.go

Lines changed: 45 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1341,14 +1341,19 @@ func (d OntapAPIREST) QtreeListByPrefix(ctx context.Context, prefix, volumePrefi
13411341
}
13421342
qtrees := Qtrees{}
13431343
for _, qtree := range qtreeList.GetPayload().QtreeResponseInlineRecords {
1344-
newQtree := d.convertQtree(qtree)
1345-
qtrees = append(qtrees, newQtree)
1344+
if newQtree := d.convertQtree(qtree); newQtree != nil {
1345+
qtrees = append(qtrees, newQtree)
1346+
}
13461347
}
13471348

13481349
return qtrees, nil
13491350
}
13501351

13511352
func (d OntapAPIREST) convertQtree(qtree *models.Qtree) *Qtree {
1353+
if qtree == nil {
1354+
return nil
1355+
}
1356+
13521357
newQtree := &Qtree{}
13531358

13541359
if qtree.Name != nil {
@@ -1379,7 +1384,11 @@ func (d OntapAPIREST) QtreeGetByName(ctx context.Context, name, volumePrefix str
13791384
Logc(ctx).WithError(err).Error(msg)
13801385
return nil, errors.New(msg)
13811386
}
1382-
return d.convertQtree(qtree), nil
1387+
converted := d.convertQtree(qtree)
1388+
if converted == nil {
1389+
return nil, errors.NotFoundError("qtree %q not found", name)
1390+
}
1391+
return converted, nil
13831392
}
13841393

13851394
func (d OntapAPIREST) QuotaEntryList(ctx context.Context, volumeName string) (QuotaEntries, error) {
@@ -1390,7 +1399,9 @@ func (d OntapAPIREST) QuotaEntryList(ctx context.Context, volumeName string) (Qu
13901399
entries := QuotaEntries{}
13911400
if response != nil && response.Payload != nil {
13921401
for _, entry := range response.Payload.QuotaRuleResponseInlineRecords {
1393-
entries = append(entries, d.convertQuota(entry))
1402+
if converted := d.convertQuota(entry); converted != nil {
1403+
entries = append(entries, converted)
1404+
}
13941405
}
13951406
}
13961407
return entries, nil
@@ -1458,20 +1469,27 @@ func (d OntapAPIREST) QuotaGetEntry(ctx context.Context, volumeName, qtreeName,
14581469
quota, err := d.api.QuotaGetEntry(ctx, volumeName, qtreeName, quotaType)
14591470
if err != nil {
14601471
Logc(ctx).WithError(err).Error("error getting quota rule")
1472+
return nil, err
14611473
}
14621474
quotaEntry := d.convertQuota(quota)
1475+
if quotaEntry == nil {
1476+
return nil, errors.NotFoundError("quota rule for volume %q qtree %q not found", volumeName, qtreeName)
1477+
}
14631478
return quotaEntry, nil
14641479
}
14651480

14661481
func (d OntapAPIREST) convertQuota(quota *models.QuotaRule) *QuotaEntry {
1482+
if quota == nil {
1483+
return nil
1484+
}
14671485
diskLimit := int64(-1)
14681486
if quota.Space != nil && quota.Space.HardLimit != nil {
14691487
diskLimit = *quota.Space.HardLimit
14701488
}
14711489
quotaEntry := &QuotaEntry{
14721490
DiskLimitBytes: diskLimit,
14731491
}
1474-
if quota != nil && quota.Volume != nil && quota.Volume.Name != nil && *quota.Volume.Name != "" {
1492+
if quota.Volume != nil && quota.Volume.Name != nil && *quota.Volume.Name != "" {
14751493
quotaEntry.Target = fmt.Sprintf("/vol/%s", *quota.Volume.Name)
14761494

14771495
if quota.Qtree != nil && quota.Qtree.Name != nil && *quota.Qtree.Name != "" {
@@ -1558,10 +1576,15 @@ func (d OntapAPIREST) VolumeWaitForStates(ctx context.Context, volumeName string
15581576
}
15591577

15601578
if vol == nil {
1579+
volumeState = ""
15611580
return fmt.Errorf("volume %v not found", volumeName)
15621581
}
15631582

1564-
Logc(ctx).Debugf("Volume %v is in state:%v", volumeName, *vol.State)
1583+
if vol.State == nil {
1584+
volumeState = ""
1585+
return fmt.Errorf("volume %v state not yet populated", volumeName)
1586+
}
1587+
15651588
volumeState = *vol.State
15661589

15671590
if collection.ContainsString(desiredStates, volumeState) {
@@ -1966,7 +1989,7 @@ func (d OntapAPIREST) SnapmirrorGet(
19661989
snapmirror.IsHealthy = *snapmirrorResponse.Healthy
19671990
if !snapmirror.IsHealthy {
19681991
unhealthyReason := snapmirrorResponse.SnapmirrorRelationshipInlineUnhealthyReason
1969-
if len(unhealthyReason) > 0 {
1992+
if len(unhealthyReason) > 0 && unhealthyReason[0] != nil && unhealthyReason[0].Message != nil {
19701993
snapmirror.UnhealthyReason = *unhealthyReason[0].Message
19711994
}
19721995
}
@@ -2138,8 +2161,10 @@ func (d OntapAPIREST) SnapmirrorBreak(
21382161
func (d OntapAPIREST) SnapmirrorUpdate(ctx context.Context, localInternalVolumeName, snapshotName string) error {
21392162
err := d.api.SnapmirrorUpdate(ctx, localInternalVolumeName, snapshotName)
21402163
if err != nil {
2141-
if restErr, err := ExtractErrorResponse(ctx, err); err == nil {
2142-
return errors.New(*restErr.Error.Message)
2164+
if restErr, extractErr := ExtractErrorResponse(ctx, err); extractErr == nil {
2165+
if restErr != nil && restErr.Error != nil && restErr.Error.Message != nil {
2166+
return errors.New(*restErr.Error.Message)
2167+
}
21432168
}
21442169
return err
21452170
}
@@ -3508,6 +3533,9 @@ func (d OntapAPIREST) NVMeAddHostToSubsystem(ctx context.Context, hostNQN, subsy
35083533
}
35093534

35103535
for _, host := range hosts {
3536+
if host == nil || host.Nqn == nil {
3537+
continue
3538+
}
35113539
if *host.Nqn == hostNQN {
35123540
// Host already part of the subsystem, no need to add it again
35133541
return nil
@@ -3537,7 +3565,7 @@ func (d OntapAPIREST) NVMeRemoveHostFromSubsystem(ctx context.Context, hostNQN,
35373565

35383566
hostFound := false
35393567
for _, host := range hosts {
3540-
if host != nil && *host.Nqn == hostNQN {
3568+
if host != nil && host.Nqn != nil && *host.Nqn == hostNQN {
35413569
hostFound = true
35423570
break
35433571
}
@@ -3589,6 +3617,9 @@ func (d OntapAPIREST) NVMeSubsystemCreate(ctx context.Context, subsystemName, co
35893617
return nil, err
35903618
}
35913619
if subsystem != nil {
3620+
if subsystem.UUID == nil || subsystem.Name == nil || subsystem.TargetNqn == nil {
3621+
return nil, fmt.Errorf("subsystem %v returned with missing fields", subsystemName)
3622+
}
35923623
return &NVMeSubsystem{UUID: *subsystem.UUID, Name: *subsystem.Name, NQN: *subsystem.TargetNqn}, nil
35933624
}
35943625
return nil, fmt.Errorf("unable to create subsystem %v", subsystemName)
@@ -3603,6 +3634,10 @@ func (d OntapAPIREST) NVMeSubsystemCreate(ctx context.Context, subsystemName, co
36033634
}
36043635
}
36053636

3637+
if subsystem.UUID == nil || subsystem.Name == nil || subsystem.TargetNqn == nil {
3638+
return nil, fmt.Errorf("subsystem %v returned with missing fields", subsystemName)
3639+
}
3640+
36063641
Logc(ctx).Debugf("Found subsystem %v and target nqns are %v", *subsystem.Name, *subsystem.TargetNqn)
36073642

36083643
return &NVMeSubsystem{UUID: *subsystem.UUID, Name: *subsystem.Name, NQN: *subsystem.TargetNqn}, nil

storage_drivers/ontap/api/abstraction_rest_test.go

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1041,6 +1041,31 @@ func TestVolumeWaitForStates(t *testing.T) {
10411041
assert.Equal(t, currentState, *volume.State, "volume state does not match")
10421042

10431043
ctrl.Finish()
1044+
1045+
// Test8: Error - Volume returned with nil State (transient ONTAP response during clone init);
1046+
// must retry and eventually time out without panicking (TRID-19789).
1047+
ctrl = gomock.NewController(t)
1048+
mock = mockapi.NewMockRestClientInterface(ctrl)
1049+
oapi, err = api.NewOntapAPIRESTFromRestClientInterface(mock)
1050+
assert.NoError(t, err)
1051+
nilStateDesiredStates := []string{"online"}
1052+
nilStateAbortStates := []string{}
1053+
nilStateVolume := &models.Volume{State: nil}
1054+
nilStateMaxElapsedTime := 2 * time.Second
1055+
1056+
mock.EXPECT().ClientConfig().Return(clientConfig).AnyTimes()
1057+
// MinTimes(2) asserts the nil-State error is treated as transient: the closure
1058+
// must be retried at least once before the backoff window elapses. This guards
1059+
// against a future change accidentally wrapping the error in backoff.Permanent,
1060+
// which would silently bypass the retry semantic the TRID-19789 fix relies on.
1061+
mock.EXPECT().VolumeGetByName(ctx, volName, gomock.Any()).Return(nilStateVolume, nil).MinTimes(2)
1062+
1063+
currentState, err = oapi.VolumeWaitForStates(ctx, "fakeVolName", nilStateDesiredStates, nilStateAbortStates, nilStateMaxElapsedTime)
1064+
1065+
assert.Error(t, err, "expected timeout error when volume state is perpetually nil")
1066+
assert.Equal(t, "", currentState, "volume state should be empty when state was never populated")
1067+
1068+
ctrl.Finish()
10441069
}
10451070

10461071
func newMockOntapAPIREST(t *testing.T) (api.OntapAPIREST, *mockapi.MockRestClientInterface) {
@@ -2946,11 +2971,19 @@ func TestQuotaGetEntry(t *testing.T) {
29462971
assert.Equal(t, "/vol/quotaVolume/quotaQtree", quotaEntries.Target)
29472972
assert.Equal(t, int64(1073741810), quotaEntries.DiskLimitBytes)
29482973

2949-
// case 2: Quota get entry failed. Backend returned an error
2974+
// case 2: Quota get entry failed. Backend returned an error - it must be propagated.
29502975
rsi.EXPECT().QuotaGetEntry(ctx, quotaVolumeName, quotaQtreeName, quotaType).Return(
2951-
&quotaRule, errors.New("error getting quota rule"))
2952-
_, err = oapi.QuotaGetEntry(ctx, quotaVolumeName, quotaQtreeName, quotaType)
2953-
assert.NoError(t, err, "no error returned while getting a quota")
2976+
nil, errors.New("error getting quota rule"))
2977+
quotaEntries, err = oapi.QuotaGetEntry(ctx, quotaVolumeName, quotaQtreeName, quotaType)
2978+
assert.Error(t, err, "expected error to be propagated when backend returns one")
2979+
assert.Nil(t, quotaEntries, "expected nil quota entry on backend error")
2980+
2981+
// case 3: Backend returned (nil, nil) - the quota rule was not found.
2982+
rsi.EXPECT().QuotaGetEntry(ctx, quotaVolumeName, quotaQtreeName, quotaType).Return(nil, nil)
2983+
quotaEntries, err = oapi.QuotaGetEntry(ctx, quotaVolumeName, quotaQtreeName, quotaType)
2984+
assert.Error(t, err, "expected NotFoundError when underlying call returns no quota rule")
2985+
assert.True(t, errors.IsNotFoundError(err), "expected error to be a NotFoundError")
2986+
assert.Nil(t, quotaEntries, "expected nil quota entry when not found")
29542987
}
29552988

29562989
func getSnapshot() *models.Snapshot {

storage_drivers/ontap/api/ontap_rest.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1302,7 +1302,7 @@ func (c *RestClient) listAllVolumeNamesBackedBySnapshot(ctx context.Context, vol
13021302
}
13031303
if vol.Clone != nil {
13041304
if vol.Clone.ParentSnapshot != nil && vol.Clone.ParentSnapshot.Name != nil && *vol.Clone.ParentSnapshot.Name == snapshotName &&
1305-
vol.Clone.ParentVolume != nil && *vol.Clone.ParentVolume.Name == volumeName {
1305+
vol.Clone.ParentVolume != nil && vol.Clone.ParentVolume.Name != nil && *vol.Clone.ParentVolume.Name == volumeName {
13061306
volumeNames = append(volumeNames, *vol.Name)
13071307
}
13081308
}
@@ -5429,7 +5429,7 @@ func (c *RestClient) QuotaSetEntry(ctx context.Context, qtreeName, volumeName, q
54295429
return c.QuotaAddEntry(ctx, volumeName, qtreeName, quotaType, diskLimit)
54305430
}
54315431

5432-
if quotaRule.UUID == nil {
5432+
if quotaRule == nil || quotaRule.UUID == nil {
54335433
return fmt.Errorf("unexpected response from quota entry lookup")
54345434
}
54355435

@@ -6169,7 +6169,10 @@ func (c *RestClient) SnapmirrorDeleteViaDestination(
61696169
if err != nil {
61706170
if restErr, extractErr := ExtractErrorResponse(ctx, err); extractErr == nil {
61716171
if restErr.Error != nil && restErr.Error.Code != nil && *restErr.Error.Code != ENTRY_DOESNT_EXIST {
6172-
return errors.New(*restErr.Error.Message)
6172+
if restErr.Error.Message != nil {
6173+
return errors.New(*restErr.Error.Message)
6174+
}
6175+
return fmt.Errorf("snapmirror destination delete failed with error code %v", *restErr.Error.Code)
61736176
}
61746177
} else {
61756178
return err

0 commit comments

Comments
 (0)