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
134 changes: 108 additions & 26 deletions e2e/nfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ import (
)

var (
// nfsWithSlowTests enables negative testing (pod creation expected to fail)
nfsWithSlowTests = false

nfsProvisioner = "csi-nfsplugin-provisioner.yaml"
nfsProvisionerRBAC = "csi-provisioner-rbac.yaml"
nfsNodePlugin = "csi-nfsplugin.yaml"
Expand Down Expand Up @@ -218,7 +221,12 @@ func createNFSStorageClass(
if err != nil {
framework.Logf("error creating StorageClass %q: %v", sc.Name, err)
if apierrs.IsAlreadyExists(err) {
return true, nil
err = deleteResource(nfsExamplePath + "storageclass.yaml")
if err != nil {
logAndFail("failed to delete NFS storageclass: %v", err)
}

return false, nil
}
if isRetryableAPIError(err) {
return false, nil
Expand All @@ -227,6 +235,8 @@ func createNFSStorageClass(
return false, fmt.Errorf("failed to create StorageClass %q: %w", sc.Name, err)
}

framework.Logf("created StorageClass %q", sc.Name)

return true, nil
})
}
Expand Down Expand Up @@ -504,11 +514,6 @@ var _ = Describe("nfs", func() {
if err != nil {
logAndFail("failed to verify mount options: %v", err)
}

err = deleteResource(nfsExamplePath + "storageclass.yaml")
if err != nil {
logAndFail("failed to delete NFS storageclass: %v", err)
}
})

It("verify RWOP volume support", func() {
Expand Down Expand Up @@ -550,10 +555,6 @@ var _ = Describe("nfs", func() {
logAndFail("failed to validate RWOP pod creation: %v", err)
}
validateSubvolumeCount(f, 0, fileSystemName, defaultSubvolumegroup)
err = deleteResource(nfsExamplePath + "storageclass.yaml")
if err != nil {
logAndFail("failed to delete NFS storageclass: %v", err)
}
})

It("create a storageclass with pool and a PVC then bind it to an app", func() {
Expand All @@ -565,10 +566,6 @@ var _ = Describe("nfs", func() {
if err != nil {
logAndFail("failed to validate NFS pvc and application binding: %v", err)
}
err = deleteResource(nfsExamplePath + "storageclass.yaml")
if err != nil {
logAndFail("failed to delete NFS storageclass: %v", err)
}
})

It("create a storageclass with relocated server and a PVC then bind it to an app", func() {
Expand Down Expand Up @@ -615,10 +612,6 @@ var _ = Describe("nfs", func() {
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 voluemattributesclass: %v", err)
Expand All @@ -636,10 +629,6 @@ var _ = Describe("nfs", func() {
if err != nil {
logAndFail("failed to validate NFS pvc and application binding: %v", err)
}
err = deleteResource(nfsExamplePath + "storageclass.yaml")
if err != nil {
logAndFail("failed to delete NFS storageclass: %v", err)
}
})

It("create a storageclass with a restricted set of clients allowed to mount it", func() {
Expand Down Expand Up @@ -668,10 +657,6 @@ var _ = Describe("nfs", func() {
if err != nil {
logAndFail("failed to delete PVC: %v", err)
}
err = deleteResource(nfsExamplePath + "storageclass.yaml")
if err != nil {
logAndFail("failed to delete NFS storageclass: %v", err)
}
})

It("create a PVC and bind it to an app", func() {
Expand Down Expand Up @@ -1249,6 +1234,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{
"csi.storage.k8s.io/controller-modify-secret-namespace": cephCSINamespace,
"csi.storage.k8s.io/controller-modify-secret-name": cephFSProvisionerSecretName,
"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

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

By("verifying 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

if nfsWithSlowTests {
By("trying 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)

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

By("applying VolumeAttributesClass to PVC to update clients")
vacName := "updated-parameters"
patchData := []byte(fmt.Sprintf(`{"spec":{"volumeAttributesClassName":"%s"}}`, vacName))
_, err = f.ClientSet.CoreV1().PersistentVolumeClaims(pvc.Namespace).Patch(
context.TODO(), pvc.Name, "application/strategic-merge-patch+json", patchData, metav1.PatchOptions{})
if err != nil {
logAndFail("failed to patch PVC with VolumeAttributesClass: %v", err)
}

By("verifying updated clients parameter")
if !checkExports(f, "my-nfs", updatedClients) {
logAndFail("failed to verify initial clients in exports")
}

By("creating app with PVC that has updated clients")
err = createApp(f.ClientSet, app, deployTimeout)
if err != nil {
logAndFail("failed to create application with updated clients: %v", err)
}

By("deleting PVC and app")
err = deletePVCAndApp("", f, pvc, app)
if err != nil {
logAndFail("failed to delete PVC or application: %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: 1 addition & 1 deletion e2e/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -2111,7 +2111,7 @@ func checkExports(f *framework.Framework, clusterID, clientString string) bool {
}

if !found {
framework.Logf("Could not find the configured clients in the list of exports")
framework.Logf("Could not find the configured clients (%s) in the list of exports (%+v)", clientString, exportList)

return false
}
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>
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ require (
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4
)

// for nfsa.UpdateCephFSExport, github.com/nixpanic/go-ceph:nfs/export-update
replace github.com/ceph/go-ceph => github.com/nixpanic/go-ceph v0.0.0-20260417074741-878fc984e61b

require (
// sigs.k8s.io/controller-runtime wants this version, it gets replaced below
k8s.io/client-go v12.0.0+incompatible
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -487,8 +487,6 @@ github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA
github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw=
github.com/ceph/ceph-nvmeof/lib/go/nvmeof v0.0.0-20260120065657-2425981cdad5 h1:giK4rX93KFW4C361EHurz46iM5nqFOKOT4EMuuroi+E=
github.com/ceph/ceph-nvmeof/lib/go/nvmeof v0.0.0-20260120065657-2425981cdad5/go.mod h1:P+rCBJEUy2Yt6vOcTh7pyh9/g+o9pOZc7kyY6k/PS5U=
github.com/ceph/go-ceph v0.38.0 h1:Ux0sIpl6VJNgY21hxuBZI9Z2Z8tQsBMJhjLjYBoa7s0=
github.com/ceph/go-ceph v0.38.0/go.mod h1:GQVPe5YWoCMOrGnpDDieQoQZRLkB0tJmIokbqxbwPBQ=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
Expand Down Expand Up @@ -919,6 +917,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8m
github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nixpanic/go-ceph v0.0.0-20260417074741-878fc984e61b h1:4yk3TAir4QxFb/V4ctiGvs3yxf62qNBnOATMFyqd6xw=
github.com/nixpanic/go-ceph v0.0.0-20260417074741-878fc984e61b/go.mod h1:UId58dqtDKTwnv3OY8rdpC+Ulz/AVpcvZqjXDICcd5c=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
Expand Down
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
}
47 changes: 47 additions & 0 deletions internal/nfs/types/volume.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,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 @@ -251,6 +256,48 @@ func (nv *NFSVolume) GetServer() (string, error) {
return nv.getAttribute(ParameterServer)
}

// SetClients updates the NFS-clients list in the NFS export.
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
if clients != "" {
clientAddrs := strings.Split(clients, ",")
exportInfo.Clients = []nfs.ClientInfo{
{
Addresses: clientAddrs,
AccessType: "rw",
Squash: nfs.NoneSquash,
},
}
}

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

return nil
}

// 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
2 changes: 0 additions & 2 deletions vendor/github.com/ceph/go-ceph/cephfs/snap_diff.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 7 additions & 2 deletions vendor/github.com/ceph/go-ceph/common/admin/nfs/admin.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 0 additions & 4 deletions vendor/github.com/ceph/go-ceph/common/admin/nfs/export.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading