From 1ac56d8cbbdb72e75fb9b083f3f76afd4010e71e Mon Sep 17 00:00:00 2001 From: Vaibhav Kamra Date: Wed, 16 Aug 2017 20:57:24 -0700 Subject: [PATCH] Add PVCRef to VolumeStats For pod volumes that reference a PVC, add a PVCRef to the corresponding volume stat. This allows metrics to be indexed/queried by PVC name which is more user-friendly than Pod reference --- pkg/kubelet/apis/stats/v1alpha1/types.go | 9 ++ pkg/kubelet/server/stats/BUILD | 7 +- .../server/stats/volume_stat_calculator.go | 27 +++- .../stats/volume_stat_calculator_test.go | 142 ++++++++++++++++++ test/e2e_node/summary_test.go | 3 +- 5 files changed, 181 insertions(+), 7 deletions(-) create mode 100644 pkg/kubelet/server/stats/volume_stat_calculator_test.go diff --git a/pkg/kubelet/apis/stats/v1alpha1/types.go b/pkg/kubelet/apis/stats/v1alpha1/types.go index e5cb0d9525e..e08128658d2 100644 --- a/pkg/kubelet/apis/stats/v1alpha1/types.go +++ b/pkg/kubelet/apis/stats/v1alpha1/types.go @@ -195,6 +195,15 @@ type VolumeStats struct { // Name is the name given to the Volume // +optional Name string `json:"name,omitempty"` + // Reference to the PVC, if one exists + // +optional + PVCRef *PVCReference `json:"pvcRef,omitempty"` +} + +// PVCReference contains enough information to describe the referenced PVC. +type PVCReference struct { + Name string `json:"name"` + Namespace string `json:"namespace"` } // FsStats contains data about filesystem usage. diff --git a/pkg/kubelet/server/stats/BUILD b/pkg/kubelet/server/stats/BUILD index a1e299d9eb0..56b5c1c3fda 100644 --- a/pkg/kubelet/server/stats/BUILD +++ b/pkg/kubelet/server/stats/BUILD @@ -29,15 +29,20 @@ go_library( go_test( name = "go_default_test", - srcs = ["summary_test.go"], + srcs = [ + "summary_test.go", + "volume_stat_calculator_test.go", + ], library = ":go_default_library", deps = [ "//pkg/kubelet/apis/stats/v1alpha1:go_default_library", "//pkg/kubelet/cm:go_default_library", "//pkg/kubelet/server/stats/testing:go_default_library", + "//pkg/volume:go_default_library", "//vendor/github.com/google/gofuzz:go_default_library", "//vendor/github.com/stretchr/testify/assert:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", ], ) diff --git a/pkg/kubelet/server/stats/volume_stat_calculator.go b/pkg/kubelet/server/stats/volume_stat_calculator.go index bde1993e21f..e3c186c75a9 100644 --- a/pkg/kubelet/server/stats/volume_stat_calculator.go +++ b/pkg/kubelet/server/stats/volume_stat_calculator.go @@ -92,8 +92,14 @@ func (s *volumeStatCalculator) calcAndStoreStats() { return } + // Get volume specs for the pod - key'd by volume name + volumesSpec := make(map[string]v1.Volume) + for _, v := range s.pod.Spec.Volumes { + volumesSpec[v.Name] = v + } + // Call GetMetrics on each Volume and copy the result to a new VolumeStats.FsStats - stats := make([]stats.VolumeStats, 0, len(volumes)) + fsStats := make([]stats.VolumeStats, 0, len(volumes)) for name, v := range volumes { metric, err := v.GetMetrics() if err != nil { @@ -103,15 +109,25 @@ func (s *volumeStatCalculator) calcAndStoreStats() { } continue } - stats = append(stats, s.parsePodVolumeStats(name, metric)) + // Lookup the volume spec and add a 'PVCReference' for volumes that reference a PVC + volSpec := volumesSpec[name] + if pvcSource := volSpec.PersistentVolumeClaim; pvcSource != nil { + pvcRef := stats.PVCReference{ + Name: pvcSource.ClaimName, + Namespace: s.pod.GetNamespace(), + } + fsStats = append(fsStats, s.parsePodVolumeStats(name, &pvcRef, metric)) + } else { + fsStats = append(fsStats, s.parsePodVolumeStats(name, nil, metric)) + } } // Store the new stats - s.latest.Store(PodVolumeStats{Volumes: stats}) + s.latest.Store(PodVolumeStats{Volumes: fsStats}) } // parsePodVolumeStats converts (internal) volume.Metrics to (external) stats.VolumeStats structures -func (s *volumeStatCalculator) parsePodVolumeStats(podName string, metric *volume.Metrics) stats.VolumeStats { +func (s *volumeStatCalculator) parsePodVolumeStats(podName string, pvcRef *stats.PVCReference, metric *volume.Metrics) stats.VolumeStats { available := uint64(metric.Available.Value()) capacity := uint64(metric.Capacity.Value()) used := uint64(metric.Used.Value()) @@ -119,7 +135,8 @@ func (s *volumeStatCalculator) parsePodVolumeStats(podName string, metric *volum inodesFree := uint64(metric.InodesFree.Value()) inodesUsed := uint64(metric.InodesUsed.Value()) return stats.VolumeStats{ - Name: podName, + Name: podName, + PVCRef: pvcRef, FsStats: stats.FsStats{Time: metric.Time, AvailableBytes: &available, CapacityBytes: &capacity, UsedBytes: &used, Inodes: &inodes, InodesFree: &inodesFree, InodesUsed: &inodesUsed}, } diff --git a/pkg/kubelet/server/stats/volume_stat_calculator_test.go b/pkg/kubelet/server/stats/volume_stat_calculator_test.go new file mode 100644 index 00000000000..42777ae09c8 --- /dev/null +++ b/pkg/kubelet/server/stats/volume_stat_calculator_test.go @@ -0,0 +1,142 @@ +/* +Copyright 2017 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 stats + +import ( + "github.com/stretchr/testify/assert" + "testing" + "time" + + k8sv1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + kubestats "k8s.io/kubernetes/pkg/kubelet/apis/stats/v1alpha1" + statstest "k8s.io/kubernetes/pkg/kubelet/server/stats/testing" + "k8s.io/kubernetes/pkg/volume" +) + +const ( + namespace0 = "test0" + pName0 = "pod0" + capacity = int64(10000000) + available = int64(5000000) + inodesTotal = int64(2000) + inodesFree = int64(1000) + + vol0 = "vol0" + vol1 = "vol1" + pvcClaimName = "pvc-fake" +) + +func TestPVCRef(t *testing.T) { + // Create pod spec to test against + podVolumes := []k8sv1.Volume{ + { + Name: vol0, + VolumeSource: k8sv1.VolumeSource{ + GCEPersistentDisk: &k8sv1.GCEPersistentDiskVolumeSource{ + PDName: "fake-device1", + }, + }, + }, + { + Name: vol1, + VolumeSource: k8sv1.VolumeSource{ + PersistentVolumeClaim: &k8sv1.PersistentVolumeClaimVolumeSource{ + ClaimName: pvcClaimName, + }, + }, + }, + } + + fakePod := &k8sv1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: pName0, + Namespace: namespace0, + UID: "UID" + pName0, + }, + Spec: k8sv1.PodSpec{ + Volumes: podVolumes, + }, + } + + // Setup mock stats provider + mockStats := new(statstest.StatsProvider) + volumes := map[string]volume.Volume{vol0: &fakeVolume{}, vol1: &fakeVolume{}} + mockStats.On("ListVolumesForPod", fakePod.UID).Return(volumes, true) + + // Calculate stats for pod + statsCalculator := newVolumeStatCalculator(mockStats, time.Minute, fakePod) + statsCalculator.calcAndStoreStats() + vs, _ := statsCalculator.GetLatest() + + assert.Len(t, vs.Volumes, 2) + // Verify 'vol0' doesn't have a PVC reference + assert.Contains(t, vs.Volumes, kubestats.VolumeStats{ + Name: vol0, + FsStats: expectedFSStats(), + }) + // Verify 'vol1' has a PVC reference + assert.Contains(t, vs.Volumes, kubestats.VolumeStats{ + Name: vol1, + PVCRef: &kubestats.PVCReference{ + Name: pvcClaimName, + Namespace: namespace0, + }, + FsStats: expectedFSStats(), + }) +} + +// Fake volume/metrics provider +var _ volume.Volume = &fakeVolume{} + +type fakeVolume struct{} + +func (v *fakeVolume) GetPath() string { return "" } + +func (v *fakeVolume) GetMetrics() (*volume.Metrics, error) { + return expectedMetrics(), nil +} + +func expectedMetrics() *volume.Metrics { + return &volume.Metrics{ + Available: resource.NewQuantity(available, resource.BinarySI), + Capacity: resource.NewQuantity(capacity, resource.BinarySI), + Used: resource.NewQuantity(available-capacity, resource.BinarySI), + Inodes: resource.NewQuantity(inodesTotal, resource.BinarySI), + InodesFree: resource.NewQuantity(inodesFree, resource.BinarySI), + InodesUsed: resource.NewQuantity(inodesTotal-inodesFree, resource.BinarySI), + } +} + +func expectedFSStats() kubestats.FsStats { + metric := expectedMetrics() + available := uint64(metric.Available.Value()) + capacity := uint64(metric.Capacity.Value()) + used := uint64(metric.Used.Value()) + inodes := uint64(metric.Inodes.Value()) + inodesFree := uint64(metric.InodesFree.Value()) + inodesUsed := uint64(metric.InodesUsed.Value()) + return kubestats.FsStats{ + AvailableBytes: &available, + CapacityBytes: &capacity, + UsedBytes: &used, + Inodes: &inodes, + InodesFree: &inodesFree, + InodesUsed: &inodesUsed, + } +} diff --git a/test/e2e_node/summary_test.go b/test/e2e_node/summary_test.go index 4b5084b7091..5d4bda17464 100644 --- a/test/e2e_node/summary_test.go +++ b/test/e2e_node/summary_test.go @@ -181,7 +181,8 @@ var _ = framework.KubeDescribe("Summary API", func() { }), "VolumeStats": gstruct.MatchAllElements(summaryObjectID, gstruct.Elements{ "test-empty-dir": gstruct.MatchAllFields(gstruct.Fields{ - "Name": Equal("test-empty-dir"), + "Name": Equal("test-empty-dir"), + "PVCRef": BeNil(), "FsStats": gstruct.MatchAllFields(gstruct.Fields{ "Time": recent(maxStatsAge), "AvailableBytes": fsCapacityBounds,