mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-25 12:43:23 +00:00
Merge pull request #18232 from pwittrock/fsacct
Auto commit by PR queue bot
This commit is contained in:
commit
23baca87b5
@ -420,6 +420,7 @@ func newMockRecycler(spec *volume.Spec, host volume.VolumeHost, config volume.Vo
|
||||
type mockRecycler struct {
|
||||
path string
|
||||
host volume.VolumeHost
|
||||
volume.MetricsNil
|
||||
}
|
||||
|
||||
func (r *mockRecycler) GetPath() string {
|
||||
|
@ -514,6 +514,7 @@ func TestGetPodVolumesFromDisk(t *testing.T) {
|
||||
|
||||
type stubVolume struct {
|
||||
path string
|
||||
volume.MetricsNil
|
||||
}
|
||||
|
||||
func (f *stubVolume) GetPath() string {
|
||||
@ -559,9 +560,9 @@ func TestMakeVolumeMounts(t *testing.T) {
|
||||
}
|
||||
|
||||
podVolumes := kubecontainer.VolumeMap{
|
||||
"disk": kubecontainer.VolumeInfo{Builder: &stubVolume{"/mnt/disk"}},
|
||||
"disk4": kubecontainer.VolumeInfo{Builder: &stubVolume{"/mnt/host"}},
|
||||
"disk5": kubecontainer.VolumeInfo{Builder: &stubVolume{"/var/lib/kubelet/podID/volumes/empty/disk5"}},
|
||||
"disk": kubecontainer.VolumeInfo{Builder: &stubVolume{path: "/mnt/disk"}},
|
||||
"disk4": kubecontainer.VolumeInfo{Builder: &stubVolume{path: "/mnt/host"}},
|
||||
"disk5": kubecontainer.VolumeInfo{Builder: &stubVolume{path: "/var/lib/kubelet/podID/volumes/empty/disk5"}},
|
||||
}
|
||||
|
||||
pod := api.Pod{
|
||||
|
@ -144,6 +144,7 @@ type awsElasticBlockStore struct {
|
||||
// Mounter interface that provides system calls to mount the global path to the pod local path.
|
||||
mounter mount.Interface
|
||||
plugin *awsElasticBlockStorePlugin
|
||||
volume.MetricsNil
|
||||
}
|
||||
|
||||
func detachDiskLogError(ebs *awsElasticBlockStore) {
|
||||
|
@ -143,6 +143,7 @@ type cephfs struct {
|
||||
readonly bool
|
||||
mounter mount.Interface
|
||||
plugin *cephfsPlugin
|
||||
volume.MetricsNil
|
||||
}
|
||||
|
||||
type cephfsBuilder struct {
|
||||
|
@ -144,6 +144,7 @@ type cinderVolume struct {
|
||||
// diskMounter provides the interface that is used to mount the actual block device.
|
||||
blockDeviceMounter mount.Interface
|
||||
plugin *cinderPlugin
|
||||
volume.MetricsNil
|
||||
}
|
||||
|
||||
func detachDiskLogError(cd *cinderVolume) {
|
||||
|
@ -90,6 +90,7 @@ type downwardAPIVolume struct {
|
||||
pod *api.Pod
|
||||
podUID types.UID // TODO: remove this redundancy as soon NewCleaner func will have *api.POD and not only types.UID
|
||||
plugin *downwardAPIPlugin
|
||||
volume.MetricsNil
|
||||
}
|
||||
|
||||
// This is the spec for the volume that this plugin wraps.
|
||||
|
@ -79,13 +79,14 @@ func (plugin *emptyDirPlugin) newBuilderInternal(spec *volume.Spec, pod *api.Pod
|
||||
medium = spec.Volume.EmptyDir.Medium
|
||||
}
|
||||
return &emptyDir{
|
||||
pod: pod,
|
||||
volName: spec.Name(),
|
||||
medium: medium,
|
||||
mounter: mounter,
|
||||
mountDetector: mountDetector,
|
||||
plugin: plugin,
|
||||
rootContext: opts.RootContext,
|
||||
pod: pod,
|
||||
volName: spec.Name(),
|
||||
medium: medium,
|
||||
mounter: mounter,
|
||||
mountDetector: mountDetector,
|
||||
plugin: plugin,
|
||||
rootContext: opts.RootContext,
|
||||
MetricsProvider: volume.NewMetricsDu(GetPath(pod.UID, spec.Name(), plugin.host)),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -96,12 +97,13 @@ func (plugin *emptyDirPlugin) NewCleaner(volName string, podUID types.UID) (volu
|
||||
|
||||
func (plugin *emptyDirPlugin) newCleanerInternal(volName string, podUID types.UID, mounter mount.Interface, mountDetector mountDetector) (volume.Cleaner, error) {
|
||||
ed := &emptyDir{
|
||||
pod: &api.Pod{ObjectMeta: api.ObjectMeta{UID: podUID}},
|
||||
volName: volName,
|
||||
medium: api.StorageMediumDefault, // might be changed later
|
||||
mounter: mounter,
|
||||
mountDetector: mountDetector,
|
||||
plugin: plugin,
|
||||
pod: &api.Pod{ObjectMeta: api.ObjectMeta{UID: podUID}},
|
||||
volName: volName,
|
||||
medium: api.StorageMediumDefault, // might be changed later
|
||||
mounter: mounter,
|
||||
mountDetector: mountDetector,
|
||||
plugin: plugin,
|
||||
MetricsProvider: volume.NewMetricsDu(GetPath(podUID, volName, plugin.host)),
|
||||
}
|
||||
return ed, nil
|
||||
}
|
||||
@ -133,6 +135,7 @@ type emptyDir struct {
|
||||
mountDetector mountDetector
|
||||
plugin *emptyDirPlugin
|
||||
rootContext string
|
||||
volume.MetricsProvider
|
||||
}
|
||||
|
||||
func (ed *emptyDir) GetAttributes() volume.Attributes {
|
||||
@ -265,8 +268,12 @@ func (ed *emptyDir) setupDir(dir string) error {
|
||||
}
|
||||
|
||||
func (ed *emptyDir) GetPath() string {
|
||||
return GetPath(ed.pod.UID, ed.volName, ed.plugin.host)
|
||||
}
|
||||
|
||||
func GetPath(uid types.UID, volName string, host volume.VolumeHost) string {
|
||||
name := emptyDirPluginName
|
||||
return ed.plugin.host.GetPodVolumeDir(ed.pod.UID, util.EscapeQualifiedNameForDisk(name), ed.volName)
|
||||
return host.GetPodVolumeDir(uid, util.EscapeQualifiedNameForDisk(name), volName)
|
||||
}
|
||||
|
||||
// TearDown simply discards everything in the directory.
|
||||
|
@ -274,3 +274,42 @@ func TestPluginBackCompat(t *testing.T) {
|
||||
t.Errorf("Got unexpected path: %s", volPath)
|
||||
}
|
||||
}
|
||||
|
||||
// TestMetrics tests that MetricProvider methods return sane values.
|
||||
func TestMetrics(t *testing.T) {
|
||||
// Create an empty temp directory for the volume
|
||||
tmpDir, err := ioutil.TempDir(os.TempDir(), "empty_dir_test")
|
||||
if err != nil {
|
||||
t.Fatalf("Can't make a tmp dir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
plug := makePluginUnderTest(t, "kubernetes.io/empty-dir", tmpDir)
|
||||
|
||||
spec := &api.Volume{
|
||||
Name: "vol1",
|
||||
}
|
||||
pod := &api.Pod{ObjectMeta: api.ObjectMeta{UID: types.UID("poduid")}}
|
||||
builder, err := plug.NewBuilder(volume.NewSpecFromVolume(spec), pod, volume.VolumeOptions{RootContext: ""})
|
||||
if err != nil {
|
||||
t.Errorf("Failed to make a new Builder: %v", err)
|
||||
}
|
||||
|
||||
// Need to create the subdirectory
|
||||
os.MkdirAll(builder.GetPath(), 0755)
|
||||
|
||||
// TODO(pwittroc): Move this into a reusable testing utility
|
||||
metrics, err := builder.GetMetrics()
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error when calling GetMetrics %v", err)
|
||||
}
|
||||
if metrics.Used.Value() != 4096 {
|
||||
t.Errorf("Expected Used %d to be 4096", metrics.Used.Value())
|
||||
}
|
||||
if metrics.Capacity.Value() <= 0 {
|
||||
t.Errorf("Expected Capacity to be greater than 0")
|
||||
}
|
||||
if metrics.Available.Value() <= 0 {
|
||||
t.Errorf("Expected Available to be greater than 0")
|
||||
}
|
||||
}
|
||||
|
@ -149,6 +149,7 @@ type fcDisk struct {
|
||||
manager diskManager
|
||||
// io handler interface
|
||||
io ioHandler
|
||||
volume.MetricsNil
|
||||
}
|
||||
|
||||
func (fc *fcDisk) GetPath() string {
|
||||
|
@ -111,6 +111,7 @@ type flockerBuilder struct {
|
||||
exe exec.Interface
|
||||
opts volume.VolumeOptions
|
||||
readOnly bool
|
||||
volume.MetricsNil
|
||||
}
|
||||
|
||||
func (b flockerBuilder) GetAttributes() volume.Attributes {
|
||||
|
@ -144,6 +144,7 @@ type gcePersistentDisk struct {
|
||||
// Mounter interface that provides system calls to mount the global path to the pod local path.
|
||||
mounter mount.Interface
|
||||
plugin *gcePersistentDiskPlugin
|
||||
volume.MetricsNil
|
||||
}
|
||||
|
||||
func detachDiskLogError(pd *gcePersistentDisk) {
|
||||
|
@ -89,6 +89,7 @@ type gitRepoVolume struct {
|
||||
volName string
|
||||
podUID types.UID
|
||||
plugin *gitRepoPlugin
|
||||
volume.MetricsNil
|
||||
}
|
||||
|
||||
var _ volume.Volume = &gitRepoVolume{}
|
||||
|
@ -142,6 +142,7 @@ type glusterfs struct {
|
||||
pod *api.Pod
|
||||
mounter mount.Interface
|
||||
plugin *glusterfsPlugin
|
||||
volume.MetricsNil
|
||||
}
|
||||
|
||||
type glusterfsBuilder struct {
|
||||
|
@ -95,20 +95,25 @@ func (plugin *hostPathPlugin) GetAccessModes() []api.PersistentVolumeAccessMode
|
||||
|
||||
func (plugin *hostPathPlugin) NewBuilder(spec *volume.Spec, pod *api.Pod, _ volume.VolumeOptions) (volume.Builder, error) {
|
||||
if spec.Volume != nil && spec.Volume.HostPath != nil {
|
||||
path := spec.Volume.HostPath.Path
|
||||
return &hostPathBuilder{
|
||||
hostPath: &hostPath{path: spec.Volume.HostPath.Path},
|
||||
hostPath: &hostPath{path: path, MetricsProvider: volume.NewMetricsDu(path)},
|
||||
readOnly: false,
|
||||
}, nil
|
||||
} else {
|
||||
path := spec.PersistentVolume.Spec.HostPath.Path
|
||||
return &hostPathBuilder{
|
||||
hostPath: &hostPath{path: spec.PersistentVolume.Spec.HostPath.Path},
|
||||
hostPath: &hostPath{path: path, MetricsProvider: volume.NewMetricsDu(path)},
|
||||
readOnly: spec.ReadOnly,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (plugin *hostPathPlugin) NewCleaner(volName string, podUID types.UID) (volume.Cleaner, error) {
|
||||
return &hostPathCleaner{&hostPath{""}}, nil
|
||||
return &hostPathCleaner{&hostPath{
|
||||
path: "",
|
||||
MetricsProvider: volume.NewMetricsDu(""),
|
||||
}}, nil
|
||||
}
|
||||
|
||||
func (plugin *hostPathPlugin) NewRecycler(spec *volume.Spec) (volume.Recycler, error) {
|
||||
@ -130,12 +135,14 @@ func newRecycler(spec *volume.Spec, host volume.VolumeHost, config volume.Volume
|
||||
if spec.PersistentVolume == nil || spec.PersistentVolume.Spec.HostPath == nil {
|
||||
return nil, fmt.Errorf("spec.PersistentVolumeSource.HostPath is nil")
|
||||
}
|
||||
path := spec.PersistentVolume.Spec.HostPath.Path
|
||||
return &hostPathRecycler{
|
||||
name: spec.Name(),
|
||||
path: spec.PersistentVolume.Spec.HostPath.Path,
|
||||
host: host,
|
||||
config: config,
|
||||
timeout: volume.CalculateTimeoutForVolume(config.RecyclerMinimumTimeout, config.RecyclerTimeoutIncrement, spec.PersistentVolume),
|
||||
name: spec.Name(),
|
||||
path: path,
|
||||
host: host,
|
||||
config: config,
|
||||
timeout: volume.CalculateTimeoutForVolume(config.RecyclerMinimumTimeout, config.RecyclerTimeoutIncrement, spec.PersistentVolume),
|
||||
MetricsProvider: volume.NewMetricsDu(path),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -143,7 +150,8 @@ func newDeleter(spec *volume.Spec, host volume.VolumeHost) (volume.Deleter, erro
|
||||
if spec.PersistentVolume != nil && spec.PersistentVolume.Spec.HostPath == nil {
|
||||
return nil, fmt.Errorf("spec.PersistentVolumeSource.HostPath is nil")
|
||||
}
|
||||
return &hostPathDeleter{spec.Name(), spec.PersistentVolume.Spec.HostPath.Path, host}, nil
|
||||
path := spec.PersistentVolume.Spec.HostPath.Path
|
||||
return &hostPathDeleter{spec.Name(), path, host, volume.NewMetricsDu(path)}, nil
|
||||
}
|
||||
|
||||
func newCreater(options volume.VolumeOptions, host volume.VolumeHost) (volume.Creater, error) {
|
||||
@ -154,6 +162,7 @@ func newCreater(options volume.VolumeOptions, host volume.VolumeHost) (volume.Cr
|
||||
// The direct at the specified path will be directly exposed to the container.
|
||||
type hostPath struct {
|
||||
path string
|
||||
volume.MetricsProvider
|
||||
}
|
||||
|
||||
func (hp *hostPath) GetPath() string {
|
||||
@ -214,6 +223,7 @@ type hostPathRecycler struct {
|
||||
host volume.VolumeHost
|
||||
config volume.VolumeConfig
|
||||
timeout int64
|
||||
volume.MetricsProvider
|
||||
}
|
||||
|
||||
func (r *hostPathRecycler) GetPath() string {
|
||||
@ -280,6 +290,7 @@ type hostPathDeleter struct {
|
||||
name string
|
||||
path string
|
||||
host volume.VolumeHost
|
||||
volume.MetricsProvider
|
||||
}
|
||||
|
||||
func (r *hostPathDeleter) GetPath() string {
|
||||
|
@ -18,6 +18,7 @@ package host_path
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
@ -92,7 +93,7 @@ func TestDeleter(t *testing.T) {
|
||||
defer os.RemoveAll(tempPath)
|
||||
err := os.MkdirAll(tempPath, 0750)
|
||||
if err != nil {
|
||||
t.Fatal("Failed to create tmp directory for deleter: %v", err)
|
||||
t.Fatalf("Failed to create tmp directory for deleter: %v", err)
|
||||
}
|
||||
|
||||
plugMgr := volume.VolumePluginMgr{}
|
||||
@ -272,3 +273,45 @@ func TestPersistentClaimReadOnlyFlag(t *testing.T) {
|
||||
t.Errorf("Expected true for builder.IsReadOnly")
|
||||
}
|
||||
}
|
||||
|
||||
// TestMetrics tests that MetricProvider methods return sane values.
|
||||
func TestMetrics(t *testing.T) {
|
||||
// Create an empty temp directory for the volume
|
||||
tmpDir, err := ioutil.TempDir(os.TempDir(), "host_path_test")
|
||||
if err != nil {
|
||||
t.Fatalf("Can't make a tmp dir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
plugMgr := volume.VolumePluginMgr{}
|
||||
plugMgr.InitPlugins(ProbeVolumePlugins(volume.VolumeConfig{}), volume.NewFakeVolumeHost(tmpDir, nil, nil))
|
||||
|
||||
plug, err := plugMgr.FindPluginByName("kubernetes.io/host-path")
|
||||
if err != nil {
|
||||
t.Errorf("Can't find the plugin by name")
|
||||
}
|
||||
spec := &api.Volume{
|
||||
Name: "vol1",
|
||||
VolumeSource: api.VolumeSource{HostPath: &api.HostPathVolumeSource{Path: tmpDir}},
|
||||
}
|
||||
pod := &api.Pod{ObjectMeta: api.ObjectMeta{UID: types.UID("poduid")}}
|
||||
builder, err := plug.NewBuilder(volume.NewSpecFromVolume(spec), pod, volume.VolumeOptions{})
|
||||
if err != nil {
|
||||
t.Errorf("Failed to make a new Builder: %v", err)
|
||||
}
|
||||
|
||||
// TODO(pwittroc): Move this into a reusable testing utility
|
||||
metrics, err := builder.GetMetrics()
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error when calling GetMetrics %v", err)
|
||||
}
|
||||
if metrics.Used.Value() != 4096 {
|
||||
t.Errorf("Expected Used %d to be 4096", metrics.Used)
|
||||
}
|
||||
if metrics.Capacity.Value() <= 0 {
|
||||
t.Errorf("Expected Capacity to be greater than 0")
|
||||
}
|
||||
if metrics.Available.Value() <= 0 {
|
||||
t.Errorf("Expected Available to be greater than 0")
|
||||
}
|
||||
}
|
||||
|
@ -147,6 +147,7 @@ type iscsiDisk struct {
|
||||
plugin *iscsiPlugin
|
||||
// Utility interface that provides API calls to the provider to attach/detach disks.
|
||||
manager diskManager
|
||||
volume.MetricsNil
|
||||
}
|
||||
|
||||
func (iscsi *iscsiDisk) GetPath() string {
|
||||
|
91
pkg/volume/metrics_du.go
Normal file
91
pkg/volume/metrics_du.go
Normal file
@ -0,0 +1,91 @@
|
||||
/*
|
||||
Copyright 2014 The Kubernetes Authors All rights reserved.
|
||||
|
||||
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 volume
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api/resource"
|
||||
"k8s.io/kubernetes/pkg/volume/util"
|
||||
)
|
||||
|
||||
var _ MetricsProvider = &metricsDu{}
|
||||
|
||||
// metricsDu represents a MetricsProvider that calculates the used and available
|
||||
// Volume space by executing the "du" command and gathering filesystem info for the Volume path.
|
||||
type metricsDu struct {
|
||||
// the directory path the volume is mounted to.
|
||||
path string
|
||||
}
|
||||
|
||||
// NewMetricsDu creates a new metricsDu with the Volume path.
|
||||
func NewMetricsDu(path string) MetricsProvider {
|
||||
return &metricsDu{path}
|
||||
}
|
||||
|
||||
// See MetricsProvider.GetMetrics
|
||||
// GetMetrics calculates the volume usage and device free space by executing "du"
|
||||
// and gathering filesystem info for the Volume path.
|
||||
func (md *metricsDu) GetMetrics() (*Metrics, error) {
|
||||
metrics := &Metrics{}
|
||||
if md.path == "" {
|
||||
return metrics, errors.New("no path defined for disk usage metrics.")
|
||||
}
|
||||
|
||||
err := md.runDu(metrics)
|
||||
if err != nil {
|
||||
return metrics, err
|
||||
}
|
||||
|
||||
err = md.getFsInfo(metrics)
|
||||
if err != nil {
|
||||
return metrics, err
|
||||
}
|
||||
|
||||
return metrics, nil
|
||||
}
|
||||
|
||||
// runDu executes the "du" command and writes the results to metrics.Used
|
||||
func (md *metricsDu) runDu(metrics *Metrics) error {
|
||||
// Uses the same niceness level as cadvisor.fs does when running du
|
||||
// Uses -B 1 to always scale to a blocksize of 1 byte
|
||||
out, err := exec.Command("nice", "-n", "19", "du", "-s", "-B", "1", md.path).CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed command 'du' on %s with error %v", md.path, err)
|
||||
}
|
||||
used, err := resource.ParseQuantity(strings.Fields(string(out))[0])
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse 'du' output %s due to error %v", out, err)
|
||||
}
|
||||
used.Format = resource.BinarySI
|
||||
metrics.Used = used
|
||||
return nil
|
||||
}
|
||||
|
||||
// getFsInfo writes metrics.Capacity and metrics.Available from the filesystem info
|
||||
func (md *metricsDu) getFsInfo(metrics *Metrics) error {
|
||||
available, capacity, err := util.FsInfo(md.path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to get FsInfo due to error %v", err)
|
||||
}
|
||||
metrics.Available = resource.NewQuantity(available, resource.BinarySI)
|
||||
metrics.Capacity = resource.NewQuantity(capacity, resource.BinarySI)
|
||||
return nil
|
||||
}
|
90
pkg/volume/metrics_du_test.go
Normal file
90
pkg/volume/metrics_du_test.go
Normal file
@ -0,0 +1,90 @@
|
||||
/*
|
||||
Copyright 2015 The Kubernetes Authors All rights reserved.
|
||||
|
||||
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 volume
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestMetricsDuGetCapacity tests that MetricsDu can read disk usage
|
||||
// for path
|
||||
func TestMetricsDuGetCapacity(t *testing.T) {
|
||||
tmpDir, err := ioutil.TempDir(os.TempDir(), "metrics_du_test")
|
||||
if err != nil {
|
||||
t.Fatalf("Can't make a tmp dir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
metrics := NewMetricsDu(tmpDir)
|
||||
|
||||
actual, err := metrics.GetMetrics()
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error when calling GetMetrics %v", err)
|
||||
}
|
||||
if actual.Used.Value() != 4096 {
|
||||
t.Errorf("Expected Used %d for empty directory to be 4096.", actual.Used.Value())
|
||||
}
|
||||
|
||||
// TODO(pwittroc): Figure out a way to test these values for correctness, maybe by formatting and mounting a file
|
||||
// as a filesystem
|
||||
if actual.Capacity.Value() <= 0 {
|
||||
t.Errorf("Expected Capacity %d to be greater than 0.", actual.Capacity.Value())
|
||||
}
|
||||
if actual.Available.Value() <= 0 {
|
||||
t.Errorf("Expected Available %d to be greater than 0.", actual.Available.Value())
|
||||
}
|
||||
|
||||
// Write a file and expect Used to increase
|
||||
ioutil.WriteFile(filepath.Join(tmpDir, "f1"), []byte("Hello World"), os.ModeTemporary)
|
||||
actual, err = metrics.GetMetrics()
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error when calling GetMetrics %v", err)
|
||||
}
|
||||
if actual.Used.Value() != 8192 {
|
||||
t.Errorf("Unexpected Used for directory with file. Expected 8192, was %d.", actual.Used.Value())
|
||||
}
|
||||
}
|
||||
|
||||
// TestMetricsDuRequireInit tests that if MetricsDu is not initialized with a path, GetMetrics
|
||||
// returns an error
|
||||
func TestMetricsDuRequirePath(t *testing.T) {
|
||||
metrics := &metricsDu{}
|
||||
actual, err := metrics.GetMetrics()
|
||||
expected := &Metrics{}
|
||||
if *actual != *expected {
|
||||
t.Errorf("Expected empty Metrics from uninitialized MetricsDu, actual %v", *actual)
|
||||
}
|
||||
if err == nil {
|
||||
t.Errorf("Expected error when calling GetMetrics on uninitialized MetricsDu, actual nil")
|
||||
}
|
||||
}
|
||||
|
||||
// TestMetricsDuRealDirectory tests that if MetricsDu is initialized to a non-existent path, GetMetrics
|
||||
// returns an error
|
||||
func TestMetricsDuRequireRealDirectory(t *testing.T) {
|
||||
metrics := NewMetricsDu("/not/a/real/directory")
|
||||
actual, err := metrics.GetMetrics()
|
||||
expected := &Metrics{}
|
||||
if *actual != *expected {
|
||||
t.Errorf("Expected empty Metrics from incorrectly initialized MetricsDu, actual %v", *actual)
|
||||
}
|
||||
if err == nil {
|
||||
t.Errorf("Expected error when calling GetMetrics on incorrectly initialized MetricsDu, actual nil")
|
||||
}
|
||||
}
|
31
pkg/volume/metrics_nil.go
Normal file
31
pkg/volume/metrics_nil.go
Normal file
@ -0,0 +1,31 @@
|
||||
/*
|
||||
Copyright 2014 The Kubernetes Authors All rights reserved.
|
||||
|
||||
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 volume
|
||||
|
||||
import "errors"
|
||||
|
||||
var _ MetricsProvider = &MetricsNil{}
|
||||
|
||||
// MetricsNil represents a MetricsProvider that does not support returning
|
||||
// Metrics. It serves as a placeholder for Volumes that do not yet support metrics.
|
||||
type MetricsNil struct{}
|
||||
|
||||
// See MetricsProvider.GetMetrics
|
||||
// GetMetrics returns an empty Metrics and an error.
|
||||
func (*MetricsNil) GetMetrics() (*Metrics, error) {
|
||||
return &Metrics{}, errors.New("metrics are not supported for MetricsNil Volumes")
|
||||
}
|
33
pkg/volume/metrics_nil_test.go
Normal file
33
pkg/volume/metrics_nil_test.go
Normal file
@ -0,0 +1,33 @@
|
||||
/*
|
||||
Copyright 2015 The Kubernetes Authors All rights reserved.
|
||||
|
||||
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 volume
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMetricsNilGetCapacity(t *testing.T) {
|
||||
metrics := &MetricsNil{}
|
||||
actual, err := metrics.GetMetrics()
|
||||
expected := &Metrics{}
|
||||
if *actual != *expected {
|
||||
t.Errorf("Expected empty Metrics, actual %v", *actual)
|
||||
}
|
||||
if err == nil {
|
||||
t.Errorf("Expected error when calling GetMetrics, actual nil")
|
||||
}
|
||||
}
|
@ -131,6 +131,7 @@ type nfs struct {
|
||||
plugin *nfsPlugin
|
||||
// decouple creating recyclers by deferring to a function. Allows for easier testing.
|
||||
newRecyclerFunc func(spec *volume.Spec, host volume.VolumeHost, volumeConfig volume.VolumeConfig) (volume.Recycler, error)
|
||||
volume.MetricsNil
|
||||
}
|
||||
|
||||
func (nfsVolume *nfs) GetPath() string {
|
||||
@ -271,6 +272,7 @@ type nfsRecycler struct {
|
||||
host volume.VolumeHost
|
||||
config volume.VolumeConfig
|
||||
timeout int64
|
||||
volume.MetricsNil
|
||||
}
|
||||
|
||||
func (r *nfsRecycler) GetPath() string {
|
||||
|
@ -92,6 +92,7 @@ func newMockRecycler(spec *volume.Spec, host volume.VolumeHost, config volume.Vo
|
||||
type mockRecycler struct {
|
||||
path string
|
||||
host volume.VolumeHost
|
||||
volume.MetricsNil
|
||||
}
|
||||
|
||||
func (r *mockRecycler) GetPath() string {
|
||||
|
@ -172,6 +172,7 @@ type rbd struct {
|
||||
mounter *mount.SafeFormatAndMount
|
||||
// Utility interface that provides API calls to the provider to attach/detach disks.
|
||||
manager diskManager
|
||||
volume.MetricsNil
|
||||
}
|
||||
|
||||
func (rbd *rbd) GetPath() string {
|
||||
|
@ -61,14 +61,14 @@ func (plugin *secretPlugin) CanSupport(spec *volume.Spec) bool {
|
||||
|
||||
func (plugin *secretPlugin) NewBuilder(spec *volume.Spec, pod *api.Pod, opts volume.VolumeOptions) (volume.Builder, error) {
|
||||
return &secretVolumeBuilder{
|
||||
secretVolume: &secretVolume{spec.Name(), pod.UID, plugin, plugin.host.GetMounter(), plugin.host.GetWriter()},
|
||||
secretVolume: &secretVolume{spec.Name(), pod.UID, plugin, plugin.host.GetMounter(), plugin.host.GetWriter(), volume.MetricsNil{}},
|
||||
secretName: spec.Volume.Secret.SecretName,
|
||||
pod: *pod,
|
||||
opts: &opts}, nil
|
||||
}
|
||||
|
||||
func (plugin *secretPlugin) NewCleaner(volName string, podUID types.UID) (volume.Cleaner, error) {
|
||||
return &secretVolumeCleaner{&secretVolume{volName, podUID, plugin, plugin.host.GetMounter(), plugin.host.GetWriter()}}, nil
|
||||
return &secretVolumeCleaner{&secretVolume{volName, podUID, plugin, plugin.host.GetMounter(), plugin.host.GetWriter(), volume.MetricsNil{}}}, nil
|
||||
}
|
||||
|
||||
type secretVolume struct {
|
||||
@ -77,6 +77,7 @@ type secretVolume struct {
|
||||
plugin *secretPlugin
|
||||
mounter mount.Interface
|
||||
writer ioutil.Writer
|
||||
volume.MetricsNil
|
||||
}
|
||||
|
||||
var _ volume.Volume = &secretVolume{}
|
||||
|
@ -136,19 +136,19 @@ func (plugin *FakeVolumePlugin) CanSupport(spec *Spec) bool {
|
||||
}
|
||||
|
||||
func (plugin *FakeVolumePlugin) NewBuilder(spec *Spec, pod *api.Pod, opts VolumeOptions) (Builder, error) {
|
||||
return &FakeVolume{pod.UID, spec.Name(), plugin}, nil
|
||||
return &FakeVolume{pod.UID, spec.Name(), plugin, MetricsNil{}}, nil
|
||||
}
|
||||
|
||||
func (plugin *FakeVolumePlugin) NewCleaner(volName string, podUID types.UID) (Cleaner, error) {
|
||||
return &FakeVolume{podUID, volName, plugin}, nil
|
||||
return &FakeVolume{podUID, volName, plugin, MetricsNil{}}, nil
|
||||
}
|
||||
|
||||
func (plugin *FakeVolumePlugin) NewRecycler(spec *Spec) (Recycler, error) {
|
||||
return &fakeRecycler{"/attributesTransferredFromSpec"}, nil
|
||||
return &fakeRecycler{"/attributesTransferredFromSpec", MetricsNil{}}, nil
|
||||
}
|
||||
|
||||
func (plugin *FakeVolumePlugin) NewDeleter(spec *Spec) (Deleter, error) {
|
||||
return &FakeDeleter{"/attributesTransferredFromSpec"}, nil
|
||||
return &FakeDeleter{"/attributesTransferredFromSpec", MetricsNil{}}, nil
|
||||
}
|
||||
|
||||
func (plugin *FakeVolumePlugin) GetAccessModes() []api.PersistentVolumeAccessMode {
|
||||
@ -159,6 +159,7 @@ type FakeVolume struct {
|
||||
PodUID types.UID
|
||||
VolName string
|
||||
Plugin *FakeVolumePlugin
|
||||
MetricsNil
|
||||
}
|
||||
|
||||
func (_ *FakeVolume) GetAttributes() Attributes {
|
||||
@ -192,6 +193,7 @@ func (fv *FakeVolume) TearDownAt(dir string) error {
|
||||
|
||||
type fakeRecycler struct {
|
||||
path string
|
||||
MetricsNil
|
||||
}
|
||||
|
||||
func (fr *fakeRecycler) Recycle() error {
|
||||
@ -214,6 +216,7 @@ func NewFakeRecycler(spec *Spec, host VolumeHost, config VolumeConfig) (Recycler
|
||||
|
||||
type FakeDeleter struct {
|
||||
path string
|
||||
MetricsNil
|
||||
}
|
||||
|
||||
func (fd *FakeDeleter) Delete() error {
|
||||
|
41
pkg/volume/util/fs_linux.go
Normal file
41
pkg/volume/util/fs_linux.go
Normal file
@ -0,0 +1,41 @@
|
||||
// +build linux
|
||||
|
||||
/*
|
||||
Copyright 2014 The Kubernetes Authors All rights reserved.
|
||||
|
||||
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 util
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// FSInfo linux returns (available bytes, byte capacity, error) for the filesystem that
|
||||
// path resides upon.
|
||||
func FsInfo(path string) (int64, int64, error) {
|
||||
statfs := &syscall.Statfs_t{}
|
||||
err := syscall.Statfs(path, statfs)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
|
||||
// Available is blocks available * fragment size
|
||||
available := int64(statfs.Bavail) * int64(statfs.Frsize)
|
||||
|
||||
// Capacity is total block count * fragment size
|
||||
capacity := int64(statfs.Blocks) * int64(statfs.Frsize)
|
||||
|
||||
return available, capacity, nil
|
||||
}
|
28
pkg/volume/util/fs_unsupported.go
Normal file
28
pkg/volume/util/fs_unsupported.go
Normal file
@ -0,0 +1,28 @@
|
||||
// +build !linux
|
||||
|
||||
/*
|
||||
Copyright 2014 The Kubernetes Authors All rights reserved.
|
||||
|
||||
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 util
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
// FSInfo unsupported returns 0 values for available and capacity and an error.
|
||||
func FsInfo(path string) (int64, int64, error) {
|
||||
return 0, 0, errors.New("FsInfo not supported for this build.")
|
||||
}
|
@ -19,6 +19,7 @@ package volume
|
||||
import (
|
||||
"io/ioutil"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/resource"
|
||||
"os"
|
||||
"path"
|
||||
)
|
||||
@ -28,6 +29,32 @@ import (
|
||||
type Volume interface {
|
||||
// GetPath returns the directory path the volume is mounted to.
|
||||
GetPath() string
|
||||
|
||||
// MetricsProvider embeds methods for exposing metrics (e.g. used,available space).
|
||||
MetricsProvider
|
||||
}
|
||||
|
||||
// MetricsProvider exposes metrics (e.g. used,available space) related to a Volume.
|
||||
type MetricsProvider interface {
|
||||
// GetMetrics returns the Metrics for the Volume. Maybe expensive for some implementations.
|
||||
GetMetrics() (*Metrics, error)
|
||||
}
|
||||
|
||||
// Metrics represents the used and available bytes of the Volume.
|
||||
type Metrics struct {
|
||||
// Used represents the total bytes used by the Volume.
|
||||
// Note: For block devices this maybe more than the total size of the files.
|
||||
Used *resource.Quantity
|
||||
|
||||
// Capacity represents the total capacity (bytes) of the volume's underlying storage.
|
||||
// For Volumes that share a filesystem with the host (e.g. emptydir, hostpath) this is the size
|
||||
// of the underlying storage, and will not equal Used + Available as the fs is shared.
|
||||
Capacity *resource.Quantity
|
||||
|
||||
// Available represents the storage space available (bytes) for the Volume.
|
||||
// For Volumes that share a filesystem with the host (e.g. emptydir, hostpath), this is the available
|
||||
// space on the underlying storage, and is shared with host processes and other Volumes.
|
||||
Available *resource.Quantity
|
||||
}
|
||||
|
||||
// Attributes represents the attributes of this builder.
|
||||
|
Loading…
Reference in New Issue
Block a user