2017-09-20 18:30:23 +00:00
|
|
|
/*
|
|
|
|
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 storage
|
|
|
|
|
|
|
|
import (
|
2017-09-21 08:10:22 +00:00
|
|
|
"fmt"
|
|
|
|
"time"
|
|
|
|
|
2017-09-20 18:30:23 +00:00
|
|
|
. "github.com/onsi/ginkgo"
|
|
|
|
. "github.com/onsi/gomega"
|
|
|
|
|
|
|
|
"k8s.io/api/core/v1"
|
2017-09-21 08:10:22 +00:00
|
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
2017-09-21 23:50:14 +00:00
|
|
|
"k8s.io/apimachinery/pkg/util/wait"
|
2017-09-20 18:30:23 +00:00
|
|
|
clientset "k8s.io/client-go/kubernetes"
|
2017-09-21 08:10:22 +00:00
|
|
|
kubeletmetrics "k8s.io/kubernetes/pkg/kubelet/metrics"
|
2017-09-20 18:30:23 +00:00
|
|
|
"k8s.io/kubernetes/test/e2e/framework"
|
|
|
|
"k8s.io/kubernetes/test/e2e/framework/metrics"
|
|
|
|
)
|
|
|
|
|
|
|
|
// This test needs to run in serial because other tests could interfere
|
|
|
|
// with metrics being tested here.
|
|
|
|
var _ = SIGDescribe("[Serial] Volume metrics", func() {
|
|
|
|
var (
|
|
|
|
c clientset.Interface
|
|
|
|
ns string
|
|
|
|
pvc *v1.PersistentVolumeClaim
|
|
|
|
metricsGrabber *metrics.MetricsGrabber
|
|
|
|
)
|
|
|
|
f := framework.NewDefaultFramework("pv")
|
|
|
|
|
|
|
|
BeforeEach(func() {
|
|
|
|
c = f.ClientSet
|
|
|
|
ns = f.Namespace.Name
|
|
|
|
framework.SkipUnlessProviderIs("gce", "gke", "aws")
|
|
|
|
defaultScName := getDefaultStorageClassName(c)
|
|
|
|
verifyDefaultStorageClass(c, defaultScName, true)
|
|
|
|
|
|
|
|
test := storageClassTest{
|
|
|
|
name: "default",
|
|
|
|
claimSize: "2Gi",
|
|
|
|
}
|
|
|
|
|
|
|
|
pvc = newClaim(test, ns, "default")
|
|
|
|
var err error
|
2017-09-21 08:10:22 +00:00
|
|
|
metricsGrabber, err = metrics.NewMetricsGrabber(c, nil, true, false, true, false, false)
|
2017-09-20 18:30:23 +00:00
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
framework.Failf("Error creating metrics grabber : %v", err)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2017-09-21 20:44:06 +00:00
|
|
|
AfterEach(func() {
|
2017-09-21 21:24:26 +00:00
|
|
|
framework.DeletePersistentVolumeClaim(c, pvc.Name, pvc.Namespace)
|
2017-09-21 20:44:06 +00:00
|
|
|
})
|
|
|
|
|
2017-09-20 18:30:23 +00:00
|
|
|
It("should create prometheus metrics for volume provisioning and attach/detach", func() {
|
|
|
|
var err error
|
|
|
|
|
2017-09-26 17:00:12 +00:00
|
|
|
if !metricsGrabber.HasRegisteredMaster() {
|
|
|
|
framework.Skipf("Environment does not support getting controller-manager metrics - skipping")
|
|
|
|
}
|
|
|
|
|
2017-09-20 18:30:23 +00:00
|
|
|
controllerMetrics, err := metricsGrabber.GrabFromControllerManager()
|
2017-09-21 23:50:14 +00:00
|
|
|
|
2017-09-20 18:30:23 +00:00
|
|
|
Expect(err).NotTo(HaveOccurred(), "Error getting c-m metrics : %v", err)
|
|
|
|
|
|
|
|
storageOpMetrics := getControllerStorageMetrics(controllerMetrics)
|
|
|
|
|
|
|
|
pvc, err = c.CoreV1().PersistentVolumeClaims(pvc.Namespace).Create(pvc)
|
|
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
Expect(pvc).ToNot(Equal(nil))
|
|
|
|
|
|
|
|
claims := []*v1.PersistentVolumeClaim{pvc}
|
|
|
|
|
|
|
|
pod := framework.MakePod(ns, claims, false, "")
|
|
|
|
pod, err = c.CoreV1().Pods(ns).Create(pod)
|
|
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
|
|
|
|
err = framework.WaitForPodRunningInNamespace(c, pod)
|
|
|
|
framework.ExpectNoError(framework.WaitForPodRunningInNamespace(c, pod), "Error starting pod ", pod.Name)
|
|
|
|
|
|
|
|
framework.Logf("Deleting pod %q/%q", pod.Namespace, pod.Name)
|
|
|
|
framework.ExpectNoError(framework.DeletePodWithWait(f, c, pod))
|
|
|
|
|
2017-09-21 23:50:14 +00:00
|
|
|
backoff := wait.Backoff{
|
|
|
|
Duration: 10 * time.Second,
|
|
|
|
Factor: 1.2,
|
2017-10-10 15:29:41 +00:00
|
|
|
Steps: 21,
|
2017-09-21 23:50:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
updatedStorageMetrics := make(map[string]int64)
|
|
|
|
|
|
|
|
waitErr := wait.ExponentialBackoff(backoff, func() (bool, error) {
|
|
|
|
updatedMetrics, err := metricsGrabber.GrabFromControllerManager()
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
framework.Logf("Error fetching controller-manager metrics")
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
updatedStorageMetrics = getControllerStorageMetrics(updatedMetrics)
|
2017-10-10 15:29:41 +00:00
|
|
|
metricCount := len(updatedStorageMetrics)
|
|
|
|
// Usually a pod deletion does not mean immediate volume detach
|
|
|
|
// we will have to retry to verify volume_detach metrics
|
|
|
|
_, detachMetricFound := updatedStorageMetrics["volume_detach"]
|
|
|
|
if metricCount < 3 || !detachMetricFound {
|
2017-09-21 23:50:14 +00:00
|
|
|
framework.Logf("Volume metrics not collected yet, going to retry")
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
return true, nil
|
|
|
|
})
|
|
|
|
Expect(waitErr).NotTo(HaveOccurred(), "Error fetching storage c-m metrics : %v", waitErr)
|
|
|
|
|
2017-09-20 18:30:23 +00:00
|
|
|
volumeOperations := []string{"volume_provision", "volume_detach", "volume_attach"}
|
|
|
|
|
|
|
|
for _, volumeOp := range volumeOperations {
|
|
|
|
verifyMetricCount(storageOpMetrics, updatedStorageMetrics, volumeOp)
|
|
|
|
}
|
|
|
|
})
|
2017-09-21 08:10:22 +00:00
|
|
|
|
2017-09-21 20:44:06 +00:00
|
|
|
It("should create volume metrics with the correct PVC ref", func() {
|
2017-09-21 08:10:22 +00:00
|
|
|
var err error
|
|
|
|
pvc, err = c.CoreV1().PersistentVolumeClaims(pvc.Namespace).Create(pvc)
|
|
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
Expect(pvc).ToNot(Equal(nil))
|
|
|
|
|
|
|
|
claims := []*v1.PersistentVolumeClaim{pvc}
|
|
|
|
pod := framework.MakePod(ns, claims, false, "")
|
|
|
|
pod, err = c.CoreV1().Pods(ns).Create(pod)
|
|
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
|
|
|
|
err = framework.WaitForPodRunningInNamespace(c, pod)
|
|
|
|
framework.ExpectNoError(framework.WaitForPodRunningInNamespace(c, pod), "Error starting pod ", pod.Name)
|
|
|
|
|
|
|
|
pod, err = c.CoreV1().Pods(ns).Get(pod.Name, metav1.GetOptions{})
|
|
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
|
|
|
|
// Verify volume stat metrics were collected for the referenced PVC
|
|
|
|
volumeStatKeys := []string{
|
|
|
|
kubeletmetrics.VolumeStatsUsedBytesKey,
|
|
|
|
kubeletmetrics.VolumeStatsCapacityBytesKey,
|
|
|
|
kubeletmetrics.VolumeStatsAvailableBytesKey,
|
|
|
|
kubeletmetrics.VolumeStatsUsedBytesKey,
|
|
|
|
kubeletmetrics.VolumeStatsInodesFreeKey,
|
|
|
|
kubeletmetrics.VolumeStatsInodesUsedKey,
|
|
|
|
}
|
2017-09-28 02:36:04 +00:00
|
|
|
// Poll kubelet metrics waiting for the volume to be picked up
|
|
|
|
// by the volume stats collector
|
|
|
|
var kubeMetrics metrics.KubeletMetrics
|
2017-09-28 00:49:05 +00:00
|
|
|
waitErr := wait.Poll(30*time.Second, 5*time.Minute, func() (bool, error) {
|
2017-09-28 02:36:04 +00:00
|
|
|
framework.Logf("Grabbing Kubelet metrics")
|
2017-09-28 00:49:05 +00:00
|
|
|
// Grab kubelet metrics from the node the pod was scheduled on
|
2017-09-28 02:36:04 +00:00
|
|
|
var err error
|
|
|
|
kubeMetrics, err = metricsGrabber.GrabFromKubelet(pod.Spec.NodeName)
|
2017-09-28 00:49:05 +00:00
|
|
|
if err != nil {
|
|
|
|
framework.Logf("Error fetching kubelet metrics")
|
|
|
|
return false, err
|
|
|
|
}
|
2017-09-28 02:36:04 +00:00
|
|
|
key := volumeStatKeys[0]
|
|
|
|
kubeletKeyName := fmt.Sprintf("%s_%s", kubeletmetrics.KubeletSubsystem, key)
|
|
|
|
if !findVolumeStatMetric(kubeletKeyName, pvc.Namespace, pvc.Name, kubeMetrics) {
|
|
|
|
return false, nil
|
2017-09-28 00:49:05 +00:00
|
|
|
}
|
|
|
|
return true, nil
|
|
|
|
})
|
|
|
|
Expect(waitErr).NotTo(HaveOccurred(), "Error finding volume metrics : %v", waitErr)
|
|
|
|
|
2017-09-28 02:36:04 +00:00
|
|
|
for _, key := range volumeStatKeys {
|
|
|
|
kubeletKeyName := fmt.Sprintf("%s_%s", kubeletmetrics.KubeletSubsystem, key)
|
|
|
|
found := findVolumeStatMetric(kubeletKeyName, pvc.Namespace, pvc.Name, kubeMetrics)
|
|
|
|
Expect(found).To(BeTrue(), "PVC %s, Namespace %s not found for %s", pvc.Name, pvc.Namespace, kubeletKeyName)
|
|
|
|
}
|
|
|
|
|
2017-09-28 00:49:05 +00:00
|
|
|
framework.Logf("Deleting pod %q/%q", pod.Namespace, pod.Name)
|
|
|
|
framework.ExpectNoError(framework.DeletePodWithWait(f, c, pod))
|
2017-09-21 08:10:22 +00:00
|
|
|
})
|
2017-09-20 18:30:23 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
func verifyMetricCount(oldMetrics map[string]int64, newMetrics map[string]int64, metricName string) {
|
|
|
|
oldCount, ok := oldMetrics[metricName]
|
2017-09-21 23:50:14 +00:00
|
|
|
// if metric does not exist in oldMap, it probably hasn't been emitted yet.
|
|
|
|
if !ok {
|
|
|
|
oldCount = 0
|
|
|
|
}
|
2017-09-20 18:30:23 +00:00
|
|
|
|
|
|
|
newCount, ok := newMetrics[metricName]
|
|
|
|
Expect(ok).To(BeTrue(), "Error getting updated metrics for %s", metricName)
|
|
|
|
|
|
|
|
Expect(oldCount + 1).To(Equal(newCount))
|
|
|
|
}
|
|
|
|
|
|
|
|
func getControllerStorageMetrics(ms metrics.ControllerManagerMetrics) map[string]int64 {
|
|
|
|
result := make(map[string]int64)
|
|
|
|
|
|
|
|
for method, samples := range ms {
|
|
|
|
if method != "storage_operation_duration_seconds_count" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, sample := range samples {
|
|
|
|
count := int64(sample.Value)
|
|
|
|
operation := string(sample.Metric["operation_name"])
|
|
|
|
result[operation] = count
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result
|
|
|
|
}
|
2017-09-21 08:10:22 +00:00
|
|
|
|
2017-09-28 00:49:05 +00:00
|
|
|
// Finds the sample in the specified metric from `KubeletMetrics` tagged with
|
|
|
|
// the specified namespace and pvc name
|
|
|
|
func findVolumeStatMetric(metricKeyName string, namespace string, pvcName string, kubeletMetrics metrics.KubeletMetrics) bool {
|
2017-09-21 08:10:22 +00:00
|
|
|
found := false
|
2017-09-21 21:24:26 +00:00
|
|
|
errCount := 0
|
2017-09-28 00:49:05 +00:00
|
|
|
framework.Logf("Looking for sample in metric `%s` tagged with namespace `%s`, PVC `%s`", metricKeyName, namespace, pvcName)
|
2017-09-21 08:10:22 +00:00
|
|
|
if samples, ok := kubeletMetrics[metricKeyName]; ok {
|
|
|
|
for _, sample := range samples {
|
2017-09-28 00:49:05 +00:00
|
|
|
framework.Logf("Found sample %s", sample.String())
|
2017-09-21 08:10:22 +00:00
|
|
|
samplePVC, ok := sample.Metric["persistentvolumeclaim"]
|
2017-09-21 20:44:06 +00:00
|
|
|
if !ok {
|
|
|
|
framework.Logf("Error getting pvc for metric %s, sample %s", metricKeyName, sample.String())
|
2017-09-21 21:24:26 +00:00
|
|
|
errCount++
|
2017-09-21 20:44:06 +00:00
|
|
|
}
|
2017-09-21 08:10:22 +00:00
|
|
|
sampleNS, ok := sample.Metric["namespace"]
|
2017-09-21 20:44:06 +00:00
|
|
|
if !ok {
|
|
|
|
framework.Logf("Error getting namespace for metric %s, sample %s", metricKeyName, sample.String())
|
2017-09-21 21:24:26 +00:00
|
|
|
errCount++
|
2017-09-21 20:44:06 +00:00
|
|
|
}
|
2017-09-21 08:10:22 +00:00
|
|
|
|
|
|
|
if string(samplePVC) == pvcName && string(sampleNS) == namespace {
|
|
|
|
found = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-09-21 21:24:26 +00:00
|
|
|
Expect(errCount).To(Equal(0), "Found invalid samples")
|
2017-09-28 00:49:05 +00:00
|
|
|
return found
|
2017-09-21 08:10:22 +00:00
|
|
|
}
|