@@ -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,24 @@ 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 hostNQNs or an error if the YAML is invalid.
554+ // allowHostNQNs entry can be empty, in that case it means no hosts are allowed to access
555+ // the subsystem (empty allow list).
556+ func parseHostsParameters (params map [string ]string ) ([]string , error ) {
557+ allowHostNQNs , exists := params [AllowHostNQNs ]
558+ if ! exists {
559+ return nil , nil
561560 }
562- if err := parseParam (nvmeof .WMbytesPerSecond , nvmeof .WMbytesPerSecond , & qos .WMbytesPerSecond ); err != nil {
563- return nil , err
561+ var allowHostsList []string
562+ if allowHostNQNs == "" {
563+ return allowHostsList , nil
564564 }
565-
566- if ! hasAnyQoS {
567- return nil , nil
565+ if err := yaml .Unmarshal ([]byte (allowHostNQNs ), & allowHostsList ); err != nil {
566+ return nil , fmt .Errorf ("invalid %s: must be a YAML list of strings: %w" , AllowHostNQNs , err )
568567 }
569568
570- return qos , nil
569+ return allowHostsList , nil
571570}
572571
573572// withGatewayConnection is a helper that manages the common pattern of:
@@ -628,6 +627,74 @@ func (cs *Server) withGatewayConnection(
628627 return fn (ctx , gateway , nvmeofData )
629628}
630629
630+ // modifyNVMeoFHosts handles adding or removing hosts from the subsystem based on the provided list of host NQNs.
631+ func (cs * Server ) modifyNVMeoFHosts (ctx context.Context , req * csi.ControllerModifyVolumeRequest , hosts []string ) error {
632+ volumeID := req .GetVolumeId ()
633+ if len (hosts ) == 0 {
634+ log .DebugLog (ctx , "No hosts to add or remove for volume %s" , volumeID )
635+
636+ return nil
637+ }
638+
639+ return cs .withGatewayConnection (ctx , req , volumeID , func (
640+ ctx context.Context ,
641+ gateway * nvmeof.GatewayRpcClient ,
642+ nvmeofData * nvmeof.NVMeoFVolumeData ,
643+ ) error {
644+ log .DebugLog (ctx , "Modifying hosts for subsystem=%s, nsid=%d: desired hosts=%v" ,
645+ nvmeofData .SubsystemNQN , nvmeofData .NamespaceID , hosts )
646+
647+ err := gateway .UpdateHostsForSubsystem (ctx , nvmeofData .SubsystemNQN , hosts )
648+ if err != nil {
649+ log .ErrorLog (ctx , "Failed to update hosts for subsystem: %v" , err )
650+
651+ return status .Errorf (codes .Internal , "failed to update hosts for subsystem: %v" , err )
652+ }
653+
654+ log .DebugLog (ctx , "Successfully modified hosts for volume %s" , volumeID )
655+
656+ return nil
657+ })
658+ }
659+
660+ // parseQoSParameters extracts and parses QoS parameters from the given map.
661+ func parseQoSParameters (params map [string ]string ) (* nvmeof.NVMeoFQosVolume , error ) {
662+ qos := & nvmeof.NVMeoFQosVolume {}
663+ hasAnyQoS := false
664+
665+ parseParam := func (key , name string , dest * * uint64 ) error {
666+ if val , exists := params [key ]; exists && val != "" {
667+ parsed , err := strconv .ParseUint (val , 10 , 64 )
668+ if err != nil {
669+ return fmt .Errorf ("invalid %s: %w" , name , err )
670+ }
671+ * dest = & parsed
672+ hasAnyQoS = true
673+ }
674+
675+ return nil
676+ }
677+
678+ if err := parseParam (nvmeof .RwIosPerSecond , nvmeof .RwIosPerSecond , & qos .RwIosPerSecond ); err != nil {
679+ return nil , err
680+ }
681+ if err := parseParam (nvmeof .RwMbytesPerSecond , nvmeof .RwMbytesPerSecond , & qos .RwMbytesPerSecond ); err != nil {
682+ return nil , err
683+ }
684+ if err := parseParam (nvmeof .RMbytesPerSecond , nvmeof .RMbytesPerSecond , & qos .RMbytesPerSecond ); err != nil {
685+ return nil , err
686+ }
687+ if err := parseParam (nvmeof .WMbytesPerSecond , nvmeof .WMbytesPerSecond , & qos .WMbytesPerSecond ); err != nil {
688+ return nil , err
689+ }
690+
691+ if ! hasAnyQoS {
692+ return nil , nil
693+ }
694+
695+ return qos , nil
696+ }
697+
631698// modifyNVMeoFQoS handles NVMe-oF gateway QoS modification.
632699func (cs * Server ) modifyNVMeoFQoS (
633700 ctx context.Context ,
@@ -759,15 +826,16 @@ func (cs *Server) createNVMeoFResources(
759826 req * csi.CreateVolumeRequest ,
760827 rbdPoolName ,
761828 rbdRadosNameSpace ,
762- rbdImageName string ,
829+ rbdImageName ,
830+ volumeID string ,
763831) (* nvmeof.NVMeoFVolumeData , error ) {
764832 // Step 1: Extract parameters (already validated)
765833 params := req .GetParameters ()
766834
767835 networkMask := params ["networkMask" ]
768836 nvmeofData := & nvmeof.NVMeoFVolumeData {}
769837
770- if err := nvmeofData .SetFromParameters (params ); err != nil {
838+ if err := nvmeofData .SetFromParameters (params , volumeID ); err != nil {
771839 return nil , fmt .Errorf ("failed to set NVMe-oF volume data: %w" , err )
772840 }
773841
@@ -781,7 +849,15 @@ func (cs *Server) createNVMeoFResources(
781849
782850 return nil , fmt .Errorf ("failed to parse QoS parameters: %w" , err )
783851 }
852+ // If VAC with hosts list is given (for external client)
853+ // We need to parse the hosts list and pass it to the gateway for creating host entries
854+ // and adding them to the subsystem.
855+ hosts , err := parseHostsParameters (mutableParams )
856+ if err != nil {
857+ log .ErrorLog (ctx , "failed to parse NVMe-oF hosts parameters: %v" , err )
784858
859+ return nil , fmt .Errorf ("failed to parse hosts parameters: %w" , err )
860+ }
785861 // Step 2: Connect to gateway
786862 config , err := getGatewayConfigFromRequest (params )
787863 if err != nil {
@@ -829,7 +905,16 @@ func (cs *Server) createNVMeoFResources(
829905 return nvmeofData , fmt .Errorf ("setting QoS limits failed: %w" , err )
830906 }
831907 }
832-
908+ if hosts != nil {
909+ log .DebugLog (ctx , "Adding hosts to subsystem: %v" , hosts )
910+ for _ , host := range hosts {
911+ // TODO - for now we create host with empty DH-CHAP keys,
912+ // in the future we can extend the VAC parameters to allow passing DH-CHAP keys for each host if needed??
913+ if err := gateway .AddHost (ctx , nvmeofData .SubsystemNQN , host , nvmeof.DHCHAPKeys {}); err != nil {
914+ return nvmeofData , fmt .Errorf ("adding host %s to subsystem failed: %w" , host , err )
915+ }
916+ }
917+ }
833918 // Step 6: If using auto-listeners, query them back for storing in metadata
834919 if networkMask != "" {
835920 autoListeners , err := gateway .ListListeners (ctx , nvmeofData .SubsystemNQN )
@@ -1029,6 +1114,15 @@ func getHostNQNFromNodeID(nodeID string) (string, error) {
10291114 return prefix + nodeID , nil
10301115}
10311116
1117+ // AllowHostNQNs is the VolumeAttributesClass mutable parameter key for specifying
1118+ // a YAML list of host NQNs to allow access to a volume. Use "*" to allow any host.
1119+ // Example:
1120+ //
1121+ // allowHostNQNs: |
1122+ // - nqn.2014-08.org.nvmexpress:host1
1123+ // - nqn.2014-08.org.nvmexpress:host2
1124+ const AllowHostNQNs = "allowHostNQNs"
1125+
10321126// VolumeContext metadata keys.
10331127const (
10341128 // NVMe-oF resource info.
0 commit comments