@@ -614,6 +614,91 @@ func TestNodeStageISCSIVolume(t *testing.T) {
614614 }
615615}
616616
617+ func TestGetChapInfoFromController (t * testing.T ) {
618+ type args struct {
619+ ctx context.Context
620+ volume string
621+ node string
622+ }
623+
624+ type data struct {
625+ chapInfo * models.IscsiChapInfo
626+ }
627+
628+ type assertions struct {
629+ Error assert.ErrorAssertionFunc
630+ Empty assert.ValueAssertionFunc
631+ Equal assert.ComparisonAssertionFunc
632+ }
633+
634+ tt := map [string ]struct {
635+ args args
636+ data data
637+ assert assertions
638+ mocks func (mockAPI * mockControllerAPI.MockTridentController , args args , data data )
639+ }{
640+ "Successfully gets CHAP credentials" : {
641+ args : args {
642+ ctx : context .Background (),
643+ volume : "foo" ,
644+ node : "bar" ,
645+ },
646+ data : data {
647+ chapInfo : & models.IscsiChapInfo {
648+ UseCHAP : true ,
649+ IscsiUsername : "user" ,
650+ IscsiInitiatorSecret : "pass" ,
651+ IscsiTargetUsername : "user2" ,
652+ IscsiTargetSecret : "pass2" ,
653+ },
654+ },
655+ assert : assertions {
656+ Error : assert .NoError ,
657+ Empty : assert .NotEmpty ,
658+ Equal : assert .Equal ,
659+ },
660+ mocks : func (mockAPI * mockControllerAPI.MockTridentController , args args , data data ) {
661+ mockAPI .EXPECT ().GetChap (args .ctx , args .volume , args .node ).Return (data .chapInfo , nil )
662+ },
663+ },
664+ "Fails to get CHAP credentials" : {
665+ args : args {
666+ ctx : context .Background (),
667+ volume : "foo" ,
668+ node : "bar" ,
669+ },
670+ data : data {
671+ chapInfo : nil ,
672+ },
673+ assert : assertions {
674+ Error : assert .Error ,
675+ Empty : assert .Empty ,
676+ Equal : assert .Equal ,
677+ },
678+ mocks : func (mockAPI * mockControllerAPI.MockTridentController , args args , data data ) {
679+ mockAPI .EXPECT ().GetChap (args .ctx , args .volume , args .node ).Return (data .chapInfo , errors .New ("api error" ))
680+ },
681+ },
682+ }
683+
684+ for name , test := range tt {
685+ t .Run (name , func (t * testing.T ) {
686+ mockCtrl := gomock .NewController (t )
687+ mockAPI := mockControllerAPI .NewMockTridentController (mockCtrl )
688+ test .mocks (mockAPI , test .args , test .data )
689+
690+ plugin := & Plugin {
691+ restClient : mockAPI ,
692+ }
693+
694+ info , err := plugin .getChapInfoFromController (test .args .ctx , test .args .volume , test .args .node )
695+ test .assert .Error (t , err )
696+ test .assert .Empty (t , info )
697+ test .assert .Equal (t , info , test .data .chapInfo )
698+ })
699+ }
700+ }
701+
617702func TestUpdateChapInfoFromController_Success (t * testing.T ) {
618703 testCtx := context .Background ()
619704 volumeName := "foo"
@@ -673,6 +758,129 @@ func TestUpdateChapInfoFromController_Error(t *testing.T) {
673758 assert .EqualValues (t , models.IscsiChapInfo {}, testPublishInfo .IscsiAccessInfo .IscsiChapInfo )
674759}
675760
761+ func TestUpdateChapInfoForSessions (t * testing.T ) {
762+ // Populate sessions with only the state that matters. The rest of the fields are not relevant for this test.
763+ publishedSessions := & models.ISCSISessions {}
764+ currentSessions := & models.ISCSISessions {}
765+
766+ // CHAP portals
767+ chapPortals := []string {"0.0.0.0" , "4.4.4.4" , "5.5.5.5" }
768+
769+ // Unique CHAP portal
770+ uniqueChapPortal := "9.9.9.9"
771+ uniqueIQN := "iqn.9999-99.com.netapp:target"
772+
773+ // non-CHAP portals
774+ nonChapPortals := []string {"1.1.1.1" , "2.2.2.2" , "3.3.3.3" }
775+
776+ sessionID := "0"
777+
778+ // Shared CHAP credentials
779+ sharedCHAPInfo := models.IscsiChapInfo {
780+ UseCHAP : true ,
781+ IscsiUsername : "user" ,
782+ IscsiInitiatorSecret : "pass" ,
783+ IscsiTargetUsername : "user2" ,
784+ IscsiTargetSecret : "pass2" ,
785+ }
786+
787+ // Add CHAP sessions to both maps.
788+ for _ , portal := range chapPortals {
789+ err := publishedSessions .AddPortal (portal , models.PortalInfo {
790+ ISCSITargetIQN : "iqn.2020-01.com.netapp:target" ,
791+ Credentials : sharedCHAPInfo ,
792+ })
793+ assert .NoError (t , err )
794+
795+ err = currentSessions .AddPortal (portal , models.PortalInfo {
796+ ISCSITargetIQN : "iqn.2020-01.com.netapp:target" ,
797+ SessionNumber : sessionID ,
798+ })
799+ assert .NoError (t , err )
800+ }
801+
802+ // Add a CHAP session with a unique IQN.
803+ err := publishedSessions .AddPortal (uniqueChapPortal , models.PortalInfo {
804+ // Use a different IQN here to prove we cache returned values from the REST API.
805+ ISCSITargetIQN : uniqueIQN ,
806+ Credentials : sharedCHAPInfo ,
807+ })
808+ assert .NoError (t , err )
809+ err = currentSessions .AddPortal (uniqueChapPortal , models.PortalInfo {
810+ ISCSITargetIQN : uniqueIQN ,
811+ SessionNumber : sessionID ,
812+ })
813+ assert .NoError (t , err )
814+
815+ // Add non-CHAP session
816+ for _ , portal := range nonChapPortals {
817+ err := publishedSessions .AddPortal (portal , models.PortalInfo {
818+ ISCSITargetIQN : "iqn.2020-01.com.netapp:target" ,
819+ Credentials : models.IscsiChapInfo {},
820+ })
821+ assert .NoError (t , err )
822+
823+ err = currentSessions .AddPortal (portal , models.PortalInfo {
824+ ISCSITargetIQN : "iqn.2020-01.com.netapp:target" ,
825+ Credentials : models.IscsiChapInfo {},
826+ SessionNumber : sessionID ,
827+ })
828+ assert .NoError (t , err )
829+ }
830+
831+ // Populate a single of LUN and volume in each session.
832+ volume := "foo"
833+ node := "bar"
834+ for _ , sessionData := range publishedSessions .Info {
835+ sessionData .LUNs .Info [0 ] = volume
836+ }
837+
838+ // Create a mock controller client that will return the expected CHAP info.
839+ mockCtrl := gomock .NewController (t )
840+ mockISCSI := mock_iscsi .NewMockISCSI (mockCtrl )
841+ mockClient := mockControllerAPI .NewMockTridentController (mockCtrl )
842+
843+ plugin := & Plugin {
844+ nodeName : node ,
845+ iscsi : mockISCSI ,
846+ restClient : mockClient ,
847+ }
848+
849+ // Expect calls on the mock client for all sessions that use CHAP.
850+ freshCHAPInfo := & models.IscsiChapInfo {
851+ UseCHAP : true ,
852+ IscsiUsername : "user2" ,
853+ IscsiInitiatorSecret : "pass2" ,
854+ IscsiTargetUsername : "user" ,
855+ IscsiTargetSecret : "pass" ,
856+ }
857+
858+ // Mock calls to the iSCSI client
859+ count := len (currentSessions .Info )
860+ mockISCSI .EXPECT ().IsSessionStale (gomock .Any (), sessionID ).Return (true ).Times (count )
861+
862+ // Mock API calls
863+ count = len (chapPortals ) - (len (chapPortals ) - 1 ) + 1 // Expect one more call for the unique CHAP portal.
864+ mockClient .EXPECT ().GetChap (
865+ gomock .Any (), volume , node ,
866+ ).Return (freshCHAPInfo , nil ).Times (count )
867+
868+ err = plugin .updateCHAPInfoForSessions (ctx , publishedSessions , currentSessions )
869+ assert .NoError (t , err )
870+
871+ // Verify that the CHAP info was updated in all sessions that use CHAP.
872+ for _ , portal := range chapPortals {
873+ chapInfoInSession := publishedSessions .Info [portal ].PortalInfo .Credentials
874+ assert .EqualValues (t , * freshCHAPInfo , chapInfoInSession )
875+ }
876+
877+ // Verify that the non-CHAP portals were not updated.
878+ for _ , portal := range nonChapPortals {
879+ chapInfoInSession := publishedSessions .Info [portal ].PortalInfo .Credentials
880+ assert .EqualValues (t , models.IscsiChapInfo {}, chapInfoInSession )
881+ }
882+ }
883+
676884type PortalAction struct {
677885 Portal string
678886 Action models.ISCSIAction
0 commit comments