1
16
17 package stats
18
19 import (
20 "errors"
21 "fmt"
22 "testing"
23 "time"
24
25 "github.com/golang/mock/gomock"
26 "github.com/stretchr/testify/assert"
27
28 csipbv1 "github.com/container-storage-interface/spec/lib/go/csi"
29 k8sv1 "k8s.io/api/core/v1"
30 "k8s.io/apimachinery/pkg/api/resource"
31 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
32 utilfeature "k8s.io/apiserver/pkg/util/feature"
33 "k8s.io/client-go/tools/record"
34 featuregatetesting "k8s.io/component-base/featuregate/testing"
35 kubestats "k8s.io/kubelet/pkg/apis/stats/v1alpha1"
36 "k8s.io/kubernetes/pkg/features"
37 statstest "k8s.io/kubernetes/pkg/kubelet/server/stats/testing"
38 "k8s.io/kubernetes/pkg/volume"
39 )
40
41 const (
42 namespace0 = "test0"
43 pName0 = "pod0"
44 capacity = int64(10000000)
45 available = int64(5000000)
46 inodesTotal = int64(2000)
47 inodesFree = int64(1000)
48
49 vol0 = "vol0"
50 vol1 = "vol1"
51 vol2 = "vol2"
52 vol3 = "vol3"
53 pvcClaimName0 = "pvc-fake0"
54 pvcClaimName1 = "pvc-fake1"
55 )
56
57 var (
58 ErrorWatchTimeout = errors.New("watch event timeout")
59
60 podVolumes = []k8sv1.Volume{
61 {
62 Name: vol0,
63 VolumeSource: k8sv1.VolumeSource{
64 GCEPersistentDisk: &k8sv1.GCEPersistentDiskVolumeSource{
65 PDName: "fake-device1",
66 },
67 },
68 },
69 {
70 Name: vol1,
71 VolumeSource: k8sv1.VolumeSource{
72 PersistentVolumeClaim: &k8sv1.PersistentVolumeClaimVolumeSource{
73 ClaimName: pvcClaimName0,
74 },
75 },
76 },
77 {
78 Name: vol2,
79 VolumeSource: k8sv1.VolumeSource{
80 PersistentVolumeClaim: &k8sv1.PersistentVolumeClaimVolumeSource{
81 ClaimName: pvcClaimName1,
82 },
83 },
84 },
85 {
86 Name: vol3,
87 VolumeSource: k8sv1.VolumeSource{
88 Ephemeral: &k8sv1.EphemeralVolumeSource{},
89 },
90 },
91 }
92
93 fakePod = &k8sv1.Pod{
94 ObjectMeta: metav1.ObjectMeta{
95 Name: pName0,
96 Namespace: namespace0,
97 UID: "UID" + pName0,
98 },
99 Spec: k8sv1.PodSpec{
100 Volumes: podVolumes,
101 },
102 }
103
104 volumeCondition = &csipbv1.VolumeCondition{}
105 )
106
107 func TestPVCRef(t *testing.T) {
108 mockCtrl := gomock.NewController(t)
109 defer mockCtrl.Finish()
110
111
112 mockStats := statstest.NewMockProvider(mockCtrl)
113 volumes := map[string]volume.Volume{vol0: &fakeVolume{}, vol1: &fakeVolume{}, vol3: &fakeVolume{}}
114 mockStats.EXPECT().ListVolumesForPod(fakePod.UID).Return(volumes, true)
115 blockVolumes := map[string]volume.BlockVolume{vol2: &fakeBlockVolume{}}
116 mockStats.EXPECT().ListBlockVolumesForPod(fakePod.UID).Return(blockVolumes, true)
117
118 eventStore := make(chan string, 1)
119 fakeEventRecorder := record.FakeRecorder{
120 Events: eventStore,
121 }
122
123
124 statsCalculator := newVolumeStatCalculator(mockStats, time.Minute, fakePod, &fakeEventRecorder)
125 statsCalculator.calcAndStoreStats()
126 vs, _ := statsCalculator.GetLatest()
127
128 assert.Len(t, append(vs.EphemeralVolumes, vs.PersistentVolumes...), 4)
129
130 assert.Contains(t, append(vs.EphemeralVolumes, vs.PersistentVolumes...), kubestats.VolumeStats{
131 Name: vol0,
132 FsStats: expectedFSStats(),
133 VolumeHealthStats: expectedVolumeHealthStats(),
134 })
135
136 assert.Contains(t, append(vs.EphemeralVolumes, vs.PersistentVolumes...), kubestats.VolumeStats{
137 Name: vol1,
138 PVCRef: &kubestats.PVCReference{
139 Name: pvcClaimName0,
140 Namespace: namespace0,
141 },
142 FsStats: expectedFSStats(),
143 VolumeHealthStats: expectedVolumeHealthStats(),
144 })
145
146 assert.Contains(t, append(vs.EphemeralVolumes, vs.PersistentVolumes...), kubestats.VolumeStats{
147 Name: vol2,
148 PVCRef: &kubestats.PVCReference{
149 Name: pvcClaimName1,
150 Namespace: namespace0,
151 },
152 FsStats: expectedBlockStats(),
153 VolumeHealthStats: expectedVolumeHealthStats(),
154 })
155
156 assert.Contains(t, append(vs.EphemeralVolumes, vs.PersistentVolumes...), kubestats.VolumeStats{
157 Name: vol3,
158 PVCRef: &kubestats.PVCReference{
159 Name: pName0 + "-" + vol3,
160 Namespace: namespace0,
161 },
162 FsStats: expectedFSStats(),
163 VolumeHealthStats: expectedVolumeHealthStats(),
164 })
165 }
166
167 func TestNormalVolumeEvent(t *testing.T) {
168 mockCtrl := gomock.NewController(t)
169 defer mockCtrl.Finish()
170 mockStats := statstest.NewMockProvider(mockCtrl)
171
172 volumes := map[string]volume.Volume{vol0: &fakeVolume{}, vol1: &fakeVolume{}}
173 mockStats.EXPECT().ListVolumesForPod(fakePod.UID).Return(volumes, true)
174 blockVolumes := map[string]volume.BlockVolume{vol2: &fakeBlockVolume{}}
175 mockStats.EXPECT().ListBlockVolumesForPod(fakePod.UID).Return(blockVolumes, true)
176
177 eventStore := make(chan string, 2)
178 fakeEventRecorder := record.FakeRecorder{
179 Events: eventStore,
180 }
181
182
183 statsCalculator := newVolumeStatCalculator(mockStats, time.Minute, fakePod, &fakeEventRecorder)
184 statsCalculator.calcAndStoreStats()
185
186 event, err := WatchEvent(eventStore)
187 assert.NotNil(t, err)
188 assert.Equal(t, "", event)
189 }
190
191 func TestAbnormalVolumeEvent(t *testing.T) {
192 defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.CSIVolumeHealth, true)()
193 mockCtrl := gomock.NewController(t)
194 defer mockCtrl.Finish()
195
196
197 mockStats := statstest.NewMockProvider(mockCtrl)
198 volumes := map[string]volume.Volume{vol0: &fakeVolume{}}
199 mockStats.EXPECT().ListVolumesForPod(fakePod.UID).Return(volumes, true)
200 blockVolumes := map[string]volume.BlockVolume{vol1: &fakeBlockVolume{}}
201 mockStats.EXPECT().ListBlockVolumesForPod(fakePod.UID).Return(blockVolumes, true)
202
203 eventStore := make(chan string, 2)
204 fakeEventRecorder := record.FakeRecorder{
205 Events: eventStore,
206 }
207
208
209 if volumeCondition != nil {
210 volumeCondition.Message = "The target path of the volume doesn't exist"
211 volumeCondition.Abnormal = true
212 }
213 statsCalculator := newVolumeStatCalculator(mockStats, time.Minute, fakePod, &fakeEventRecorder)
214 statsCalculator.calcAndStoreStats()
215
216 event, err := WatchEvent(eventStore)
217 assert.Nil(t, err)
218 assert.Equal(t, fmt.Sprintf("Warning VolumeConditionAbnormal Volume %s: The target path of the volume doesn't exist", "vol0"), event)
219 }
220
221 func WatchEvent(eventChan <-chan string) (string, error) {
222 select {
223 case event := <-eventChan:
224 return event, nil
225 case <-time.After(5 * time.Second):
226 return "", ErrorWatchTimeout
227 }
228 }
229
230
231 var _ volume.Volume = &fakeVolume{}
232
233 type fakeVolume struct{}
234
235 func (v *fakeVolume) GetPath() string { return "" }
236
237 func (v *fakeVolume) GetMetrics() (*volume.Metrics, error) {
238 return expectedMetrics(), nil
239 }
240
241 func expectedMetrics() *volume.Metrics {
242 vMetrics := &volume.Metrics{
243 Available: resource.NewQuantity(available, resource.BinarySI),
244 Capacity: resource.NewQuantity(capacity, resource.BinarySI),
245 Used: resource.NewQuantity(available-capacity, resource.BinarySI),
246 Inodes: resource.NewQuantity(inodesTotal, resource.BinarySI),
247 InodesFree: resource.NewQuantity(inodesFree, resource.BinarySI),
248 InodesUsed: resource.NewQuantity(inodesTotal-inodesFree, resource.BinarySI),
249 }
250
251 if volumeCondition != nil {
252 vMetrics.Message = &volumeCondition.Message
253 vMetrics.Abnormal = &volumeCondition.Abnormal
254 }
255
256 return vMetrics
257 }
258
259 func expectedFSStats() kubestats.FsStats {
260 metric := expectedMetrics()
261 available := uint64(metric.Available.Value())
262 capacity := uint64(metric.Capacity.Value())
263 used := uint64(metric.Used.Value())
264 inodes := uint64(metric.Inodes.Value())
265 inodesFree := uint64(metric.InodesFree.Value())
266 inodesUsed := uint64(metric.InodesUsed.Value())
267 return kubestats.FsStats{
268 AvailableBytes: &available,
269 CapacityBytes: &capacity,
270 UsedBytes: &used,
271 Inodes: &inodes,
272 InodesFree: &inodesFree,
273 InodesUsed: &inodesUsed,
274 }
275 }
276
277 func expectedVolumeHealthStats() *kubestats.VolumeHealthStats {
278 metric := expectedMetrics()
279 hs := &kubestats.VolumeHealthStats{}
280
281 if metric != nil && metric.Abnormal != nil {
282 hs.Abnormal = *metric.Abnormal
283 }
284
285 return hs
286 }
287
288
289 var _ volume.BlockVolume = &fakeBlockVolume{}
290
291 type fakeBlockVolume struct{}
292
293 func (v *fakeBlockVolume) GetGlobalMapPath(*volume.Spec) (string, error) { return "", nil }
294
295 func (v *fakeBlockVolume) GetPodDeviceMapPath() (string, string) { return "", "" }
296
297 func (v *fakeBlockVolume) SupportsMetrics() bool { return true }
298
299 func (v *fakeBlockVolume) GetMetrics() (*volume.Metrics, error) {
300 return expectedBlockMetrics(), nil
301 }
302
303 func expectedBlockMetrics() *volume.Metrics {
304 vMetrics := &volume.Metrics{
305 Available: resource.NewQuantity(available, resource.BinarySI),
306 Capacity: resource.NewQuantity(capacity, resource.BinarySI),
307 Used: resource.NewQuantity(available-capacity, resource.BinarySI),
308 }
309
310 if volumeCondition != nil {
311 vMetrics.Abnormal = &volumeCondition.Abnormal
312 }
313
314 return vMetrics
315 }
316
317 func expectedBlockStats() kubestats.FsStats {
318 metric := expectedBlockMetrics()
319 available := uint64(metric.Available.Value())
320 capacity := uint64(metric.Capacity.Value())
321 used := uint64(metric.Used.Value())
322 null := uint64(0)
323 return kubestats.FsStats{
324 AvailableBytes: &available,
325 CapacityBytes: &capacity,
326 UsedBytes: &used,
327 Inodes: &null,
328 InodesFree: &null,
329 InodesUsed: &null,
330 }
331 }
332
View as plain text