Commit 448e0c44 authored by Robert Krawitz's avatar Robert Krawitz Committed by Robert Krawitz

Apply quotas via syscalls using cgo.

parent 5b97b286
...@@ -440,6 +440,13 @@ const ( ...@@ -440,6 +440,13 @@ const (
// //
// Enables the regional PD feature on GCE. // Enables the regional PD feature on GCE.
deprecatedGCERegionalPersistentDisk featuregate.Feature = "GCERegionalPersistentDisk" deprecatedGCERegionalPersistentDisk featuregate.Feature = "GCERegionalPersistentDisk"
// owner: @RobertKrawitz
// alpha: v1.15
//
// Allow use of filesystems for ephemeral storage monitoring.
// Only applies if LocalStorageCapacityIsolation is set.
FSQuotaForLSCIMonitoring = "FSQuotaForLSCIMonitoring"
) )
func init() { func init() {
...@@ -514,6 +521,7 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS ...@@ -514,6 +521,7 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS
TTLAfterFinished: {Default: false, PreRelease: featuregate.Alpha}, TTLAfterFinished: {Default: false, PreRelease: featuregate.Alpha},
KubeletPodResources: {Default: false, PreRelease: featuregate.Alpha}, KubeletPodResources: {Default: false, PreRelease: featuregate.Alpha},
WindowsGMSA: {Default: false, PreRelease: featuregate.Alpha}, WindowsGMSA: {Default: false, PreRelease: featuregate.Alpha},
FSQuotaForLSCIMonitoring: {Default: false, PreRelease: utilfeature.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:
......
...@@ -395,13 +395,27 @@ func podDiskUsage(podStats statsapi.PodStats, pod *v1.Pod, statsToMeasure []fsSt ...@@ -395,13 +395,27 @@ func podDiskUsage(podStats statsapi.PodStats, pod *v1.Pod, statsToMeasure []fsSt
}, nil }, nil
} }
func internalIsLocalEphemeralVolume(pod *v1.Pod, volume v1.Volume) bool {
return volume.GitRepo != nil ||
(volume.EmptyDir != nil && volume.EmptyDir.Medium != v1.StorageMediumMemory) ||
volume.ConfigMap != nil || volume.DownwardAPI != nil
}
// IsLocalEphemeralVolume determines whether a given volume name is ephemeral
func IsLocalEphemeralVolume(pod *v1.Pod, volumeName string) (bool, error) {
for _, volume := range pod.Spec.Volumes {
if volume.Name == volumeName {
return internalIsLocalEphemeralVolume(pod, volume), nil
}
}
return false, fmt.Errorf("Volume %s not found in pod %v", volumeName, pod)
}
// localEphemeralVolumeNames returns the set of ephemeral volumes for the pod that are local // localEphemeralVolumeNames returns the set of ephemeral volumes for the pod that are local
func localEphemeralVolumeNames(pod *v1.Pod) []string { func localEphemeralVolumeNames(pod *v1.Pod) []string {
result := []string{} result := []string{}
for _, volume := range pod.Spec.Volumes { for _, volume := range pod.Spec.Volumes {
if volume.GitRepo != nil || if internalIsLocalEphemeralVolume(pod, volume) {
(volume.EmptyDir != nil && volume.EmptyDir.Medium != v1.StorageMediumMemory) ||
volume.ConfigMap != nil || volume.DownwardAPI != nil {
result = append(result, volume.Name) result = append(result, volume.Name)
} }
} }
......
...@@ -14,7 +14,9 @@ go_library( ...@@ -14,7 +14,9 @@ go_library(
], ],
importpath = "k8s.io/kubernetes/pkg/kubelet/volumemanager/cache", importpath = "k8s.io/kubernetes/pkg/kubelet/volumemanager/cache",
deps = [ deps = [
"//pkg/api/v1/resource:go_default_library",
"//pkg/features:go_default_library", "//pkg/features:go_default_library",
"//pkg/kubelet/eviction:go_default_library",
"//pkg/volume:go_default_library", "//pkg/volume:go_default_library",
"//pkg/volume/util:go_default_library", "//pkg/volume/util:go_default_library",
"//pkg/volume/util/operationexecutor:go_default_library", "//pkg/volume/util/operationexecutor:go_default_library",
......
...@@ -25,6 +25,8 @@ import ( ...@@ -25,6 +25,8 @@ import (
"sync" "sync"
"k8s.io/api/core/v1" "k8s.io/api/core/v1"
apiv1resource "k8s.io/kubernetes/pkg/api/v1/resource"
limits "k8s.io/kubernetes/pkg/kubelet/eviction"
"k8s.io/kubernetes/pkg/volume" "k8s.io/kubernetes/pkg/volume"
"k8s.io/kubernetes/pkg/volume/util" "k8s.io/kubernetes/pkg/volume/util"
"k8s.io/kubernetes/pkg/volume/util/operationexecutor" "k8s.io/kubernetes/pkg/volume/util/operationexecutor"
...@@ -160,6 +162,10 @@ type volumeToMount struct { ...@@ -160,6 +162,10 @@ type volumeToMount struct {
// reportedInUse indicates that the volume was successfully added to the // reportedInUse indicates that the volume was successfully added to the
// VolumesInUse field in the node's status. // VolumesInUse field in the node's status.
reportedInUse bool reportedInUse bool
// desiredSizeLimit indicates the desired upper bound on the size of the volume
// (if so implemented)
desiredSizeLimit int64
} }
// The pod object represents a pod that references the underlying volume and // The pod object represents a pod that references the underlying volume and
...@@ -226,6 +232,17 @@ func (dsw *desiredStateOfWorld) AddPodToVolume( ...@@ -226,6 +232,17 @@ func (dsw *desiredStateOfWorld) AddPodToVolume(
} }
if _, volumeExists := dsw.volumesToMount[volumeName]; !volumeExists { if _, volumeExists := dsw.volumesToMount[volumeName]; !volumeExists {
var sizeLimit int64
sizeLimit = 0
isLocal, _ := limits.IsLocalEphemeralVolume(pod, volumeSpec.Name())
if isLocal {
_, podLimits := apiv1resource.PodRequestsAndLimits(pod)
ephemeralStorageLimit := podLimits[v1.ResourceEphemeralStorage]
sizeLimit = ephemeralStorageLimit.Value()
if sizeLimit == 0 {
sizeLimit = -1
}
}
dsw.volumesToMount[volumeName] = volumeToMount{ dsw.volumesToMount[volumeName] = volumeToMount{
volumeName: volumeName, volumeName: volumeName,
podsToMount: make(map[types.UniquePodName]podToMount), podsToMount: make(map[types.UniquePodName]podToMount),
...@@ -233,6 +250,7 @@ func (dsw *desiredStateOfWorld) AddPodToVolume( ...@@ -233,6 +250,7 @@ func (dsw *desiredStateOfWorld) AddPodToVolume(
pluginIsDeviceMountable: deviceMountable, pluginIsDeviceMountable: deviceMountable,
volumeGidValue: volumeGidValue, volumeGidValue: volumeGidValue,
reportedInUse: false, reportedInUse: false,
desiredSizeLimit: sizeLimit,
} }
} }
...@@ -360,7 +378,8 @@ func (dsw *desiredStateOfWorld) GetVolumesToMount() []VolumeToMount { ...@@ -360,7 +378,8 @@ func (dsw *desiredStateOfWorld) GetVolumesToMount() []VolumeToMount {
PluginIsDeviceMountable: volumeObj.pluginIsDeviceMountable, PluginIsDeviceMountable: volumeObj.pluginIsDeviceMountable,
OuterVolumeSpecName: podObj.outerVolumeSpecName, OuterVolumeSpecName: podObj.outerVolumeSpecName,
VolumeGidValue: volumeObj.volumeGidValue, VolumeGidValue: volumeObj.volumeGidValue,
ReportedInUse: volumeObj.reportedInUse}}) ReportedInUse: volumeObj.reportedInUse,
DesiredSizeLimit: volumeObj.desiredSizeLimit}})
} }
} }
return volumesToMount return volumesToMount
......
...@@ -20,6 +20,7 @@ go_library( ...@@ -20,6 +20,7 @@ go_library(
"//pkg/util/mount:go_default_library", "//pkg/util/mount:go_default_library",
"//pkg/volume:go_default_library", "//pkg/volume:go_default_library",
"//pkg/volume/util:go_default_library", "//pkg/volume/util:go_default_library",
"//pkg/volume/util/quota:go_default_library",
"//staging/src/k8s.io/api/core/v1:go_default_library", "//staging/src/k8s.io/api/core/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/resource:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/api/resource:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
......
...@@ -30,6 +30,7 @@ import ( ...@@ -30,6 +30,7 @@ import (
"k8s.io/kubernetes/pkg/util/mount" "k8s.io/kubernetes/pkg/util/mount"
"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/quota"
utilstrings "k8s.io/utils/strings" utilstrings "k8s.io/utils/strings"
) )
...@@ -174,6 +175,7 @@ type emptyDir struct { ...@@ -174,6 +175,7 @@ type emptyDir struct {
mounter mount.Interface mounter mount.Interface
mountDetector mountDetector mountDetector mountDetector
plugin *emptyDirPlugin plugin *emptyDirPlugin
desiredSize int64
volume.MetricsProvider volume.MetricsProvider
} }
...@@ -234,6 +236,15 @@ func (ed *emptyDir) SetUpAt(dir string, mounterArgs volume.MounterArgs) error { ...@@ -234,6 +236,15 @@ func (ed *emptyDir) SetUpAt(dir string, mounterArgs volume.MounterArgs) error {
if err == nil { if err == nil {
volumeutil.SetReady(ed.getMetaDir()) volumeutil.SetReady(ed.getMetaDir())
} }
if mounterArgs.DesiredSize != 0 {
if hasQuotas, _ := quota.SupportsQuotas(ed.mounter, dir); hasQuotas {
klog.V(3).Infof("emptydir trying to assign quota")
err := quota.AssignQuota(ed.mounter, dir, mounterArgs.PodUID, mounterArgs.DesiredSize)
if err != nil {
klog.V(3).Infof("Set quota failed %v", err)
}
}
}
return err return err
} }
...@@ -397,9 +408,14 @@ func (ed *emptyDir) TearDownAt(dir string) error { ...@@ -397,9 +408,14 @@ func (ed *emptyDir) TearDownAt(dir string) error {
} }
func (ed *emptyDir) teardownDefault(dir string) error { func (ed *emptyDir) teardownDefault(dir string) error {
// Remove any quota
err := quota.ClearQuota(ed.mounter, dir)
if err != nil {
klog.V(3).Infof("Failed to clear quota on %s: %v", dir, err)
}
// Renaming the directory is not required anymore because the operation executor // Renaming the directory is not required anymore because the operation executor
// now handles duplicate operations on the same volume // now handles duplicate operations on the same volume
err := os.RemoveAll(dir) err = os.RemoveAll(dir)
if err != nil { if err != nil {
return err return err
} }
......
...@@ -91,6 +91,7 @@ filegroup( ...@@ -91,6 +91,7 @@ filegroup(
"//pkg/volume/util/nestedpendingoperations:all-srcs", "//pkg/volume/util/nestedpendingoperations:all-srcs",
"//pkg/volume/util/nsenter:all-srcs", "//pkg/volume/util/nsenter:all-srcs",
"//pkg/volume/util/operationexecutor:all-srcs", "//pkg/volume/util/operationexecutor:all-srcs",
"//pkg/volume/util/quota:all-srcs",
"//pkg/volume/util/recyclerclient:all-srcs", "//pkg/volume/util/recyclerclient:all-srcs",
"//pkg/volume/util/subpath:all-srcs", "//pkg/volume/util/subpath:all-srcs",
"//pkg/volume/util/types:all-srcs", "//pkg/volume/util/types:all-srcs",
......
...@@ -14,6 +14,7 @@ go_library( ...@@ -14,6 +14,7 @@ go_library(
"//staging/src/k8s.io/apimachinery/pkg/api/resource:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/api/resource:go_default_library",
], ],
"@io_bazel_rules_go//go/platform:darwin": [ "@io_bazel_rules_go//go/platform:darwin": [
"//pkg/volume/util/quota:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/resource:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/api/resource:go_default_library",
"//vendor/golang.org/x/sys/unix:go_default_library", "//vendor/golang.org/x/sys/unix:go_default_library",
], ],
...@@ -24,6 +25,7 @@ go_library( ...@@ -24,6 +25,7 @@ go_library(
"//staging/src/k8s.io/apimachinery/pkg/api/resource:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/api/resource:go_default_library",
], ],
"@io_bazel_rules_go//go/platform:linux": [ "@io_bazel_rules_go//go/platform:linux": [
"//pkg/volume/util/quota:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/resource:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/api/resource:go_default_library",
"//vendor/golang.org/x/sys/unix:go_default_library", "//vendor/golang.org/x/sys/unix:go_default_library",
], ],
......
...@@ -27,6 +27,7 @@ import ( ...@@ -27,6 +27,7 @@ import (
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
"k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/api/resource"
"k8s.io/kubernetes/pkg/volume/util/quota"
) )
// FSInfo linux returns (available bytes, byte capacity, byte usage, total inodes, inodes free, inode usage, error) // FSInfo linux returns (available bytes, byte capacity, byte usage, total inodes, inodes free, inode usage, error)
...@@ -56,6 +57,13 @@ func FsInfo(path string) (int64, int64, int64, int64, int64, int64, error) { ...@@ -56,6 +57,13 @@ func FsInfo(path string) (int64, int64, int64, int64, int64, int64, error) {
// DiskUsage gets disk usage of specified path. // DiskUsage gets disk usage of specified path.
func DiskUsage(path string) (*resource.Quantity, error) { func DiskUsage(path string) (*resource.Quantity, error) {
// First check whether the quota system knows about this directory
data, err := quota.GetConsumption(path)
if err == nil {
var q resource.Quantity
q.Set(data)
return &q, nil
}
// Uses the same niceness level as cadvisor.fs does when running du // Uses the same niceness level as cadvisor.fs does when running du
// Uses -B 1 to always scale to a blocksize of 1 byte // Uses -B 1 to always scale to a blocksize of 1 byte
out, err := exec.Command("nice", "-n", "19", "du", "-s", "-B", "1", path).CombinedOutput() out, err := exec.Command("nice", "-n", "19", "du", "-s", "-B", "1", path).CombinedOutput()
...@@ -76,6 +84,11 @@ func Find(path string) (int64, error) { ...@@ -76,6 +84,11 @@ func Find(path string) (int64, error) {
if path == "" { if path == "" {
return 0, fmt.Errorf("invalid directory") return 0, fmt.Errorf("invalid directory")
} }
// First check whether the quota system knows about this directory
inodes, err := quota.GetInodes(path)
if err == nil {
return inodes, nil
}
var counter byteCounter var counter byteCounter
var stderr bytes.Buffer var stderr bytes.Buffer
findCmd := exec.Command("find", path, "-xdev", "-printf", ".") findCmd := exec.Command("find", path, "-xdev", "-printf", ".")
......
...@@ -346,6 +346,10 @@ type VolumeToMount struct { ...@@ -346,6 +346,10 @@ type VolumeToMount struct {
// ReportedInUse indicates that the volume was successfully added to the // ReportedInUse indicates that the volume was successfully added to the
// VolumesInUse field in the node's status. // VolumesInUse field in the node's status.
ReportedInUse bool ReportedInUse bool
// DesiredSizeLimit indicates the desired upper bound on the size of the volume
// (if so implemented)
DesiredSizeLimit int64
} }
// GenerateMsgDetailed returns detailed msgs for volumes to mount // GenerateMsgDetailed returns detailed msgs for volumes to mount
......
...@@ -702,8 +702,9 @@ func (og *operationGenerator) GenerateMountVolumeFunc( ...@@ -702,8 +702,9 @@ func (og *operationGenerator) GenerateMountVolumeFunc(
// Execute mount // Execute mount
mountErr := volumeMounter.SetUp(volume.MounterArgs{ mountErr := volumeMounter.SetUp(volume.MounterArgs{
FsGroup: fsGroup, FsGroup: fsGroup,
PodUID: string(volumeToMount.Pod.UID), DesiredSize: volumeToMount.DesiredSizeLimit,
PodUID: string(volumeToMount.Pod.UID),
}) })
if mountErr != nil { if mountErr != nil {
// On failure, return error. Caller will log and retry. // On failure, return error. Caller will log and retry.
......
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = [
"project.go",
"quota.go",
"quota_linux.go",
"quota_unsupported.go",
],
importpath = "k8s.io/kubernetes/pkg/volume/util/quota",
visibility = ["//visibility:public"],
deps = [
"//pkg/features:go_default_library",
"//pkg/util/mount:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library",
] + select({
"@io_bazel_rules_go//go/platform:linux": [
"//pkg/volume/util/quota/common:go_default_library",
"//pkg/volume/util/quota/extfs:go_default_library",
"//pkg/volume/util/quota/xfs:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/uuid:go_default_library",
"//vendor/golang.org/x/sys/unix:go_default_library",
"//vendor/k8s.io/klog:go_default_library",
],
"//conditions:default": [],
}),
)
go_test(
name = "go_default_test",
srcs = ["quota_linux_test.go"],
embed = [":go_default_library"],
deps = select({
"@io_bazel_rules_go//go/platform:linux": [
"//pkg/features:go_default_library",
"//pkg/util/mount:go_default_library",
"//pkg/volume/util/quota/common:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library",
],
"//conditions:default": [],
}),
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//pkg/volume/util/quota/common:all-srcs",
"//pkg/volume/util/quota/extfs:all-srcs",
"//pkg/volume/util/quota/xfs:all-srcs",
],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"quota_linux_common.go",
"quota_linux_common_impl.go",
],
cgo = True,
importpath = "k8s.io/kubernetes/pkg/volume/util/quota/common",
visibility = ["//visibility:public"],
deps = select({
"@io_bazel_rules_go//go/platform:linux": [
"//vendor/golang.org/x/sys/unix:go_default_library",
"//vendor/k8s.io/klog:go_default_library",
],
"//conditions:default": [],
}),
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
// +build linux
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package common
// QuotaID -- generic quota identifier
type QuotaID int32
const (
// BadQuotaID -- Invalid quota
BadQuotaID QuotaID = 0
)
// FirstQuota is the quota ID we start with.
// XXXXXXX Need a better way of doing this...
var FirstQuota QuotaID = 1048577
// LinuxVolumeQuotaProvider returns an appropriate quota applier
// object if we can support quotas on this device
type LinuxVolumeQuotaProvider interface {
// GetQuotaApplier retrieves an object that can apply
// quotas (or nil if this provider cannot support quotas
// on the device)
GetQuotaApplier(mountpoint string, backingDev string) LinuxVolumeQuotaApplier
}
// LinuxVolumeQuotaApplier is a generic interface to any quota
// mechanism supported by Linux
type LinuxVolumeQuotaApplier interface {
// GetQuotaOnDir gets the quota ID (if any) that applies to
// this directory
GetQuotaOnDir(path string) (QuotaID, error)
// SetQuotaOnDir applies the specified quota ID to a directory.
// Negative value for bytes means that a non-enforcing quota
// should be applied (perhaps by setting a quota too large to
// be hit)
SetQuotaOnDir(path string, id QuotaID, bytes int64) error
// QuotaIDIsInUse determines whether the quota ID is in use.
// Implementations should not check /etc/project or /etc/projid,
// only whether their underlying mechanism already has the ID in
// use.
// Return value of false with no error means that the ID is not
// in use; true means that it is already in use. An error
// return means that any quota ID will fail.
QuotaIDIsInUse(path string, id QuotaID) (bool, error)
// GetConsumption returns the consumption (in bytes) of the
// directory, determined by the implementation's quota-based
// mechanism. If it is unable to do so using that mechanism,
// it should return an error and allow higher layers to
// enumerate the directory.
GetConsumption(path string, id QuotaID) (int64, error)
// GetInodes returns the number of inodes used by the
// directory, determined by the implementation's quota-based
// mechanism. If it is unable to do so using that mechanism,
// it should return an error and allow higher layers to
// enumerate the directory.
GetInodes(path string, id QuotaID) (int64, error)
}
// +build linux
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package common
/*
#include <stdlib.h>
#include <dirent.h>
#include <linux/fs.h>
#include <linux/quota.h>
#include <linux/dqblk_xfs.h>
#include <errno.h>
#ifndef FS_XFLAG_PROJINHERIT
struct fsxattr {
__u32 fsx_xflags;
__u32 fsx_extsize;
__u32 fsx_nextents;
__u32 fsx_projid;
unsigned char fsx_pad[12];
};
#define FS_XFLAG_PROJINHERIT 0x00000200
#endif
#ifndef FS_IOC_FSGETXATTR
#define FS_IOC_FSGETXATTR _IOR ('X', 31, struct fsxattr)
#endif
#ifndef FS_IOC_FSSETXATTR
#define FS_IOC_FSSETXATTR _IOW ('X', 32, struct fsxattr)
#endif
#ifndef PRJQUOTA
#define PRJQUOTA 2
#endif
#ifndef Q_XGETQSTAT_PRJQUOTA
#define Q_XGETQSTAT_PRJQUOTA QCMD(Q_XGETQSTAT, PRJQUOTA)
#endif
*/
import "C"
import (
"fmt"
"syscall"
"unsafe"
"golang.org/x/sys/unix"
"k8s.io/klog"
)
// IsFilesystemOfType determines whether the filesystem specified is of the type
// specified by the magic number
func IsFilesystemOfType(mountpoint string, backingDev string, magic int64) bool {
var buf syscall.Statfs_t
err := syscall.Statfs(mountpoint, &buf)
if err != nil {
klog.V(3).Infof("Extfs Unable to statfs %s: %v", mountpoint, err)
return false
}
if buf.Type != magic {
return false
}
var qstat C.fs_quota_stat_t
CPath := C.CString(backingDev)
defer free(CPath)
_, _, errno := unix.Syscall6(unix.SYS_QUOTACTL, uintptr(C.Q_XGETQSTAT_PRJQUOTA), uintptr(unsafe.Pointer(CPath)), 0, uintptr(unsafe.Pointer(&qstat)), 0, 0)
return errno == 0 && qstat.qs_flags&C.FS_QUOTA_PDQ_ENFD > 0 && qstat.qs_flags&C.FS_QUOTA_PDQ_ACCT > 0
}
func free(p *C.char) {
C.free(unsafe.Pointer(p))
}
func openDir(path string) (*C.DIR, error) {
Cpath := C.CString(path)
defer free(Cpath)
dir := C.opendir(Cpath)
if dir == nil {
return nil, fmt.Errorf("Can't open dir")
}
return dir, nil
}
func closeDir(dir *C.DIR) {
if dir != nil {
C.closedir(dir)
}
}
func getDirFd(dir *C.DIR) uintptr {
return uintptr(C.dirfd(dir))
}
// GetQuotaOnDir retrieves the quota ID (if any) associated with the specified directory
func GetQuotaOnDir(path string) (QuotaID, error) {
dir, err := openDir(path)
if err != nil {
klog.V(3).Infof("Can't open directory %s: %#+v", path, err)
return BadQuotaID, err
}
defer closeDir(dir)
var fsx C.struct_fsxattr
_, _, errno := unix.Syscall(unix.SYS_IOCTL, getDirFd(dir), C.FS_IOC_FSGETXATTR,
uintptr(unsafe.Pointer(&fsx)))
if errno != 0 {
return BadQuotaID, fmt.Errorf("Failed to get quota ID for %s: %v", path, errno.Error())
}
if fsx.fsx_projid == 0 {
return BadQuotaID, fmt.Errorf("Failed to get quota ID for %s: %s", path, "no applicable quota")
}
return QuotaID(fsx.fsx_projid), nil
}
// ApplyProjectToDir applies the specified quota ID to the specified directory
func ApplyProjectToDir(path string, id QuotaID) error {
dir, err := openDir(path)
if err != nil {
return err
}
defer closeDir(dir)
var fsx C.struct_fsxattr
_, _, errno := unix.Syscall(unix.SYS_IOCTL, getDirFd(dir), C.FS_IOC_FSGETXATTR,
uintptr(unsafe.Pointer(&fsx)))
if errno != 0 {
return fmt.Errorf("Failed to get quota ID for %s: %v", path, errno.Error())
}
fsx.fsx_projid = C.__u32(id)
fsx.fsx_xflags |= C.FS_XFLAG_PROJINHERIT
_, _, errno = unix.Syscall(unix.SYS_IOCTL, getDirFd(dir), C.FS_IOC_FSSETXATTR,
uintptr(unsafe.Pointer(&fsx)))
if errno != 0 {
return fmt.Errorf("Failed to set quota ID for %s: %v", path, errno.Error())
}
return nil
}
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = ["quota_extfs.go"],
cgo = True,
importpath = "k8s.io/kubernetes/pkg/volume/util/quota/extfs",
visibility = ["//visibility:public"],
deps = select({
"@io_bazel_rules_go//go/platform:linux": [
"//pkg/volume/util/quota/common:go_default_library",
"//vendor/golang.org/x/sys/unix:go_default_library",
"//vendor/k8s.io/klog:go_default_library",
],
"//conditions:default": [],
}),
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
// +build linux
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package extfs
/*
#include <stdlib.h>
#include <dirent.h>
#include <linux/fs.h>
#include <linux/quota.h>
#include <errno.h>
#ifndef PRJQUOTA
#define PRJQUOTA 2
#endif
#ifndef Q_SETPQUOTA
#define Q_SETPQUOTA (unsigned) QCMD(Q_SETQUOTA, PRJQUOTA)
#endif
#ifndef Q_GETPQUOTA
#define Q_GETPQUOTA (unsigned) QCMD(Q_GETQUOTA, PRJQUOTA)
#endif
*/
import "C"
import (
"fmt"
"syscall"
"unsafe"
"golang.org/x/sys/unix"
"k8s.io/klog"
"k8s.io/kubernetes/pkg/volume/util/quota/common"
)
// ext4fs empirically has a maximum quota size of 2^48 - 1 1KiB blocks (256 petabytes)
const (
linuxExtfsMagic = 0xef53
quotaBsize = 1024 // extfs specific
bitsPerWord = 32 << (^uint(0) >> 63) // either 32 or 64
maxQuota int64 = (1<<(bitsPerWord-1) - 1) & (1<<58 - 1) // either 1<<31 - 1 or 1<<58 - 1
)
// VolumeProvider supplies a quota applier to the generic code.
type VolumeProvider struct {
}
// GetQuotaApplier -- does this backing device support quotas that
// can be applied to directories?
func (*VolumeProvider) GetQuotaApplier(mountpoint string, backingDev string) common.LinuxVolumeQuotaApplier {
if common.IsFilesystemOfType(mountpoint, backingDev, linuxExtfsMagic) {
return extfsVolumeQuota{backingDev}
}
return nil
}
type extfsVolumeQuota struct {
backingDev string
}
// GetQuotaOnDir -- get the quota ID that applies to this directory.
func (v extfsVolumeQuota) GetQuotaOnDir(path string) (common.QuotaID, error) {
return common.GetQuotaOnDir(path)
}
// SetQuotaOnDir -- apply the specified quota to the directory. If
// bytes is not greater than zero, the quota should be applied in a
// way that is non-enforcing (either explicitly so or by setting a
// quota larger than anything the user may possibly create)
func (v extfsVolumeQuota) SetQuotaOnDir(path string, id common.QuotaID, bytes int64) error {
klog.V(3).Infof("extfsSetQuotaOn %s ID %v bytes %v", path, id, bytes)
if bytes < 0 || bytes > maxQuota {
bytes = maxQuota
}
var d C.struct_if_dqblk
d.dqb_bhardlimit = C.__u64(bytes / quotaBsize)
d.dqb_bsoftlimit = d.dqb_bhardlimit
d.dqb_ihardlimit = 0
d.dqb_isoftlimit = 0
d.dqb_valid = C.QIF_LIMITS
var cs = C.CString(v.backingDev)
defer C.free(unsafe.Pointer(cs))
_, _, errno := unix.Syscall6(unix.SYS_QUOTACTL, C.Q_SETPQUOTA,
uintptr(unsafe.Pointer(cs)), uintptr(id),
uintptr(unsafe.Pointer(&d)), 0, 0)
if errno != 0 {
return fmt.Errorf("Failed to set quota limit for ID %d on %s: %v",
id, path, errno.Error())
}
return common.ApplyProjectToDir(path, id)
}
func (v extfsVolumeQuota) getQuotaInfo(path string, id common.QuotaID) (C.struct_if_dqblk, syscall.Errno) {
var d C.struct_if_dqblk
var cs = C.CString(v.backingDev)
defer C.free(unsafe.Pointer(cs))
_, _, errno := unix.Syscall6(unix.SYS_QUOTACTL, C.Q_GETPQUOTA,
uintptr(unsafe.Pointer(cs)), uintptr(C.__u32(id)),
uintptr(unsafe.Pointer(&d)), 0, 0)
return d, errno
}
// QuotaIDIsInUse -- determine whether the quota ID is already in use.
func (v extfsVolumeQuota) QuotaIDIsInUse(path string, id common.QuotaID) (bool, error) {
d, errno := v.getQuotaInfo(path, id)
isInUse := !(d.dqb_bhardlimit == 0 && d.dqb_bsoftlimit == 0 && d.dqb_curspace == 0 &&
d.dqb_ihardlimit == 0 && d.dqb_isoftlimit == 0 && d.dqb_curinodes == 0 &&
d.dqb_btime == 0 && d.dqb_itime == 0)
return errno == 0 && isInUse, nil
}
// GetConsumption -- retrieve the consumption (in bytes) of the directory
// Note that with ext[[:digit:]]fs the quota consumption is in bytes
// per man quotactl
func (v extfsVolumeQuota) GetConsumption(path string, id common.QuotaID) (int64, error) {
d, errno := v.getQuotaInfo(path, id)
if errno != 0 {
return 0, fmt.Errorf("Failed to get quota for %s: %s", path, errno.Error())
}
klog.V(3).Infof("Consumption for %s is %v", path, d.dqb_curspace)
return int64(d.dqb_curspace), nil
}
// GetInodes -- retrieve the number of inodes in use under the directory
func (v extfsVolumeQuota) GetInodes(path string, id common.QuotaID) (int64, error) {
d, errno := v.getQuotaInfo(path, id)
if errno != 0 {
return 0, fmt.Errorf("Failed to get quota for %s: %s", path, errno.Error())
}
klog.V(3).Infof("Inode consumption for %s is %v", path, d.dqb_curinodes)
return int64(d.dqb_curinodes), nil
}
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package quota
import (
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/kubernetes/pkg/features"
"k8s.io/kubernetes/pkg/util/mount"
)
// Interface -- quota interface
type Interface interface {
// Does the path provided support quotas, and if so, what types
SupportsQuotas(m mount.Interface, path string) (bool, error)
// Assign a quota (picked by the quota mechanism) to a path,
// and return it.
AssignQuota(m mount.Interface, path string, poduid string, bytes int64) error
// Get the quota-based storage consumption for the path
GetConsumption(path string) (int64, error)
// Get the quota-based inode consumption for the path
GetInodes(path string) (int64, error)
// Remove the quota from a path
// Implementations may assume that any data covered by the
// quota has already been removed.
ClearQuota(m mount.Interface, path string, poduid string) error
}
func enabledQuotasForMonitoring() bool {
return utilfeature.DefaultFeatureGate.Enabled(features.FSQuotaForLSCIMonitoring)
}
// +build !linux
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package quota
import (
"errors"
"k8s.io/kubernetes/pkg/util/mount"
)
// Dummy quota implementation for systems that do not implement support
// for volume quotas
var errNotImplemented = errors.New("not implemented")
// SupportsQuotas -- dummy implementation
func SupportsQuotas(_ mount.Interface, _ string) (bool, error) {
return false, errNotImplemented
}
// AssignQuota -- dummy implementation
func AssignQuota(_ mount.Interface, _ string, _ string, _ int64) error {
return errNotImplemented
}
// GetConsumption -- dummy implementation
func GetConsumption(_ string) (int64, error) {
return 0, errNotImplemented
}
// GetInodes -- dummy implementation
func GetInodes(_ string) (int64, error) {
return 0, errNotImplemented
}
// ClearQuota -- dummy implementation
func ClearQuota(_ mount.Interface, _ string) error {
return errNotImplemented
}
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = ["quota_xfs.go"],
cgo = True,
importpath = "k8s.io/kubernetes/pkg/volume/util/quota/xfs",
visibility = ["//visibility:public"],
deps = select({
"@io_bazel_rules_go//go/platform:linux": [
"//pkg/volume/util/quota/common:go_default_library",
"//vendor/golang.org/x/sys/unix:go_default_library",
"//vendor/k8s.io/klog:go_default_library",
],
"//conditions:default": [],
}),
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
// +build linux
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package xfs
/*
#include <stdlib.h>
#include <dirent.h>
#include <linux/fs.h>
#include <linux/quota.h>
#include <linux/dqblk_xfs.h>
#include <errno.h>
#ifndef PRJQUOTA
#define PRJQUOTA 2
#endif
#ifndef XFS_PROJ_QUOTA
#define XFS_PROJ_QUOTA 2
#endif
#ifndef Q_XSETPQLIM
#define Q_XSETPQLIM QCMD(Q_XSETQLIM, PRJQUOTA)
#endif
#ifndef Q_XGETPQUOTA
#define Q_XGETPQUOTA QCMD(Q_XGETQUOTA, PRJQUOTA)
#endif
*/
import "C"
import (
"fmt"
"syscall"
"unsafe"
"golang.org/x/sys/unix"
"k8s.io/klog"
"k8s.io/kubernetes/pkg/volume/util/quota/common"
)
const (
linuxXfsMagic = 0x58465342
// Documented in man xfs_quota(8); not necessarily the same
// as the filesystem blocksize
quotaBsize = 512
bitsPerWord = 32 << (^uint(0) >> 63) // either 32 or 64
maxQuota int64 = 1<<(bitsPerWord-1) - 1 // either 1<<31 - 1 or 1<<63 - 1
)
// VolumeProvider supplies a quota applier to the generic code.
type VolumeProvider struct {
}
// GetQuotaApplier -- does this backing device support quotas that
// can be applied to directories?
func (*VolumeProvider) GetQuotaApplier(mountpoint string, backingDev string) common.LinuxVolumeQuotaApplier {
if common.IsFilesystemOfType(mountpoint, backingDev, linuxXfsMagic) {
return xfsVolumeQuota{backingDev}
}
return nil
}
type xfsVolumeQuota struct {
backingDev string
}
// GetQuotaOnDir -- get the quota ID that applies to this directory.
func (v xfsVolumeQuota) GetQuotaOnDir(path string) (common.QuotaID, error) {
return common.GetQuotaOnDir(path)
}
// SetQuotaOnDir -- apply the specified quota to the directory. If
// bytes is not greater than zero, the quota should be applied in a
// way that is non-enforcing (either explicitly so or by setting a
// quota larger than anything the user may possibly create)
func (v xfsVolumeQuota) SetQuotaOnDir(path string, id common.QuotaID, bytes int64) error {
klog.V(3).Infof("xfsSetQuotaOn %s ID %v bytes %v", path, id, bytes)
if bytes < 0 || bytes > maxQuota {
bytes = maxQuota
}
var d C.fs_disk_quota_t
d.d_version = C.FS_DQUOT_VERSION
d.d_id = C.__u32(id)
d.d_flags = C.XFS_PROJ_QUOTA
d.d_fieldmask = C.FS_DQ_BHARD
d.d_blk_hardlimit = C.__u64(bytes / quotaBsize)
d.d_blk_softlimit = d.d_blk_hardlimit
var cs = C.CString(v.backingDev)
defer C.free(unsafe.Pointer(cs))
_, _, errno := unix.Syscall6(unix.SYS_QUOTACTL, C.Q_XSETPQLIM,
uintptr(unsafe.Pointer(cs)), uintptr(d.d_id),
uintptr(unsafe.Pointer(&d)), 0, 0)
if errno != 0 {
return fmt.Errorf("Failed to set quota limit for ID %d on %s: %v",
id, path, errno.Error())
}
return common.ApplyProjectToDir(path, id)
}
func (v xfsVolumeQuota) getQuotaInfo(path string, id common.QuotaID) (C.fs_disk_quota_t, syscall.Errno) {
var d C.fs_disk_quota_t
var cs = C.CString(v.backingDev)
defer C.free(unsafe.Pointer(cs))
_, _, errno := unix.Syscall6(unix.SYS_QUOTACTL, C.Q_XGETPQUOTA,
uintptr(unsafe.Pointer(cs)), uintptr(C.__u32(id)),
uintptr(unsafe.Pointer(&d)), 0, 0)
return d, errno
}
// QuotaIDIsInUse -- determine whether the quota ID is already in use.
func (v xfsVolumeQuota) QuotaIDIsInUse(path string, id common.QuotaID) (bool, error) {
_, errno := v.getQuotaInfo(path, id)
return errno == 0, nil
}
// GetConsumption -- retrieve the consumption (in bytes) of the directory
func (v xfsVolumeQuota) GetConsumption(path string, id common.QuotaID) (int64, error) {
d, errno := v.getQuotaInfo(path, id)
if errno != 0 {
return 0, fmt.Errorf("Failed to get quota for %s: %s", path, errno.Error())
}
klog.V(3).Infof("Consumption for %s is %v", path, d.d_bcount*quotaBsize)
return int64(d.d_bcount) * quotaBsize, nil
}
// GetInodes -- retrieve the number of inodes in use under the directory
func (v xfsVolumeQuota) GetInodes(path string, id common.QuotaID) (int64, error) {
d, errno := v.getQuotaInfo(path, id)
if errno != 0 {
return 0, fmt.Errorf("Failed to get quota for %s: %s", path, errno.Error())
}
klog.V(3).Infof("Inode consumption for %s is %v", path, d.d_icount)
return int64(d.d_icount), nil
}
...@@ -103,8 +103,9 @@ type Attributes struct { ...@@ -103,8 +103,9 @@ type Attributes struct {
// MounterArgs provides more easily extensible arguments to Mounter // MounterArgs provides more easily extensible arguments to Mounter
type MounterArgs struct { type MounterArgs struct {
FsGroup *int64 FsGroup *int64
PodUID string DesiredSize int64
PodUID string
} }
// Mounter interface provides methods to set up/mount the volume. // Mounter interface provides methods to set up/mount the volume.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment