Skip to content

Commit a21d755

Browse files
authored
Require healthy paths during iscsi volume expansion
Trident now checks the state of all paths to a LUN before rescanning or resizing the multipath map. If any path is unhealthy, expansion is deferred until all paths are remediated. Trident now also requires stable reads of path health, SCSI disk size, and multipath device size before returning success.
1 parent b84275c commit a21d755

18 files changed

Lines changed: 2262 additions & 1449 deletions

File tree

frontend/csi/node_server.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -768,7 +768,7 @@ func (p *Plugin) nodePrepareISCSIVolumeForExpansion(
768768
}).Debug("PublishInfo for block device to expand.")
769769

770770
// Resize the volume.
771-
if err := p.iscsi.ResizeVolumeRetry(ctx, publishInfo, requiredBytes, ResizeISCSIVolumeTimeout); err != nil {
771+
if err := p.iscsi.ExpandVolume(ctx, publishInfo, requiredBytes); err != nil {
772772
Logc(ctx).WithFields(LogFields{
773773
"lunID": publishInfo.IscsiLunNumber,
774774
"devicePath": publishInfo.DevicePath,

frontend/csi/node_server_test.go

Lines changed: 19 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2025 NetApp, Inc. All Rights Reserved.
1+
// Copyright 2026 NetApp, Inc. All Rights Reserved.
22

33
package csi
44

@@ -36,7 +36,7 @@ import (
3636
mockcore "github.com/netapp/trident/mocks/mock_core"
3737
mockControllerAPI "github.com/netapp/trident/mocks/mock_frontend/mock_csi/mock_controller_api"
3838
mockNodeHelpers "github.com/netapp/trident/mocks/mock_frontend/mock_csi/mock_node_helpers"
39-
mock_blockdevice "github.com/netapp/trident/mocks/mock_utils/mock_blockdevice"
39+
"github.com/netapp/trident/mocks/mock_utils/mock_blockdevice"
4040
"github.com/netapp/trident/mocks/mock_utils/mock_devices"
4141
"github.com/netapp/trident/mocks/mock_utils/mock_filesystem"
4242
"github.com/netapp/trident/mocks/mock_utils/mock_iscsi"
@@ -79,9 +79,7 @@ func TestNodeStageVolume(t *testing.T) {
7979
mockISCSIClient.EXPECT().EnsureVolumeFormattedAndMounted(
8080
gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(),
8181
)
82-
mockISCSIClient.EXPECT().ResizeVolumeRetry(
83-
gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(),
84-
).Return(nil)
82+
mockISCSIClient.EXPECT().ExpandVolume(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil)
8583
mockISCSIClient.EXPECT().AddSession(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any())
8684
return mockISCSIClient
8785
},
@@ -260,9 +258,7 @@ func TestNodeStageISCSIVolume(t *testing.T) {
260258
mockISCSIClient.EXPECT().EnsureVolumeFormattedAndMounted(
261259
gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(),
262260
)
263-
mockISCSIClient.EXPECT().ResizeVolumeRetry(
264-
gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(),
265-
).Return(nil)
261+
mockISCSIClient.EXPECT().ExpandVolume(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil)
266262
mockISCSIClient.EXPECT().AddSession(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any())
267263
return mockISCSIClient
268264
},
@@ -547,8 +543,8 @@ func TestNodeStageISCSIVolume(t *testing.T) {
547543
gomock.Any(), gomock.Any(), gomock.Any(),
548544
).Return(int64(1), nil)
549545
mockISCSIClient.EXPECT().EnsureVolumeFormattedAndMounted(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any())
550-
mockISCSIClient.EXPECT().ResizeVolumeRetry(
551-
gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(),
546+
mockISCSIClient.EXPECT().ExpandVolume(
547+
gomock.Any(), gomock.Any(), gomock.Any(),
552548
).Return(errors.New("volume not attached"))
553549
mockISCSIClient.EXPECT().AddSession(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any())
554550
return mockISCSIClient
@@ -570,8 +566,8 @@ func TestNodeStageISCSIVolume(t *testing.T) {
570566
mockISCSIClient.EXPECT().EnsureVolumeFormattedAndMounted(
571567
gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(),
572568
)
573-
mockISCSIClient.EXPECT().ResizeVolumeRetry(
574-
gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(),
569+
mockISCSIClient.EXPECT().ExpandVolume(
570+
gomock.Any(), gomock.Any(), gomock.Any(),
575571
).Return(errors.New("volume resize failed"))
576572
mockISCSIClient.EXPECT().AddSession(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any())
577573
return mockISCSIClient
@@ -593,9 +589,7 @@ func TestNodeStageISCSIVolume(t *testing.T) {
593589
mockISCSIClient.EXPECT().EnsureVolumeFormattedAndMounted(
594590
gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(),
595591
)
596-
mockISCSIClient.EXPECT().ResizeVolumeRetry(
597-
gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(),
598-
).Return(nil)
592+
mockISCSIClient.EXPECT().ExpandVolume(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil)
599593
mockISCSIClient.EXPECT().AddSession(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any())
600594
return mockISCSIClient
601595
},
@@ -2769,7 +2763,7 @@ func TestNodeStageVolume_Multithreaded(t *testing.T) {
27692763
gomock.Any(), gomock.Any(), gomock.Any(),
27702764
).Return(int64(1), nil)
27712765
mockISCSIClient.EXPECT().EnsureVolumeFormattedAndMounted(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any())
2772-
mockISCSIClient.EXPECT().ResizeVolumeRetry(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil)
2766+
mockISCSIClient.EXPECT().ExpandVolume(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil)
27732767
mockISCSIClient.EXPECT().AddSession(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any())
27742768
mockTrackingClient.EXPECT().WriteTrackingInfo(gomock.Any(), gomock.Any(), gomock.Any()).Times(2).Return(nil)
27752769
}
@@ -2925,7 +2919,7 @@ func TestNodeStageVolume_Multithreaded(t *testing.T) {
29252919
gomock.Any(), gomock.Any(), gomock.Any(),
29262920
).Return(int64(1), nil)
29272921
mockISCSIClient.EXPECT().EnsureVolumeFormattedAndMounted(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any())
2928-
mockISCSIClient.EXPECT().ResizeVolumeRetry(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil)
2922+
mockISCSIClient.EXPECT().ExpandVolume(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil)
29292923
mockISCSIClient.EXPECT().AddSession(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any())
29302924
mockTrackingClient.EXPECT().WriteTrackingInfo(gomock.Any(), gomock.Any(), gomock.Any()).Times(2).Return(nil)
29312925
}
@@ -13001,7 +12995,7 @@ func TestNodeExpandVolume(t *testing.T) {
1300112995
},
1300212996
setupISCSIMock: func() iscsi.ISCSI {
1300312997
mockISCSIClient := mock_iscsi.NewMockISCSI(gomock.NewController(t))
13004-
mockISCSIClient.EXPECT().ResizeVolumeRetry(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
12998+
mockISCSIClient.EXPECT().ExpandVolume(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
1300512999
return mockISCSIClient
1300613000
},
1300713001
mockFilesystem: func() filesystem.Filesystem {
@@ -13041,7 +13035,7 @@ func TestNodeExpandVolume(t *testing.T) {
1304113035
},
1304213036
setupISCSIMock: func() iscsi.ISCSI {
1304313037
mockISCSIClient := mock_iscsi.NewMockISCSI(gomock.NewController(t))
13044-
mockISCSIClient.EXPECT().ResizeVolumeRetry(gomock.Any(), gomock.Any(), gomock.Any(),
13038+
mockISCSIClient.EXPECT().ExpandVolume(gomock.Any(), gomock.Any(),
1304513039
gomock.Any()).Return(errors.New("failure")).AnyTimes()
1304613040
return mockISCSIClient
1304713041
},
@@ -13076,7 +13070,7 @@ func TestNodeExpandVolume(t *testing.T) {
1307613070
},
1307713071
setupISCSIMock: func() iscsi.ISCSI {
1307813072
mockISCSIClient := mock_iscsi.NewMockISCSI(gomock.NewController(t))
13079-
mockISCSIClient.EXPECT().ResizeVolumeRetry(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
13073+
mockISCSIClient.EXPECT().ExpandVolume(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
1308013074
return mockISCSIClient
1308113075
},
1308213076

@@ -13110,7 +13104,7 @@ func TestNodeExpandVolume(t *testing.T) {
1311013104
},
1311113105
setupISCSIMock: func() iscsi.ISCSI {
1311213106
mockISCSIClient := mock_iscsi.NewMockISCSI(gomock.NewController(t))
13113-
mockISCSIClient.EXPECT().ResizeVolumeRetry(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
13107+
mockISCSIClient.EXPECT().ExpandVolume(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
1311413108
return mockISCSIClient
1311513109
},
1311613110
mockFilesystem: func() filesystem.Filesystem {
@@ -13260,7 +13254,7 @@ func TestNodeExpandVolume(t *testing.T) {
1326013254
},
1326113255
setupISCSIMock: func() iscsi.ISCSI {
1326213256
mockISCSIClient := mock_iscsi.NewMockISCSI(gomock.NewController(t))
13263-
mockISCSIClient.EXPECT().ResizeVolumeRetry(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
13257+
mockISCSIClient.EXPECT().ExpandVolume(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
1326413258
return mockISCSIClient
1326513259
},
1326613260
mockFilesystem: func() filesystem.Filesystem {
@@ -13307,7 +13301,7 @@ func TestNodeExpandVolume(t *testing.T) {
1330713301
},
1330813302
setupISCSIMock: func() iscsi.ISCSI {
1330913303
mockISCSIClient := mock_iscsi.NewMockISCSI(gomock.NewController(t))
13310-
mockISCSIClient.EXPECT().ResizeVolumeRetry(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
13304+
mockISCSIClient.EXPECT().ExpandVolume(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
1331113305
return mockISCSIClient
1331213306
},
1331313307
mockFilesystem: func() filesystem.Filesystem {
@@ -13354,7 +13348,7 @@ func TestNodeExpandVolume(t *testing.T) {
1335413348
},
1335513349
setupISCSIMock: func() iscsi.ISCSI {
1335613350
mockISCSIClient := mock_iscsi.NewMockISCSI(gomock.NewController(t))
13357-
mockISCSIClient.EXPECT().ResizeVolumeRetry(gomock.Any(), gomock.Any(), gomock.Any(),
13351+
mockISCSIClient.EXPECT().ExpandVolume(gomock.Any(), gomock.Any(),
1335813352
gomock.Any()).Return(nil).AnyTimes()
1335913353
return mockISCSIClient
1336013354
},
@@ -13402,7 +13396,7 @@ func TestNodeExpandVolume(t *testing.T) {
1340213396
},
1340313397
setupISCSIMock: func() iscsi.ISCSI {
1340413398
mockISCSIClient := mock_iscsi.NewMockISCSI(gomock.NewController(t))
13405-
mockISCSIClient.EXPECT().ResizeVolumeRetry(gomock.Any(), gomock.Any(), gomock.Any(),
13399+
mockISCSIClient.EXPECT().ExpandVolume(gomock.Any(), gomock.Any(),
1340613400
gomock.Any()).Return(errors.New("failure")).AnyTimes()
1340713401
return mockISCSIClient
1340813402
},

mocks/mock_storage_drivers/mock_gcp/mock_api.go

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

mocks/mock_utils/mock_devices/mock_devices_client.go

Lines changed: 14 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

mocks/mock_utils/mock_iscsi/mock_iscsi_client.go

Lines changed: 14 additions & 28 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/collection/list.go

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright 2025 NetApp, Inc. All Rights Reserved.
1+
// Copyright 2026 NetApp, Inc. All Rights Reserved.
22

33
package collection
44

@@ -142,3 +142,24 @@ func StringInSlice(s string, list []string) bool {
142142
}
143143
return false
144144
}
145+
146+
// EqualValues accepts 2 slices of any standard comparable type and returns whether their values are equivalent.
147+
func EqualValues[C comparable](s1 []C, s2 []C) bool {
148+
if len(s1) != len(s2) {
149+
return false
150+
}
151+
152+
elemOccurrences := make(map[any]int, len(s1))
153+
for _, elem := range s1 {
154+
elemOccurrences[elem] += 1
155+
}
156+
157+
for _, v := range s2 {
158+
elemOccurrences[v]--
159+
if elemOccurrences[v] < 0 {
160+
return false
161+
}
162+
}
163+
164+
return true
165+
}

pkg/collection/list_test.go

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,102 @@ func TestReplaceAtIndex(t *testing.T) {
392392
assert.Equal(t, "boo", actual)
393393
}
394394

395+
func TestEqualValues(t *testing.T) {
396+
tests := map[string]struct {
397+
s1 []string
398+
s2 []string
399+
equal bool
400+
}{
401+
"identical slices": {
402+
s1: []string{"a", "b", "c"},
403+
s2: []string{"a", "b", "c"},
404+
equal: true,
405+
},
406+
"same elements different order": {
407+
s1: []string{"c", "a", "b"},
408+
s2: []string{"a", "b", "c"},
409+
equal: true,
410+
},
411+
"both nil": {
412+
s1: nil,
413+
s2: nil,
414+
equal: true,
415+
},
416+
"both empty": {
417+
s1: []string{},
418+
s2: []string{},
419+
equal: true,
420+
},
421+
"nil and empty are equal": {
422+
s1: nil,
423+
s2: []string{},
424+
equal: true,
425+
},
426+
"empty and nil are equal": {
427+
s1: []string{},
428+
s2: nil,
429+
equal: true,
430+
},
431+
"different lengths": {
432+
s1: []string{"a", "b"},
433+
s2: []string{"a", "b", "c"},
434+
equal: false,
435+
},
436+
"same length different values": {
437+
s1: []string{"a", "b", "c"},
438+
s2: []string{"a", "b", "d"},
439+
equal: false,
440+
},
441+
"one nil one populated": {
442+
s1: nil,
443+
s2: []string{"a"},
444+
equal: false,
445+
},
446+
"one empty one populated": {
447+
s1: []string{},
448+
s2: []string{"a"},
449+
equal: false,
450+
},
451+
"duplicate elements equal cardinality": {
452+
s1: []string{"a", "a", "b"},
453+
s2: []string{"a", "b", "a"},
454+
equal: true,
455+
},
456+
"duplicate elements unequal cardinality": {
457+
s1: []string{"a", "a", "b"},
458+
s2: []string{"a", "b", "b"},
459+
equal: false,
460+
},
461+
"single element equal": {
462+
s1: []string{"x"},
463+
s2: []string{"x"},
464+
equal: true,
465+
},
466+
"single element not equal": {
467+
s1: []string{"x"},
468+
s2: []string{"y"},
469+
equal: false,
470+
},
471+
}
472+
473+
for name, tc := range tests {
474+
t.Run(name, func(t *testing.T) {
475+
assert.Equal(t, tc.equal, EqualValues(tc.s1, tc.s2))
476+
})
477+
}
478+
479+
// Also test with int type to verify generics work.
480+
t.Run("int slices same elements different order", func(t *testing.T) {
481+
assert.True(t, EqualValues([]int{3, 1, 2}, []int{1, 2, 3}))
482+
})
483+
t.Run("int slices different values", func(t *testing.T) {
484+
assert.False(t, EqualValues([]int{1, 2, 3}, []int{1, 2, 4}))
485+
})
486+
t.Run("int nil and empty are equal", func(t *testing.T) {
487+
assert.True(t, EqualValues[int](nil, []int{}))
488+
})
489+
}
490+
395491
func TestAppendToStringList(t *testing.T) {
396492
tests := []struct {
397493
stringList string

0 commit comments

Comments
 (0)