mirror of
				https://github.com/optim-enterprises-bv/kubernetes.git
				synced 2025-11-04 04:08:16 +00:00 
			
		
		
		
	Merge pull request #126014 from PannagaRao/kep-ephemeral-storage-quota
pkg/volume/*: Enable quotas in user namespace
This commit is contained in:
		@@ -1087,7 +1087,7 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	LegacyServiceAccountTokenCleanUp: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // GA in 1.30; remove in 1.32
 | 
						LegacyServiceAccountTokenCleanUp: {Default: true, PreRelease: featuregate.GA, LockToDefault: true}, // GA in 1.30; remove in 1.32
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	LocalStorageCapacityIsolationFSQuotaMonitoring: {Default: false, PreRelease: featuregate.Alpha},
 | 
						LocalStorageCapacityIsolationFSQuotaMonitoring: {Default: true, PreRelease: featuregate.Beta},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	LogarithmicScaleDown: {Default: true, PreRelease: featuregate.GA, LockToDefault: true},
 | 
						LogarithmicScaleDown: {Default: true, PreRelease: featuregate.GA, LockToDefault: true},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -386,6 +386,8 @@ func (m *UsernsManager) createUserNs(pod *v1.Pod) (userNs userNamespace, err err
 | 
				
			|||||||
func (m *UsernsManager) GetOrCreateUserNamespaceMappings(pod *v1.Pod, runtimeHandler string) (*runtimeapi.UserNamespace, error) {
 | 
					func (m *UsernsManager) GetOrCreateUserNamespaceMappings(pod *v1.Pod, runtimeHandler string) (*runtimeapi.UserNamespace, error) {
 | 
				
			||||||
	featureEnabled := utilfeature.DefaultFeatureGate.Enabled(features.UserNamespacesSupport)
 | 
						featureEnabled := utilfeature.DefaultFeatureGate.Enabled(features.UserNamespacesSupport)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// TODO: If the default value for hostUsers ever changes, change the default value of
 | 
				
			||||||
 | 
						// userNamespacesEnabled as well
 | 
				
			||||||
	if pod == nil || pod.Spec.HostUsers == nil {
 | 
						if pod == nil || pod.Spec.HostUsers == nil {
 | 
				
			||||||
		// if the feature is enabled, specify to use the node mode...
 | 
							// if the feature is enabled, specify to use the node mode...
 | 
				
			||||||
		if featureEnabled {
 | 
							if featureEnabled {
 | 
				
			||||||
@@ -512,3 +514,7 @@ func (m *UsernsManager) CleanupOrphanedPodUsernsAllocations(pods []*v1.Pod, runn
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func EnabledUserNamespacesSupport() bool {
 | 
				
			||||||
 | 
						return utilfeature.DefaultFeatureGate.Enabled(features.UserNamespacesSupport)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -34,6 +34,7 @@ import (
 | 
				
			|||||||
	v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper"
 | 
						v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper"
 | 
				
			||||||
	"k8s.io/kubernetes/pkg/features"
 | 
						"k8s.io/kubernetes/pkg/features"
 | 
				
			||||||
	"k8s.io/kubernetes/pkg/kubelet/cm"
 | 
						"k8s.io/kubernetes/pkg/kubelet/cm"
 | 
				
			||||||
 | 
						usernamespacefeature "k8s.io/kubernetes/pkg/kubelet/userns"
 | 
				
			||||||
	"k8s.io/kubernetes/pkg/volume"
 | 
						"k8s.io/kubernetes/pkg/volume"
 | 
				
			||||||
	volumeutil "k8s.io/kubernetes/pkg/volume/util"
 | 
						volumeutil "k8s.io/kubernetes/pkg/volume/util"
 | 
				
			||||||
	"k8s.io/kubernetes/pkg/volume/util/fsquota"
 | 
						"k8s.io/kubernetes/pkg/volume/util/fsquota"
 | 
				
			||||||
@@ -294,12 +295,19 @@ func (ed *emptyDir) assignQuota(dir string, mounterSize *resource.Quantity) erro
 | 
				
			|||||||
	if mounterSize != nil {
 | 
						if mounterSize != nil {
 | 
				
			||||||
		// Deliberately shadow the outer use of err as noted
 | 
							// Deliberately shadow the outer use of err as noted
 | 
				
			||||||
		// above.
 | 
							// above.
 | 
				
			||||||
		hasQuotas, err := fsquota.SupportsQuotas(ed.mounter, dir)
 | 
							// hostUsers field is interpreted as true by default, so a pod is by default
 | 
				
			||||||
 | 
							// not confined by a user namespace.
 | 
				
			||||||
 | 
							userNamespacesEnabled := false
 | 
				
			||||||
 | 
							if usernamespacefeature.EnabledUserNamespacesSupport() {
 | 
				
			||||||
 | 
								userNamespacesEnabled = ed.pod.Spec.HostUsers != nil && !*ed.pod.Spec.HostUsers
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							hasQuotas, err := fsquota.SupportsQuotas(ed.mounter, dir, userNamespacesEnabled)
 | 
				
			||||||
 | 
							klog.V(3).Infof("assignQuota called, hasQuotas = %t userNamespacesEnabled = %t", hasQuotas, userNamespacesEnabled)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			klog.V(3).Infof("Unable to check for quota support on %s: %s", dir, err.Error())
 | 
								klog.V(3).Infof("Unable to check for quota support on %s: %s", dir, err.Error())
 | 
				
			||||||
		} else if hasQuotas {
 | 
							} else if hasQuotas {
 | 
				
			||||||
			klog.V(4).Infof("emptydir trying to assign quota %v on %s", mounterSize, dir)
 | 
								klog.V(3).Infof("emptydir trying to assign quota %v on %s", mounterSize, dir)
 | 
				
			||||||
			if err := fsquota.AssignQuota(ed.mounter, dir, ed.pod.UID, mounterSize); err != nil {
 | 
								if err := fsquota.AssignQuota(ed.mounter, dir, ed.pod.UID, mounterSize, userNamespacesEnabled); err != nil {
 | 
				
			||||||
				klog.V(3).Infof("Set quota on %s failed %s", dir, err.Error())
 | 
									klog.V(3).Infof("Set quota on %s failed %s", dir, err.Error())
 | 
				
			||||||
				return err
 | 
									return err
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
@@ -514,7 +522,11 @@ func (ed *emptyDir) TearDownAt(dir string) error {
 | 
				
			|||||||
func (ed *emptyDir) teardownDefault(dir string) error {
 | 
					func (ed *emptyDir) teardownDefault(dir string) error {
 | 
				
			||||||
	if utilfeature.DefaultFeatureGate.Enabled(features.LocalStorageCapacityIsolationFSQuotaMonitoring) {
 | 
						if utilfeature.DefaultFeatureGate.Enabled(features.LocalStorageCapacityIsolationFSQuotaMonitoring) {
 | 
				
			||||||
		// Remove any quota
 | 
							// Remove any quota
 | 
				
			||||||
		err := fsquota.ClearQuota(ed.mounter, dir)
 | 
							userNamespacesEnabled := false
 | 
				
			||||||
 | 
							if usernamespacefeature.EnabledUserNamespacesSupport() {
 | 
				
			||||||
 | 
								userNamespacesEnabled = ed.pod.Spec.HostUsers != nil && !*ed.pod.Spec.HostUsers
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							err := fsquota.ClearQuota(ed.mounter, dir, userNamespacesEnabled)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			klog.Warningf("Warning: Failed to clear quota on %s: %v", dir, err)
 | 
								klog.Warningf("Warning: Failed to clear quota on %s: %v", dir, err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -225,11 +225,11 @@ func GetQuotaOnDir(m mount.Interface, path string) (common.QuotaID, error) {
 | 
				
			|||||||
	return getApplier(path).GetQuotaOnDir(path)
 | 
						return getApplier(path).GetQuotaOnDir(path)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func clearQuotaOnDir(m mount.Interface, path string) error {
 | 
					func clearQuotaOnDir(m mount.Interface, path string, userNamespacesEnabled bool) error {
 | 
				
			||||||
	// Since we may be called without path being in the map,
 | 
						// Since we may be called without path being in the map,
 | 
				
			||||||
	// we explicitly have to check in this case.
 | 
						// we explicitly have to check in this case.
 | 
				
			||||||
	klog.V(4).Infof("clearQuotaOnDir %s", path)
 | 
						klog.V(4).Infof("clearQuotaOnDir %s", path)
 | 
				
			||||||
	supportsQuotas, err := SupportsQuotas(m, path)
 | 
						supportsQuotas, err := SupportsQuotas(m, path, userNamespacesEnabled)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		// Log-and-continue instead of returning an error for now
 | 
							// Log-and-continue instead of returning an error for now
 | 
				
			||||||
		// due to unspecified backwards compatibility concerns (a subject to revise)
 | 
							// due to unspecified backwards compatibility concerns (a subject to revise)
 | 
				
			||||||
@@ -269,11 +269,17 @@ func clearQuotaOnDir(m mount.Interface, path string) error {
 | 
				
			|||||||
// don't cache the result because nothing will clean it up.
 | 
					// don't cache the result because nothing will clean it up.
 | 
				
			||||||
// However, do cache the device->applier map; the number of devices
 | 
					// However, do cache the device->applier map; the number of devices
 | 
				
			||||||
// is bounded.
 | 
					// is bounded.
 | 
				
			||||||
func SupportsQuotas(m mount.Interface, path string) (bool, error) {
 | 
					// User namespaces prevent changes to project IDs on the filesystem,
 | 
				
			||||||
 | 
					// ensuring xfs-quota metrics' reliability; hence, userNamespacesEnabled is checked.
 | 
				
			||||||
 | 
					func SupportsQuotas(m mount.Interface, path string, userNamespacesEnabled bool) (bool, error) {
 | 
				
			||||||
	if !enabledQuotasForMonitoring() {
 | 
						if !enabledQuotasForMonitoring() {
 | 
				
			||||||
		klog.V(3).Info("SupportsQuotas called, but quotas disabled")
 | 
							klog.V(3).Info("SupportsQuotas called, but quotas disabled")
 | 
				
			||||||
		return false, nil
 | 
							return false, nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						if !userNamespacesEnabled {
 | 
				
			||||||
 | 
							klog.V(3).Info("SupportQuotas called and LocalStorageCapacityIsolationFSQuotaMonitoring enabled, but pod is not in a user namespace")
 | 
				
			||||||
 | 
							return false, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	supportsQuotasLock.Lock()
 | 
						supportsQuotasLock.Lock()
 | 
				
			||||||
	defer supportsQuotasLock.Unlock()
 | 
						defer supportsQuotasLock.Unlock()
 | 
				
			||||||
	if supportsQuotas, ok := supportsQuotasMap[path]; ok {
 | 
						if supportsQuotas, ok := supportsQuotasMap[path]; ok {
 | 
				
			||||||
@@ -307,12 +313,12 @@ func SupportsQuotas(m mount.Interface, path string) (bool, error) {
 | 
				
			|||||||
// AssignQuota chooses the quota ID based on the pod UID and path.
 | 
					// AssignQuota chooses the quota ID based on the pod UID and path.
 | 
				
			||||||
// If the pod UID is identical to another one known, it may (but presently
 | 
					// If the pod UID is identical to another one known, it may (but presently
 | 
				
			||||||
// doesn't) choose the same quota ID as other volumes in the pod.
 | 
					// doesn't) choose the same quota ID as other volumes in the pod.
 | 
				
			||||||
func AssignQuota(m mount.Interface, path string, poduid types.UID, bytes *resource.Quantity) error { //nolint:staticcheck
 | 
					func AssignQuota(m mount.Interface, path string, poduid types.UID, bytes *resource.Quantity, userNamespacesEnabled bool) error { //nolint:staticcheck
 | 
				
			||||||
	if bytes == nil {
 | 
						if bytes == nil {
 | 
				
			||||||
		return fmt.Errorf("attempting to assign null quota to %s", path)
 | 
							return fmt.Errorf("attempting to assign null quota to %s", path)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	ibytes := bytes.Value()
 | 
						ibytes := bytes.Value()
 | 
				
			||||||
	if ok, err := SupportsQuotas(m, path); !ok {
 | 
						if ok, err := SupportsQuotas(m, path, userNamespacesEnabled); !ok {
 | 
				
			||||||
		return fmt.Errorf("quotas not supported on %s: %v", path, err)
 | 
							return fmt.Errorf("quotas not supported on %s: %v", path, err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	quotaLock.Lock()
 | 
						quotaLock.Lock()
 | 
				
			||||||
@@ -410,7 +416,7 @@ func GetInodes(path string) (*resource.Quantity, error) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ClearQuota -- remove the quota assigned to a directory
 | 
					// ClearQuota -- remove the quota assigned to a directory
 | 
				
			||||||
func ClearQuota(m mount.Interface, path string) error {
 | 
					func ClearQuota(m mount.Interface, path string, userNamespacesEnabled bool) error {
 | 
				
			||||||
	klog.V(3).Infof("ClearQuota %s", path)
 | 
						klog.V(3).Infof("ClearQuota %s", path)
 | 
				
			||||||
	if !enabledQuotasForMonitoring() {
 | 
						if !enabledQuotasForMonitoring() {
 | 
				
			||||||
		return fmt.Errorf("clearQuota called, but quotas disabled")
 | 
							return fmt.Errorf("clearQuota called, but quotas disabled")
 | 
				
			||||||
@@ -426,7 +432,7 @@ func ClearQuota(m mount.Interface, path string) error {
 | 
				
			|||||||
		// be found, which needs to be cleaned up.
 | 
							// be found, which needs to be cleaned up.
 | 
				
			||||||
		defer delete(supportsQuotasMap, path)
 | 
							defer delete(supportsQuotasMap, path)
 | 
				
			||||||
		defer clearApplier(path)
 | 
							defer clearApplier(path)
 | 
				
			||||||
		return clearQuotaOnDir(m, path)
 | 
							return clearQuotaOnDir(m, path, userNamespacesEnabled)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	_, ok = podQuotaMap[poduid]
 | 
						_, ok = podQuotaMap[poduid]
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
@@ -443,7 +449,7 @@ func ClearQuota(m mount.Interface, path string) error {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	count, ok := podDirCountMap[poduid]
 | 
						count, ok := podDirCountMap[poduid]
 | 
				
			||||||
	if count <= 1 || !ok {
 | 
						if count <= 1 || !ok {
 | 
				
			||||||
		err = clearQuotaOnDir(m, path)
 | 
							err = clearQuotaOnDir(m, path, userNamespacesEnabled)
 | 
				
			||||||
		// This error should be noted; we still need to clean up
 | 
							// This error should be noted; we still need to clean up
 | 
				
			||||||
		// and otherwise handle in the same way.
 | 
							// and otherwise handle in the same way.
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -366,19 +366,19 @@ func (v testVolumeQuota) GetInodes(_ string, _ common.QuotaID) (int64, error) {
 | 
				
			|||||||
	return 1, nil
 | 
						return 1, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func fakeSupportsQuotas(path string) (bool, error) {
 | 
					func fakeSupportsQuotas(path string, userNamespacesEnabled bool) (bool, error) {
 | 
				
			||||||
	dummySetFSInfo(path)
 | 
						dummySetFSInfo(path)
 | 
				
			||||||
	return SupportsQuotas(dummyQuotaTest(), path)
 | 
						return SupportsQuotas(dummyQuotaTest(), path, userNamespacesEnabled)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func fakeAssignQuota(path string, poduid types.UID, bytes int64) error {
 | 
					func fakeAssignQuota(path string, poduid types.UID, bytes int64, userNamespacesEnabled bool) error {
 | 
				
			||||||
	dummySetFSInfo(path)
 | 
						dummySetFSInfo(path)
 | 
				
			||||||
	return AssignQuota(dummyQuotaTest(), path, poduid, resource.NewQuantity(bytes, resource.DecimalSI))
 | 
						return AssignQuota(dummyQuotaTest(), path, poduid, resource.NewQuantity(bytes, resource.DecimalSI), userNamespacesEnabled)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func fakeClearQuota(path string) error {
 | 
					func fakeClearQuota(path string, userNamespacesEnabled bool) error {
 | 
				
			||||||
	dummySetFSInfo(path)
 | 
						dummySetFSInfo(path)
 | 
				
			||||||
	return ClearQuota(dummyQuotaTest(), path)
 | 
						return ClearQuota(dummyQuotaTest(), path, userNamespacesEnabled)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type quotaTestCase struct {
 | 
					type quotaTestCase struct {
 | 
				
			||||||
@@ -391,6 +391,7 @@ type quotaTestCase struct {
 | 
				
			|||||||
	expectedProjid                   string
 | 
						expectedProjid                   string
 | 
				
			||||||
	supportsQuota                    bool
 | 
						supportsQuota                    bool
 | 
				
			||||||
	expectsSetQuota                  bool
 | 
						expectsSetQuota                  bool
 | 
				
			||||||
 | 
						userNamespacesEnabled            bool
 | 
				
			||||||
	deltaExpectedPodQuotaCount       int
 | 
						deltaExpectedPodQuotaCount       int
 | 
				
			||||||
	deltaExpectedDirQuotaCount       int
 | 
						deltaExpectedDirQuotaCount       int
 | 
				
			||||||
	deltaExpectedQuotaPodCount       int
 | 
						deltaExpectedQuotaPodCount       int
 | 
				
			||||||
@@ -444,59 +445,64 @@ volume1048581:1048581
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
var quotaTestCases = []quotaTestCase{
 | 
					var quotaTestCases = []quotaTestCase{
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		"SupportsQuotaOnQuotaVolume",
 | 
							"SupportsQuotaOnQuotaVolumeWithUserNamespace",
 | 
				
			||||||
		"/quota1/a", "", 1024, "Supports", "", "",
 | 
							"/quota1/a", "", 1024, "Supports", "", "",
 | 
				
			||||||
		true, true, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1,
 | 
							true, true, true, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1,
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							"SupportsQuotaOnQuotaVolumeWithoutUserNamespace",
 | 
				
			||||||
 | 
							"/quota1/a", "", 1024, "Supports", "", "",
 | 
				
			||||||
 | 
							true, true, false, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		"AssignQuotaFirstTime",
 | 
							"AssignQuotaFirstTime",
 | 
				
			||||||
		"/quota1/a", "", 1024, "Set", projects1, projid1,
 | 
							"/quota1/a", "", 1024, "Set", projects1, projid1,
 | 
				
			||||||
		true, true, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0,
 | 
							true, true, true, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0,
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		"AssignQuotaFirstTime",
 | 
							"AssignQuotaFirstTime",
 | 
				
			||||||
		"/quota1/b", "x", 1024, "Set", projects2, projid2,
 | 
							"/quota1/b", "x", 1024, "Set", projects2, projid2,
 | 
				
			||||||
		true, true, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1,
 | 
							true, true, true, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1,
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		"AssignQuotaFirstTime",
 | 
							"AssignQuotaFirstTime",
 | 
				
			||||||
		"/quota2/b", "x", 1024, "Set", projects3, projid3,
 | 
							"/quota2/b", "x", 1024, "Set", projects3, projid3,
 | 
				
			||||||
		true, true, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
 | 
							true, true, true, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		"AssignQuotaSecondTimeWithSameSize",
 | 
							"AssignQuotaSecondTimeWithSameSize",
 | 
				
			||||||
		"/quota1/b", "x", 1024, "Set", projects3, projid3,
 | 
							"/quota1/b", "x", 1024, "Set", projects3, projid3,
 | 
				
			||||||
		true, true, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 | 
							true, true, true, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		"AssignQuotaSecondTimeWithDifferentSize",
 | 
							"AssignQuotaSecondTimeWithDifferentSize",
 | 
				
			||||||
		"/quota2/b", "x", 2048, "Set", projects3, projid3,
 | 
							"/quota2/b", "x", 2048, "Set", projects3, projid3,
 | 
				
			||||||
		true, false, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 | 
							true, false, true, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		"ClearQuotaFirstTime",
 | 
							"ClearQuotaFirstTime",
 | 
				
			||||||
		"/quota1/b", "", 1024, "Clear", projects4, projid4,
 | 
							"/quota1/b", "", 1024, "Clear", projects4, projid4,
 | 
				
			||||||
		true, true, -1, -1, -1, -1, 0, -1, -1, -1, -1, -1, -1,
 | 
							true, true, true, -1, -1, -1, -1, 0, -1, -1, -1, -1, -1, -1,
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		"SupportsQuotaOnNonQuotaVolume",
 | 
							"SupportsQuotaOnNonQuotaVolume",
 | 
				
			||||||
		"/noquota/a", "", 1024, "Supports", projects4, projid4,
 | 
							"/noquota/a", "", 1024, "Supports", projects4, projid4,
 | 
				
			||||||
		false, false, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 | 
							false, false, true, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		"ClearQuotaFirstTime",
 | 
							"ClearQuotaFirstTime",
 | 
				
			||||||
		"/quota1/a", "", 1024, "Clear", projects5, projid5,
 | 
							"/quota1/a", "", 1024, "Clear", projects5, projid5,
 | 
				
			||||||
		true, true, -1, -1, -1, -1, 0, -1, -1, -1, -1, -1, -1,
 | 
							true, true, true, -1, -1, -1, -1, 0, -1, -1, -1, -1, -1, -1,
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		"ClearQuotaSecondTime",
 | 
							"ClearQuotaSecondTime",
 | 
				
			||||||
		"/quota1/a", "", 1024, "Clear", projects5, projid5,
 | 
							"/quota1/a", "", 1024, "Clear", projects5, projid5,
 | 
				
			||||||
		true, false, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 | 
							true, false, true, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		"ClearQuotaFirstTime",
 | 
							"ClearQuotaFirstTime",
 | 
				
			||||||
		"/quota2/b", "", 1024, "Clear", "", "",
 | 
							"/quota2/b", "", 1024, "Clear", "", "",
 | 
				
			||||||
		true, true, -1, -1, -1, -1, 0, -1, -1, -1, -1, -1, -1,
 | 
							true, true, true, -1, -1, -1, -1, 0, -1, -1, -1, -1, -1, -1,
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -534,20 +540,21 @@ func runCaseEnabled(t *testing.T, testcase quotaTestCase, seq int) bool {
 | 
				
			|||||||
	var err error
 | 
						var err error
 | 
				
			||||||
	switch testcase.op {
 | 
						switch testcase.op {
 | 
				
			||||||
	case "Supports":
 | 
						case "Supports":
 | 
				
			||||||
		supports, err := fakeSupportsQuotas(testcase.path)
 | 
							supports, err := fakeSupportsQuotas(testcase.path, testcase.userNamespacesEnabled)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			fail = true
 | 
								fail = true
 | 
				
			||||||
			t.Errorf("Case %v (%s, %s, %v) Got error in fakeSupportsQuotas: %v", seq, testcase.name, testcase.path, true, err)
 | 
								t.Errorf("Case %v (%s, %s, %v) Got error in fakeSupportsQuotas: %v", seq, testcase.name, testcase.path, true, err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		if supports != testcase.supportsQuota {
 | 
							expectedSupport := testcase.supportsQuota && testcase.userNamespacesEnabled
 | 
				
			||||||
 | 
							if supports != expectedSupport {
 | 
				
			||||||
			fail = true
 | 
								fail = true
 | 
				
			||||||
			t.Errorf("Case %v (%s, %s, %v) fakeSupportsQuotas got %v, expect %v", seq, testcase.name, testcase.path, true, supports, testcase.supportsQuota)
 | 
								t.Errorf("Case %v (%s, %s, userNamespacesEnabled: %v) fakeSupportsQuotas got %v, expect %v", seq, testcase.name, testcase.path, testcase.userNamespacesEnabled, supports, expectedSupport)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		return fail
 | 
							return fail
 | 
				
			||||||
	case "Set":
 | 
						case "Set":
 | 
				
			||||||
		err = fakeAssignQuota(testcase.path, testcase.poduid, testcase.bytes)
 | 
							err = fakeAssignQuota(testcase.path, testcase.poduid, testcase.bytes, testcase.userNamespacesEnabled)
 | 
				
			||||||
	case "Clear":
 | 
						case "Clear":
 | 
				
			||||||
		err = fakeClearQuota(testcase.path)
 | 
							err = fakeClearQuota(testcase.path, testcase.userNamespacesEnabled)
 | 
				
			||||||
	case "GetConsumption":
 | 
						case "GetConsumption":
 | 
				
			||||||
		_, err = GetConsumption(testcase.path)
 | 
							_, err = GetConsumption(testcase.path)
 | 
				
			||||||
	case "GetInodes":
 | 
						case "GetInodes":
 | 
				
			||||||
@@ -571,15 +578,15 @@ func runCaseDisabled(t *testing.T, testcase quotaTestCase, seq int) bool {
 | 
				
			|||||||
	var supports bool
 | 
						var supports bool
 | 
				
			||||||
	switch testcase.op {
 | 
						switch testcase.op {
 | 
				
			||||||
	case "Supports":
 | 
						case "Supports":
 | 
				
			||||||
		if supports, _ = fakeSupportsQuotas(testcase.path); supports {
 | 
							if supports, _ = fakeSupportsQuotas(testcase.path, testcase.userNamespacesEnabled); supports {
 | 
				
			||||||
			t.Errorf("Case %v (%s, %s, %v) supports quotas but shouldn't", seq, testcase.name, testcase.path, false)
 | 
								t.Errorf("Case %v (%s, %s, %v) supports quotas but shouldn't", seq, testcase.name, testcase.path, false)
 | 
				
			||||||
			return true
 | 
								return true
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		return false
 | 
							return false
 | 
				
			||||||
	case "Set":
 | 
						case "Set":
 | 
				
			||||||
		err = fakeAssignQuota(testcase.path, testcase.poduid, testcase.bytes)
 | 
							err = fakeAssignQuota(testcase.path, testcase.poduid, testcase.bytes, testcase.userNamespacesEnabled)
 | 
				
			||||||
	case "Clear":
 | 
						case "Clear":
 | 
				
			||||||
		err = fakeClearQuota(testcase.path)
 | 
							err = fakeClearQuota(testcase.path, testcase.userNamespacesEnabled)
 | 
				
			||||||
	case "GetConsumption":
 | 
						case "GetConsumption":
 | 
				
			||||||
		_, err = GetConsumption(testcase.path)
 | 
							_, err = GetConsumption(testcase.path)
 | 
				
			||||||
	case "GetInodes":
 | 
						case "GetInodes":
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -39,12 +39,12 @@ func GetQuotaOnDir(_ mount.Interface, _ string) (common.QuotaID, error) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// SupportsQuotas -- dummy implementation
 | 
					// SupportsQuotas -- dummy implementation
 | 
				
			||||||
func SupportsQuotas(_ mount.Interface, _ string) (bool, error) {
 | 
					func SupportsQuotas(_ mount.Interface, _ string, _ bool) (bool, error) {
 | 
				
			||||||
	return false, errNotImplemented
 | 
						return false, errNotImplemented
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// AssignQuota -- dummy implementation
 | 
					// AssignQuota -- dummy implementation
 | 
				
			||||||
func AssignQuota(_ mount.Interface, _ string, _ types.UID, _ *resource.Quantity) error {
 | 
					func AssignQuota(_ mount.Interface, _ string, _ types.UID, _ *resource.Quantity, _ bool) error {
 | 
				
			||||||
	return errNotImplemented
 | 
						return errNotImplemented
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -59,6 +59,6 @@ func GetInodes(_ string) (*resource.Quantity, error) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ClearQuota -- dummy implementation
 | 
					// ClearQuota -- dummy implementation
 | 
				
			||||||
func ClearQuota(_ mount.Interface, _ string) error {
 | 
					func ClearQuota(_ mount.Interface, _ string, _ bool) error {
 | 
				
			||||||
	return errNotImplemented
 | 
						return errNotImplemented
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -43,7 +43,7 @@ const (
 | 
				
			|||||||
	LSCIQuotaFeature = features.LocalStorageCapacityIsolationFSQuotaMonitoring
 | 
						LSCIQuotaFeature = features.LocalStorageCapacityIsolationFSQuotaMonitoring
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func runOneQuotaTest(f *framework.Framework, quotasRequested bool) {
 | 
					func runOneQuotaTest(f *framework.Framework, quotasRequested bool, userNamespacesEnabled bool) {
 | 
				
			||||||
	evictionTestTimeout := 10 * time.Minute
 | 
						evictionTestTimeout := 10 * time.Minute
 | 
				
			||||||
	sizeLimit := resource.MustParse("100Mi")
 | 
						sizeLimit := resource.MustParse("100Mi")
 | 
				
			||||||
	useOverLimit := 101 /* Mb */
 | 
						useOverLimit := 101 /* Mb */
 | 
				
			||||||
