Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 97 additions & 0 deletions e2e/nfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -1249,6 +1249,103 @@ var _ = Describe("nfs", func() {
validateOmapCount(f, 0, cephfsType, metadataPool, volumesType)
})

It("create a storageclass with clients restriction and modify it with VolumeAttributesClass", func() {
if !k8sVersionGreaterEquals(c, 1, 34) {
framework.Logf("skipping VolumeAttributesClass test, needs Kubernetes >= 1.34")

return
}

// Initial clients list - restrictive (Cloudflare DNS, should fail to mount)
initialClients := "1.1.1.1"
// Updated clients list - permissive (allow all clients)
updatedClients := "0.0.0.0/0"

err := createNFSStorageClass(f.ClientSet, f, false, map[string]string{
"clients": initialClients,
})
if err != nil {
logAndFail("failed to create NFS storageclass: %v", err)
}
err = createNFSVolumeAttributesClass(f.ClientSet, f, map[string]string{
"clients": updatedClients,
})
if err != nil {
logAndFail("failed to create NFS volumeattributesclass: %v", err)
}

pvc, err := loadPVC(pvcPath)
if err != nil {
logAndFail("Could not load PVC: %v", err)
}
pvc.Namespace = f.UniqueName

// Create PVC first without VolumeAttributesClass
err = createPVCAndvalidatePV(f.ClientSet, pvc, deployTimeout)
if err != nil {
logAndFail("failed to create PVC: %v", err)
}

// Verify initial restrictive clients parameter
if !checkExports(f, "my-nfs", initialClients) {
logAndFail("failed to verify initial clients in exports")
}

app, err := loadApp(appPath)
if err != nil {
logAndFail("failed to load application: %v", err)
}
app.Namespace = f.UniqueName
app.Spec.Volumes[0].PersistentVolumeClaim.ClaimName = pvc.Name

// Try to create app with restrictive clients - should fail to reach running state
err = createApp(f.ClientSet, app, deployTimeout)
if err == nil {
logAndFail("app should have failed to start with restrictive clients, but succeeded")
}
framework.Logf("app correctly failed to start with restrictive clients: %v", err)

// Delete the failed app
err = deletePod(app.Name, app.Namespace, f.ClientSet, deployTimeout)
if err != nil {
logAndFail("failed to delete app: %v", err)
}

// Apply VolumeAttributesClass to PVC to update clients
vacName := "updated-parameters"
pvc.Spec.VolumeAttributesClassName = &vacName
_, err = f.ClientSet.CoreV1().PersistentVolumeClaims(pvc.Namespace).Update(
context.TODO(), pvc, metav1.UpdateOptions{})
if err != nil {
logAndFail("failed to update PVC with VolumeAttributesClass: %v", err)
}

// Now create app again - VolumeAttributesClass should update clients to 0.0.0.0/0
err = createApp(f.ClientSet, app, deployTimeout)
if err != nil {
logAndFail("failed to create application with updated clients: %v", err)
}

// Verify the updated clients parameter is applied
if !checkExports(f, "my-nfs", updatedClients) {
logAndFail("failed to verify updated clients in exports")
}

// delete PVC and app
err = deletePVCAndApp("", f, pvc, app)
if err != nil {
logAndFail("failed to delete PVC or application: %v", err)
}
err = deleteResource(nfsExamplePath + "storageclass.yaml")
if err != nil {
logAndFail("failed to delete NFS storageclass: %v", err)
}
err = deleteNFSVolumeAttributesClass(f.ClientSet, f)
if err != nil {
logAndFail("failed to delete NFS volumeattributesclass: %v", err)
}
})

It("delete NFS provisioner and plugin secret", func() {
// delete nfs provisioner secret
err := deleteCephUser(f, keyringCephFSProvisionerUsername)
Expand Down
2 changes: 2 additions & 0 deletions examples/nfs/storageclass.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ parameters:
# access to the export to the set of hostnames, networks or ip addresses
# specified. The <client-list> is a comma delimited string,
# for example: "192.168.0.10,192.168.1.0/8"
# This parameter can be updated after volume creation using a
# VolumeAttributesClass (see volumeattributesclass.yaml example).
# clients: <client-list>

reclaimPolicy: Delete
Expand Down
5 changes: 5 additions & 0 deletions examples/nfs/volumeattributesclass.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,8 @@ parameters:
# "server" can be an alternative NFS-server that should be used when the
# volume is attached the next time to a node.
server: to-be-deployed.example.net

# "clients" can be used to update the list of hostnames, networks or IP
# addresses that are allowed to access the NFS export. The <client-list>
# is a comma delimited string, for example: "192.168.0.10,192.168.1.0/8"
# clients: <client-list>
7 changes: 7 additions & 0 deletions internal/nfs/controller/controllerserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -259,5 +259,12 @@ func (cs *Server) ControllerModifyVolume(
}
}

if clients, ok := req.GetMutableParameters()[nfs.ParameterClients]; ok {
err := nfsVolume.SetClients(clients)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
}

return &csi.ControllerModifyVolumeResponse{}, nil
}
49 changes: 49 additions & 0 deletions internal/nfs/types/volume.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ const (
// ParameterServer is set in the parameters on volume creation and in
// the VolumeContext.
ParameterServer = "server"

// ParameterClients is set in the parameters on volume creation and
// configured for the export in the NFS-server. It is not stored in
// the VolumeContext.
ParameterClients = "clients"
)

// NFSVolume presents the API for consumption by the CSI-controller to create,
Expand Down Expand Up @@ -246,6 +251,50 @@ func (nv *NFSVolume) GetServer() (string, error) {
return nv.getAttribute(ParameterServer)
}

// SetClients updates the NFS-clients list in the NFS export and stores it in the CephFS journal.
func (nv *NFSVolume) SetClients(clients string) error {
if !nv.connected {
return fmt.Errorf("can not set clients for %q: %w", nv, ErrNotConnected)
}

nfsCluster, err := nv.getNFSCluster()
if err != nil {
return fmt.Errorf("failed to identify NFS cluster: %w", err)
}

nfsa, err := nv.conn.GetNFSAdmin()
if err != nil {
return fmt.Errorf("failed to get NFSAdmin: %w", err)
}

// Fetch current export info
exportInfo, err := nfsa.ExportInfo(nfsCluster, nv.GetExportPath())
if err != nil {
return fmt.Errorf("failed to get export info for %q: %w", nv.GetExportPath(), err)
}

// Update the export with new clients list
export := nfs.CephFSExportSpec{
FileSystemName: exportInfo.FSAL.FileSystemName,
ClusterID: nfsCluster,
PseudoPath: nv.GetExportPath(),
Path: exportInfo.Path,
SecType: exportInfo.SecType,
}

if clients != "" {
export.ClientAddr = strings.Split(clients, ",")
}

_, err = nfsa.CreateCephFSExport(export)
if err != nil {
return fmt.Errorf("failed to update export %q with new clients: %w", nv.GetExportPath(), err)
}

// Store the new clients value in the journal for persistence
return nv.setAttribute(ParameterClients, clients)
}

// createExportCommand returns the "ceph nfs export create ..." command
// arguments (without "ceph"). The order of the parameters matches old Ceph
// releases, new Ceph releases added --option formats, which can be added when
Expand Down
Loading