nvmeof: Add external client support #6251
Conversation
ba8d811 to
9e8560e
Compare
9e8560e to
b22de20
Compare
| func parseHostsParameters(params map[string]string) ([]string, error) { | ||
| allowHostNQNs, exists := params[AllowHostNQNs] | ||
| if !exists || allowHostNQNs == "" { | ||
| return nil, nil |
There was a problem hiding this comment.
In case allowHostNQNs == "", no hosts should be allowed anymore? Probably it makes sense to return an empty []string instead of nil.
Note that this is different from not having the AllowHostNQNs parameter at all. If the parameter is not given, the allowed hosts should not be modified.
| if err := parseParam(nvmeof.RMbytesPerSecond, nvmeof.RMbytesPerSecond, &qos.RMbytesPerSecond); err != nil { | ||
| return nil, err | ||
| // parseHostsParameters parses the hosts yaml list parameter and validates its contents. | ||
| // It returns a slice of hostnames or an error if the YAML is invalid. |
|
|
||
| if !hasAnyQoS { | ||
| if len(allowHostsList) == 0 { | ||
| return nil, nil |
There was a problem hiding this comment.
same here, if all hosts are removed from the list, no host should still have access.
| } | ||
|
|
||
| // Convert to sets for easier comparison | ||
| currentHostSet := make(map[string]bool) |
There was a problem hiding this comment.
this feels a little complicated... why not
desiredHostsas function parameter, copy it tohostsToAdd- loop though
currentHostsand- if in
desiredHostsalready, remove it fromhostsToAdd - if not in
desiredHosts, add to tohostsToRemove
- if in
- remove all
hostsToRemove - add all
hostsToAdd
you can use the slices package to do the checking with slices.Contains(desiredHosts, hostNQN), for example.
| v.SubsystemNQN = parameters["subsystemNQN"] | ||
|
|
||
| if v.SubsystemNQN == "" { | ||
| v.SubsystemNQN = "nqn.2016-06.io.ceph:subsystem." + volumeID |
There was a problem hiding this comment.
the volume ID can be quite long, is there a limit on the name of the subsystemNQN?
There was a problem hiding this comment.
I tested it on our ceph cluster. and it can handle it.
I tried to test this entire feature on ODF, but I see some errors in ODF building . I will double check it there once the ODF Jenkins will be back to be stable.
There was a problem hiding this comment.
Ok. According to the CSI specification, a volume-id can be up to 128 characters. If that is acceptable for the subsystemnqn, all is good. Otherwise it may be create a uuid or some kind of hash of the volume-id?
b22de20 to
d9bc798
Compare
|
This PR is not tested yet. There is an issue in ODF Jenkins. Once it will resolve I will test this PR. UPDATE: |
d9bc798 to
6f8aef8
Compare
| // | ||
| // allowHostNQNs: | | ||
| // - nqn.2014-08.org.nvmexpress:host1 | ||
| // - nqn.2014-08.org.nvmexpress:host2 |
There was a problem hiding this comment.
make sure this example is included in the StorageClass too
There was a problem hiding this comment.
I don't get it 🤔
the PVC may have VAC (with allowHostNQNs) , but we dont add this param into the StorageClass
Extracted common gateway connection logic into `withGatewayConnection()` helper to avoid code duplication between QoS and host management operations (in the next commit). The helper manages: - Secret retrieval - NVMe-oF metadata extraction from volume - Gateway connection with automatic cleanup - Operation execution via callback function Refactored modifyNVMeoFQoS to use the new helper. Signed-off-by: gadi-didi <gadi.didi@ibm.com>
6f8aef8 to
d8185d1
Compare
There was a problem hiding this comment.
Pull request overview
This PR extends the NVMe-oF controller path to better support external (non-Kubernetes) clients by making subsystem host access mutable via VolumeAttributesClass parameters and by auto-generating a subsystem NQN when one is not provided.
Changes:
- Auto-generate a default NVMe-oF subsystem NQN during CreateVolume when
subsystemNQNis not provided. - Add gateway-side host listing and host reconciliation (add/remove) to match a desired
allowHostNQNslist. - Extend
ControllerModifyVolume()to handle both QoS updates and host allow-list updates, using a shared gateway-connection helper.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| internal/nvmeof/volume.go | Makes subsystemNQN optional by generating a default based on volumeID. |
| internal/nvmeof/volume_test.go | Adds coverage for default subsystem NQN generation when missing. |
| internal/nvmeof/nvmeof.go | Adds ListHosts() and UpdateHostsForSubsystem() reconciliation logic. |
| internal/nvmeof/controller/controllerserver.go | Parses allowHostNQNs, wires host/QoS updates into ControllerModifyVolume, and passes volumeID for default NQN generation. |
Comments suppressed due to low confidence (1)
internal/nvmeof/controller/controllerserver.go:1121
- The comment for
AllowHostNQNsdocuments that users can set the value to "" to allow any host, butparseHostsParametersonly accepts a YAML list of strings and will fail to parse "". Either implement explicit support for the wildcard value (and define how it maps to gateway behavior), or update the documentation/validation to disallow it.
// AllowHostNQNs is the VolumeAttributesClass mutable parameter key for specifying
// a YAML list of host NQNs to allow access to a volume. Use "*" to allow any host.
// Example:
//
// allowHostNQNs: |
// - nqn.2014-08.org.nvmexpress:host1
// - nqn.2014-08.org.nvmexpress:host2
const AllowHostNQNs = "allowHostNQNs"
| if len(hosts) == 0 { | ||
| log.DebugLog(ctx, "No hosts to add or remove for volume %s", volumeID) | ||
|
|
||
| return nil | ||
| } | ||
|
|
| // parseHostsParameters parses the hosts yaml list parameter and validates its contents. | ||
| // It returns a slice of hostNQNs or an error if the YAML is invalid. | ||
| // allowHostNQNs entry can be empty, in that case it means no hosts are allowed to access | ||
| // the subsystem (empty allow list). | ||
| func parseHostsParameters(params map[string]string) ([]string, error) { | ||
| allowHostNQNs, exists := params[AllowHostNQNs] | ||
| if !exists { | ||
| return nil, nil | ||
| } | ||
| if err := parseParam(nvmeof.RwMbytesPerSecond, nvmeof.RwMbytesPerSecond, &qos.RwMbytesPerSecond); err != nil { | ||
| return nil, err | ||
| var allowHostsList []string | ||
| if allowHostNQNs == "" { | ||
| return allowHostsList, nil | ||
| } | ||
| if err := parseParam(nvmeof.RMbytesPerSecond, nvmeof.RMbytesPerSecond, &qos.RMbytesPerSecond); err != nil { | ||
| return nil, err | ||
| } | ||
| if err := parseParam(nvmeof.WMbytesPerSecond, nvmeof.WMbytesPerSecond, &qos.WMbytesPerSecond); err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| if !hasAnyQoS { | ||
| return nil, nil | ||
| if err := yaml.Unmarshal([]byte(allowHostNQNs), &allowHostsList); err != nil { | ||
| return nil, fmt.Errorf("invalid %s: must be a YAML list of strings: %w", AllowHostNQNs, err) | ||
| } | ||
|
|
||
| return qos, nil | ||
| return allowHostsList, nil |
d8185d1 to
852fd6c
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (2)
internal/nvmeof/controller/controllerserver.go:561
parseHostsParametersreturns a nil slice whenallowHostNQNsis present but empty. Downstream callers usehostsList != nilto decide whether to reconcile, so settingallowHostNQNs: ""cannot remove all currently-allowed hosts (it is treated like the parameter is absent). Return a non-nil empty slice (or return an explicitpresentboolean) when the key exists but is empty so ControllerModifyVolume can reconcile to an empty allow-list.
func parseHostsParameters(params map[string]string) ([]string, error) {
allowHostNQNs, exists := params[AllowHostNQNs]
if !exists {
return nil, nil
}
var allowHostsList []string
if allowHostNQNs == "" {
return allowHostsList, nil
}
internal/nvmeof/controller/mutable_params_test.go:175
- The test case for empty
allowHostNQNscurrently expects a nil slice, which matches the current parsing but also hides the need to distinguish “parameter present but empty list” from “parameter absent”. Update this test to expect a non-nil empty slice (or otherwise assert the presence semantics) so regressions in host reconciliation (clearing all hosts) are caught.
Implements dynamic host access control for NVMe-oF volumes to support
external(non-Kubernetes) clients through VolumeAttributesClass.
Changes:
- Add `parseHostsParameters()` to parse YAML host list from VAC
- Implement `modifyNVMeoFHosts()` for runtime host updates
- Add `ListHosts()` and `UpdateHostsForSubsystem()` for host updateing
- Support allowHostNQNs parameter in CreateVolume and ControllerModifyVolume
- Auto-generate subsystem NQN from volumeID if not provided
Users can now specify external hosts in VAC mutable parameters:
allowHostNQNs: |
- nqn.2014-08.org.nvmexpress:host1
- nqn.2014-08.org.nvmexpress:host2
Signed-off-by: gadi-didi <gadi.didi@ibm.com>
852fd6c to
b16bc7d
Compare
@nixpanic
Add external client support
Support
allowHostNQNsin VAC for external (non-K8s) clients. Hosts can be managed via mutable parameters.Add host reconciliation logic (add/remove) for NVMe-oF subsystems.
auto-generate subsystem NQN if not provided (for external Client- they have dedicated StorageClass, without
SubsystemNQNlabel) .In this design, each PVC gets its own NVMe-oF subsystem. That means:
SubsystemNQNin the SC) .issue: #6240
Checklist:
guidelines in the developer
guide.
Request
notes
updated with breaking and/or notable changes for the next major release.
Show available bot commands
These commands are normally not required, but in case of issues, leave any of
the following bot commands in an otherwise empty comment in this PR:
/retest ci/centos/<job-name>: retest the<job-name>after unrelatedfailure (please report the failure too!)