@@ -63,7 +63,10 @@ func runOneQuotaTest(f *framework.Framework, quotasRequested bool) {
 | 
				
			|||||||
			defer withFeatureGate(LSCIQuotaFeature, quotasRequested)()
 | 
								defer withFeatureGate(LSCIQuotaFeature, quotasRequested)()
 | 
				
			||||||
			// TODO: remove hardcoded kubelet volume directory path
 | 
								// TODO: remove hardcoded kubelet volume directory path
 | 
				
			||||||
			// framework.TestContext.KubeVolumeDir is currently not populated for node e2e
 | 
								// framework.TestContext.KubeVolumeDir is currently not populated for node e2e
 | 
				
			||||||
			if quotasRequested && !supportsQuotas("/var/lib/kubelet") {
 | 
								if !supportsUserNS(ctx, f) {
 | 
				
			||||||
 | 
									e2eskipper.Skipf("runtime does not support user namespaces")
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if quotasRequested && !supportsQuotas("/var/lib/kubelet", userNamespacesEnabled) {
 | 
				
			||||||
				// No point in running this as a positive test if quotas are not
 | 
									// No point in running this as a positive test if quotas are not
 | 
				
			||||||
				// enabled on the underlying filesystem.
 | 
									// enabled on the underlying filesystem.
 | 
				
			||||||
				e2eskipper.Skipf("Cannot run LocalStorageCapacityIsolationFSQuotaMonitoring on filesystem without project quota enabled")
 | 
									e2eskipper.Skipf("Cannot run LocalStorageCapacityIsolationFSQuotaMonitoring on filesystem without project quota enabled")
 | 
				
			||||||
@@ -98,11 +101,12 @@ func runOneQuotaTest(f *framework.Framework, quotasRequested bool) {
 | 
				
			|||||||
// pod that creates a file, deletes it, and writes data to it.  If
 | 
					// pod that creates a file, deletes it, and writes data to it.  If
 | 
				
			||||||
// quotas are used to monitor, it will detect this deleted-but-in-use
 | 
					// quotas are used to monitor, it will detect this deleted-but-in-use
 | 
				
			||||||
// file; if du is used to monitor, it will not detect this.
 | 
					// file; if du is used to monitor, it will not detect this.
 | 
				
			||||||
var _ = SIGDescribe("LocalStorageCapacityIsolationFSQuotaMonitoring", framework.WithSlow(), framework.WithSerial(), framework.WithDisruptive(), feature.LocalStorageCapacityIsolationQuota, nodefeature.LSCIQuotaMonitoring, func() {
 | 
					var _ = SIGDescribe("LocalStorageCapacityIsolationFSQuotaMonitoring", framework.WithSlow(), framework.WithSerial(), framework.WithDisruptive(), feature.LocalStorageCapacityIsolationQuota, nodefeature.LSCIQuotaMonitoring, nodefeature.UserNamespacesSupport, feature.UserNamespacesSupport, func() {
 | 
				
			||||||
	f := framework.NewDefaultFramework("localstorage-quota-monitoring-test")
 | 
						f := framework.NewDefaultFramework("localstorage-quota-monitoring-test")
 | 
				
			||||||
	f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged
 | 
						f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged
 | 
				
			||||||
	runOneQuotaTest(f, true)
 | 
						runOneQuotaTest(f, true, true)
 | 
				
			||||||
	runOneQuotaTest(f, false)
 | 
						runOneQuotaTest(f, true, false)
 | 
				
			||||||
 | 
						runOneQuotaTest(f, false, true)
 | 
				
			||||||
	addAfterEachForCleaningUpPods(f)
 | 
						addAfterEachForCleaningUpPods(f)
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -152,7 +156,7 @@ func diskConcealingPod(name string, diskConsumedMB int, volumeSource *v1.VolumeS
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// Don't bother returning an error; if something goes wrong,
 | 
					// Don't bother returning an error; if something goes wrong,
 | 
				
			||||||
// simply treat it as "no".
 | 
					// simply treat it as "no".
 | 
				
			||||||
func supportsQuotas(dir string) bool {
 | 
					func supportsQuotas(dir string, userNamespacesEnabled bool) bool {
 | 
				
			||||||
	supportsQuota, err := fsquota.SupportsQuotas(mount.New(""), dir)
 | 
						supportsQuota, err := fsquota.SupportsQuotas(mount.New(""), dir, userNamespacesEnabled)
 | 
				
			||||||
	return supportsQuota && err == nil
 | 
						return supportsQuota && err == nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user