@@ -25,6 +25,7 @@ import (
2525 "strconv"
2626
2727 "github.com/container-storage-interface/spec/lib/go/csi"
28+ "github.com/ghodss/yaml"
2829 "google.golang.org/grpc/codes"
2930 "google.golang.org/grpc/status"
3031
@@ -166,7 +167,7 @@ func (cs *Server) CreateVolume(
166167 }
167168 }()
168169
169- nvmeofData , err = cs .createNVMeoFResources (ctx , req , rbdPoolName , rbdRadosNameSpace , rbdImageName )
170+ nvmeofData , err = cs .createNVMeoFResources (ctx , req , rbdPoolName , rbdRadosNameSpace , rbdImageName , volumeID )
170171 if err != nil {
171172 log .ErrorLog (ctx , "NVMe-oF resource setup failed for volumeID %s: %v" , volumeID , err )
172173
@@ -352,11 +353,22 @@ func (cs *Server) ControllerModifyVolume(
352353
353354 return nil , status .Errorf (codes .InvalidArgument , "failed to parse QoS parameters: %v" , err )
354355 }
356+ hostsList , err := parseHostsParameters (params )
357+ if err != nil {
358+ log .ErrorLog (ctx , "failed to parse NVMe-oF hosts parameters: %v" , err )
359+
360+ return nil , status .Errorf (codes .InvalidArgument , "failed to parse hosts parameters: %v" , err )
361+ }
355362 if nvmeofQoS != nil {
356363 if err := cs .modifyNVMeoFQoS (ctx , req , nvmeofQoS ); err != nil {
357364 return nil , err
358365 }
359366 }
367+ if hostsList != nil {
368+ if err := cs .modifyNVMeoFHosts (ctx , req , hostsList ); err != nil {
369+ return nil , err
370+ }
371+ }
360372
361373 return & csi.ControllerModifyVolumeResponse {}, nil
362374}
@@ -466,6 +478,11 @@ func validateCreateVolumeRequest(req *csi.CreateVolumeRequest) error {
466478 if err != nil {
467479 return fmt .Errorf ("invalid NVMe-oF QoS parameters: %w" , err )
468480 }
481+
482+ _ , err = parseHostsParameters (mutableParams )
483+ if err != nil {
484+ return fmt .Errorf ("invalid NVMe-oF hosts parameters (for external clients): %w" , err )
485+ }
469486 err = validateDHCHAPParameter (params ["dhchapMode" ])
470487 if err != nil {
471488 return err
@@ -532,42 +549,22 @@ func validateNetworkMask(networkMask string) error {
532549 return nil
533550}
534551
535- // parseQoSParameters extracts and parses QoS parameters from the given map.
536- func parseQoSParameters (params map [string ]string ) (* nvmeof.NVMeoFQosVolume , error ) {
537- qos := & nvmeof.NVMeoFQosVolume {}
538- hasAnyQoS := false
539-
540- parseParam := func (key , name string , dest * * uint64 ) error {
541- if val , exists := params [key ]; exists && val != "" {
542- parsed , err := strconv .ParseUint (val , 10 , 64 )
543- if err != nil {
544- return fmt .Errorf ("invalid %s: %w" , name , err )
545- }
546- * dest = & parsed
547- hasAnyQoS = true
548- }
549-
550- return nil
551- }
552-
553- if err := parseParam (nvmeof .RwIosPerSecond , nvmeof .RwIosPerSecond , & qos .RwIosPerSecond ); err != nil {
554- return nil , err
555- }
556- if err := parseParam (nvmeof .RwMbytesPerSecond , nvmeof .RwMbytesPerSecond , & qos .RwMbytesPerSecond ); err != nil {
557- return nil , err
558- }
559- if err := parseParam (nvmeof .RMbytesPerSecond , nvmeof .RMbytesPerSecond , & qos .RMbytesPerSecond ); err != nil {
560- return nil , err
552+ // parseHostsParameters parses the hosts yaml list parameter and validates its contents.
553+ // It returns a slice of hostnames or an error if the YAML is invalid.
554+ func parseHostsParameters (params map [string ]string ) ([]string , error ) {
555+ allowHostNQNs , exists := params [AllowHostNQNs ]
556+ if ! exists || allowHostNQNs == "" {
557+ return nil , nil
561558 }
562- if err := parseParam (nvmeof .WMbytesPerSecond , nvmeof .WMbytesPerSecond , & qos .WMbytesPerSecond ); err != nil {
563- return nil , err
559+ var allowHostsList []string
560+ if err := yaml .Unmarshal ([]byte (allowHostNQNs ), & allowHostsList ); err != nil {
561+ return nil , fmt .Errorf ("invalid %s: must be a YAML list of strings: %w" , AllowHostNQNs , err )
564562 }
565-
566- if ! hasAnyQoS {
563+ if len (allowHostsList ) == 0 {
567564 return nil , nil
568565 }
569566
570- return qos , nil
567+ return allowHostsList , nil
571568}
572569
573570// withGatewayConnection is a helper that manages the common pattern of:
@@ -628,6 +625,74 @@ func (cs *Server) withGatewayConnection(
628625 return fn (ctx , gateway , nvmeofData )
629626}
630627
628+ // modifyNVMeoFHosts handles adding or removing hosts from the subsystem based on the provided list of host NQNs.
629+ func (cs * Server ) modifyNVMeoFHosts (ctx context.Context , req * csi.ControllerModifyVolumeRequest , hosts []string ) error {
630+ volumeID := req .GetVolumeId ()
631+ if len (hosts ) == 0 {
632+ log .DebugLog (ctx , "No hosts to add or remove for volume %s" , volumeID )
633+
634+ return nil
635+ }
636+
637+ return cs .withGatewayConnection (ctx , req , volumeID , func (
638+ ctx context.Context ,
639+ gateway * nvmeof.GatewayRpcClient ,
640+ nvmeofData * nvmeof.NVMeoFVolumeData ,
641+ ) error {
642+ log .DebugLog (ctx , "Modifying hosts for subsystem=%s, nsid=%d: desired hosts=%v" ,
643+ nvmeofData .SubsystemNQN , nvmeofData .NamespaceID , hosts )
644+
645+ err := gateway .UpdateHostsForSubsystem (ctx , nvmeofData .SubsystemNQN , hosts )
646+ if err != nil {
647+ log .ErrorLog (ctx , "Failed to update hosts for subsystem: %v" , err )
648+
649+ return status .Errorf (codes .Internal , "failed to update hosts for subsystem: %v" , err )
650+ }
651+
652+ log .DebugLog (ctx , "Successfully modified hosts for volume %s" , volumeID )
653+
654+ return nil
655+ })
656+ }
657+
658+ // parseQoSParameters extracts and parses QoS parameters from the given map.
659+ func parseQoSParameters (params map [string ]string ) (* nvmeof.NVMeoFQosVolume , error ) {
660+ qos := & nvmeof.NVMeoFQosVolume {}
661+ hasAnyQoS := false
662+
663+ parseParam := func (key , name string , dest * * uint64 ) error {
664+ if val , exists := params [key ]; exists && val != "" {
665+ parsed , err := strconv .ParseUint (val , 10 , 64 )
666+ if err != nil {
667+ return fmt .Errorf ("invalid %s: %w" , name , err )
668+ }
669+ * dest = & parsed
670+ hasAnyQoS = true
671+ }
672+
673+ return nil
674+ }
675+
676+ if err := parseParam (nvmeof .RwIosPerSecond , nvmeof .RwIosPerSecond , & qos .RwIosPerSecond ); err != nil {
677+ return nil , err
678+ }
679+ if err := parseParam (nvmeof .RwMbytesPerSecond , nvmeof .RwMbytesPerSecond , & qos .RwMbytesPerSecond ); err != nil {
680+ return nil , err
681+ }
682+ if err := parseParam (nvmeof .RMbytesPerSecond , nvmeof .RMbytesPerSecond , & qos .RMbytesPerSecond ); err != nil {
683+ return nil , err
684+ }
685+ if err := parseParam (nvmeof .WMbytesPerSecond , nvmeof .WMbytesPerSecond , & qos .WMbytesPerSecond ); err != nil {
686+ return nil , err
687+ }
688+
689+ if ! hasAnyQoS {
690+ return nil , nil
691+ }
692+
693+ return qos , nil
694+ }
695+
631696// modifyNVMeoFQoS handles NVMe-oF gateway QoS modification.
632697func (cs * Server ) modifyNVMeoFQoS (
633698 ctx context.Context ,
@@ -759,15 +824,16 @@ func (cs *Server) createNVMeoFResources(
759824 req * csi.CreateVolumeRequest ,
760825 rbdPoolName ,
761826 rbdRadosNameSpace ,
762- rbdImageName string ,
827+ rbdImageName ,
828+ volumeID string ,
763829) (* nvmeof.NVMeoFVolumeData , error ) {
764830 // Step 1: Extract parameters (already validated)
765831 params := req .GetParameters ()
766832
767833 networkMask := params ["networkMask" ]
768834 nvmeofData := & nvmeof.NVMeoFVolumeData {}
769835
770- if err := nvmeofData .SetFromParameters (params ); err != nil {
836+ if err := nvmeofData .SetFromParameters (params , volumeID ); err != nil {
771837 return nil , fmt .Errorf ("failed to set NVMe-oF volume data: %w" , err )
772838 }
773839
@@ -781,7 +847,14 @@ func (cs *Server) createNVMeoFResources(
781847
782848 return nil , fmt .Errorf ("failed to parse QoS parameters: %w" , err )
783849 }
850+ // If VAC with hosts list is given (for external client)
851+ // We need to parse the hosts list and pass it to the gateway for creating host entries and adding them to the subsystem.
852+ hosts , err := parseHostsParameters (mutableParams )
853+ if err != nil {
854+ log .ErrorLog (ctx , "failed to parse NVMe-oF hosts parameters: %v" , err )
784855
856+ return nil , fmt .Errorf ("failed to parse hosts parameters: %w" , err )
857+ }
785858 // Step 2: Connect to gateway
786859 config , err := getGatewayConfigFromRequest (params )
787860 if err != nil {
@@ -829,7 +902,16 @@ func (cs *Server) createNVMeoFResources(
829902 return nvmeofData , fmt .Errorf ("setting QoS limits failed: %w" , err )
830903 }
831904 }
832-
905+ if hosts != nil {
906+ log .DebugLog (ctx , "Adding hosts to subsystem: %v" , hosts )
907+ for _ , host := range hosts {
908+ // TODO - for now we create host with empty DH-CHAP keys,
909+ // in the future we can extend the VAC parameters to allow passing DH-CHAP keys for each host if needed??
910+ if err := gateway .AddHost (ctx , nvmeofData .SubsystemNQN , host , nvmeof.DHCHAPKeys {}); err != nil {
911+ return nvmeofData , fmt .Errorf ("adding host %s to subsystem failed: %w" , host , err )
912+ }
913+ }
914+ }
833915 // Step 6: If using auto-listeners, query them back for storing in metadata
834916 if networkMask != "" {
835917 autoListeners , err := gateway .ListListeners (ctx , nvmeofData .SubsystemNQN )
@@ -1029,6 +1111,15 @@ func getHostNQNFromNodeID(nodeID string) (string, error) {
10291111 return prefix + nodeID , nil
10301112}
10311113
1114+ // AllowHostNQNs is the VolumeAttributesClass mutable parameter key for specifying
1115+ // a YAML list of host NQNs to allow access to a volume. Use "*" to allow any host.
1116+ // Example:
1117+ //
1118+ // allowHostNQNs: |
1119+ // - nqn.2014-08.org.nvmexpress:host1
1120+ // - nqn.2014-08.org.nvmexpress:host2
1121+ const AllowHostNQNs = "allowHostNQNs"
1122+
10321123// VolumeContext metadata keys.
10331124const (
10341125 // NVMe-oF resource info.
0 commit comments