@@ -12746,6 +12746,78 @@ func TestPublishVolumeConcurrentCore_PublicationAlreadyExists(t *testing.T) {
1274612746 assert.NoError(t, err, "PublishVolume should succeed even when publication already exists")
1274712747}
1274812748
12749+ // TestPublishVolume_AccessModeClobbered_ConcurrentCore documents and verifies the bug where
12750+ // publishInfo.VolumeAccessInfo = volume.Config.AccessInfo overwrites the CSI-provided AccessMode
12751+ // and ReadOnly fields because VolumeAccessInfo is embedded in VolumePublishInfo.
12752+ // The concurrent core creates the VolumePublication AFTER this overwrite (unlike the orchestrator
12753+ // core which creates it before), so the VP ends up with zeroed AccessMode and ReadOnly.
12754+ // The test also verifies that Labels are correctly set on the VolumePublication.
12755+ func TestPublishVolume_AccessModeClobbered_ConcurrentCore(t *testing.T) {
12756+ config.CurrentDriverContext = config.ContextCSI
12757+ mockCtrl := gomock.NewController(t)
12758+ defer mockCtrl.Finish()
12759+ db.Initialize()
12760+
12761+ mockStoreClient := mockpersistentstore.NewMockStoreClient(mockCtrl)
12762+
12763+ o := getConcurrentOrchestrator()
12764+ o.storeClient = mockStoreClient
12765+
12766+ driver := mockstorage.NewMockDriver(mockCtrl)
12767+ driver.EXPECT().Name().Return(config.FakeStorageDriverName).AnyTimes()
12768+ driver.EXPECT().GetStorageBackendSpecs(gomock.Any(), gomock.Any()).Return(nil)
12769+ driver.EXPECT().CreateFollowup(gomock.Any(), gomock.Any()).Return(nil)
12770+ driver.EXPECT().Publish(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil)
12771+
12772+ fakeNode := getFakeNode("testNode")
12773+ addNodesToCache(t, fakeNode)
12774+ fakeBackend := getFakeBackend("testBackend", "uuid", driver)
12775+ addBackendsToCache(t, fakeBackend)
12776+
12777+ // Volume with AccessInfo that has ZERO AccessMode and ReadOnly=false (the default).
12778+ // This simulates a real NAS volume where AccessInfo.AccessMode is never explicitly set.
12779+ fakeVolume := getFakeVolume("testVolume", "uuid")
12780+ fakeVolume.Config.AccessInfo = models.VolumeAccessInfo{
12781+ NfsAccessInfo: models.NfsAccessInfo{NfsServerIP: "10.0.0.1", NfsPath: "/vol1"},
12782+ }
12783+ addVolumesToCache(t, fakeVolume)
12784+
12785+ // Capture the VolumePublication passed to AddVolumePublication
12786+ var capturedVP *models.VolumePublication
12787+ mockStoreClient.EXPECT().AddVolumePublication(gomock.Any(), gomock.Any()).DoAndReturn(
12788+ func(_ context.Context, vp *models.VolumePublication) error {
12789+ capturedVP = vp
12790+ return nil
12791+ })
12792+ mockStoreClient.EXPECT().UpdateVolume(gomock.Any(), gomock.Any()).Return(nil)
12793+
12794+ // CSI request with non-zero AccessMode (SINGLE_NODE_MULTI_WRITER=7) and ReadOnly=true
12795+ const csiAccessMode int32 = 7
12796+ publishInfo := &models.VolumePublishInfo{
12797+ HostName: "testNode",
12798+ }
12799+ publishInfo.ReadOnly = true
12800+ publishInfo.AccessMode = csiAccessMode
12801+
12802+ err := o.PublishVolume(testCtx, "testVolume", publishInfo)
12803+ require.NoError(t, err)
12804+ require.NotNil(t, capturedVP, "AddVolumePublication should have been called")
12805+
12806+ // BUG: The VolumeAccessInfo overwrite at line 3745 clobbers these CSI-provided values.
12807+ // publishInfo.VolumeAccessInfo = volume.Config.AccessInfo replaces the entire embedded struct,
12808+ // zeroing AccessMode (from 7 to 0) and ReadOnly (from true to false).
12809+ assert.Equal(t, csiAccessMode, capturedVP.AccessMode,
12810+ "VP AccessMode should preserve CSI-provided value, not be clobbered by volume.Config.AccessInfo")
12811+ assert.True(t, capturedVP.ReadOnly,
12812+ "VP ReadOnly should preserve CSI-provided value, not be clobbered by volume.Config.AccessInfo")
12813+
12814+ // BUG: Labels are not set in the concurrent core, unlike the orchestrator core's
12815+ // generateVolumePublication helper which initializes Labels with TridentNodeNameLabel.
12816+ assert.NotNil(t, capturedVP.Labels, "VP should have Labels set")
12817+ assert.Equal(t, "testNode", capturedVP.Labels[config.TridentNodeNameLabel],
12818+ "VP Labels should include TridentNodeNameLabel")
12819+ }
12820+
1274912821func TestUnpublishVolumeConcurrentCore(t *testing.T) {
1275012822 tests := []struct {
1275112823 name string
0 commit comments