Merge pull request #103244 from verult/fsgroup-to-csi

Delegate applying FSGroup to CSI driver through NodeStageVolume and NodePublishVolume
This commit is contained in:
Kubernetes Prow Robot 2021-07-06 16:22:10 -07:00 committed by GitHub
commit 15222a599f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 472 additions and 103 deletions

View File

@ -544,7 +544,7 @@ func (attacher *testPluginAttacher) GetDeviceMountPath(spec *volume.Spec) (strin
return "", nil return "", nil
} }
func (attacher *testPluginAttacher) MountDevice(spec *volume.Spec, devicePath string, deviceMountPath string) error { func (attacher *testPluginAttacher) MountDevice(spec *volume.Spec, devicePath string, deviceMountPath string, _ volume.DeviceMounterArgs) error {
attacher.pluginLock.Lock() attacher.pluginLock.Lock()
defer attacher.pluginLock.Unlock() defer attacher.pluginLock.Unlock()
if spec == nil { if spec == nil {

View File

@ -369,6 +369,13 @@ const (
// a volume in a Pod. // a volume in a Pod.
ConfigurableFSGroupPolicy featuregate.Feature = "ConfigurableFSGroupPolicy" ConfigurableFSGroupPolicy featuregate.Feature = "ConfigurableFSGroupPolicy"
// owner: @gnufied, @verult
// alpha: v1.22
// If supported by the CSI driver, delegates the role of applying FSGroup to
// the driver by passing FSGroup through the NodeStageVolume and
// NodePublishVolume calls.
DelegateFSGroupToCSIDriver featuregate.Feature = "DelegateFSGroupToCSIDriver"
// owner: @RobertKrawitz, @derekwaynecarr // owner: @RobertKrawitz, @derekwaynecarr
// beta: v1.15 // beta: v1.15
// GA: v1.20 // GA: v1.20
@ -860,6 +867,7 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS
PodSecurity: {Default: false, PreRelease: featuregate.Alpha}, PodSecurity: {Default: false, PreRelease: featuregate.Alpha},
ReadWriteOncePod: {Default: false, PreRelease: featuregate.Alpha}, ReadWriteOncePod: {Default: false, PreRelease: featuregate.Alpha},
CSRDuration: {Default: true, PreRelease: featuregate.Beta}, CSRDuration: {Default: true, PreRelease: featuregate.Beta},
DelegateFSGroupToCSIDriver: {Default: false, PreRelease: featuregate.Alpha},
// inherited features from generic apiserver, relisted here to get a conflict if it is changed // inherited features from generic apiserver, relisted here to get a conflict if it is changed
// unintentionally on either side: // unintentionally on either side:

View File

@ -206,7 +206,7 @@ func (attacher *awsElasticBlockStoreAttacher) GetDeviceMountPath(
} }
// FIXME: this method can be further pruned. // FIXME: this method can be further pruned.
func (attacher *awsElasticBlockStoreAttacher) MountDevice(spec *volume.Spec, devicePath string, deviceMountPath string) error { func (attacher *awsElasticBlockStoreAttacher) MountDevice(spec *volume.Spec, devicePath string, deviceMountPath string, _ volume.DeviceMounterArgs) error {
mounter := attacher.host.GetMounter(awsElasticBlockStorePluginName) mounter := attacher.host.GetMounter(awsElasticBlockStorePluginName)
notMnt, err := mounter.IsLikelyNotMountPoint(deviceMountPath) notMnt, err := mounter.IsLikelyNotMountPoint(deviceMountPath)
if err != nil { if err != nil {

View File

@ -202,7 +202,7 @@ func (a *azureDiskAttacher) GetDeviceMountPath(spec *volume.Spec) (string, error
return makeGlobalPDPath(a.plugin.host, volumeSource.DataDiskURI, isManagedDisk) return makeGlobalPDPath(a.plugin.host, volumeSource.DataDiskURI, isManagedDisk)
} }
func (a *azureDiskAttacher) MountDevice(spec *volume.Spec, devicePath string, deviceMountPath string) error { func (a *azureDiskAttacher) MountDevice(spec *volume.Spec, devicePath string, deviceMountPath string, _ volume.DeviceMounterArgs) error {
mounter := a.plugin.host.GetMounter(azureDataDiskPluginName) mounter := a.plugin.host.GetMounter(azureDataDiskPluginName)
notMnt, err := mounter.IsLikelyNotMountPoint(deviceMountPath) notMnt, err := mounter.IsLikelyNotMountPoint(deviceMountPath)

View File

@ -268,7 +268,7 @@ func (attacher *cinderDiskAttacher) GetDeviceMountPath(
} }
// FIXME: this method can be further pruned. // FIXME: this method can be further pruned.
func (attacher *cinderDiskAttacher) MountDevice(spec *volume.Spec, devicePath string, deviceMountPath string) error { func (attacher *cinderDiskAttacher) MountDevice(spec *volume.Spec, devicePath string, deviceMountPath string, _ volume.DeviceMounterArgs) error {
mounter := attacher.host.GetMounter(cinderVolumePluginName) mounter := attacher.host.GetMounter(cinderVolumePluginName)
notMnt, err := mounter.IsLikelyNotMountPoint(deviceMountPath) notMnt, err := mounter.IsLikelyNotMountPoint(deviceMountPath)
if err != nil { if err != nil {

View File

@ -27,6 +27,7 @@ import (
"time" "time"
"k8s.io/apimachinery/pkg/util/clock" "k8s.io/apimachinery/pkg/util/clock"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/klog/v2" "k8s.io/klog/v2"
v1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
@ -38,6 +39,7 @@ import (
"k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/util/wait"
"k8s.io/apimachinery/pkg/watch" "k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/kubernetes" "k8s.io/client-go/kubernetes"
"k8s.io/kubernetes/pkg/features"
"k8s.io/kubernetes/pkg/volume" "k8s.io/kubernetes/pkg/volume"
volumetypes "k8s.io/kubernetes/pkg/volume/util/types" volumetypes "k8s.io/kubernetes/pkg/volume/util/types"
) )
@ -264,7 +266,7 @@ func (c *csiAttacher) GetDeviceMountPath(spec *volume.Spec) (string, error) {
return deviceMountPath, nil return deviceMountPath, nil
} }
func (c *csiAttacher) MountDevice(spec *volume.Spec, devicePath string, deviceMountPath string) error { func (c *csiAttacher) MountDevice(spec *volume.Spec, devicePath string, deviceMountPath string, deviceMounterArgs volume.DeviceMounterArgs) error {
klog.V(4).Infof(log("attacher.MountDevice(%s, %s)", devicePath, deviceMountPath)) klog.V(4).Infof(log("attacher.MountDevice(%s, %s)", devicePath, deviceMountPath))
if deviceMountPath == "" { if deviceMountPath == "" {
@ -365,6 +367,19 @@ func (c *csiAttacher) MountDevice(spec *volume.Spec, devicePath string, deviceMo
mountOptions = spec.PersistentVolume.Spec.MountOptions mountOptions = spec.PersistentVolume.Spec.MountOptions
} }
var nodeStageFSGroupArg *int64
if utilfeature.DefaultFeatureGate.Enabled(features.DelegateFSGroupToCSIDriver) {
driverSupportsCSIVolumeMountGroup, err := csi.NodeSupportsVolumeMountGroup(ctx)
if err != nil {
return volumetypes.NewTransientOperationFailure(log("attacher.MountDevice failed to determine if the node service has VOLUME_MOUNT_GROUP capability: %v", err))
}
if driverSupportsCSIVolumeMountGroup {
klog.V(3).Infof("Driver %s supports applying FSGroup (has VOLUME_MOUNT_GROUP node capability). Delegating FSGroup application to the driver through NodeStageVolume.", csiSource.Driver)
nodeStageFSGroupArg = deviceMounterArgs.FsGroup
}
}
fsType := csiSource.FSType fsType := csiSource.FSType
err = csi.NodeStageVolume(ctx, err = csi.NodeStageVolume(ctx,
csiSource.VolumeHandle, csiSource.VolumeHandle,
@ -374,7 +389,8 @@ func (c *csiAttacher) MountDevice(spec *volume.Spec, devicePath string, deviceMo
accessMode, accessMode,
nodeStageSecrets, nodeStageSecrets,
csiSource.VolumeAttributes, csiSource.VolumeAttributes,
mountOptions) mountOptions,
nodeStageFSGroupArg)
if err != nil { if err != nil {
return err return err

View File

@ -1068,7 +1068,10 @@ func TestAttacherGetDeviceMountPath(t *testing.T) {
} }
func TestAttacherMountDevice(t *testing.T) { func TestAttacherMountDevice(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DelegateFSGroupToCSIDriver, true)()
pvName := "test-pv" pvName := "test-pv"
var testFSGroup int64 = 3000
nonFinalError := volumetypes.NewUncertainProgressError("") nonFinalError := volumetypes.NewUncertainProgressError("")
transientError := volumetypes.NewTransientOperationFailure("") transientError := volumetypes.NewTransientOperationFailure("")
@ -1078,6 +1081,10 @@ func TestAttacherMountDevice(t *testing.T) {
devicePath string devicePath string
deviceMountPath string deviceMountPath string
stageUnstageSet bool stageUnstageSet bool
fsGroup *int64
expectedVolumeMountGroup string
delegateFSGroupFeatureGate bool
driverSupportsVolumeMountGroup bool
shouldFail bool shouldFail bool
createAttachment bool createAttachment bool
populateDeviceMountPath bool populateDeviceMountPath bool
@ -1184,6 +1191,57 @@ func TestAttacherMountDevice(t *testing.T) {
shouldFail: true, shouldFail: true,
spec: volume.NewSpecFromPersistentVolume(makeTestPV(pvName, 10, testDriver, "test-vol1"), true), spec: volume.NewSpecFromPersistentVolume(makeTestPV(pvName, 10, testDriver, "test-vol1"), true),
}, },
{
testName: "fsgroup provided, DelegateFSGroupToCSIDriver feature enabled, driver supports volume mount group; expect fsgroup to be passed to NodeStageVolume",
volName: "test-vol1",
devicePath: "path1",
deviceMountPath: "path2",
fsGroup: &testFSGroup,
delegateFSGroupFeatureGate: true,
driverSupportsVolumeMountGroup: true,
expectedVolumeMountGroup: "3000",
stageUnstageSet: true,
createAttachment: true,
spec: volume.NewSpecFromPersistentVolume(makeTestPV(pvName, 10, testDriver, "test-vol1"), false),
},
{
testName: "fsgroup not provided, DelegateFSGroupToCSIDriver feature enabled, driver supports volume mount group; expect fsgroup not to be passed to NodeStageVolume",
volName: "test-vol1",
devicePath: "path1",
deviceMountPath: "path2",
delegateFSGroupFeatureGate: true,
driverSupportsVolumeMountGroup: true,
expectedVolumeMountGroup: "",
stageUnstageSet: true,
createAttachment: true,
spec: volume.NewSpecFromPersistentVolume(makeTestPV(pvName, 10, testDriver, "test-vol1"), false),
},
{
testName: "fsgroup provided, DelegateFSGroupToCSIDriver feature enabled, driver does not support volume mount group; expect fsgroup not to be passed to NodeStageVolume",
volName: "test-vol1",
devicePath: "path1",
deviceMountPath: "path2",
fsGroup: &testFSGroup,
delegateFSGroupFeatureGate: true,
driverSupportsVolumeMountGroup: false,
expectedVolumeMountGroup: "",
stageUnstageSet: true,
createAttachment: true,
spec: volume.NewSpecFromPersistentVolume(makeTestPV(pvName, 10, testDriver, "test-vol1"), false),
},
{
testName: "fsgroup provided, DelegateFSGroupToCSIDriver feature disabled, driver supports volume mount group; expect fsgroup not to be passed to NodeStageVolume",
volName: "test-vol1",
devicePath: "path1",
deviceMountPath: "path2",
fsGroup: &testFSGroup,
delegateFSGroupFeatureGate: false,
driverSupportsVolumeMountGroup: true,
expectedVolumeMountGroup: "",
stageUnstageSet: true,
createAttachment: true,
spec: volume.NewSpecFromPersistentVolume(makeTestPV(pvName, 10, testDriver, "test-vol1"), false),
},
} }
for _, tc := range testCases { for _, tc := range testCases {
@ -1198,6 +1256,8 @@ func TestAttacherMountDevice(t *testing.T) {
t.Run(tc.testName, func(t *testing.T) { t.Run(tc.testName, func(t *testing.T) {
t.Logf("Running test case: %s", tc.testName) t.Logf("Running test case: %s", tc.testName)
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DelegateFSGroupToCSIDriver, tc.delegateFSGroupFeatureGate)()
// Setup // Setup
// Create a new attacher // Create a new attacher
fakeClient := fakeclient.NewSimpleClientset() fakeClient := fakeclient.NewSimpleClientset()
@ -1209,7 +1269,7 @@ func TestAttacherMountDevice(t *testing.T) {
t.Fatalf("failed to create new attacher: %v", err0) t.Fatalf("failed to create new attacher: %v", err0)
} }
csiAttacher := getCsiAttacherFromVolumeAttacher(attacher, tc.watchTimeout) csiAttacher := getCsiAttacherFromVolumeAttacher(attacher, tc.watchTimeout)
csiAttacher.csiClient = setupClient(t, tc.stageUnstageSet) csiAttacher.csiClient = setupClientWithVolumeMountGroup(t, tc.stageUnstageSet, tc.driverSupportsVolumeMountGroup)
if tc.deviceMountPath != "" { if tc.deviceMountPath != "" {
tc.deviceMountPath = filepath.Join(tmpDir, tc.deviceMountPath) tc.deviceMountPath = filepath.Join(tmpDir, tc.deviceMountPath)
@ -1247,7 +1307,11 @@ func TestAttacherMountDevice(t *testing.T) {
} }
// Run // Run
err := csiAttacher.MountDevice(tc.spec, tc.devicePath, tc.deviceMountPath) err := csiAttacher.MountDevice(
tc.spec,
tc.devicePath,
tc.deviceMountPath,
volume.DeviceMounterArgs{FsGroup: tc.fsGroup})
// Verify // Verify
if err != nil { if err != nil {
@ -1302,6 +1366,9 @@ func TestAttacherMountDevice(t *testing.T) {
if !reflect.DeepEqual(vol.MountFlags, tc.spec.PersistentVolume.Spec.MountOptions) { if !reflect.DeepEqual(vol.MountFlags, tc.spec.PersistentVolume.Spec.MountOptions) {
t.Errorf("expected mount options: %v, got: %v", tc.spec.PersistentVolume.Spec.MountOptions, vol.MountFlags) t.Errorf("expected mount options: %v, got: %v", tc.spec.PersistentVolume.Spec.MountOptions, vol.MountFlags)
} }
if vol.VolumeMountGroup != tc.expectedVolumeMountGroup {
t.Errorf("expected volume mount group %q, got: %q", tc.expectedVolumeMountGroup, vol.VolumeMountGroup)
}
} }
// Verify the deviceMountPath was created by the plugin // Verify the deviceMountPath was created by the plugin
@ -1321,12 +1388,16 @@ func TestAttacherMountDevice(t *testing.T) {
func TestAttacherMountDeviceWithInline(t *testing.T) { func TestAttacherMountDeviceWithInline(t *testing.T) {
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSIInlineVolume, true)() defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSIInlineVolume, true)()
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DelegateFSGroupToCSIDriver, true)()
pvName := "test-pv" pvName := "test-pv"
var testFSGroup int64 = 3000
testCases := []struct { testCases := []struct {
testName string testName string
volName string volName string
devicePath string devicePath string
deviceMountPath string deviceMountPath string
fsGroup *int64
expectedVolumeMountGroup string
stageUnstageSet bool stageUnstageSet bool
shouldFail bool shouldFail bool
spec *volume.Spec spec *volume.Spec
@ -1390,6 +1461,16 @@ func TestAttacherMountDeviceWithInline(t *testing.T) {
deviceMountPath: "path2", deviceMountPath: "path2",
shouldFail: true, shouldFail: true,
}, },
{
testName: "fsgroup set",
volName: "test-vol1",
devicePath: "path1",
deviceMountPath: "path2",
fsGroup: &testFSGroup,
expectedVolumeMountGroup: "3000",
stageUnstageSet: true,
spec: volume.NewSpecFromPersistentVolume(makeTestPV(pvName, 10, testDriver, "test-vol1"), false),
},
} }
for _, tc := range testCases { for _, tc := range testCases {
@ -1410,7 +1491,7 @@ func TestAttacherMountDeviceWithInline(t *testing.T) {
t.Fatalf("failed to create new attacher: %v", err0) t.Fatalf("failed to create new attacher: %v", err0)
} }
csiAttacher := getCsiAttacherFromVolumeAttacher(attacher, tc.watchTimeout) csiAttacher := getCsiAttacherFromVolumeAttacher(attacher, tc.watchTimeout)
csiAttacher.csiClient = setupClient(t, tc.stageUnstageSet) csiAttacher.csiClient = setupClientWithVolumeMountGroup(t, tc.stageUnstageSet, true /* volumeMountGroup */)
if tc.deviceMountPath != "" { if tc.deviceMountPath != "" {
tc.deviceMountPath = filepath.Join(tmpDir, tc.deviceMountPath) tc.deviceMountPath = filepath.Join(tmpDir, tc.deviceMountPath)
@ -1435,7 +1516,11 @@ func TestAttacherMountDeviceWithInline(t *testing.T) {
}() }()
// Run // Run
err = csiAttacher.MountDevice(tc.spec, tc.devicePath, tc.deviceMountPath) err = csiAttacher.MountDevice(
tc.spec,
tc.devicePath,
tc.deviceMountPath,
volume.DeviceMounterArgs{FsGroup: tc.fsGroup})
// Verify // Verify
if err != nil { if err != nil {
@ -1467,6 +1552,9 @@ func TestAttacherMountDeviceWithInline(t *testing.T) {
if vol.Path != tc.deviceMountPath { if vol.Path != tc.deviceMountPath {
t.Errorf("expected mount path: %s. got: %s", tc.deviceMountPath, vol.Path) t.Errorf("expected mount path: %s. got: %s", tc.deviceMountPath, vol.Path)
} }
if vol.VolumeMountGroup != tc.expectedVolumeMountGroup {
t.Errorf("expected volume mount group %q, got: %q", tc.expectedVolumeMountGroup, vol.VolumeMountGroup)
}
} }
wg.Wait() wg.Wait()

View File

@ -193,7 +193,8 @@ func (m *csiBlockMapper) stageVolumeForBlock(
accessMode, accessMode,
nodeStageSecrets, nodeStageSecrets,
csiSource.VolumeAttributes, csiSource.VolumeAttributes,
nil /* MountOptions */) nil, /* MountOptions */
nil /* fsGroup */)
if err != nil { if err != nil {
return "", err return "", err
@ -265,7 +266,8 @@ func (m *csiBlockMapper) publishVolumeForBlock(
volAttribs, volAttribs,
nodePublishSecrets, nodePublishSecrets,
fsTypeBlockName, fsTypeBlockName,
[]string{}, []string{}, /* mountOptions */
nil, /* fsGroup */
) )
if err != nil { if err != nil {

View File

@ -22,6 +22,7 @@ import (
"fmt" "fmt"
"io" "io"
"net" "net"
"strconv"
"sync" "sync"
csipbv1 "github.com/container-storage-interface/spec/lib/go/csi" csipbv1 "github.com/container-storage-interface/spec/lib/go/csi"
@ -43,6 +44,10 @@ type csiClient interface {
maxVolumePerNode int64, maxVolumePerNode int64,
accessibleTopology map[string]string, accessibleTopology map[string]string,
err error) err error)
// The caller is responsible for checking whether the driver supports
// applying FSGroup by calling NodeSupportsVolumeMountGroup().
// If the driver does not, fsGroup must be set to nil.
NodePublishVolume( NodePublishVolume(
ctx context.Context, ctx context.Context,
volumeid string, volumeid string,
@ -55,13 +60,19 @@ type csiClient interface {
secrets map[string]string, secrets map[string]string,
fsType string, fsType string,
mountOptions []string, mountOptions []string,
fsGroup *int64,
) error ) error
NodeExpandVolume(ctx context.Context, rsOpts csiResizeOptions) (resource.Quantity, error) NodeExpandVolume(ctx context.Context, rsOpts csiResizeOptions) (resource.Quantity, error)
NodeUnpublishVolume( NodeUnpublishVolume(
ctx context.Context, ctx context.Context,
volID string, volID string,
targetPath string, targetPath string,
) error ) error
// The caller is responsible for checking whether the driver supports
// applying FSGroup by calling NodeSupportsVolumeMountGroup().
// If the driver does not, fsGroup must be set to nil.
NodeStageVolume(ctx context.Context, NodeStageVolume(ctx context.Context,
volID string, volID string,
publishVolumeInfo map[string]string, publishVolumeInfo map[string]string,
@ -71,6 +82,7 @@ type csiClient interface {
secrets map[string]string, secrets map[string]string,
volumeContext map[string]string, volumeContext map[string]string,
mountOptions []string, mountOptions []string,
fsGroup *int64,
) error ) error
NodeGetVolumeStats( NodeGetVolumeStats(
@ -83,6 +95,7 @@ type csiClient interface {
NodeSupportsNodeExpand(ctx context.Context) (bool, error) NodeSupportsNodeExpand(ctx context.Context) (bool, error)
NodeSupportsVolumeStats(ctx context.Context) (bool, error) NodeSupportsVolumeStats(ctx context.Context) (bool, error)
NodeSupportsSingleNodeMultiWriterAccessMode(ctx context.Context) (bool, error) NodeSupportsSingleNodeMultiWriterAccessMode(ctx context.Context) (bool, error)
NodeSupportsVolumeMountGroup(ctx context.Context) (bool, error)
} }
// Strongly typed address // Strongly typed address
@ -209,6 +222,7 @@ func (c *csiDriverClient) NodePublishVolume(
secrets map[string]string, secrets map[string]string,
fsType string, fsType string,
mountOptions []string, mountOptions []string,
fsGroup *int64,
) error { ) error {
klog.V(4).Info(log("calling NodePublishVolume rpc [volid=%s,target_path=%s]", volID, targetPath)) klog.V(4).Info(log("calling NodePublishVolume rpc [volid=%s,target_path=%s]", volID, targetPath))
if volID == "" { if volID == "" {
@ -255,11 +269,15 @@ func (c *csiDriverClient) NodePublishVolume(
Block: &csipbv1.VolumeCapability_BlockVolume{}, Block: &csipbv1.VolumeCapability_BlockVolume{},
} }
} else { } else {
req.VolumeCapability.AccessType = &csipbv1.VolumeCapability_Mount{ mountVolume := &csipbv1.VolumeCapability_MountVolume{
Mount: &csipbv1.VolumeCapability_MountVolume{
FsType: fsType, FsType: fsType,
MountFlags: mountOptions, MountFlags: mountOptions,
}, }
if fsGroup != nil {
mountVolume.VolumeMountGroup = strconv.FormatInt(*fsGroup, 10 /* base */)
}
req.VolumeCapability.AccessType = &csipbv1.VolumeCapability_Mount{
Mount: mountVolume,
} }
} }
@ -371,6 +389,7 @@ func (c *csiDriverClient) NodeStageVolume(ctx context.Context,
secrets map[string]string, secrets map[string]string,
volumeContext map[string]string, volumeContext map[string]string,
mountOptions []string, mountOptions []string,
fsGroup *int64,
) error { ) error {
klog.V(4).Info(log("calling NodeStageVolume rpc [volid=%s,staging_target_path=%s]", volID, stagingTargetPath)) klog.V(4).Info(log("calling NodeStageVolume rpc [volid=%s,staging_target_path=%s]", volID, stagingTargetPath))
if volID == "" { if volID == "" {
@ -412,11 +431,15 @@ func (c *csiDriverClient) NodeStageVolume(ctx context.Context,
Block: &csipbv1.VolumeCapability_BlockVolume{}, Block: &csipbv1.VolumeCapability_BlockVolume{},
} }
} else { } else {
req.VolumeCapability.AccessType = &csipbv1.VolumeCapability_Mount{ mountVolume := &csipbv1.VolumeCapability_MountVolume{
Mount: &csipbv1.VolumeCapability_MountVolume{
FsType: fsType, FsType: fsType,
MountFlags: mountOptions, MountFlags: mountOptions,
}, }
if fsGroup != nil {
mountVolume.VolumeMountGroup = strconv.FormatInt(*fsGroup, 10 /* base */)
}
req.VolumeCapability.AccessType = &csipbv1.VolumeCapability_Mount{
Mount: mountVolume,
} }
} }
@ -454,12 +477,10 @@ func (c *csiDriverClient) NodeUnstageVolume(ctx context.Context, volID, stagingT
} }
func (c *csiDriverClient) NodeSupportsNodeExpand(ctx context.Context) (bool, error) { func (c *csiDriverClient) NodeSupportsNodeExpand(ctx context.Context) (bool, error) {
klog.V(4).Info(log("calling NodeGetCapabilities rpc to determine if Node has EXPAND_VOLUME capability"))
return c.nodeSupportsCapability(ctx, csipbv1.NodeServiceCapability_RPC_EXPAND_VOLUME) return c.nodeSupportsCapability(ctx, csipbv1.NodeServiceCapability_RPC_EXPAND_VOLUME)
} }
func (c *csiDriverClient) NodeSupportsStageUnstage(ctx context.Context) (bool, error) { func (c *csiDriverClient) NodeSupportsStageUnstage(ctx context.Context) (bool, error) {
klog.V(4).Info(log("calling NodeGetCapabilities rpc to determine if NodeSupportsStageUnstage"))
return c.nodeSupportsCapability(ctx, csipbv1.NodeServiceCapability_RPC_STAGE_UNSTAGE_VOLUME) return c.nodeSupportsCapability(ctx, csipbv1.NodeServiceCapability_RPC_STAGE_UNSTAGE_VOLUME)
} }
@ -553,12 +574,10 @@ func (c *csiClientGetter) Get() (csiClient, error) {
} }
func (c *csiDriverClient) NodeSupportsVolumeStats(ctx context.Context) (bool, error) { func (c *csiDriverClient) NodeSupportsVolumeStats(ctx context.Context) (bool, error) {
klog.V(5).Info(log("calling NodeGetCapabilities rpc to determine if NodeSupportsVolumeStats"))
return c.nodeSupportsCapability(ctx, csipbv1.NodeServiceCapability_RPC_GET_VOLUME_STATS) return c.nodeSupportsCapability(ctx, csipbv1.NodeServiceCapability_RPC_GET_VOLUME_STATS)
} }
func (c *csiDriverClient) NodeSupportsSingleNodeMultiWriterAccessMode(ctx context.Context) (bool, error) { func (c *csiDriverClient) NodeSupportsSingleNodeMultiWriterAccessMode(ctx context.Context) (bool, error) {
klog.V(4).Info(log("calling NodeGetCapabilities rpc to determine if NodeSupportsSingleNodeMultiWriterAccessMode"))
return c.nodeSupportsCapability(ctx, csipbv1.NodeServiceCapability_RPC_SINGLE_NODE_MULTI_WRITER) return c.nodeSupportsCapability(ctx, csipbv1.NodeServiceCapability_RPC_SINGLE_NODE_MULTI_WRITER)
} }
@ -637,11 +656,15 @@ func (c *csiDriverClient) NodeGetVolumeStats(ctx context.Context, volID string,
} }
func (c *csiDriverClient) nodeSupportsVolumeCondition(ctx context.Context) (bool, error) { func (c *csiDriverClient) nodeSupportsVolumeCondition(ctx context.Context) (bool, error) {
klog.V(5).Info(log("calling NodeGetCapabilities rpc to determine if nodeSupportsVolumeCondition"))
return c.nodeSupportsCapability(ctx, csipbv1.NodeServiceCapability_RPC_VOLUME_CONDITION) return c.nodeSupportsCapability(ctx, csipbv1.NodeServiceCapability_RPC_VOLUME_CONDITION)
} }
func (c *csiDriverClient) NodeSupportsVolumeMountGroup(ctx context.Context) (bool, error) {
return c.nodeSupportsCapability(ctx, csipbv1.NodeServiceCapability_RPC_VOLUME_MOUNT_GROUP)
}
func (c *csiDriverClient) nodeSupportsCapability(ctx context.Context, capabilityType csipbv1.NodeServiceCapability_RPC_Type) (bool, error) { func (c *csiDriverClient) nodeSupportsCapability(ctx context.Context, capabilityType csipbv1.NodeServiceCapability_RPC_Type) (bool, error) {
klog.V(4).Info(log("calling NodeGetCapabilities rpc to determine if the node service has %s capability", capabilityType))
capabilities, err := c.nodeGetCapabilities(ctx) capabilities, err := c.nodeGetCapabilities(ctx)
if err != nil { if err != nil {
return false, err return false, err

View File

@ -23,6 +23,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"reflect" "reflect"
"strconv"
"testing" "testing"
csipbv1 "github.com/container-storage-interface/spec/lib/go/csi" csipbv1 "github.com/container-storage-interface/spec/lib/go/csi"
@ -72,6 +73,13 @@ func newFakeCsiDriverClientWithVolumeStatsAndCondition(t *testing.T, volumeStats
} }
} }
func newFakeCsiDriverClientWithVolumeMountGroup(t *testing.T, stagingCapable, volumeMountGroupSet bool) *fakeCsiDriverClient {
return &fakeCsiDriverClient{
t: t,
nodeClient: fake.NewNodeClientWithVolumeMountGroup(stagingCapable, volumeMountGroupSet),
}
}
func (c *fakeCsiDriverClient) NodeGetInfo(ctx context.Context) ( func (c *fakeCsiDriverClient) NodeGetInfo(ctx context.Context) (
nodeID string, nodeID string,
maxVolumePerNode int64, maxVolumePerNode int64,
@ -152,6 +160,7 @@ func (c *fakeCsiDriverClient) NodePublishVolume(
secrets map[string]string, secrets map[string]string,
fsType string, fsType string,
mountOptions []string, mountOptions []string,
fsGroup *int64,
) error { ) error {
c.t.Log("calling fake.NodePublishVolume...") c.t.Log("calling fake.NodePublishVolume...")
req := &csipbv1.NodePublishVolumeRequest{ req := &csipbv1.NodePublishVolumeRequest{
@ -174,11 +183,15 @@ func (c *fakeCsiDriverClient) NodePublishVolume(
Block: &csipbv1.VolumeCapability_BlockVolume{}, Block: &csipbv1.VolumeCapability_BlockVolume{},
} }
} else { } else {
req.VolumeCapability.AccessType = &csipbv1.VolumeCapability_Mount{ mountVolume := &csipbv1.VolumeCapability_MountVolume{
Mount: &csipbv1.VolumeCapability_MountVolume{
FsType: fsType, FsType: fsType,
MountFlags: mountOptions, MountFlags: mountOptions,
}, }
if fsGroup != nil {
mountVolume.VolumeMountGroup = strconv.FormatInt(*fsGroup, 10 /* base */)
}
req.VolumeCapability.AccessType = &csipbv1.VolumeCapability_Mount{
Mount: mountVolume,
} }
} }
@ -209,6 +222,7 @@ func (c *fakeCsiDriverClient) NodeStageVolume(ctx context.Context,
secrets map[string]string, secrets map[string]string,
volumeContext map[string]string, volumeContext map[string]string,
mountOptions []string, mountOptions []string,
fsGroup *int64,
) error { ) error {
c.t.Log("calling fake.NodeStageVolume...") c.t.Log("calling fake.NodeStageVolume...")
req := &csipbv1.NodeStageVolumeRequest{ req := &csipbv1.NodeStageVolumeRequest{
@ -228,11 +242,15 @@ func (c *fakeCsiDriverClient) NodeStageVolume(ctx context.Context,
Block: &csipbv1.VolumeCapability_BlockVolume{}, Block: &csipbv1.VolumeCapability_BlockVolume{},
} }
} else { } else {
req.VolumeCapability.AccessType = &csipbv1.VolumeCapability_Mount{ mountVolume := &csipbv1.VolumeCapability_MountVolume{
Mount: &csipbv1.VolumeCapability_MountVolume{
FsType: fsType, FsType: fsType,
MountFlags: mountOptions, MountFlags: mountOptions,
}, }
if fsGroup != nil {
mountVolume.VolumeMountGroup = strconv.FormatInt(*fsGroup, 10 /* base */)
}
req.VolumeCapability.AccessType = &csipbv1.VolumeCapability_Mount{
Mount: mountVolume,
} }
} }
@ -263,6 +281,28 @@ func (c *fakeCsiDriverClient) NodeSupportsStageUnstage(ctx context.Context) (boo
return c.nodeSupportsCapability(ctx, csipbv1.NodeServiceCapability_RPC_STAGE_UNSTAGE_VOLUME) return c.nodeSupportsCapability(ctx, csipbv1.NodeServiceCapability_RPC_STAGE_UNSTAGE_VOLUME)
} }
func (c *fakeCsiDriverClient) NodeSupportsVolumeMountGroup(ctx context.Context) (bool, error) {
c.t.Log("calling fake.NodeGetCapabilities for NodeSupportsVolumeMountGroup...")
req := &csipbv1.NodeGetCapabilitiesRequest{}
resp, err := c.nodeClient.NodeGetCapabilities(ctx, req)
if err != nil {
return false, err
}
capabilities := resp.GetCapabilities()
volumeMountGroupSet := false
if capabilities == nil {
return false, nil
}
for _, capability := range capabilities {
if capability.GetRpc().GetType() == csipbv1.NodeServiceCapability_RPC_VOLUME_MOUNT_GROUP {
volumeMountGroupSet = true
}
}
return volumeMountGroupSet, nil
}
func (c *fakeCsiDriverClient) NodeExpandVolume(ctx context.Context, opts csiResizeOptions) (resource.Quantity, error) { func (c *fakeCsiDriverClient) NodeExpandVolume(ctx context.Context, opts csiResizeOptions) (resource.Quantity, error) {
c.t.Log("calling fake.NodeExpandVolume") c.t.Log("calling fake.NodeExpandVolume")
req := &csipbv1.NodeExpandVolumeRequest{ req := &csipbv1.NodeExpandVolumeRequest{
@ -345,6 +385,10 @@ func setupClientWithVolumeStats(t *testing.T, volumeStatsSet bool) csiClient {
return newFakeCsiDriverClientWithVolumeStats(t, volumeStatsSet) return newFakeCsiDriverClientWithVolumeStats(t, volumeStatsSet)
} }
func setupClientWithVolumeMountGroup(t *testing.T, stageUnstageSet bool, volumeMountGroupSet bool) csiClient {
return newFakeCsiDriverClientWithVolumeMountGroup(t, stageUnstageSet, volumeMountGroupSet)
}
func checkErr(t *testing.T, expectedAnError bool, actualError error) { func checkErr(t *testing.T, expectedAnError bool, actualError error) {
t.Helper() t.Helper()
@ -423,6 +467,8 @@ func TestClientNodeGetInfo(t *testing.T) {
} }
func TestClientNodePublishVolume(t *testing.T) { func TestClientNodePublishVolume(t *testing.T) {
var testFSGroup int64 = 3000
tmpDir, err := utiltesting.MkTmpdir("csi-test") tmpDir, err := utiltesting.MkTmpdir("csi-test")
if err != nil { if err != nil {
t.Fatalf("can't create temp dir: %v", err) t.Fatalf("can't create temp dir: %v", err)
@ -435,6 +481,8 @@ func TestClientNodePublishVolume(t *testing.T) {
volID string volID string
targetPath string targetPath string
fsType string fsType string
fsGroup *int64
expectedVolumeMountGroup string
mustFail bool mustFail bool
err error err error
}{ }{
@ -443,16 +491,18 @@ func TestClientNodePublishVolume(t *testing.T) {
{name: "missing target path", volID: "vol-test", mustFail: true}, {name: "missing target path", volID: "vol-test", mustFail: true},
{name: "bad fs", volID: "vol-test", targetPath: testPath, fsType: "badfs", mustFail: true}, {name: "bad fs", volID: "vol-test", targetPath: testPath, fsType: "badfs", mustFail: true},
{name: "grpc error", volID: "vol-test", targetPath: testPath, mustFail: true, err: errors.New("grpc error")}, {name: "grpc error", volID: "vol-test", targetPath: testPath, mustFail: true, err: errors.New("grpc error")},
{name: "fsgroup", volID: "vol-test", targetPath: testPath, fsGroup: &testFSGroup, expectedVolumeMountGroup: "3000"},
} }
for _, tc := range testCases { for _, tc := range testCases {
t.Logf("test case: %s", tc.name) t.Logf("test case: %s", tc.name)
nodeClient := fake.NewNodeClient(false /* stagingCapable */)
nodeClient.SetNextError(tc.err)
fakeCloser := fake.NewCloser(t) fakeCloser := fake.NewCloser(t)
client := &csiDriverClient{ client := &csiDriverClient{
driverName: "Fake Driver Name", driverName: "Fake Driver Name",
nodeV1ClientCreator: func(addr csiAddr, m *MetricsManager) (csipbv1.NodeClient, io.Closer, error) { nodeV1ClientCreator: func(addr csiAddr, m *MetricsManager) (csipbv1.NodeClient, io.Closer, error) {
nodeClient := fake.NewNodeClient(false /* stagingCapable */)
nodeClient.SetNextError(tc.err)
return nodeClient, fakeCloser, nil return nodeClient, fakeCloser, nil
}, },
} }
@ -469,9 +519,15 @@ func TestClientNodePublishVolume(t *testing.T) {
map[string]string{}, map[string]string{},
tc.fsType, tc.fsType,
[]string{}, []string{},
tc.fsGroup,
) )
checkErr(t, tc.mustFail, err) checkErr(t, tc.mustFail, err)
volumeMountGroup := nodeClient.GetNodePublishedVolumes()[tc.volID].VolumeMountGroup
if volumeMountGroup != tc.expectedVolumeMountGroup {
t.Errorf("Expected VolumeMountGroup in NodePublishVolumeRequest to be %q, got: %q", tc.expectedVolumeMountGroup, volumeMountGroup)
}
if !tc.mustFail { if !tc.mustFail {
fakeCloser.Check() fakeCloser.Check()
} }
@ -521,6 +577,8 @@ func TestClientNodeUnpublishVolume(t *testing.T) {
} }
func TestClientNodeStageVolume(t *testing.T) { func TestClientNodeStageVolume(t *testing.T) {
var testFSGroup int64 = 3000
tmpDir, err := utiltesting.MkTmpdir("csi-test") tmpDir, err := utiltesting.MkTmpdir("csi-test")
if err != nil { if err != nil {
t.Fatalf("can't create temp dir: %v", err) t.Fatalf("can't create temp dir: %v", err)
@ -535,6 +593,8 @@ func TestClientNodeStageVolume(t *testing.T) {
fsType string fsType string
secrets map[string]string secrets map[string]string
mountOptions []string mountOptions []string
fsGroup *int64
expectedVolumeMountGroup string
mustFail bool mustFail bool
err error err error
}{ }{
@ -543,16 +603,18 @@ func TestClientNodeStageVolume(t *testing.T) {
{name: "missing target path", volID: "vol-test", mustFail: true}, {name: "missing target path", volID: "vol-test", mustFail: true},
{name: "bad fs", volID: "vol-test", stagingTargetPath: testPath, fsType: "badfs", mustFail: true}, {name: "bad fs", volID: "vol-test", stagingTargetPath: testPath, fsType: "badfs", mustFail: true},
{name: "grpc error", volID: "vol-test", stagingTargetPath: testPath, mustFail: true, err: errors.New("grpc error")}, {name: "grpc error", volID: "vol-test", stagingTargetPath: testPath, mustFail: true, err: errors.New("grpc error")},
{name: "fsgroup", volID: "vol-test", stagingTargetPath: testPath, fsGroup: &testFSGroup, expectedVolumeMountGroup: "3000"},
} }
for _, tc := range testCases { for _, tc := range testCases {
t.Logf("Running test case: %s", tc.name) t.Logf("Running test case: %s", tc.name)
nodeClient := fake.NewNodeClientWithVolumeMountGroup(true /* stagingCapable */, true /* volumeMountGroupCapable */)
nodeClient.SetNextError(tc.err)
fakeCloser := fake.NewCloser(t) fakeCloser := fake.NewCloser(t)
client := &csiDriverClient{ client := &csiDriverClient{
driverName: "Fake Driver Name", driverName: "Fake Driver Name",
nodeV1ClientCreator: func(addr csiAddr, m *MetricsManager) (csipbv1.NodeClient, io.Closer, error) { nodeV1ClientCreator: func(addr csiAddr, m *MetricsManager) (csipbv1.NodeClient, io.Closer, error) {
nodeClient := fake.NewNodeClient(false /* stagingCapable */)
nodeClient.SetNextError(tc.err)
return nodeClient, fakeCloser, nil return nodeClient, fakeCloser, nil
}, },
} }
@ -567,9 +629,15 @@ func TestClientNodeStageVolume(t *testing.T) {
tc.secrets, tc.secrets,
map[string]string{"attr0": "val0"}, map[string]string{"attr0": "val0"},
tc.mountOptions, tc.mountOptions,
tc.fsGroup,
) )
checkErr(t, tc.mustFail, err) checkErr(t, tc.mustFail, err)
volumeMountGroup := nodeClient.GetNodeStagedVolumes()[tc.volID].VolumeMountGroup
if volumeMountGroup != tc.expectedVolumeMountGroup {
t.Errorf("expected VolumeMountGroup parameter in NodePublishVolumeRequest to be %q, got: %q", tc.expectedVolumeMountGroup, volumeMountGroup)
}
if !tc.mustFail { if !tc.mustFail {
fakeCloser.Check() fakeCloser.Check()
} }
@ -621,6 +689,81 @@ func TestClientNodeUnstageVolume(t *testing.T) {
} }
} }
func TestClientNodeSupportsStageUnstage(t *testing.T) {
testClientNodeSupportsCapabilities(t,
func(client *csiDriverClient) (bool, error) {
return client.NodeSupportsStageUnstage(context.Background())
},
func(stagingCapable bool) *fake.NodeClient {
// Creates a staging-capable client
return fake.NewNodeClient(stagingCapable)
})
}
func TestClientNodeSupportsNodeExpand(t *testing.T) {
testClientNodeSupportsCapabilities(t,
func(client *csiDriverClient) (bool, error) {
return client.NodeSupportsNodeExpand(context.Background())
},
func(expansionCapable bool) *fake.NodeClient {
return fake.NewNodeClientWithExpansion(false /* stageCapable */, expansionCapable)
})
}
func TestClientNodeSupportsVolumeStats(t *testing.T) {
testClientNodeSupportsCapabilities(t,
func(client *csiDriverClient) (bool, error) {
return client.NodeSupportsVolumeStats(context.Background())
},
func(volumeStatsCapable bool) *fake.NodeClient {
return fake.NewNodeClientWithVolumeStats(volumeStatsCapable)
})
}
func TestClientNodeSupportsVolumeMountGroup(t *testing.T) {
testClientNodeSupportsCapabilities(t,
func(client *csiDriverClient) (bool, error) {
return client.NodeSupportsVolumeMountGroup(context.Background())
},
func(volumeMountGroupCapable bool) *fake.NodeClient {
return fake.NewNodeClientWithVolumeMountGroup(false /* stagingCapable */, volumeMountGroupCapable)
})
}
func testClientNodeSupportsCapabilities(
t *testing.T,
capabilityMethodToTest func(*csiDriverClient) (bool, error),
nodeClientGenerator func(bool) *fake.NodeClient) {
testCases := []struct {
name string
capable bool
}{
{name: "positive", capable: true},
{name: "negative", capable: false},
}
for _, tc := range testCases {
t.Logf("Running test case: %s", tc.name)
fakeCloser := fake.NewCloser(t)
client := &csiDriverClient{
driverName: "Fake Driver Name",
nodeV1ClientCreator: func(addr csiAddr, m *MetricsManager) (csipbv1.NodeClient, io.Closer, error) {
nodeClient := nodeClientGenerator(tc.capable)
return nodeClient, fakeCloser, nil
},
}
got, _ := capabilityMethodToTest(client)
if got != tc.capable {
t.Errorf("Expected capability support to be %v, got: %v", tc.capable, got)
}
fakeCloser.Check()
}
}
func TestNodeExpandVolume(t *testing.T) { func TestNodeExpandVolume(t *testing.T) {
testCases := []struct { testCases := []struct {
name string name string

View File

@ -235,6 +235,20 @@ func (c *csiMountMgr) SetUpAt(dir string, mounterArgs volume.MounterArgs) error
} }
volAttribs = mergeMap(volAttribs, serviceAccountTokenAttrs) volAttribs = mergeMap(volAttribs, serviceAccountTokenAttrs)
driverSupportsCSIVolumeMountGroup := false
var nodePublishFSGroupArg *int64
if utilfeature.DefaultFeatureGate.Enabled(features.DelegateFSGroupToCSIDriver) {
driverSupportsCSIVolumeMountGroup, err = csi.NodeSupportsVolumeMountGroup(ctx)
if err != nil {
return volumetypes.NewTransientOperationFailure(log("mounter.SetUpAt failed to determine if the node service has VOLUME_MOUNT_GROUP capability: %v", err))
}
if driverSupportsCSIVolumeMountGroup {
klog.V(3).Infof("Driver %s supports applying FSGroup (has VOLUME_MOUNT_GROUP node capability). Delegating FSGroup application to the driver through NodePublishVolume.", c.driverName)
nodePublishFSGroupArg = mounterArgs.FsGroup
}
}
err = csi.NodePublishVolume( err = csi.NodePublishVolume(
ctx, ctx,
volumeHandle, volumeHandle,
@ -247,6 +261,7 @@ func (c *csiMountMgr) SetUpAt(dir string, mounterArgs volume.MounterArgs) error
nodePublishSecrets, nodePublishSecrets,
fsType, fsType,
mountOptions, mountOptions,
nodePublishFSGroupArg,
) )
if err != nil { if err != nil {
@ -264,7 +279,9 @@ func (c *csiMountMgr) SetUpAt(dir string, mounterArgs volume.MounterArgs) error
klog.V(2).Info(log("error checking for SELinux support: %s", err)) klog.V(2).Info(log("error checking for SELinux support: %s", err))
} }
if c.supportsFSGroup(fsType, mounterArgs.FsGroup, c.fsGroupPolicy) { if !driverSupportsCSIVolumeMountGroup && c.supportsFSGroup(fsType, mounterArgs.FsGroup, c.fsGroupPolicy) {
// Driver doesn't support applying FSGroup. Kubelet must apply it instead.
// fullPluginName helps to distinguish different driver from csi plugin // fullPluginName helps to distinguish different driver from csi plugin
err := volume.SetVolumeOwnership(c, mounterArgs.FsGroup, mounterArgs.FSGroupChangePolicy, util.FSGroupCompleteHook(c.plugin, c.spec)) err := volume.SetVolumeOwnership(c, mounterArgs.FsGroup, mounterArgs.FSGroupChangePolicy, util.FSGroupCompleteHook(c.plugin, c.spec))
if err != nil { if err != nil {

View File

@ -657,6 +657,9 @@ func TestMounterSetUpWithFSGroup(t *testing.T) {
fsGroup int64 fsGroup int64
driverFSGroupPolicy bool driverFSGroupPolicy bool
supportMode storage.FSGroupPolicy supportMode storage.FSGroupPolicy
delegateFSGroupFeatureGate bool
driverSupportsVolumeMountGroup bool
expectedFSGroupInNodePublish string
}{ }{
{ {
name: "default fstype, with no fsgroup (should not apply fsgroup)", name: "default fstype, with no fsgroup (should not apply fsgroup)",
@ -785,12 +788,48 @@ func TestMounterSetUpWithFSGroup(t *testing.T) {
driverFSGroupPolicy: true, driverFSGroupPolicy: true,
supportMode: storage.FileFSGroupPolicy, supportMode: storage.FileFSGroupPolicy,
}, },
{
name: "fsgroup provided, DelegateFSGroupToCSIDriver feature enabled, driver supports volume mount group; expect fsgroup to be passed to NodePublishVolume",
fsType: "ext4",
setFsGroup: true,
fsGroup: 3000,
delegateFSGroupFeatureGate: true,
driverSupportsVolumeMountGroup: true,
expectedFSGroupInNodePublish: "3000",
},
{
name: "fsgroup not provided, DelegateFSGroupToCSIDriver feature enabled, driver supports volume mount group; expect fsgroup not to be passed to NodePublishVolume",
fsType: "ext4",
setFsGroup: false,
delegateFSGroupFeatureGate: true,
driverSupportsVolumeMountGroup: true,
expectedFSGroupInNodePublish: "",
},
{
name: "fsgroup provided, DelegateFSGroupToCSIDriver feature enabled, driver does not support volume mount group; expect fsgroup not to be passed to NodePublishVolume",
fsType: "ext4",
setFsGroup: true,
fsGroup: 3000,
delegateFSGroupFeatureGate: true,
driverSupportsVolumeMountGroup: false,
expectedFSGroupInNodePublish: "",
},
{
name: "fsgroup provided, DelegateFSGroupToCSIDriver feature disabled, driver supports volume mount group; expect fsgroup not to be passed to NodePublishVolume",
fsType: "ext4",
setFsGroup: true,
fsGroup: 3000,
delegateFSGroupFeatureGate: false,
driverSupportsVolumeMountGroup: true,
expectedFSGroupInNodePublish: "",
},
} }
for i, tc := range testCases { for i, tc := range testCases {
t.Logf("Running test %s", tc.name) t.Logf("Running test %s", tc.name)
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSIVolumeFSGroupPolicy, tc.driverFSGroupPolicy)() defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSIVolumeFSGroupPolicy, tc.driverFSGroupPolicy)()
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.DelegateFSGroupToCSIDriver, tc.delegateFSGroupFeatureGate)()
volName := fmt.Sprintf("test-vol-%d", i) volName := fmt.Sprintf("test-vol-%d", i)
registerFakePlugin(testDriver, "endpoint", []string{"1.0.0"}, t) registerFakePlugin(testDriver, "endpoint", []string{"1.0.0"}, t)
@ -821,7 +860,7 @@ func TestMounterSetUpWithFSGroup(t *testing.T) {
if tc.driverFSGroupPolicy { if tc.driverFSGroupPolicy {
csiMounter.fsGroupPolicy = tc.supportMode csiMounter.fsGroupPolicy = tc.supportMode
} }
csiMounter.csiClient = setupClient(t, true) csiMounter.csiClient = setupClientWithVolumeMountGroup(t, true /* stageUnstageSet */, tc.driverSupportsVolumeMountGroup)
attachID := getAttachmentName(csiMounter.volumeID, string(csiMounter.driverName), string(plug.host.GetNodeName())) attachID := getAttachmentName(csiMounter.volumeID, string(csiMounter.driverName), string(plug.host.GetNodeName()))
attachment := makeTestAttachment(attachID, "test-node", pvName) attachment := makeTestAttachment(attachID, "test-node", pvName)
@ -854,6 +893,9 @@ func TestMounterSetUpWithFSGroup(t *testing.T) {
if pubs[csiMounter.volumeID].Path != csiMounter.GetPath() { if pubs[csiMounter.volumeID].Path != csiMounter.GetPath() {
t.Error("csi server may not have received NodePublishVolume call") t.Error("csi server may not have received NodePublishVolume call")
} }
if pubs[csiMounter.volumeID].VolumeMountGroup != tc.expectedFSGroupInNodePublish {
t.Errorf("expected VolumeMountGroup parameter in NodePublishVolumeRequest to be %q, got: %q", tc.expectedFSGroupInNodePublish, pubs[csiMounter.volumeID].VolumeMountGroup)
}
} }
} }

View File

@ -400,7 +400,7 @@ func TestCSI_VolumeAll(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("csiTest.VolumeAll deviceMounter.GetdeviceMountPath failed %s", err) t.Fatalf("csiTest.VolumeAll deviceMounter.GetdeviceMountPath failed %s", err)
} }
if err := csiDevMounter.MountDevice(volSpec, devicePath, devMountPath); err != nil { if err := csiDevMounter.MountDevice(volSpec, devicePath, devMountPath, volume.DeviceMounterArgs{}); err != nil {
t.Fatalf("csiTest.VolumeAll deviceMounter.MountDevice failed: %v", err) t.Fatalf("csiTest.VolumeAll deviceMounter.MountDevice failed: %v", err)
} }
t.Log("csiTest.VolumeAll device mounted at path:", devMountPath) t.Log("csiTest.VolumeAll device mounted at path:", devMountPath)

View File

@ -75,6 +75,7 @@ type CSIVolume struct {
DeviceMountPath string DeviceMountPath string
FSType string FSType string
MountFlags []string MountFlags []string
VolumeMountGroup string
} }
// NodeClient returns CSI node client // NodeClient returns CSI node client
@ -86,6 +87,7 @@ type NodeClient struct {
volumeStatsSet bool volumeStatsSet bool
volumeConditionSet bool volumeConditionSet bool
singleNodeMultiWriterSet bool singleNodeMultiWriterSet bool
volumeMountGroupSet bool
nodeGetInfoResp *csipb.NodeGetInfoResponse nodeGetInfoResp *csipb.NodeGetInfoResponse
nodeVolumeStatsResp *csipb.NodeGetVolumeStatsResponse nodeVolumeStatsResp *csipb.NodeGetVolumeStatsResponse
FakeNodeExpansionRequest *csipb.NodeExpandVolumeRequest FakeNodeExpansionRequest *csipb.NodeExpandVolumeRequest
@ -134,6 +136,15 @@ func NewNodeClientWithSingleNodeMultiWriter(singleNodeMultiWriterSet bool) *Node
} }
} }
func NewNodeClientWithVolumeMountGroup(stageUnstageSet, volumeMountGroupSet bool) *NodeClient {
return &NodeClient{
nodePublishedVolumes: make(map[string]CSIVolume),
nodeStagedVolumes: make(map[string]CSIVolume),
stageUnstageSet: stageUnstageSet,
volumeMountGroupSet: volumeMountGroupSet,
}
}
// SetNextError injects next expected error // SetNextError injects next expected error
func (f *NodeClient) SetNextError(err error) { func (f *NodeClient) SetNextError(err error) {
f.nextErr = err f.nextErr = err
@ -217,6 +228,7 @@ func (f *NodeClient) NodePublishVolume(ctx context.Context, req *csipb.NodePubli
if req.GetVolumeCapability().GetMount() != nil { if req.GetVolumeCapability().GetMount() != nil {
publishedVolume.FSType = req.GetVolumeCapability().GetMount().FsType publishedVolume.FSType = req.GetVolumeCapability().GetMount().FsType
publishedVolume.MountFlags = req.GetVolumeCapability().GetMount().MountFlags publishedVolume.MountFlags = req.GetVolumeCapability().GetMount().MountFlags
publishedVolume.VolumeMountGroup = req.GetVolumeCapability().GetMount().VolumeMountGroup
} }
f.nodePublishedVolumes[req.GetVolumeId()] = publishedVolume f.nodePublishedVolumes[req.GetVolumeId()] = publishedVolume
return &csipb.NodePublishVolumeResponse{}, nil return &csipb.NodePublishVolumeResponse{}, nil
@ -268,6 +280,7 @@ func (f *NodeClient) NodeStageVolume(ctx context.Context, req *csipb.NodeStageVo
if mounted != nil { if mounted != nil {
fsType = mounted.GetFsType() fsType = mounted.GetFsType()
csiVol.MountFlags = mounted.GetMountFlags() csiVol.MountFlags = mounted.GetMountFlags()
csiVol.VolumeMountGroup = mounted.VolumeMountGroup
} }
if !strings.Contains(fsTypes, fsType) { if !strings.Contains(fsTypes, fsType) {
return nil, errors.New("invalid fstype") return nil, errors.New("invalid fstype")
@ -385,6 +398,16 @@ func (f *NodeClient) NodeGetCapabilities(ctx context.Context, in *csipb.NodeGetC
}, },
}) })
} }
if f.volumeMountGroupSet {
resp.Capabilities = append(resp.Capabilities, &csipb.NodeServiceCapability{
Type: &csipb.NodeServiceCapability_Rpc{
Rpc: &csipb.NodeServiceCapability_RPC{
Type: csipb.NodeServiceCapability_RPC_VOLUME_MOUNT_GROUP,
},
},
})
}
return resp, nil return resp, nil
} }

View File

@ -94,7 +94,7 @@ func (attacher *fcAttacher) GetDeviceMountPath(
return attacher.manager.MakeGlobalPDName(*mounter.fcDisk), nil return attacher.manager.MakeGlobalPDName(*mounter.fcDisk), nil
} }
func (attacher *fcAttacher) MountDevice(spec *volume.Spec, devicePath string, deviceMountPath string) error { func (attacher *fcAttacher) MountDevice(spec *volume.Spec, devicePath string, deviceMountPath string, _ volume.DeviceMounterArgs) error {
mounter := attacher.host.GetMounter(fcPluginName) mounter := attacher.host.GetMounter(fcPluginName)
notMnt, err := mounter.IsLikelyNotMountPoint(deviceMountPath) notMnt, err := mounter.IsLikelyNotMountPoint(deviceMountPath)
if err != nil { if err != nil {

View File

@ -19,7 +19,7 @@ package flexvolume
import ( import (
"time" "time"
"k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
"k8s.io/klog/v2" "k8s.io/klog/v2"
"k8s.io/kubernetes/pkg/volume" "k8s.io/kubernetes/pkg/volume"
@ -70,7 +70,7 @@ func (a *flexVolumeAttacher) GetDeviceMountPath(spec *volume.Spec) (string, erro
} }
// MountDevice is part of the volume.Attacher interface // MountDevice is part of the volume.Attacher interface
func (a *flexVolumeAttacher) MountDevice(spec *volume.Spec, devicePath string, deviceMountPath string) error { func (a *flexVolumeAttacher) MountDevice(spec *volume.Spec, devicePath string, deviceMountPath string, _ volume.DeviceMounterArgs) error {
// Mount only once. // Mount only once.
alreadyMounted, err := prepareForMount(a.plugin.host.GetMounter(a.plugin.GetPluginName()), deviceMountPath) alreadyMounted, err := prepareForMount(a.plugin.host.GetMounter(a.plugin.GetPluginName()), deviceMountPath)
if err != nil { if err != nil {

View File

@ -20,7 +20,7 @@ import (
"testing" "testing"
"time" "time"
"k8s.io/api/core/v1" v1 "k8s.io/api/core/v1"
"k8s.io/kubernetes/pkg/volume" "k8s.io/kubernetes/pkg/volume"
"k8s.io/kubernetes/test/utils/harness" "k8s.io/kubernetes/test/utils/harness"
) )
@ -70,7 +70,7 @@ func TestMountDevice(tt *testing.T) {
) )
a, _ := plugin.NewAttacher() a, _ := plugin.NewAttacher()
a.MountDevice(spec, "/dev/sdx", rootDir+"/mount-dir") a.MountDevice(spec, "/dev/sdx", rootDir+"/mount-dir", volume.DeviceMounterArgs{})
} }
func TestIsVolumeAttached(tt *testing.T) { func TestIsVolumeAttached(tt *testing.T) {

View File

@ -288,7 +288,7 @@ func (attacher *gcePersistentDiskAttacher) GetDeviceMountPath(
return makeGlobalPDName(attacher.host, volumeSource.PDName), nil return makeGlobalPDName(attacher.host, volumeSource.PDName), nil
} }
func (attacher *gcePersistentDiskAttacher) MountDevice(spec *volume.Spec, devicePath string, deviceMountPath string) error { func (attacher *gcePersistentDiskAttacher) MountDevice(spec *volume.Spec, devicePath string, deviceMountPath string, _ volume.DeviceMounterArgs) error {
// Only mount the PD globally once. // Only mount the PD globally once.
mounter := attacher.host.GetMounter(gcePersistentDiskPluginName) mounter := attacher.host.GetMounter(gcePersistentDiskPluginName)
notMnt, err := mounter.IsLikelyNotMountPoint(deviceMountPath) notMnt, err := mounter.IsLikelyNotMountPoint(deviceMountPath)

View File

@ -98,7 +98,7 @@ func (attacher *iscsiAttacher) GetDeviceMountPath(
return attacher.manager.MakeGlobalPDName(*mounter.iscsiDisk), nil return attacher.manager.MakeGlobalPDName(*mounter.iscsiDisk), nil
} }
func (attacher *iscsiAttacher) MountDevice(spec *volume.Spec, devicePath string, deviceMountPath string) error { func (attacher *iscsiAttacher) MountDevice(spec *volume.Spec, devicePath string, deviceMountPath string, _ volume.DeviceMounterArgs) error {
mounter := attacher.host.GetMounter(iscsiPluginName) mounter := attacher.host.GetMounter(iscsiPluginName)
notMnt, err := mounter.IsLikelyNotMountPoint(deviceMountPath) notMnt, err := mounter.IsLikelyNotMountPoint(deviceMountPath)
if err != nil { if err != nil {

View File

@ -355,7 +355,7 @@ func (dm *deviceMounter) mountLocalBlockDevice(spec *volume.Spec, devicePath str
return nil return nil
} }
func (dm *deviceMounter) MountDevice(spec *volume.Spec, devicePath string, deviceMountPath string) error { func (dm *deviceMounter) MountDevice(spec *volume.Spec, devicePath string, deviceMountPath string, _ volume.DeviceMounterArgs) error {
if spec.PersistentVolume.Spec.Local == nil || len(spec.PersistentVolume.Spec.Local.Path) == 0 { if spec.PersistentVolume.Spec.Local == nil || len(spec.PersistentVolume.Spec.Local.Path) == 0 {
return fmt.Errorf("local volume source is nil or local path is not set") return fmt.Errorf("local volume source is nil or local path is not set")
} }

View File

@ -231,7 +231,7 @@ func TestBlockDeviceGlobalPathAndMountDevice(t *testing.T) {
fmt.Println("expected global path is:", expectedGlobalPath) fmt.Println("expected global path is:", expectedGlobalPath)
err = dm.MountDevice(pvSpec, tmpBlockDir, expectedGlobalPath) err = dm.MountDevice(pvSpec, tmpBlockDir, expectedGlobalPath, volume.DeviceMounterArgs{})
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -276,7 +276,7 @@ func TestFSGlobalPathAndMountDevice(t *testing.T) {
} }
// Actually, we will do nothing if the local path is FS type // Actually, we will do nothing if the local path is FS type
err = dm.MountDevice(pvSpec, tmpFSDir, expectedGlobalPath) err = dm.MountDevice(pvSpec, tmpFSDir, expectedGlobalPath, volume.DeviceMounterArgs{})
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View File

@ -146,7 +146,7 @@ func (attacher *rbdAttacher) GetDeviceMountPath(spec *volume.Spec) (string, erro
// MountDevice implements Attacher.MountDevice. It is called by the kubelet to // MountDevice implements Attacher.MountDevice. It is called by the kubelet to
// mount device at the given mount path. // mount device at the given mount path.
// This method is idempotent, callers are responsible for retrying on failure. // This method is idempotent, callers are responsible for retrying on failure.
func (attacher *rbdAttacher) MountDevice(spec *volume.Spec, devicePath string, deviceMountPath string) error { func (attacher *rbdAttacher) MountDevice(spec *volume.Spec, devicePath string, deviceMountPath string, _ volume.DeviceMounterArgs) error {
klog.V(4).Infof("rbd: mouting device %s to %s", devicePath, deviceMountPath) klog.V(4).Infof("rbd: mouting device %s to %s", devicePath, deviceMountPath)
notMnt, err := attacher.mounter.IsLikelyNotMountPoint(deviceMountPath) notMnt, err := attacher.mounter.IsLikelyNotMountPoint(deviceMountPath)
if err != nil { if err != nil {

View File

@ -281,7 +281,7 @@ func doTestPlugin(t *testing.T, c *testcase) {
if deviceMountPath != c.expectedDeviceMountPath { if deviceMountPath != c.expectedDeviceMountPath {
t.Errorf("Unexpected mount path, expected %q, not: %q", c.expectedDeviceMountPath, deviceMountPath) t.Errorf("Unexpected mount path, expected %q, not: %q", c.expectedDeviceMountPath, deviceMountPath)
} }
err = attacher.MountDevice(c.spec, devicePath, deviceMountPath) err = attacher.MountDevice(c.spec, devicePath, deviceMountPath, volume.DeviceMounterArgs{})
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View File

@ -1046,7 +1046,7 @@ func (fv *FakeVolume) mountDeviceInternal(spec *Spec, devicePath string, deviceM
return nil return nil
} }
func (fv *FakeVolume) MountDevice(spec *Spec, devicePath string, deviceMountPath string) error { func (fv *FakeVolume) MountDevice(spec *Spec, devicePath string, deviceMountPath string, _ volume.DeviceMounterArgs) error {
return fv.mountDeviceInternal(spec, devicePath, deviceMountPath) return fv.mountDeviceInternal(spec, devicePath, deviceMountPath)
} }

View File

@ -616,7 +616,9 @@ func (og *operationGenerator) GenerateMountVolumeFunc(
err = volumeDeviceMounter.MountDevice( err = volumeDeviceMounter.MountDevice(
volumeToMount.VolumeSpec, volumeToMount.VolumeSpec,
devicePath, devicePath,
deviceMountPath) deviceMountPath,
volume.DeviceMounterArgs{FsGroup: fsGroup},
)
if err != nil { if err != nil {
og.checkForFailedMount(volumeToMount, err) og.checkForFailedMount(volumeToMount, err)
og.markDeviceErrorState(volumeToMount, devicePath, deviceMountPath, err, actualStateOfWorld) og.markDeviceErrorState(volumeToMount, devicePath, deviceMountPath, err, actualStateOfWorld)

View File

@ -271,6 +271,11 @@ type Attacher interface {
WaitForAttach(spec *Spec, devicePath string, pod *v1.Pod, timeout time.Duration) (string, error) WaitForAttach(spec *Spec, devicePath string, pod *v1.Pod, timeout time.Duration) (string, error)
} }
// DeviceMounterArgs provides auxiliary, optional arguments to DeviceMounter.
type DeviceMounterArgs struct {
FsGroup *int64
}
// DeviceMounter can mount a block volume to a global path. // DeviceMounter can mount a block volume to a global path.
type DeviceMounter interface { type DeviceMounter interface {
// GetDeviceMountPath returns a path where the device should // GetDeviceMountPath returns a path where the device should
@ -285,7 +290,7 @@ type DeviceMounter interface {
// - TransientOperationFailure // - TransientOperationFailure
// - UncertainProgressError // - UncertainProgressError
// - Error of any other type should be considered a final error // - Error of any other type should be considered a final error
MountDevice(spec *Spec, devicePath string, deviceMountPath string) error MountDevice(spec *Spec, devicePath string, deviceMountPath string, deviceMounterArgs DeviceMounterArgs) error
} }
type BulkVolumeVerifier interface { type BulkVolumeVerifier interface {

View File

@ -208,7 +208,7 @@ func (plugin *vsphereVolumePlugin) GetDeviceMountRefs(deviceMountPath string) ([
} }
// MountDevice mounts device to global mount point. // MountDevice mounts device to global mount point.
func (attacher *vsphereVMDKAttacher) MountDevice(spec *volume.Spec, devicePath string, deviceMountPath string) error { func (attacher *vsphereVMDKAttacher) MountDevice(spec *volume.Spec, devicePath string, deviceMountPath string, _ volume.DeviceMounterArgs) error {
klog.Infof("vsphere MountDevice mount %s to %s", devicePath, deviceMountPath) klog.Infof("vsphere MountDevice mount %s to %s", devicePath, deviceMountPath)
mounter := attacher.host.GetMounter(vsphereVolumePluginName) mounter := attacher.host.GetMounter(vsphereVolumePluginName)
notMnt, err := mounter.IsLikelyNotMountPoint(deviceMountPath) notMnt, err := mounter.IsLikelyNotMountPoint(deviceMountPath)