blob: 9a96db207b4daca2b8ba05b94f0a0ab212005dfe [file] [log] [blame]
Lorenz Brunf2b7ab62022-05-04 19:06:00 +02001From 9f2b5274f50a7e4d5ae096e549c0c16addcc447b Mon Sep 17 00:00:00 2001
Lorenz Brun878f5f92020-05-12 16:15:39 +02002From: Lorenz Brun <lorenz@brun.one>
3Date: Tue, 17 Mar 2020 21:41:08 +0100
Lorenz Brunf2b7ab62022-05-04 19:06:00 +02004Subject: [PATCH 2/5] Add a native volume metrics implementation
Lorenz Brun878f5f92020-05-12 16:15:39 +02005
6---
Lorenz Brun878f5f92020-05-12 16:15:39 +02007 pkg/volume/metrics_native.go | 101 +++++++++++++++++++++++++++++
8 pkg/volume/metrics_native_test.go | 102 ++++++++++++++++++++++++++++++
Lorenz Brund13c1c62022-03-30 19:58:58 +02009 2 files changed, 203 insertions(+)
Lorenz Brun878f5f92020-05-12 16:15:39 +020010 create mode 100644 pkg/volume/metrics_native.go
11 create mode 100644 pkg/volume/metrics_native_test.go
12
Lorenz Brun878f5f92020-05-12 16:15:39 +020013diff --git a/pkg/volume/metrics_native.go b/pkg/volume/metrics_native.go
14new file mode 100644
15index 00000000000..3934b946f2e
16--- /dev/null
17+++ b/pkg/volume/metrics_native.go
18@@ -0,0 +1,101 @@
19+/*
20+Copyright 2020 The Kubernetes Authors.
21+
22+Licensed under the Apache License, Version 2.0 (the "License");
23+you may not use this file except in compliance with the License.
24+You may obtain a copy of the License at
25+
26+ http://www.apache.org/licenses/LICENSE-2.0
27+
28+Unless required by applicable law or agreed to in writing, software
29+distributed under the License is distributed on an "AS IS" BASIS,
30+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
31+See the License for the specific language governing permissions and
32+limitations under the License.
33+*/
34+
35+package volume
36+
37+import (
38+ "os"
39+ "path/filepath"
40+
41+ "golang.org/x/sys/unix"
42+ "k8s.io/apimachinery/pkg/api/resource"
43+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
44+)
45+
46+var _ MetricsProvider = &metricsNative{}
47+
48+// MetricsNative represents a MetricsProvider that calculates the volume metrics with either a quota
49+// or by walking the path using syscalls.
50+type metricsNative struct {
51+ path string
52+}
53+
54+// NewMetricsNative returns a new metricsNative provider
55+func NewMetricsNative(path string) MetricsProvider {
56+ return &metricsNative{path}
57+}
58+
59+// GetMetrics returns an empty Metrics and an error.
60+// See MetricsProvider.GetMetrics
61+func (m *metricsNative) GetMetrics() (*Metrics, error) {
62+ var inodesCount int64
63+ var bytesCount int64
64+
65+ var getMetricsRecursive func(path string) error
66+ getMetricsRecursive = func(path string) error {
67+ var stat unix.Stat_t
68+ err := unix.Lstat(path, &stat)
69+ if os.IsNotExist(err) {
70+ return nil
71+ } else if err != nil {
72+ return err
73+ }
74+ // TODO: This double-counts hardlinks
75+ bytesCount += stat.Blocks * 512
76+ inodesCount++
77+ if stat.Mode&unix.S_IFDIR != 0 && stat.Mode&unix.S_IFLNK == 0 {
78+ fd, err := os.Open(path)
79+ if os.IsNotExist(err) {
80+ return nil
81+ } else if err != nil {
82+ return err
83+ }
84+ // We manually close fd before recursing, otherwise we have too many FDs open
85+
86+ entries, err := fd.Readdirnames(0)
87+ if err != nil {
88+ fd.Close()
89+ return err
90+ }
91+ fd.Close()
92+ for _, entry := range entries {
93+ if err := getMetricsRecursive(filepath.Join(path, entry)); err != nil {
94+ return err
95+ }
96+ }
97+ }
98+ return nil
99+ }
100+
101+ if err := getMetricsRecursive(m.path); err != nil {
102+ return &Metrics{}, err
103+ }
104+
105+ var statfs unix.Statfs_t
106+ if err := unix.Statfs(m.path, &statfs); err != nil {
107+ return &Metrics{}, err
108+ }
109+
110+ return &Metrics{
111+ Time: metav1.Now(),
112+ Used: resource.NewQuantity(bytesCount, resource.BinarySI),
113+ InodesUsed: resource.NewQuantity(inodesCount, resource.BinarySI),
114+ Available: resource.NewQuantity(int64(statfs.Bavail)*statfs.Bsize, resource.BinarySI),
115+ Capacity: resource.NewQuantity(int64(statfs.Blocks)*statfs.Bsize, resource.BinarySI),
116+ Inodes: resource.NewQuantity(int64(statfs.Files)*statfs.Bsize, resource.BinarySI),
117+ InodesFree: resource.NewQuantity(int64(statfs.Ffree)*statfs.Bsize, resource.BinarySI),
118+ }, nil
119+}
120diff --git a/pkg/volume/metrics_native_test.go b/pkg/volume/metrics_native_test.go
121new file mode 100644
122index 00000000000..2d5546591ce
123--- /dev/null
124+++ b/pkg/volume/metrics_native_test.go
125@@ -0,0 +1,102 @@
126+// +build linux
127+
128+/*
129+Copyright 2015 The Kubernetes Authors.
130+
131+Licensed under the Apache License, Version 2.0 (the "License");
132+you may not use this file except in compliance with the License.
133+You may obtain a copy of the License at
134+
135+ http://www.apache.org/licenses/LICENSE-2.0
136+
137+Unless required by applicable law or agreed to in writing, software
138+distributed under the License is distributed on an "AS IS" BASIS,
139+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
140+See the License for the specific language governing permissions and
141+limitations under the License.
142+*/
143+
144+package volume_test
145+
146+import (
147+ "io/ioutil"
148+ "os"
149+ "path/filepath"
150+ "testing"
151+
152+ utiltesting "k8s.io/client-go/util/testing"
153+ . "k8s.io/kubernetes/pkg/volume"
154+ volumetest "k8s.io/kubernetes/pkg/volume/testing"
155+)
156+
157+// TestMetricsNativeGetCapacity tests that MetricsNative can read disk usage
158+// for path
159+func TestMetricsNativeGetCapacity(t *testing.T) {
160+ tmpDir, err := utiltesting.MkTmpdir("metrics_du_test")
161+ if err != nil {
162+ t.Fatalf("Can't make a tmp dir: %v", err)
163+ }
164+ defer os.RemoveAll(tmpDir)
165+ metrics := NewMetricsNative(tmpDir)
166+
167+ expectedEmptyDirUsage, err := volumetest.FindEmptyDirectoryUsageOnTmpfs()
168+ if err != nil {
169+ t.Errorf("Unexpected error finding expected empty directory usage on tmpfs: %v", err)
170+ }
171+
172+ actual, err := metrics.GetMetrics()
173+ if err != nil {
174+ t.Errorf("Unexpected error when calling GetMetrics %v", err)
175+ }
176+ if e, a := expectedEmptyDirUsage.Value(), actual.Used.Value(); e != a {
177+ t.Errorf("Unexpected value for empty directory; expected %v, got %v", e, a)
178+ }
179+
180+ // TODO(pwittroc): Figure out a way to test these values for correctness, maybe by formatting and mounting a file
181+ // as a filesystem
182+ if a := actual.Capacity.Value(); a <= 0 {
183+ t.Errorf("Expected Capacity %d to be greater than 0.", a)
184+ }
185+ if a := actual.Available.Value(); a <= 0 {
186+ t.Errorf("Expected Available %d to be greater than 0.", a)
187+ }
188+
189+ // Write a file in a directory and expect Used to increase
190+ os.MkdirAll(filepath.Join(tmpDir, "d1"), 0755)
191+ ioutil.WriteFile(filepath.Join(tmpDir, "d1", "f1"), []byte("Hello World"), os.ModeTemporary)
192+ actual, err = metrics.GetMetrics()
193+ if err != nil {
194+ t.Errorf("Unexpected error when calling GetMetrics %v", err)
195+ }
196+ if e, a := (2*expectedEmptyDirUsage.Value() + getExpectedBlockSize(filepath.Join(tmpDir, "d1", "f1"))), actual.Used.Value(); e != a {
197+ t.Errorf("Unexpected Used for directory with file. Expected %v, got %d.", e, a)
198+ }
199+}
200+
201+// TestMetricsNativeRequireInit tests that if MetricsNative is not initialized with a path, GetMetrics
202+// returns an error
203+func TestMetricsNativeRequirePath(t *testing.T) {
204+ metrics := NewMetricsNative("")
205+ actual, err := metrics.GetMetrics()
206+ expected := &Metrics{}
207+ if !volumetest.MetricsEqualIgnoreTimestamp(actual, expected) {
208+ t.Errorf("Expected empty Metrics from uninitialized MetricsNative, actual %v", *actual)
209+ }
210+ if err == nil {
211+ t.Errorf("Expected error when calling GetMetrics on uninitialized MetricsNative, actual nil")
212+ }
213+}
214+
215+// TestMetricsNativeRealDirectory tests that if MetricsNative is initialized to a non-existent path, GetMetrics
216+// returns an error
217+func TestMetricsNativeRequireRealDirectory(t *testing.T) {
218+ metrics := NewMetricsNative("/not/a/real/directory")
219+ actual, err := metrics.GetMetrics()
220+ expected := &Metrics{}
221+ if !volumetest.MetricsEqualIgnoreTimestamp(actual, expected) {
222+ t.Errorf("Expected empty Metrics from incorrectly initialized MetricsNative, actual %v", *actual)
223+ }
224+ if err == nil {
225+ t.Errorf("Expected error when calling GetMetrics on incorrectly initialized MetricsNative, actual nil")
226+ }
227+}
228--
2292.25.1
230