@@ -26,6 +26,7 @@ import (
2626 "strconv"
2727
2828 "github.com/container-storage-interface/spec/lib/go/csi"
29+ "github.com/ghodss/yaml"
2930 "google.golang.org/grpc/codes"
3031 "google.golang.org/grpc/status"
3132
@@ -167,7 +168,7 @@ func (cs *Server) CreateVolume(
167168 }
168169 }()
169170
170- nvmeofData , err = cs .createNVMeoFResources (ctx , req , rbdPoolName , rbdRadosNameSpace , rbdImageName )
171+ nvmeofData , err = cs .createNVMeoFResources (ctx , req , rbdPoolName , rbdRadosNameSpace , rbdImageName , volumeID )
171172 if err != nil {
172173 log .ErrorLog (ctx , "NVMe-oF resource setup failed for volumeID %s: %v" , volumeID , err )
173174
@@ -353,11 +354,22 @@ func (cs *Server) ControllerModifyVolume(
353354
354355 return nil , status .Errorf (codes .InvalidArgument , "failed to parse QoS parameters: %v" , err )
355356 }
357+ hostsList , err := parseHostsParameters (params )
358+ if err != nil {
359+ log .ErrorLog (ctx , "failed to parse NVMe-oF hosts parameters: %v" , err )
360+
361+ return nil , status .Errorf (codes .InvalidArgument , "failed to parse hosts parameters: %v" , err )
362+ }
356363 if nvmeofQoS != nil {
357364 if err := cs .modifyNVMeoFQoS (ctx , req , nvmeofQoS ); err != nil {
358365 return nil , err
359366 }
360367 }
368+ if hostsList != nil {
369+ if err := cs .modifyNVMeoFHosts (ctx , req , hostsList ); err != nil {
370+ return nil , err
371+ }
372+ }
361373
362374 return & csi.ControllerModifyVolumeResponse {}, nil
363375}
@@ -425,14 +437,10 @@ func validateDHCHAPParameter(dhchapMode string) error {
425437func validateCreateVolumeRequest (req * csi.CreateVolumeRequest ) error {
426438 // Validate required parameters
427439 params := req .GetParameters ()
428- requiredParams := []string {
429- "subsystemNQN" , "nvmeofGatewayAddress" ,
430- }
431- for _ , param := range requiredParams {
432- if params [param ] == "" {
433- return fmt .Errorf ("missing required parameter: %s" , param )
434- }
440+ if params ["nvmeofGatewayAddress" ] == "" {
441+ return errors .New ("missing required parameter nvmeofGatewayAddress" )
435442 }
443+
436444 // Validate listeners JSON if provided
437445 countOfListeners , err := validateListenersParameter (params ["listeners" ])
438446 if err != nil {
@@ -467,6 +475,11 @@ func validateCreateVolumeRequest(req *csi.CreateVolumeRequest) error {
467475 if err != nil {
468476 return fmt .Errorf ("invalid NVMe-oF QoS parameters: %w" , err )
469477 }
478+
479+ _ , err = parseHostsParameters (mutableParams )
480+ if err != nil {
481+ return fmt .Errorf ("invalid NVMe-oF hosts parameters (for external clients): %w" , err )
482+ }
470483 err = validateDHCHAPParameter (params ["dhchapMode" ])
471484 if err != nil {
472485 return err
@@ -533,42 +546,24 @@ func validateNetworkMask(networkMask string) error {
533546 return nil
534547}
535548
536- // parseQoSParameters extracts and parses QoS parameters from the given map.
537- func parseQoSParameters (params map [string ]string ) (* nvmeof.NVMeoFQosVolume , error ) {
538- qos := & nvmeof.NVMeoFQosVolume {}
539- hasAnyQoS := false
540-
541- parseParam := func (key , name string , dest * * uint64 ) error {
542- if val , exists := params [key ]; exists && val != "" {
543- parsed , err := strconv .ParseUint (val , 10 , 64 )
544- if err != nil {
545- return fmt .Errorf ("invalid %s: %w" , name , err )
546- }
547- * dest = & parsed
548- hasAnyQoS = true
549- }
550-
551- return nil
552- }
553-
554- if err := parseParam (nvmeof .RwIosPerSecond , nvmeof .RwIosPerSecond , & qos .RwIosPerSecond ); err != nil {
555- return nil , err
556- }
557- if err := parseParam (nvmeof .RwMbytesPerSecond , nvmeof .RwMbytesPerSecond , & qos .RwMbytesPerSecond ); err != nil {
558- return nil , err
559- }
560- if err := parseParam (nvmeof .RMbytesPerSecond , nvmeof .RMbytesPerSecond , & qos .RMbytesPerSecond ); err != nil {
561- return nil , err
549+ // parseHostsParameters parses the hosts yaml list parameter and validates its contents.
550+ // It returns a slice of hostNQNs or an error if the YAML is invalid.
551+ // allowHostNQNs entry can be empty, in that case it means no hosts are allowed to access
552+ // the subsystem (empty allow list).
553+ func parseHostsParameters (params map [string ]string ) ([]string , error ) {
554+ allowHostNQNs , exists := params [AllowHostNQNs ]
555+ if ! exists {
556+ return nil , nil
562557 }
563- if err := parseParam (nvmeof .WMbytesPerSecond , nvmeof .WMbytesPerSecond , & qos .WMbytesPerSecond ); err != nil {
564- return nil , err
558+ var allowHostsList []string
559+ if allowHostNQNs == "" {
560+ return allowHostsList , nil
565561 }
566-
567- if ! hasAnyQoS {
568- return nil , nil
562+ if err := yaml .Unmarshal ([]byte (allowHostNQNs ), & allowHostsList ); err != nil {
563+ return nil , fmt .Errorf ("invalid %s: must be a YAML list of strings: %w" , AllowHostNQNs , err )
569564 }
570565
571- return qos , nil
566+ return allowHostsList , nil
572567}
573568
574569// withGatewayConnection is a helper that manages the common pattern of:
@@ -629,6 +624,69 @@ func (cs *Server) withGatewayConnection(
629624 return fn (ctx , gateway , nvmeofData )
630625}
631626
627+ // modifyNVMeoFHosts handles adding or removing hosts from the subsystem based on the provided list of host NQNs.
628+ func (cs * Server ) modifyNVMeoFHosts (ctx context.Context , req * csi.ControllerModifyVolumeRequest , hosts []string ) error {
629+ volumeID := req .GetVolumeId ()
630+
631+ return cs .withGatewayConnection (ctx , req , volumeID , func (
632+ ctx context.Context ,
633+ gateway * nvmeof.GatewayRpcClient ,
634+ nvmeofData * nvmeof.NVMeoFVolumeData ,
635+ ) error {
636+ log .DebugLog (ctx , "Modifying hosts for subsystem=%s, nsid=%d: desired hosts=%v" ,
637+ nvmeofData .SubsystemNQN , nvmeofData .NamespaceID , hosts )
638+
639+ err := gateway .UpdateHostsForSubsystem (ctx , nvmeofData .SubsystemNQN , hosts )
640+ if err != nil {
641+ log .ErrorLog (ctx , "Failed to update hosts for subsystem: %v" , err )
642+
643+ return status .Errorf (codes .Internal , "failed to update hosts for subsystem: %v" , err )
644+ }
645+
646+ log .DebugLog (ctx , "Successfully modified hosts for volume %s" , volumeID )
647+
648+ return nil
649+ })
650+ }
651+
652+ // parseQoSParameters extracts and parses QoS parameters from the given map.
653+ func parseQoSParameters (params map [string ]string ) (* nvmeof.NVMeoFQosVolume , error ) {
654+ qos := & nvmeof.NVMeoFQosVolume {}
655+ hasAnyQoS := false
656+
657+ parseParam := func (key , name string , dest * * uint64 ) error {
658+ if val , exists := params [key ]; exists && val != "" {
659+ parsed , err := strconv .ParseUint (val , 10 , 64 )
660+ if err != nil {
661+ return fmt .Errorf ("invalid %s: %w" , name , err )
662+ }
663+ * dest = & parsed
664+ hasAnyQoS = true
665+ }
666+
667+ return nil
668+ }
669+
670+ if err := parseParam (nvmeof .RwIosPerSecond , nvmeof .RwIosPerSecond , & qos .RwIosPerSecond ); err != nil {
671+ return nil , err
672+ }
673+ if err := parseParam (nvmeof .RwMbytesPerSecond , nvmeof .RwMbytesPerSecond , & qos .RwMbytesPerSecond ); err != nil {
674+ return nil , err
675+ }
676+ if err := parseParam (nvmeof .RMbytesPerSecond , nvmeof .RMbytesPerSecond , & qos .RMbytesPerSecond ); err != nil {
677+ return nil , err
678+ }
679+ if err := parseParam (nvmeof .WMbytesPerSecond , nvmeof .WMbytesPerSecond , & qos .WMbytesPerSecond ); err != nil {
680+ return nil , err
681+ }
682+
683+ if ! hasAnyQoS {
684+ return nil , nil
685+ }
686+
687+ return qos , nil
688+ }
689+
632690// modifyNVMeoFQoS handles NVMe-oF gateway QoS modification.
633691func (cs * Server ) modifyNVMeoFQoS (
634692 ctx context.Context ,
@@ -760,15 +818,16 @@ func (cs *Server) createNVMeoFResources(
760818 req * csi.CreateVolumeRequest ,
761819 rbdPoolName ,
762820 rbdRadosNameSpace ,
763- rbdImageName string ,
821+ rbdImageName ,
822+ volumeID string ,
764823) (* nvmeof.NVMeoFVolumeData , error ) {
765824 // Step 1: Extract parameters (already validated)
766825 params := req .GetParameters ()
767826
768827 networkMask := params ["networkMask" ]
769828 nvmeofData := & nvmeof.NVMeoFVolumeData {}
770829
771- if err := nvmeofData .SetFromParameters (params ); err != nil {
830+ if err := nvmeofData .SetFromParameters (params , volumeID ); err != nil {
772831 return nil , fmt .Errorf ("failed to set NVMe-oF volume data: %w" , err )
773832 }
774833
@@ -782,7 +841,15 @@ func (cs *Server) createNVMeoFResources(
782841
783842 return nil , fmt .Errorf ("failed to parse QoS parameters: %w" , err )
784843 }
844+ // If VAC with hosts list is given (for external client)
845+ // We need to parse the hosts list and pass it to the gateway for creating host entries
846+ // and adding them to the subsystem.
847+ hosts , err := parseHostsParameters (mutableParams )
848+ if err != nil {
849+ log .ErrorLog (ctx , "failed to parse NVMe-oF hosts parameters: %v" , err )
785850
851+ return nil , fmt .Errorf ("failed to parse hosts parameters: %w" , err )
852+ }
786853 // Step 2: Connect to gateway
787854 config , err := getGatewayConfigFromRequest (params )
788855 if err != nil {
@@ -830,7 +897,16 @@ func (cs *Server) createNVMeoFResources(
830897 return nvmeofData , fmt .Errorf ("setting QoS limits failed: %w" , err )
831898 }
832899 }
833-
900+ if hosts != nil {
901+ log .DebugLog (ctx , "Adding hosts to subsystem: %v" , hosts )
902+ for _ , host := range hosts {
903+ // TODO - for now we create host with empty DH-CHAP keys,
904+ // in the future we can extend the VAC parameters to allow passing DH-CHAP keys for each host if needed??
905+ if err := gateway .AddHost (ctx , nvmeofData .SubsystemNQN , host , nvmeof.DHCHAPKeys {}); err != nil {
906+ return nvmeofData , fmt .Errorf ("adding host %s to subsystem failed: %w" , host , err )
907+ }
908+ }
909+ }
834910 // Step 6: If using auto-listeners, query them back for storing in metadata
835911 if networkMask != "" {
836912 autoListeners , err := gateway .ListListeners (ctx , nvmeofData .SubsystemNQN )
@@ -1030,6 +1106,15 @@ func getHostNQNFromNodeID(nodeID string) (string, error) {
10301106 return prefix + nodeID , nil
10311107}
10321108
1109+ // AllowHostNQNs is the VolumeAttributesClass mutable parameter key for specifying
1110+ // a YAML list of host NQNs to allow access to a volume. Use "*" to allow any host.
1111+ // Example:
1112+ //
1113+ // allowHostNQNs: |
1114+ // - nqn.2014-08.org.nvmexpress:host1
1115+ // - nqn.2014-08.org.nvmexpress:host2
1116+ const AllowHostNQNs = "allowHostNQNs"
1117+
10331118// VolumeContext metadata keys.
10341119const (
10351120 // NVMe-oF resource info.
0 commit comments