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