mirror of
https://github.com/ceph/ceph-csi.git
synced 2025-06-13 02:33:34 +00:00
vendor files
This commit is contained in:
79
vendor/k8s.io/kubernetes/pkg/kubelet/stats/BUILD
generated
vendored
Normal file
79
vendor/k8s.io/kubernetes/pkg/kubelet/stats/BUILD
generated
vendored
Normal file
@ -0,0 +1,79 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"cadvisor_stats_provider.go",
|
||||
"cri_stats_provider.go",
|
||||
"helper.go",
|
||||
"stats_provider.go",
|
||||
],
|
||||
importpath = "k8s.io/kubernetes/pkg/kubelet/stats",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//pkg/kubelet/apis/cri:go_default_library",
|
||||
"//pkg/kubelet/apis/cri/v1alpha1/runtime:go_default_library",
|
||||
"//pkg/kubelet/apis/stats/v1alpha1:go_default_library",
|
||||
"//pkg/kubelet/cadvisor:go_default_library",
|
||||
"//pkg/kubelet/cm:go_default_library",
|
||||
"//pkg/kubelet/container:go_default_library",
|
||||
"//pkg/kubelet/leaky:go_default_library",
|
||||
"//pkg/kubelet/network:go_default_library",
|
||||
"//pkg/kubelet/pod:go_default_library",
|
||||
"//pkg/kubelet/server/stats:go_default_library",
|
||||
"//pkg/kubelet/types:go_default_library",
|
||||
"//vendor/github.com/golang/glog:go_default_library",
|
||||
"//vendor/github.com/golang/protobuf/proto:go_default_library",
|
||||
"//vendor/github.com/google/cadvisor/fs:go_default_library",
|
||||
"//vendor/github.com/google/cadvisor/info/v1:go_default_library",
|
||||
"//vendor/github.com/google/cadvisor/info/v2:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"cadvisor_stats_provider_test.go",
|
||||
"cri_stats_provider_test.go",
|
||||
"helper_test.go",
|
||||
"stats_provider_test.go",
|
||||
],
|
||||
importpath = "k8s.io/kubernetes/pkg/kubelet/stats",
|
||||
library = ":go_default_library",
|
||||
deps = [
|
||||
"//pkg/kubelet/apis/cri/testing:go_default_library",
|
||||
"//pkg/kubelet/apis/cri/v1alpha1/runtime:go_default_library",
|
||||
"//pkg/kubelet/apis/stats/v1alpha1:go_default_library",
|
||||
"//pkg/kubelet/cadvisor/testing:go_default_library",
|
||||
"//pkg/kubelet/container:go_default_library",
|
||||
"//pkg/kubelet/container/testing:go_default_library",
|
||||
"//pkg/kubelet/leaky:go_default_library",
|
||||
"//pkg/kubelet/pod/testing:go_default_library",
|
||||
"//pkg/kubelet/server/stats:go_default_library",
|
||||
"//pkg/kubelet/types:go_default_library",
|
||||
"//vendor/github.com/google/cadvisor/fs:go_default_library",
|
||||
"//vendor/github.com/google/cadvisor/info/v1:go_default_library",
|
||||
"//vendor/github.com/google/cadvisor/info/v2:go_default_library",
|
||||
"//vendor/github.com/google/gofuzz:go_default_library",
|
||||
"//vendor/github.com/stretchr/testify/assert:go_default_library",
|
||||
"//vendor/github.com/stretchr/testify/require:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
|
||||
],
|
||||
)
|
361
vendor/k8s.io/kubernetes/pkg/kubelet/stats/cadvisor_stats_provider.go
generated
vendored
Normal file
361
vendor/k8s.io/kubernetes/pkg/kubelet/stats/cadvisor_stats_provider.go
generated
vendored
Normal file
@ -0,0 +1,361 @@
|
||||
/*
|
||||
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 (
|
||||
"fmt"
|
||||
"path"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/glog"
|
||||
cadvisorapiv2 "github.com/google/cadvisor/info/v2"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
statsapi "k8s.io/kubernetes/pkg/kubelet/apis/stats/v1alpha1"
|
||||
"k8s.io/kubernetes/pkg/kubelet/cadvisor"
|
||||
"k8s.io/kubernetes/pkg/kubelet/cm"
|
||||
kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
|
||||
"k8s.io/kubernetes/pkg/kubelet/leaky"
|
||||
"k8s.io/kubernetes/pkg/kubelet/server/stats"
|
||||
kubetypes "k8s.io/kubernetes/pkg/kubelet/types"
|
||||
)
|
||||
|
||||
// cadvisorStatsProvider implements the containerStatsProvider interface by
|
||||
// getting the container stats from cAdvisor. This is needed by docker and rkt
|
||||
// integrations since they do not provide stats from CRI.
|
||||
type cadvisorStatsProvider struct {
|
||||
// cadvisor is used to get the stats of the cgroup for the containers that
|
||||
// are managed by pods.
|
||||
cadvisor cadvisor.Interface
|
||||
// resourceAnalyzer is used to get the volume stats of the pods.
|
||||
resourceAnalyzer stats.ResourceAnalyzer
|
||||
// imageService is used to get the stats of the image filesystem.
|
||||
imageService kubecontainer.ImageService
|
||||
}
|
||||
|
||||
// newCadvisorStatsProvider returns a containerStatsProvider that provides
|
||||
// container stats from cAdvisor.
|
||||
func newCadvisorStatsProvider(
|
||||
cadvisor cadvisor.Interface,
|
||||
resourceAnalyzer stats.ResourceAnalyzer,
|
||||
imageService kubecontainer.ImageService,
|
||||
) containerStatsProvider {
|
||||
return &cadvisorStatsProvider{
|
||||
cadvisor: cadvisor,
|
||||
resourceAnalyzer: resourceAnalyzer,
|
||||
imageService: imageService,
|
||||
}
|
||||
}
|
||||
|
||||
// ListPodStats returns the stats of all the pod-managed containers.
|
||||
func (p *cadvisorStatsProvider) ListPodStats() ([]statsapi.PodStats, error) {
|
||||
// Gets node root filesystem information and image filesystem stats, which
|
||||
// will be used to populate the available and capacity bytes/inodes in
|
||||
// container stats.
|
||||
rootFsInfo, err := p.cadvisor.RootFsInfo()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get rootFs info: %v", err)
|
||||
}
|
||||
imageFsInfo, err := p.cadvisor.ImagesFsInfo()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get imageFs info: %v", err)
|
||||
}
|
||||
infos, err := getCadvisorContainerInfo(p.cadvisor)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get container info from cadvisor: %v", err)
|
||||
}
|
||||
// removeTerminatedContainerInfo will also remove pod level cgroups, so save the infos into allInfos first
|
||||
allInfos := infos
|
||||
infos = removeTerminatedContainerInfo(infos)
|
||||
// Map each container to a pod and update the PodStats with container data.
|
||||
podToStats := map[statsapi.PodReference]*statsapi.PodStats{}
|
||||
for key, cinfo := range infos {
|
||||
// On systemd using devicemapper each mount into the container has an
|
||||
// associated cgroup. We ignore them to ensure we do not get duplicate
|
||||
// entries in our summary. For details on .mount units:
|
||||
// http://man7.org/linux/man-pages/man5/systemd.mount.5.html
|
||||
if strings.HasSuffix(key, ".mount") {
|
||||
continue
|
||||
}
|
||||
// Build the Pod key if this container is managed by a Pod
|
||||
if !isPodManagedContainer(&cinfo) {
|
||||
continue
|
||||
}
|
||||
ref := buildPodRef(cinfo.Spec.Labels)
|
||||
|
||||
// Lookup the PodStats for the pod using the PodRef. If none exists,
|
||||
// initialize a new entry.
|
||||
podStats, found := podToStats[ref]
|
||||
if !found {
|
||||
podStats = &statsapi.PodStats{PodRef: ref}
|
||||
podToStats[ref] = podStats
|
||||
}
|
||||
|
||||
// Update the PodStats entry with the stats from the container by
|
||||
// adding it to podStats.Containers.
|
||||
containerName := kubetypes.GetContainerName(cinfo.Spec.Labels)
|
||||
if containerName == leaky.PodInfraContainerName {
|
||||
// Special case for infrastructure container which is hidden from
|
||||
// the user and has network stats.
|
||||
podStats.Network = cadvisorInfoToNetworkStats("pod:"+ref.Namespace+"_"+ref.Name, &cinfo)
|
||||
podStats.StartTime = metav1.NewTime(cinfo.Spec.CreationTime)
|
||||
} else {
|
||||
podStats.Containers = append(podStats.Containers, *cadvisorInfoToContainerStats(containerName, &cinfo, &rootFsInfo, &imageFsInfo))
|
||||
}
|
||||
}
|
||||
|
||||
// Add each PodStats to the result.
|
||||
result := make([]statsapi.PodStats, 0, len(podToStats))
|
||||
for _, podStats := range podToStats {
|
||||
// Lookup the volume stats for each pod.
|
||||
podUID := types.UID(podStats.PodRef.UID)
|
||||
var ephemeralStats []statsapi.VolumeStats
|
||||
if vstats, found := p.resourceAnalyzer.GetPodVolumeStats(podUID); found {
|
||||
ephemeralStats = make([]statsapi.VolumeStats, len(vstats.EphemeralVolumes))
|
||||
copy(ephemeralStats, vstats.EphemeralVolumes)
|
||||
podStats.VolumeStats = append(vstats.EphemeralVolumes, vstats.PersistentVolumes...)
|
||||
}
|
||||
podStats.EphemeralStorage = calcEphemeralStorage(podStats.Containers, ephemeralStats, &rootFsInfo)
|
||||
// Lookup the pod-level cgroup's CPU and memory stats
|
||||
podInfo := getcadvisorPodInfoFromPodUID(podUID, allInfos)
|
||||
if podInfo != nil {
|
||||
cpu, memory := cadvisorInfoToCPUandMemoryStats(podInfo)
|
||||
podStats.CPU = cpu
|
||||
podStats.Memory = memory
|
||||
}
|
||||
result = append(result, *podStats)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func calcEphemeralStorage(containers []statsapi.ContainerStats, volumes []statsapi.VolumeStats, rootFsInfo *cadvisorapiv2.FsInfo) *statsapi.FsStats {
|
||||
result := &statsapi.FsStats{
|
||||
Time: metav1.NewTime(rootFsInfo.Timestamp),
|
||||
AvailableBytes: &rootFsInfo.Available,
|
||||
CapacityBytes: &rootFsInfo.Capacity,
|
||||
InodesFree: rootFsInfo.InodesFree,
|
||||
Inodes: rootFsInfo.Inodes,
|
||||
}
|
||||
for _, container := range containers {
|
||||
addContainerUsage(result, &container)
|
||||
}
|
||||
for _, volume := range volumes {
|
||||
result.UsedBytes = addUsage(result.UsedBytes, volume.FsStats.UsedBytes)
|
||||
result.InodesUsed = addUsage(result.InodesUsed, volume.InodesUsed)
|
||||
result.Time = maxUpdateTime(&result.Time, &volume.FsStats.Time)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func addContainerUsage(stat *statsapi.FsStats, container *statsapi.ContainerStats) {
|
||||
if rootFs := container.Rootfs; rootFs != nil {
|
||||
stat.Time = maxUpdateTime(&stat.Time, &rootFs.Time)
|
||||
stat.InodesUsed = addUsage(stat.InodesUsed, rootFs.InodesUsed)
|
||||
stat.UsedBytes = addUsage(stat.UsedBytes, rootFs.UsedBytes)
|
||||
if logs := container.Logs; logs != nil {
|
||||
stat.UsedBytes = addUsage(stat.UsedBytes, logs.UsedBytes)
|
||||
stat.Time = maxUpdateTime(&stat.Time, &logs.Time)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func maxUpdateTime(first, second *metav1.Time) metav1.Time {
|
||||
if first.Before(second) {
|
||||
return *second
|
||||
}
|
||||
return *first
|
||||
}
|
||||
func addUsage(first, second *uint64) *uint64 {
|
||||
if first == nil {
|
||||
return second
|
||||
} else if second == nil {
|
||||
return first
|
||||
}
|
||||
total := *first + *second
|
||||
return &total
|
||||
}
|
||||
|
||||
// ImageFsStats returns the stats of the filesystem for storing images.
|
||||
func (p *cadvisorStatsProvider) ImageFsStats() (*statsapi.FsStats, error) {
|
||||
imageFsInfo, err := p.cadvisor.ImagesFsInfo()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get imageFs info: %v", err)
|
||||
}
|
||||
imageStats, err := p.imageService.ImageStats()
|
||||
if err != nil || imageStats == nil {
|
||||
return nil, fmt.Errorf("failed to get image stats: %v", err)
|
||||
}
|
||||
|
||||
var imageFsInodesUsed *uint64
|
||||
if imageFsInfo.Inodes != nil && imageFsInfo.InodesFree != nil {
|
||||
imageFsIU := *imageFsInfo.Inodes - *imageFsInfo.InodesFree
|
||||
imageFsInodesUsed = &imageFsIU
|
||||
}
|
||||
|
||||
return &statsapi.FsStats{
|
||||
Time: metav1.NewTime(imageFsInfo.Timestamp),
|
||||
AvailableBytes: &imageFsInfo.Available,
|
||||
CapacityBytes: &imageFsInfo.Capacity,
|
||||
UsedBytes: &imageStats.TotalStorageBytes,
|
||||
InodesFree: imageFsInfo.InodesFree,
|
||||
Inodes: imageFsInfo.Inodes,
|
||||
InodesUsed: imageFsInodesUsed,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// buildPodRef returns a PodReference that identifies the Pod managing cinfo
|
||||
func buildPodRef(containerLabels map[string]string) statsapi.PodReference {
|
||||
podName := kubetypes.GetPodName(containerLabels)
|
||||
podNamespace := kubetypes.GetPodNamespace(containerLabels)
|
||||
podUID := kubetypes.GetPodUID(containerLabels)
|
||||
return statsapi.PodReference{Name: podName, Namespace: podNamespace, UID: podUID}
|
||||
}
|
||||
|
||||
// isPodManagedContainer returns true if the cinfo container is managed by a Pod
|
||||
func isPodManagedContainer(cinfo *cadvisorapiv2.ContainerInfo) bool {
|
||||
podName := kubetypes.GetPodName(cinfo.Spec.Labels)
|
||||
podNamespace := kubetypes.GetPodNamespace(cinfo.Spec.Labels)
|
||||
managed := podName != "" && podNamespace != ""
|
||||
if !managed && podName != podNamespace {
|
||||
glog.Warningf(
|
||||
"Expect container to have either both podName (%s) and podNamespace (%s) labels, or neither.",
|
||||
podName, podNamespace)
|
||||
}
|
||||
return managed
|
||||
}
|
||||
|
||||
// getcadvisorPodInfoFromPodUID returns a pod cgroup information by matching the podUID with its CgroupName identifier base name
|
||||
func getcadvisorPodInfoFromPodUID(podUID types.UID, infos map[string]cadvisorapiv2.ContainerInfo) *cadvisorapiv2.ContainerInfo {
|
||||
for key, info := range infos {
|
||||
if cm.IsSystemdStyleName(key) {
|
||||
key = cm.RevertFromSystemdToCgroupStyleName(key)
|
||||
}
|
||||
if cm.GetPodCgroupNameSuffix(podUID) == path.Base(key) {
|
||||
return &info
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// removeTerminatedContainerInfo returns the specified containerInfo but with
|
||||
// the stats of the terminated containers removed.
|
||||
//
|
||||
// A ContainerInfo is considered to be of a terminated container if it has an
|
||||
// older CreationTime and zero CPU instantaneous and memory RSS usage.
|
||||
func removeTerminatedContainerInfo(containerInfo map[string]cadvisorapiv2.ContainerInfo) map[string]cadvisorapiv2.ContainerInfo {
|
||||
cinfoMap := make(map[containerID][]containerInfoWithCgroup)
|
||||
for key, cinfo := range containerInfo {
|
||||
if !isPodManagedContainer(&cinfo) {
|
||||
continue
|
||||
}
|
||||
cinfoID := containerID{
|
||||
podRef: buildPodRef(cinfo.Spec.Labels),
|
||||
containerName: kubetypes.GetContainerName(cinfo.Spec.Labels),
|
||||
}
|
||||
cinfoMap[cinfoID] = append(cinfoMap[cinfoID], containerInfoWithCgroup{
|
||||
cinfo: cinfo,
|
||||
cgroup: key,
|
||||
})
|
||||
}
|
||||
result := make(map[string]cadvisorapiv2.ContainerInfo)
|
||||
for _, refs := range cinfoMap {
|
||||
if len(refs) == 1 {
|
||||
result[refs[0].cgroup] = refs[0].cinfo
|
||||
continue
|
||||
}
|
||||
sort.Sort(ByCreationTime(refs))
|
||||
i := 0
|
||||
for ; i < len(refs); i++ {
|
||||
if hasMemoryAndCPUInstUsage(&refs[i].cinfo) {
|
||||
// Stops removing when we first see an info with non-zero
|
||||
// CPU/Memory usage.
|
||||
break
|
||||
}
|
||||
}
|
||||
for ; i < len(refs); i++ {
|
||||
result[refs[i].cgroup] = refs[i].cinfo
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// ByCreationTime implements sort.Interface for []containerInfoWithCgroup based
|
||||
// on the cinfo.Spec.CreationTime field.
|
||||
type ByCreationTime []containerInfoWithCgroup
|
||||
|
||||
func (a ByCreationTime) Len() int { return len(a) }
|
||||
func (a ByCreationTime) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a ByCreationTime) Less(i, j int) bool {
|
||||
if a[i].cinfo.Spec.CreationTime.Equal(a[j].cinfo.Spec.CreationTime) {
|
||||
// There shouldn't be two containers with the same name and/or the same
|
||||
// creation time. However, to make the logic here robust, we break the
|
||||
// tie by moving the one without CPU instantaneous or memory RSS usage
|
||||
// to the beginning.
|
||||
return hasMemoryAndCPUInstUsage(&a[j].cinfo)
|
||||
}
|
||||
return a[i].cinfo.Spec.CreationTime.Before(a[j].cinfo.Spec.CreationTime)
|
||||
}
|
||||
|
||||
// containerID is the identity of a container in a pod.
|
||||
type containerID struct {
|
||||
podRef statsapi.PodReference
|
||||
containerName string
|
||||
}
|
||||
|
||||
// containerInfoWithCgroup contains the ContainerInfo and its cgroup name.
|
||||
type containerInfoWithCgroup struct {
|
||||
cinfo cadvisorapiv2.ContainerInfo
|
||||
cgroup string
|
||||
}
|
||||
|
||||
// hasMemoryAndCPUInstUsage returns true if the specified container info has
|
||||
// both non-zero CPU instantaneous usage and non-zero memory RSS usage, and
|
||||
// false otherwise.
|
||||
func hasMemoryAndCPUInstUsage(info *cadvisorapiv2.ContainerInfo) bool {
|
||||
if !info.Spec.HasCpu || !info.Spec.HasMemory {
|
||||
return false
|
||||
}
|
||||
cstat, found := latestContainerStats(info)
|
||||
if !found {
|
||||
return false
|
||||
}
|
||||
if cstat.CpuInst == nil {
|
||||
return false
|
||||
}
|
||||
return cstat.CpuInst.Usage.Total != 0 && cstat.Memory.RSS != 0
|
||||
}
|
||||
|
||||
func getCadvisorContainerInfo(ca cadvisor.Interface) (map[string]cadvisorapiv2.ContainerInfo, error) {
|
||||
infos, err := ca.ContainerInfoV2("/", cadvisorapiv2.RequestOptions{
|
||||
IdType: cadvisorapiv2.TypeName,
|
||||
Count: 2, // 2 samples are needed to compute "instantaneous" CPU
|
||||
Recursive: true,
|
||||
})
|
||||
if err != nil {
|
||||
if _, ok := infos["/"]; ok {
|
||||
// If the failure is partial, log it and return a best-effort
|
||||
// response.
|
||||
glog.Errorf("Partial failure issuing cadvisor.ContainerInfoV2: %v", err)
|
||||
} else {
|
||||
return nil, fmt.Errorf("failed to get root cgroup stats: %v", err)
|
||||
}
|
||||
}
|
||||
return infos, nil
|
||||
}
|
284
vendor/k8s.io/kubernetes/pkg/kubelet/stats/cadvisor_stats_provider_test.go
generated
vendored
Normal file
284
vendor/k8s.io/kubernetes/pkg/kubelet/stats/cadvisor_stats_provider_test.go
generated
vendored
Normal file
@ -0,0 +1,284 @@
|
||||
/*
|
||||
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 (
|
||||
"testing"
|
||||
|
||||
cadvisorapiv2 "github.com/google/cadvisor/info/v2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
statsapi "k8s.io/kubernetes/pkg/kubelet/apis/stats/v1alpha1"
|
||||
cadvisortest "k8s.io/kubernetes/pkg/kubelet/cadvisor/testing"
|
||||
kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
|
||||
containertest "k8s.io/kubernetes/pkg/kubelet/container/testing"
|
||||
"k8s.io/kubernetes/pkg/kubelet/leaky"
|
||||
serverstats "k8s.io/kubernetes/pkg/kubelet/server/stats"
|
||||
)
|
||||
|
||||
func TestRemoveTerminatedContainerInfo(t *testing.T) {
|
||||
const (
|
||||
seedPastPod0Infra = 1000
|
||||
seedPastPod0Container0 = 2000
|
||||
seedPod0Infra = 3000
|
||||
seedPod0Container0 = 4000
|
||||
)
|
||||
const (
|
||||
namespace = "test"
|
||||
pName0 = "pod0"
|
||||
cName00 = "c0"
|
||||
)
|
||||
infos := map[string]cadvisorapiv2.ContainerInfo{
|
||||
// ContainerInfo with past creation time and no CPU/memory usage for
|
||||
// simulating uncleaned cgroups of already terminated containers, which
|
||||
// should not be shown in the results.
|
||||
"/pod0-i-terminated-1": getTerminatedContainerInfo(seedPastPod0Infra, pName0, namespace, leaky.PodInfraContainerName),
|
||||
"/pod0-c0-terminated-1": getTerminatedContainerInfo(seedPastPod0Container0, pName0, namespace, cName00),
|
||||
|
||||
// Same as above but uses the same creation time as the latest
|
||||
// containers. They are terminated containers, so they should not be in
|
||||
// the results.
|
||||
"/pod0-i-terminated-2": getTerminatedContainerInfo(seedPod0Infra, pName0, namespace, leaky.PodInfraContainerName),
|
||||
"/pod0-c0-terminated-2": getTerminatedContainerInfo(seedPod0Container0, pName0, namespace, cName00),
|
||||
|
||||
// The latest containers, which should be in the results.
|
||||
"/pod0-i": getTestContainerInfo(seedPod0Infra, pName0, namespace, leaky.PodInfraContainerName),
|
||||
"/pod0-c0": getTestContainerInfo(seedPod0Container0, pName0, namespace, cName00),
|
||||
|
||||
// Duplicated containers with non-zero CPU and memory usage. This case
|
||||
// shouldn't happen unless something goes wrong, but we want to test
|
||||
// that the metrics reporting logic works in this scenario.
|
||||
"/pod0-i-duplicated": getTestContainerInfo(seedPod0Infra, pName0, namespace, leaky.PodInfraContainerName),
|
||||
"/pod0-c0-duplicated": getTestContainerInfo(seedPod0Container0, pName0, namespace, cName00),
|
||||
}
|
||||
output := removeTerminatedContainerInfo(infos)
|
||||
assert.Len(t, output, 4)
|
||||
for _, c := range []string{"/pod0-i", "/pod0-c0", "/pod0-i-duplicated", "/pod0-c0-duplicated"} {
|
||||
if _, found := output[c]; !found {
|
||||
t.Errorf("%q is expected to be in the output\n", c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCadvisorListPodStats(t *testing.T) {
|
||||
const (
|
||||
namespace0 = "test0"
|
||||
namespace2 = "test2"
|
||||
)
|
||||
const (
|
||||
seedRoot = 0
|
||||
seedRuntime = 100
|
||||
seedKubelet = 200
|
||||
seedMisc = 300
|
||||
seedPod0Infra = 1000
|
||||
seedPod0Container0 = 2000
|
||||
seedPod0Container1 = 2001
|
||||
seedPod1Infra = 3000
|
||||
seedPod1Container = 4000
|
||||
seedPod2Infra = 5000
|
||||
seedPod2Container = 6000
|
||||
seedEphemeralVolume1 = 10000
|
||||
seedEphemeralVolume2 = 10001
|
||||
seedPersistentVolume1 = 20000
|
||||
seedPersistentVolume2 = 20001
|
||||
)
|
||||
const (
|
||||
pName0 = "pod0"
|
||||
pName1 = "pod1"
|
||||
pName2 = "pod0" // ensure pName2 conflicts with pName0, but is in a different namespace
|
||||
)
|
||||
const (
|
||||
cName00 = "c0"
|
||||
cName01 = "c1"
|
||||
cName10 = "c0" // ensure cName10 conflicts with cName02, but is in a different pod
|
||||
cName20 = "c1" // ensure cName20 conflicts with cName01, but is in a different pod + namespace
|
||||
)
|
||||
const (
|
||||
rootfsCapacity = uint64(10000000)
|
||||
rootfsAvailable = uint64(5000000)
|
||||
rootfsInodesFree = uint64(1000)
|
||||
rootfsInodes = uint64(2000)
|
||||
imagefsCapacity = uint64(20000000)
|
||||
imagefsAvailable = uint64(8000000)
|
||||
imagefsInodesFree = uint64(2000)
|
||||
imagefsInodes = uint64(4000)
|
||||
)
|
||||
|
||||
prf0 := statsapi.PodReference{Name: pName0, Namespace: namespace0, UID: "UID" + pName0}
|
||||
prf1 := statsapi.PodReference{Name: pName1, Namespace: namespace0, UID: "UID" + pName1}
|
||||
prf2 := statsapi.PodReference{Name: pName2, Namespace: namespace2, UID: "UID" + pName2}
|
||||
infos := map[string]cadvisorapiv2.ContainerInfo{
|
||||
"/": getTestContainerInfo(seedRoot, "", "", ""),
|
||||
"/docker-daemon": getTestContainerInfo(seedRuntime, "", "", ""),
|
||||
"/kubelet": getTestContainerInfo(seedKubelet, "", "", ""),
|
||||
"/system": getTestContainerInfo(seedMisc, "", "", ""),
|
||||
// Pod0 - Namespace0
|
||||
"/pod0-i": getTestContainerInfo(seedPod0Infra, pName0, namespace0, leaky.PodInfraContainerName),
|
||||
"/pod0-c0": getTestContainerInfo(seedPod0Container0, pName0, namespace0, cName00),
|
||||
"/pod0-c1": getTestContainerInfo(seedPod0Container1, pName0, namespace0, cName01),
|
||||
// Pod1 - Namespace0
|
||||
"/pod1-i": getTestContainerInfo(seedPod1Infra, pName1, namespace0, leaky.PodInfraContainerName),
|
||||
"/pod1-c0": getTestContainerInfo(seedPod1Container, pName1, namespace0, cName10),
|
||||
// Pod2 - Namespace2
|
||||
"/pod2-i": getTestContainerInfo(seedPod2Infra, pName2, namespace2, leaky.PodInfraContainerName),
|
||||
"/pod2-c0": getTestContainerInfo(seedPod2Container, pName2, namespace2, cName20),
|
||||
"/kubepods/burstable/podUIDpod0": getTestContainerInfo(seedPod0Infra, pName0, namespace0, leaky.PodInfraContainerName),
|
||||
"/kubepods/podUIDpod1": getTestContainerInfo(seedPod1Infra, pName1, namespace0, leaky.PodInfraContainerName),
|
||||
}
|
||||
|
||||
freeRootfsInodes := rootfsInodesFree
|
||||
totalRootfsInodes := rootfsInodes
|
||||
rootfs := cadvisorapiv2.FsInfo{
|
||||
Capacity: rootfsCapacity,
|
||||
Available: rootfsAvailable,
|
||||
InodesFree: &freeRootfsInodes,
|
||||
Inodes: &totalRootfsInodes,
|
||||
}
|
||||
|
||||
freeImagefsInodes := imagefsInodesFree
|
||||
totalImagefsInodes := imagefsInodes
|
||||
imagefs := cadvisorapiv2.FsInfo{
|
||||
Capacity: imagefsCapacity,
|
||||
Available: imagefsAvailable,
|
||||
InodesFree: &freeImagefsInodes,
|
||||
Inodes: &totalImagefsInodes,
|
||||
}
|
||||
|
||||
// memory limit overrides for each container (used to test available bytes if a memory limit is known)
|
||||
memoryLimitOverrides := map[string]uint64{
|
||||
"/": uint64(1 << 30),
|
||||
"/pod2-c0": uint64(1 << 15),
|
||||
}
|
||||
for name, memoryLimitOverride := range memoryLimitOverrides {
|
||||
info, found := infos[name]
|
||||
if !found {
|
||||
t.Errorf("No container defined with name %v", name)
|
||||
}
|
||||
info.Spec.Memory.Limit = memoryLimitOverride
|
||||
infos[name] = info
|
||||
}
|
||||
|
||||
options := cadvisorapiv2.RequestOptions{
|
||||
IdType: cadvisorapiv2.TypeName,
|
||||
Count: 2,
|
||||
Recursive: true,
|
||||
}
|
||||
|
||||
mockCadvisor := new(cadvisortest.Mock)
|
||||
mockCadvisor.
|
||||
On("ContainerInfoV2", "/", options).Return(infos, nil).
|
||||
On("RootFsInfo").Return(rootfs, nil).
|
||||
On("ImagesFsInfo").Return(imagefs, nil)
|
||||
|
||||
mockRuntime := new(containertest.Mock)
|
||||
mockRuntime.
|
||||
On("ImageStats").Return(&kubecontainer.ImageStats{TotalStorageBytes: 123}, nil)
|
||||
|
||||
ephemeralVolumes := []statsapi.VolumeStats{getPodVolumeStats(seedEphemeralVolume1, "ephemeralVolume1"),
|
||||
getPodVolumeStats(seedEphemeralVolume2, "ephemeralVolume2")}
|
||||
persistentVolumes := []statsapi.VolumeStats{getPodVolumeStats(seedPersistentVolume1, "persistentVolume1"),
|
||||
getPodVolumeStats(seedPersistentVolume2, "persistentVolume2")}
|
||||
volumeStats := serverstats.PodVolumeStats{
|
||||
EphemeralVolumes: ephemeralVolumes,
|
||||
PersistentVolumes: persistentVolumes,
|
||||
}
|
||||
|
||||
resourceAnalyzer := &fakeResourceAnalyzer{podVolumeStats: volumeStats}
|
||||
|
||||
p := NewCadvisorStatsProvider(mockCadvisor, resourceAnalyzer, nil, nil, mockRuntime)
|
||||
pods, err := p.ListPodStats()
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, 3, len(pods))
|
||||
indexPods := make(map[statsapi.PodReference]statsapi.PodStats, len(pods))
|
||||
for _, pod := range pods {
|
||||
indexPods[pod.PodRef] = pod
|
||||
}
|
||||
|
||||
// Validate Pod0 Results
|
||||
ps, found := indexPods[prf0]
|
||||
assert.True(t, found)
|
||||
assert.Len(t, ps.Containers, 2)
|
||||
indexCon := make(map[string]statsapi.ContainerStats, len(ps.Containers))
|
||||
for _, con := range ps.Containers {
|
||||
indexCon[con.Name] = con
|
||||
}
|
||||
con := indexCon[cName00]
|
||||
assert.EqualValues(t, testTime(creationTime, seedPod0Container0).Unix(), con.StartTime.Time.Unix())
|
||||
checkCPUStats(t, "Pod0Container0", seedPod0Container0, con.CPU)
|
||||
checkMemoryStats(t, "Pod0Conainer0", seedPod0Container0, infos["/pod0-c0"], con.Memory)
|
||||
|
||||
con = indexCon[cName01]
|
||||
assert.EqualValues(t, testTime(creationTime, seedPod0Container1).Unix(), con.StartTime.Time.Unix())
|
||||
checkCPUStats(t, "Pod0Container1", seedPod0Container1, con.CPU)
|
||||
checkMemoryStats(t, "Pod0Container1", seedPod0Container1, infos["/pod0-c1"], con.Memory)
|
||||
|
||||
assert.EqualValues(t, testTime(creationTime, seedPod0Infra).Unix(), ps.StartTime.Time.Unix())
|
||||
checkNetworkStats(t, "Pod0", seedPod0Infra, ps.Network)
|
||||
checkEphemeralStats(t, "Pod0", []int{seedPod0Container0, seedPod0Container1}, []int{seedEphemeralVolume1, seedEphemeralVolume2}, ps.EphemeralStorage)
|
||||
checkCPUStats(t, "Pod0", seedPod0Infra, ps.CPU)
|
||||
checkMemoryStats(t, "Pod0", seedPod0Infra, infos["/pod0-i"], ps.Memory)
|
||||
|
||||
// Validate Pod1 Results
|
||||
ps, found = indexPods[prf1]
|
||||
assert.True(t, found)
|
||||
assert.Len(t, ps.Containers, 1)
|
||||
con = ps.Containers[0]
|
||||
assert.Equal(t, cName10, con.Name)
|
||||
checkCPUStats(t, "Pod1Container0", seedPod1Container, con.CPU)
|
||||
checkMemoryStats(t, "Pod1Container0", seedPod1Container, infos["/pod1-c0"], con.Memory)
|
||||
checkNetworkStats(t, "Pod1", seedPod1Infra, ps.Network)
|
||||
|
||||
// Validate Pod2 Results
|
||||
ps, found = indexPods[prf2]
|
||||
assert.True(t, found)
|
||||
assert.Len(t, ps.Containers, 1)
|
||||
con = ps.Containers[0]
|
||||
assert.Equal(t, cName20, con.Name)
|
||||
checkCPUStats(t, "Pod2Container0", seedPod2Container, con.CPU)
|
||||
checkMemoryStats(t, "Pod2Container0", seedPod2Container, infos["/pod2-c0"], con.Memory)
|
||||
checkNetworkStats(t, "Pod2", seedPod2Infra, ps.Network)
|
||||
}
|
||||
|
||||
func TestCadvisorImagesFsStats(t *testing.T) {
|
||||
var (
|
||||
assert = assert.New(t)
|
||||
mockCadvisor = new(cadvisortest.Mock)
|
||||
mockRuntime = new(containertest.Mock)
|
||||
|
||||
seed = 1000
|
||||
imageFsInfo = getTestFsInfo(seed)
|
||||
imageStats = &kubecontainer.ImageStats{TotalStorageBytes: 100}
|
||||
)
|
||||
|
||||
mockCadvisor.On("ImagesFsInfo").Return(imageFsInfo, nil)
|
||||
mockRuntime.On("ImageStats").Return(imageStats, nil)
|
||||
|
||||
provider := newCadvisorStatsProvider(mockCadvisor, &fakeResourceAnalyzer{}, mockRuntime)
|
||||
stats, err := provider.ImageFsStats()
|
||||
assert.NoError(err)
|
||||
|
||||
assert.Equal(imageFsInfo.Timestamp, stats.Time.Time)
|
||||
assert.Equal(imageFsInfo.Available, *stats.AvailableBytes)
|
||||
assert.Equal(imageFsInfo.Capacity, *stats.CapacityBytes)
|
||||
assert.Equal(imageStats.TotalStorageBytes, *stats.UsedBytes)
|
||||
assert.Equal(imageFsInfo.InodesFree, stats.InodesFree)
|
||||
assert.Equal(imageFsInfo.Inodes, stats.Inodes)
|
||||
assert.Equal(*imageFsInfo.Inodes-*imageFsInfo.InodesFree, *stats.InodesUsed)
|
||||
|
||||
mockCadvisor.AssertExpectations(t)
|
||||
}
|
407
vendor/k8s.io/kubernetes/pkg/kubelet/stats/cri_stats_provider.go
generated
vendored
Normal file
407
vendor/k8s.io/kubernetes/pkg/kubelet/stats/cri_stats_provider.go
generated
vendored
Normal file
@ -0,0 +1,407 @@
|
||||
/*
|
||||
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 (
|
||||
"fmt"
|
||||
"path"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/golang/protobuf/proto"
|
||||
cadvisorfs "github.com/google/cadvisor/fs"
|
||||
|
||||
cadvisorapiv2 "github.com/google/cadvisor/info/v2"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
internalapi "k8s.io/kubernetes/pkg/kubelet/apis/cri"
|
||||
runtimeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
|
||||
statsapi "k8s.io/kubernetes/pkg/kubelet/apis/stats/v1alpha1"
|
||||
"k8s.io/kubernetes/pkg/kubelet/cadvisor"
|
||||
"k8s.io/kubernetes/pkg/kubelet/server/stats"
|
||||
kubetypes "k8s.io/kubernetes/pkg/kubelet/types"
|
||||
)
|
||||
|
||||
// criStatsProvider implements the containerStatsProvider interface by getting
|
||||
// the container stats from CRI.
|
||||
type criStatsProvider struct {
|
||||
// cadvisor is used to get the node root filesystem's stats (such as the
|
||||
// capacity/available bytes/inodes) that will be populated in per container
|
||||
// filesystem stats.
|
||||
cadvisor cadvisor.Interface
|
||||
// resourceAnalyzer is used to get the volume stats of the pods.
|
||||
resourceAnalyzer stats.ResourceAnalyzer
|
||||
// runtimeService is used to get the status and stats of the pods and its
|
||||
// managed containers.
|
||||
runtimeService internalapi.RuntimeService
|
||||
// imageService is used to get the stats of the image filesystem.
|
||||
imageService internalapi.ImageManagerService
|
||||
}
|
||||
|
||||
// newCRIStatsProvider returns a containerStatsProvider implementation that
|
||||
// provides container stats using CRI.
|
||||
func newCRIStatsProvider(
|
||||
cadvisor cadvisor.Interface,
|
||||
resourceAnalyzer stats.ResourceAnalyzer,
|
||||
runtimeService internalapi.RuntimeService,
|
||||
imageService internalapi.ImageManagerService,
|
||||
) containerStatsProvider {
|
||||
return &criStatsProvider{
|
||||
cadvisor: cadvisor,
|
||||
resourceAnalyzer: resourceAnalyzer,
|
||||
runtimeService: runtimeService,
|
||||
imageService: imageService,
|
||||
}
|
||||
}
|
||||
|
||||
// ListPodStats returns the stats of all the pod-managed containers.
|
||||
func (p *criStatsProvider) ListPodStats() ([]statsapi.PodStats, error) {
|
||||
// Gets node root filesystem information, which will be used to populate
|
||||
// the available and capacity bytes/inodes in container stats.
|
||||
rootFsInfo, err := p.cadvisor.RootFsInfo()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get rootFs info: %v", err)
|
||||
}
|
||||
|
||||
containers, err := p.runtimeService.ListContainers(&runtimeapi.ContainerFilter{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to list all containers: %v", err)
|
||||
}
|
||||
|
||||
// Creates pod sandbox map.
|
||||
podSandboxMap := make(map[string]*runtimeapi.PodSandbox)
|
||||
podSandboxes, err := p.runtimeService.ListPodSandbox(&runtimeapi.PodSandboxFilter{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to list all pod sandboxes: %v", err)
|
||||
}
|
||||
for _, s := range podSandboxes {
|
||||
podSandboxMap[s.Id] = s
|
||||
}
|
||||
|
||||
// uuidToFsInfo is a map from filesystem UUID to its stats. This will be
|
||||
// used as a cache to avoid querying cAdvisor for the filesystem stats with
|
||||
// the same UUID many times.
|
||||
uuidToFsInfo := make(map[runtimeapi.StorageIdentifier]*cadvisorapiv2.FsInfo)
|
||||
|
||||
// sandboxIDToPodStats is a temporary map from sandbox ID to its pod stats.
|
||||
sandboxIDToPodStats := make(map[string]*statsapi.PodStats)
|
||||
|
||||
resp, err := p.runtimeService.ListContainerStats(&runtimeapi.ContainerStatsFilter{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to list all container stats: %v", err)
|
||||
}
|
||||
|
||||
containers = removeTerminatedContainer(containers)
|
||||
// Creates container map.
|
||||
containerMap := make(map[string]*runtimeapi.Container)
|
||||
for _, c := range containers {
|
||||
containerMap[c.Id] = c
|
||||
}
|
||||
|
||||
caInfos, err := getCRICadvisorStats(p.cadvisor)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get container info from cadvisor: %v", err)
|
||||
}
|
||||
|
||||
for _, stats := range resp {
|
||||
containerID := stats.Attributes.Id
|
||||
container, found := containerMap[containerID]
|
||||
if !found {
|
||||
glog.Errorf("Unknown id %q in container map.", containerID)
|
||||
continue
|
||||
}
|
||||
|
||||
podSandboxID := container.PodSandboxId
|
||||
podSandbox, found := podSandboxMap[podSandboxID]
|
||||
if !found {
|
||||
glog.Errorf("Unknown id %q in pod sandbox map.", podSandboxID)
|
||||
continue
|
||||
}
|
||||
|
||||
// Creates the stats of the pod (if not created yet) which the
|
||||
// container belongs to.
|
||||
ps, found := sandboxIDToPodStats[podSandboxID]
|
||||
if !found {
|
||||
ps = buildPodStats(podSandbox)
|
||||
// Fill stats from cadvisor is available for full set of required pod stats
|
||||
caPodSandbox, found := caInfos[podSandboxID]
|
||||
if !found {
|
||||
glog.V(4).Info("Unable to find cadvisor stats for sandbox %q", podSandboxID)
|
||||
} else {
|
||||
p.addCadvisorPodStats(ps, &caPodSandbox)
|
||||
}
|
||||
sandboxIDToPodStats[podSandboxID] = ps
|
||||
}
|
||||
cs := p.makeContainerStats(stats, container, &rootFsInfo, uuidToFsInfo)
|
||||
// If cadvisor stats is available for the container, use it to populate
|
||||
// container stats
|
||||
caStats, caFound := caInfos[containerID]
|
||||
if !caFound {
|
||||
glog.V(4).Info("Unable to find cadvisor stats for %q", containerID)
|
||||
} else {
|
||||
p.addCadvisorContainerStats(cs, &caStats)
|
||||
}
|
||||
ps.Containers = append(ps.Containers, *cs)
|
||||
}
|
||||
|
||||
result := make([]statsapi.PodStats, 0, len(sandboxIDToPodStats))
|
||||
for _, s := range sandboxIDToPodStats {
|
||||
p.makePodStorageStats(s, &rootFsInfo)
|
||||
result = append(result, *s)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// ImageFsStats returns the stats of the image filesystem.
|
||||
func (p *criStatsProvider) ImageFsStats() (*statsapi.FsStats, error) {
|
||||
resp, err := p.imageService.ImageFsInfo()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// CRI may return the stats of multiple image filesystems but we only
|
||||
// return the first one.
|
||||
//
|
||||
// TODO(yguo0905): Support returning stats of multiple image filesystems.
|
||||
for _, fs := range resp {
|
||||
s := &statsapi.FsStats{
|
||||
Time: metav1.NewTime(time.Unix(0, fs.Timestamp)),
|
||||
UsedBytes: &fs.UsedBytes.Value,
|
||||
InodesUsed: &fs.InodesUsed.Value,
|
||||
}
|
||||
imageFsInfo := p.getFsInfo(fs.StorageId)
|
||||
if imageFsInfo != nil {
|
||||
// The image filesystem UUID is unknown to the local node or
|
||||
// there's an error on retrieving the stats. In these cases, we
|
||||
// omit those stats and return the best-effort partial result. See
|
||||
// https://github.com/kubernetes/heapster/issues/1793.
|
||||
s.AvailableBytes = &imageFsInfo.Available
|
||||
s.CapacityBytes = &imageFsInfo.Capacity
|
||||
s.InodesFree = imageFsInfo.InodesFree
|
||||
s.Inodes = imageFsInfo.Inodes
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("imageFs information is unavailable")
|
||||
}
|
||||
|
||||
// getFsInfo returns the information of the filesystem with the specified
|
||||
// storageID. If any error occurs, this function logs the error and returns
|
||||
// nil.
|
||||
func (p *criStatsProvider) getFsInfo(storageID *runtimeapi.StorageIdentifier) *cadvisorapiv2.FsInfo {
|
||||
if storageID == nil {
|
||||
glog.V(2).Infof("Failed to get filesystem info: storageID is nil.")
|
||||
return nil
|
||||
}
|
||||
fsInfo, err := p.cadvisor.GetFsInfoByFsUUID(storageID.Uuid)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("Failed to get the info of the filesystem with id %q: %v.", storageID.Uuid, err)
|
||||
if err == cadvisorfs.ErrNoSuchDevice {
|
||||
glog.V(2).Info(msg)
|
||||
} else {
|
||||
glog.Error(msg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return &fsInfo
|
||||
}
|
||||
|
||||
// buildPodStats returns a PodStats that identifies the Pod managing cinfo
|
||||
func buildPodStats(podSandbox *runtimeapi.PodSandbox) *statsapi.PodStats {
|
||||
return &statsapi.PodStats{
|
||||
PodRef: statsapi.PodReference{
|
||||
Name: podSandbox.Metadata.Name,
|
||||
UID: podSandbox.Metadata.Uid,
|
||||
Namespace: podSandbox.Metadata.Namespace,
|
||||
},
|
||||
// The StartTime in the summary API is the pod creation time.
|
||||
StartTime: metav1.NewTime(time.Unix(0, podSandbox.CreatedAt)),
|
||||
}
|
||||
}
|
||||
|
||||
func (p *criStatsProvider) makePodStorageStats(s *statsapi.PodStats, rootFsInfo *cadvisorapiv2.FsInfo) *statsapi.PodStats {
|
||||
podUID := types.UID(s.PodRef.UID)
|
||||
if vstats, found := p.resourceAnalyzer.GetPodVolumeStats(podUID); found {
|
||||
ephemeralStats := make([]statsapi.VolumeStats, len(vstats.EphemeralVolumes))
|
||||
copy(ephemeralStats, vstats.EphemeralVolumes)
|
||||
s.VolumeStats = append(vstats.EphemeralVolumes, vstats.PersistentVolumes...)
|
||||
s.EphemeralStorage = calcEphemeralStorage(s.Containers, ephemeralStats, rootFsInfo)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (p *criStatsProvider) addCadvisorPodStats(
|
||||
ps *statsapi.PodStats,
|
||||
caPodSandbox *cadvisorapiv2.ContainerInfo,
|
||||
) {
|
||||
ps.Network = cadvisorInfoToNetworkStats(ps.PodRef.Name, caPodSandbox)
|
||||
}
|
||||
|
||||
func (p *criStatsProvider) makeContainerStats(
|
||||
stats *runtimeapi.ContainerStats,
|
||||
container *runtimeapi.Container,
|
||||
rootFsInfo *cadvisorapiv2.FsInfo,
|
||||
uuidToFsInfo map[runtimeapi.StorageIdentifier]*cadvisorapiv2.FsInfo,
|
||||
) *statsapi.ContainerStats {
|
||||
result := &statsapi.ContainerStats{
|
||||
Name: stats.Attributes.Metadata.Name,
|
||||
// The StartTime in the summary API is the container creation time.
|
||||
StartTime: metav1.NewTime(time.Unix(0, container.CreatedAt)),
|
||||
// Work around heapster bug. https://github.com/kubernetes/kubernetes/issues/54962
|
||||
// TODO(random-liu): Remove this after heapster is updated to newer than 1.5.0-beta.0.
|
||||
CPU: &statsapi.CPUStats{
|
||||
UsageNanoCores: proto.Uint64(0),
|
||||
},
|
||||
Memory: &statsapi.MemoryStats{
|
||||
RSSBytes: proto.Uint64(0),
|
||||
},
|
||||
Rootfs: &statsapi.FsStats{},
|
||||
Logs: &statsapi.FsStats{
|
||||
Time: metav1.NewTime(rootFsInfo.Timestamp),
|
||||
AvailableBytes: &rootFsInfo.Available,
|
||||
CapacityBytes: &rootFsInfo.Capacity,
|
||||
InodesFree: rootFsInfo.InodesFree,
|
||||
Inodes: rootFsInfo.Inodes,
|
||||
// UsedBytes and InodesUsed are unavailable from CRI stats.
|
||||
//
|
||||
// TODO(yguo0905): Get this information from kubelet and
|
||||
// populate the two fields here.
|
||||
},
|
||||
// UserDefinedMetrics is not supported by CRI.
|
||||
}
|
||||
if stats.Cpu != nil {
|
||||
result.CPU.Time = metav1.NewTime(time.Unix(0, stats.Cpu.Timestamp))
|
||||
if stats.Cpu.UsageCoreNanoSeconds != nil {
|
||||
result.CPU.UsageCoreNanoSeconds = &stats.Cpu.UsageCoreNanoSeconds.Value
|
||||
}
|
||||
}
|
||||
if stats.Memory != nil {
|
||||
result.Memory.Time = metav1.NewTime(time.Unix(0, stats.Memory.Timestamp))
|
||||
if stats.Memory.WorkingSetBytes != nil {
|
||||
result.Memory.WorkingSetBytes = &stats.Memory.WorkingSetBytes.Value
|
||||
}
|
||||
}
|
||||
if stats.WritableLayer != nil {
|
||||
result.Rootfs.Time = metav1.NewTime(time.Unix(0, stats.WritableLayer.Timestamp))
|
||||
if stats.WritableLayer.UsedBytes != nil {
|
||||
result.Rootfs.UsedBytes = &stats.WritableLayer.UsedBytes.Value
|
||||
}
|
||||
if stats.WritableLayer.InodesUsed != nil {
|
||||
result.Rootfs.InodesUsed = &stats.WritableLayer.InodesUsed.Value
|
||||
}
|
||||
}
|
||||
storageID := stats.GetWritableLayer().GetStorageId()
|
||||
if storageID != nil {
|
||||
imageFsInfo, found := uuidToFsInfo[*storageID]
|
||||
if !found {
|
||||
imageFsInfo = p.getFsInfo(storageID)
|
||||
uuidToFsInfo[*storageID] = imageFsInfo
|
||||
}
|
||||
if imageFsInfo != nil {
|
||||
// The image filesystem UUID is unknown to the local node or there's an
|
||||
// error on retrieving the stats. In these cases, we omit those stats
|
||||
// and return the best-effort partial result. See
|
||||
// https://github.com/kubernetes/heapster/issues/1793.
|
||||
result.Rootfs.AvailableBytes = &imageFsInfo.Available
|
||||
result.Rootfs.CapacityBytes = &imageFsInfo.Capacity
|
||||
result.Rootfs.InodesFree = imageFsInfo.InodesFree
|
||||
result.Rootfs.Inodes = imageFsInfo.Inodes
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// removeTerminatedContainer returns the specified container but with
|
||||
// the stats of the terminated containers removed.
|
||||
func removeTerminatedContainer(containers []*runtimeapi.Container) []*runtimeapi.Container {
|
||||
containerMap := make(map[containerID][]*runtimeapi.Container)
|
||||
// Sort order by create time
|
||||
sort.Slice(containers, func(i, j int) bool {
|
||||
return containers[i].CreatedAt < containers[j].CreatedAt
|
||||
})
|
||||
for _, container := range containers {
|
||||
refID := containerID{
|
||||
podRef: buildPodRef(container.Labels),
|
||||
containerName: kubetypes.GetContainerName(container.Labels),
|
||||
}
|
||||
containerMap[refID] = append(containerMap[refID], container)
|
||||
}
|
||||
|
||||
result := make([]*runtimeapi.Container, 0)
|
||||
for _, refs := range containerMap {
|
||||
if len(refs) == 1 {
|
||||
result = append(result, refs[0])
|
||||
continue
|
||||
}
|
||||
found := false
|
||||
for i := 0; i < len(refs); i++ {
|
||||
if refs[i].State == runtimeapi.ContainerState_CONTAINER_RUNNING {
|
||||
found = true
|
||||
result = append(result, refs[i])
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
result = append(result, refs[len(refs)-1])
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (p *criStatsProvider) addCadvisorContainerStats(
|
||||
cs *statsapi.ContainerStats,
|
||||
caPodStats *cadvisorapiv2.ContainerInfo,
|
||||
) {
|
||||
if caPodStats.Spec.HasCustomMetrics {
|
||||
cs.UserDefinedMetrics = cadvisorInfoToUserDefinedMetrics(caPodStats)
|
||||
}
|
||||
|
||||
cpu, memory := cadvisorInfoToCPUandMemoryStats(caPodStats)
|
||||
if cpu != nil {
|
||||
cs.CPU = cpu
|
||||
}
|
||||
if memory != nil {
|
||||
cs.Memory = memory
|
||||
}
|
||||
}
|
||||
|
||||
func getCRICadvisorStats(ca cadvisor.Interface) (map[string]cadvisorapiv2.ContainerInfo, error) {
|
||||
stats := make(map[string]cadvisorapiv2.ContainerInfo)
|
||||
infos, err := getCadvisorContainerInfo(ca)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to fetch cadvisor stats: %v", err)
|
||||
}
|
||||
infos = removeTerminatedContainerInfo(infos)
|
||||
for key, info := range infos {
|
||||
// On systemd using devicemapper each mount into the container has an
|
||||
// associated cgroup. We ignore them to ensure we do not get duplicate
|
||||
// entries in our summary. For details on .mount units:
|
||||
// http://man7.org/linux/man-pages/man5/systemd.mount.5.html
|
||||
if strings.HasSuffix(key, ".mount") {
|
||||
continue
|
||||
}
|
||||
// Build the Pod key if this container is managed by a Pod
|
||||
if !isPodManagedContainer(&info) {
|
||||
continue
|
||||
}
|
||||
stats[path.Base(key)] = info
|
||||
}
|
||||
return stats, nil
|
||||
}
|
419
vendor/k8s.io/kubernetes/pkg/kubelet/stats/cri_stats_provider_test.go
generated
vendored
Normal file
419
vendor/k8s.io/kubernetes/pkg/kubelet/stats/cri_stats_provider_test.go
generated
vendored
Normal file
@ -0,0 +1,419 @@
|
||||
/*
|
||||
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 (
|
||||
"math/rand"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
cadvisorfs "github.com/google/cadvisor/fs"
|
||||
cadvisorapiv2 "github.com/google/cadvisor/info/v2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
critest "k8s.io/kubernetes/pkg/kubelet/apis/cri/testing"
|
||||
runtimeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
|
||||
statsapi "k8s.io/kubernetes/pkg/kubelet/apis/stats/v1alpha1"
|
||||
cadvisortest "k8s.io/kubernetes/pkg/kubelet/cadvisor/testing"
|
||||
kubecontainertest "k8s.io/kubernetes/pkg/kubelet/container/testing"
|
||||
"k8s.io/kubernetes/pkg/kubelet/leaky"
|
||||
kubepodtest "k8s.io/kubernetes/pkg/kubelet/pod/testing"
|
||||
serverstats "k8s.io/kubernetes/pkg/kubelet/server/stats"
|
||||
)
|
||||
|
||||
func TestCRIListPodStats(t *testing.T) {
|
||||
const (
|
||||
seedRoot = 0
|
||||
seedRuntime = 100
|
||||
seedKubelet = 200
|
||||
seedMisc = 300
|
||||
seedSandbox0 = 1000
|
||||
seedContainer0 = 2000
|
||||
seedSandbox1 = 3000
|
||||
seedContainer1 = 4000
|
||||
seedContainer2 = 5000
|
||||
seedSandbox2 = 6000
|
||||
seedContainer3 = 7000
|
||||
)
|
||||
|
||||
const (
|
||||
pName0 = "pod0"
|
||||
pName1 = "pod1"
|
||||
pName2 = "pod2"
|
||||
)
|
||||
|
||||
const (
|
||||
cName0 = "container0-name"
|
||||
cName1 = "container1-name"
|
||||
cName2 = "container2-name"
|
||||
cName3 = "container3-name"
|
||||
)
|
||||
|
||||
var (
|
||||
imageFsStorageUUID = "imagefs-storage-uuid"
|
||||
unknownStorageUUID = "unknown-storage-uuid"
|
||||
imageFsInfo = getTestFsInfo(2000)
|
||||
rootFsInfo = getTestFsInfo(1000)
|
||||
|
||||
sandbox0 = makeFakePodSandbox("sandbox0-name", "sandbox0-uid", "sandbox0-ns")
|
||||
container0 = makeFakeContainer(sandbox0, cName0, 0, false)
|
||||
containerStats0 = makeFakeContainerStats(container0, imageFsStorageUUID)
|
||||
container1 = makeFakeContainer(sandbox0, cName1, 0, false)
|
||||
containerStats1 = makeFakeContainerStats(container1, unknownStorageUUID)
|
||||
|
||||
sandbox1 = makeFakePodSandbox("sandbox1-name", "sandbox1-uid", "sandbox1-ns")
|
||||
container2 = makeFakeContainer(sandbox1, cName2, 0, false)
|
||||
containerStats2 = makeFakeContainerStats(container2, imageFsStorageUUID)
|
||||
|
||||
sandbox2 = makeFakePodSandbox("sandbox2-name", "sandbox2-uid", "sandbox2-ns")
|
||||
container3 = makeFakeContainer(sandbox2, cName3, 0, true)
|
||||
containerStats3 = makeFakeContainerStats(container3, imageFsStorageUUID)
|
||||
container4 = makeFakeContainer(sandbox2, cName3, 1, false)
|
||||
containerStats4 = makeFakeContainerStats(container4, imageFsStorageUUID)
|
||||
)
|
||||
|
||||
var (
|
||||
mockCadvisor = new(cadvisortest.Mock)
|
||||
mockRuntimeCache = new(kubecontainertest.MockRuntimeCache)
|
||||
mockPodManager = new(kubepodtest.MockManager)
|
||||
resourceAnalyzer = new(fakeResourceAnalyzer)
|
||||
fakeRuntimeService = critest.NewFakeRuntimeService()
|
||||
fakeImageService = critest.NewFakeImageService()
|
||||
)
|
||||
|
||||
infos := map[string]cadvisorapiv2.ContainerInfo{
|
||||
"/": getTestContainerInfo(seedRoot, "", "", ""),
|
||||
"/kubelet": getTestContainerInfo(seedKubelet, "", "", ""),
|
||||
"/system": getTestContainerInfo(seedMisc, "", "", ""),
|
||||
sandbox0.PodSandboxStatus.Id: getTestContainerInfo(seedSandbox0, pName0, sandbox0.PodSandboxStatus.Metadata.Namespace, leaky.PodInfraContainerName),
|
||||
container0.ContainerStatus.Id: getTestContainerInfo(seedContainer0, pName0, sandbox0.PodSandboxStatus.Metadata.Namespace, cName0),
|
||||
container1.ContainerStatus.Id: getTestContainerInfo(seedContainer1, pName0, sandbox0.PodSandboxStatus.Metadata.Namespace, cName1),
|
||||
sandbox1.PodSandboxStatus.Id: getTestContainerInfo(seedSandbox1, pName1, sandbox1.PodSandboxStatus.Metadata.Namespace, leaky.PodInfraContainerName),
|
||||
container2.ContainerStatus.Id: getTestContainerInfo(seedContainer2, pName1, sandbox1.PodSandboxStatus.Metadata.Namespace, cName2),
|
||||
sandbox2.PodSandboxStatus.Id: getTestContainerInfo(seedSandbox2, pName2, sandbox2.PodSandboxStatus.Metadata.Namespace, leaky.PodInfraContainerName),
|
||||
container4.ContainerStatus.Id: getTestContainerInfo(seedContainer3, pName2, sandbox2.PodSandboxStatus.Metadata.Namespace, cName3),
|
||||
}
|
||||
|
||||
options := cadvisorapiv2.RequestOptions{
|
||||
IdType: cadvisorapiv2.TypeName,
|
||||
Count: 2,
|
||||
Recursive: true,
|
||||
}
|
||||
|
||||
mockCadvisor.
|
||||
On("ContainerInfoV2", "/", options).Return(infos, nil).
|
||||
On("RootFsInfo").Return(rootFsInfo, nil).
|
||||
On("GetFsInfoByFsUUID", imageFsStorageUUID).Return(imageFsInfo, nil).
|
||||
On("GetFsInfoByFsUUID", unknownStorageUUID).Return(cadvisorapiv2.FsInfo{}, cadvisorfs.ErrNoSuchDevice)
|
||||
fakeRuntimeService.SetFakeSandboxes([]*critest.FakePodSandbox{
|
||||
sandbox0, sandbox1, sandbox2,
|
||||
})
|
||||
fakeRuntimeService.SetFakeContainers([]*critest.FakeContainer{
|
||||
container0, container1, container2, container3, container4,
|
||||
})
|
||||
fakeRuntimeService.SetFakeContainerStats([]*runtimeapi.ContainerStats{
|
||||
containerStats0, containerStats1, containerStats2, containerStats3, containerStats4,
|
||||
})
|
||||
|
||||
ephemeralVolumes := makeFakeVolumeStats([]string{"ephVolume1, ephVolumes2"})
|
||||
persistentVolumes := makeFakeVolumeStats([]string{"persisVolume1, persisVolumes2"})
|
||||
resourceAnalyzer.podVolumeStats = serverstats.PodVolumeStats{
|
||||
EphemeralVolumes: ephemeralVolumes,
|
||||
PersistentVolumes: persistentVolumes,
|
||||
}
|
||||
|
||||
provider := NewCRIStatsProvider(
|
||||
mockCadvisor,
|
||||
resourceAnalyzer,
|
||||
mockPodManager,
|
||||
mockRuntimeCache,
|
||||
fakeRuntimeService,
|
||||
fakeImageService)
|
||||
|
||||
stats, err := provider.ListPodStats()
|
||||
assert := assert.New(t)
|
||||
assert.NoError(err)
|
||||
assert.Equal(3, len(stats))
|
||||
|
||||
podStatsMap := make(map[statsapi.PodReference]statsapi.PodStats)
|
||||
for _, s := range stats {
|
||||
podStatsMap[s.PodRef] = s
|
||||
}
|
||||
|
||||
p0 := podStatsMap[statsapi.PodReference{Name: "sandbox0-name", UID: "sandbox0-uid", Namespace: "sandbox0-ns"}]
|
||||
assert.Equal(sandbox0.CreatedAt, p0.StartTime.UnixNano())
|
||||
assert.Equal(2, len(p0.Containers))
|
||||
|
||||
checkEphemeralStorageStats(assert, p0, ephemeralVolumes, []*runtimeapi.ContainerStats{containerStats0, containerStats1})
|
||||
|
||||
containerStatsMap := make(map[string]statsapi.ContainerStats)
|
||||
for _, s := range p0.Containers {
|
||||
containerStatsMap[s.Name] = s
|
||||
}
|
||||
|
||||
c0 := containerStatsMap[cName0]
|
||||
assert.Equal(container0.CreatedAt, c0.StartTime.UnixNano())
|
||||
checkCRICPUAndMemoryStats(assert, c0, infos[container0.ContainerStatus.Id].Stats[0])
|
||||
checkCRIRootfsStats(assert, c0, containerStats0, &imageFsInfo)
|
||||
checkCRILogsStats(assert, c0, &rootFsInfo)
|
||||
c1 := containerStatsMap[cName1]
|
||||
assert.Equal(container1.CreatedAt, c1.StartTime.UnixNano())
|
||||
checkCRICPUAndMemoryStats(assert, c1, infos[container1.ContainerStatus.Id].Stats[0])
|
||||
checkCRIRootfsStats(assert, c1, containerStats1, nil)
|
||||
checkCRILogsStats(assert, c1, &rootFsInfo)
|
||||
checkCRINetworkStats(assert, p0.Network, infos[sandbox0.PodSandboxStatus.Id].Stats[0].Network)
|
||||
|
||||
p1 := podStatsMap[statsapi.PodReference{Name: "sandbox1-name", UID: "sandbox1-uid", Namespace: "sandbox1-ns"}]
|
||||
assert.Equal(sandbox1.CreatedAt, p1.StartTime.UnixNano())
|
||||
assert.Equal(1, len(p1.Containers))
|
||||
|
||||
checkEphemeralStorageStats(assert, p1, ephemeralVolumes, []*runtimeapi.ContainerStats{containerStats2})
|
||||
c2 := p1.Containers[0]
|
||||
assert.Equal(cName2, c2.Name)
|
||||
assert.Equal(container2.CreatedAt, c2.StartTime.UnixNano())
|
||||
checkCRICPUAndMemoryStats(assert, c2, infos[container2.ContainerStatus.Id].Stats[0])
|
||||
checkCRIRootfsStats(assert, c2, containerStats2, &imageFsInfo)
|
||||
checkCRILogsStats(assert, c2, &rootFsInfo)
|
||||
checkCRINetworkStats(assert, p1.Network, infos[sandbox1.PodSandboxStatus.Id].Stats[0].Network)
|
||||
|
||||
p2 := podStatsMap[statsapi.PodReference{Name: "sandbox2-name", UID: "sandbox2-uid", Namespace: "sandbox2-ns"}]
|
||||
assert.Equal(sandbox2.CreatedAt, p2.StartTime.UnixNano())
|
||||
assert.Equal(1, len(p2.Containers))
|
||||
|
||||
checkEphemeralStorageStats(assert, p2, ephemeralVolumes, []*runtimeapi.ContainerStats{containerStats4})
|
||||
|
||||
c3 := p2.Containers[0]
|
||||
assert.Equal(cName3, c3.Name)
|
||||
assert.Equal(container4.CreatedAt, c3.StartTime.UnixNano())
|
||||
checkCRICPUAndMemoryStats(assert, c3, infos[container4.ContainerStatus.Id].Stats[0])
|
||||
checkCRIRootfsStats(assert, c3, containerStats4, &imageFsInfo)
|
||||
|
||||
checkCRILogsStats(assert, c3, &rootFsInfo)
|
||||
checkCRINetworkStats(assert, p2.Network, infos[sandbox2.PodSandboxStatus.Id].Stats[0].Network)
|
||||
|
||||
mockCadvisor.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func TestCRIImagesFsStats(t *testing.T) {
|
||||
var (
|
||||
imageFsStorageUUID = "imagefs-storage-uuid"
|
||||
imageFsInfo = getTestFsInfo(2000)
|
||||
imageFsUsage = makeFakeImageFsUsage(imageFsStorageUUID)
|
||||
)
|
||||
var (
|
||||
mockCadvisor = new(cadvisortest.Mock)
|
||||
mockRuntimeCache = new(kubecontainertest.MockRuntimeCache)
|
||||
mockPodManager = new(kubepodtest.MockManager)
|
||||
resourceAnalyzer = new(fakeResourceAnalyzer)
|
||||
fakeRuntimeService = critest.NewFakeRuntimeService()
|
||||
fakeImageService = critest.NewFakeImageService()
|
||||
)
|
||||
|
||||
mockCadvisor.On("GetFsInfoByFsUUID", imageFsStorageUUID).Return(imageFsInfo, nil)
|
||||
fakeImageService.SetFakeFilesystemUsage([]*runtimeapi.FilesystemUsage{
|
||||
imageFsUsage,
|
||||
})
|
||||
|
||||
provider := NewCRIStatsProvider(
|
||||
mockCadvisor,
|
||||
resourceAnalyzer,
|
||||
mockPodManager,
|
||||
mockRuntimeCache,
|
||||
fakeRuntimeService,
|
||||
fakeImageService)
|
||||
|
||||
stats, err := provider.ImageFsStats()
|
||||
assert := assert.New(t)
|
||||
assert.NoError(err)
|
||||
|
||||
assert.Equal(imageFsUsage.Timestamp, stats.Time.UnixNano())
|
||||
assert.Equal(imageFsInfo.Available, *stats.AvailableBytes)
|
||||
assert.Equal(imageFsInfo.Capacity, *stats.CapacityBytes)
|
||||
assert.Equal(imageFsInfo.InodesFree, stats.InodesFree)
|
||||
assert.Equal(imageFsInfo.Inodes, stats.Inodes)
|
||||
assert.Equal(imageFsUsage.UsedBytes.Value, *stats.UsedBytes)
|
||||
assert.Equal(imageFsUsage.InodesUsed.Value, *stats.InodesUsed)
|
||||
|
||||
mockCadvisor.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func makeFakePodSandbox(name, uid, namespace string) *critest.FakePodSandbox {
|
||||
p := &critest.FakePodSandbox{
|
||||
PodSandboxStatus: runtimeapi.PodSandboxStatus{
|
||||
Metadata: &runtimeapi.PodSandboxMetadata{
|
||||
Name: name,
|
||||
Uid: uid,
|
||||
Namespace: namespace,
|
||||
},
|
||||
State: runtimeapi.PodSandboxState_SANDBOX_READY,
|
||||
CreatedAt: time.Now().UnixNano(),
|
||||
},
|
||||
}
|
||||
p.PodSandboxStatus.Id = critest.BuildSandboxName(p.PodSandboxStatus.Metadata)
|
||||
return p
|
||||
}
|
||||
|
||||
func makeFakeContainer(sandbox *critest.FakePodSandbox, name string, attempt uint32, terminated bool) *critest.FakeContainer {
|
||||
sandboxID := sandbox.PodSandboxStatus.Id
|
||||
c := &critest.FakeContainer{
|
||||
SandboxID: sandboxID,
|
||||
ContainerStatus: runtimeapi.ContainerStatus{
|
||||
Metadata: &runtimeapi.ContainerMetadata{Name: name, Attempt: attempt},
|
||||
Image: &runtimeapi.ImageSpec{},
|
||||
ImageRef: "fake-image-ref",
|
||||
CreatedAt: time.Now().UnixNano(),
|
||||
},
|
||||
}
|
||||
c.ContainerStatus.Labels = map[string]string{
|
||||
"io.kubernetes.pod.name": sandbox.Metadata.Name,
|
||||
"io.kubernetes.pod.uid": sandbox.Metadata.Uid,
|
||||
"io.kubernetes.pod.namespace": sandbox.Metadata.Namespace,
|
||||
"io.kubernetes.container.name": name,
|
||||
}
|
||||
if terminated {
|
||||
c.ContainerStatus.State = runtimeapi.ContainerState_CONTAINER_EXITED
|
||||
} else {
|
||||
c.ContainerStatus.State = runtimeapi.ContainerState_CONTAINER_RUNNING
|
||||
}
|
||||
c.ContainerStatus.Id = critest.BuildContainerName(c.ContainerStatus.Metadata, sandboxID)
|
||||
return c
|
||||
}
|
||||
|
||||
func makeFakeContainerStats(container *critest.FakeContainer, imageFsUUID string) *runtimeapi.ContainerStats {
|
||||
containerStats := &runtimeapi.ContainerStats{
|
||||
Attributes: &runtimeapi.ContainerAttributes{
|
||||
Id: container.ContainerStatus.Id,
|
||||
Metadata: container.ContainerStatus.Metadata,
|
||||
},
|
||||
WritableLayer: &runtimeapi.FilesystemUsage{
|
||||
Timestamp: time.Now().UnixNano(),
|
||||
StorageId: &runtimeapi.StorageIdentifier{Uuid: imageFsUUID},
|
||||
UsedBytes: &runtimeapi.UInt64Value{Value: rand.Uint64() / 100},
|
||||
InodesUsed: &runtimeapi.UInt64Value{Value: rand.Uint64() / 100},
|
||||
},
|
||||
}
|
||||
if container.State == runtimeapi.ContainerState_CONTAINER_EXITED {
|
||||
containerStats.Cpu = nil
|
||||
containerStats.Memory = nil
|
||||
} else {
|
||||
containerStats.Cpu = &runtimeapi.CpuUsage{
|
||||
Timestamp: time.Now().UnixNano(),
|
||||
UsageCoreNanoSeconds: &runtimeapi.UInt64Value{Value: rand.Uint64()},
|
||||
}
|
||||
containerStats.Memory = &runtimeapi.MemoryUsage{
|
||||
Timestamp: time.Now().UnixNano(),
|
||||
WorkingSetBytes: &runtimeapi.UInt64Value{Value: rand.Uint64()},
|
||||
}
|
||||
}
|
||||
return containerStats
|
||||
}
|
||||
|
||||
func makeFakeImageFsUsage(fsUUID string) *runtimeapi.FilesystemUsage {
|
||||
return &runtimeapi.FilesystemUsage{
|
||||
Timestamp: time.Now().UnixNano(),
|
||||
StorageId: &runtimeapi.StorageIdentifier{Uuid: fsUUID},
|
||||
UsedBytes: &runtimeapi.UInt64Value{Value: rand.Uint64()},
|
||||
InodesUsed: &runtimeapi.UInt64Value{Value: rand.Uint64()},
|
||||
}
|
||||
}
|
||||
|
||||
func makeFakeVolumeStats(volumeNames []string) []statsapi.VolumeStats {
|
||||
volumes := make([]statsapi.VolumeStats, len(volumeNames))
|
||||
availableBytes := rand.Uint64()
|
||||
capacityBytes := rand.Uint64()
|
||||
usedBytes := rand.Uint64() / 100
|
||||
inodes := rand.Uint64()
|
||||
inodesFree := rand.Uint64()
|
||||
inodesUsed := rand.Uint64() / 100
|
||||
for i, name := range volumeNames {
|
||||
fsStats := statsapi.FsStats{
|
||||
Time: metav1.NewTime(time.Now()),
|
||||
AvailableBytes: &availableBytes,
|
||||
CapacityBytes: &capacityBytes,
|
||||
UsedBytes: &usedBytes,
|
||||
Inodes: &inodes,
|
||||
InodesFree: &inodesFree,
|
||||
InodesUsed: &inodesUsed,
|
||||
}
|
||||
volumes[i] = statsapi.VolumeStats{
|
||||
FsStats: fsStats,
|
||||
Name: name,
|
||||
}
|
||||
}
|
||||
return volumes
|
||||
}
|
||||
|
||||
func checkCRICPUAndMemoryStats(assert *assert.Assertions, actual statsapi.ContainerStats, cs *cadvisorapiv2.ContainerStats) {
|
||||
assert.Equal(cs.Timestamp.UnixNano(), actual.CPU.Time.UnixNano())
|
||||
assert.Equal(cs.Cpu.Usage.Total, *actual.CPU.UsageCoreNanoSeconds)
|
||||
assert.Equal(cs.CpuInst.Usage.Total, *actual.CPU.UsageNanoCores)
|
||||
|
||||
assert.Equal(cs.Memory.Usage, *actual.Memory.UsageBytes)
|
||||
assert.Equal(cs.Memory.WorkingSet, *actual.Memory.WorkingSetBytes)
|
||||
assert.Equal(cs.Memory.RSS, *actual.Memory.RSSBytes)
|
||||
assert.Equal(cs.Memory.ContainerData.Pgfault, *actual.Memory.PageFaults)
|
||||
assert.Equal(cs.Memory.ContainerData.Pgmajfault, *actual.Memory.MajorPageFaults)
|
||||
}
|
||||
|
||||
func checkCRIRootfsStats(assert *assert.Assertions, actual statsapi.ContainerStats, cs *runtimeapi.ContainerStats, imageFsInfo *cadvisorapiv2.FsInfo) {
|
||||
assert.Equal(cs.WritableLayer.Timestamp, actual.Rootfs.Time.UnixNano())
|
||||
if imageFsInfo != nil {
|
||||
assert.Equal(imageFsInfo.Available, *actual.Rootfs.AvailableBytes)
|
||||
assert.Equal(imageFsInfo.Capacity, *actual.Rootfs.CapacityBytes)
|
||||
assert.Equal(*imageFsInfo.InodesFree, *actual.Rootfs.InodesFree)
|
||||
assert.Equal(*imageFsInfo.Inodes, *actual.Rootfs.Inodes)
|
||||
} else {
|
||||
assert.Nil(actual.Rootfs.AvailableBytes)
|
||||
assert.Nil(actual.Rootfs.CapacityBytes)
|
||||
assert.Nil(actual.Rootfs.InodesFree)
|
||||
assert.Nil(actual.Rootfs.Inodes)
|
||||
}
|
||||
assert.Equal(cs.WritableLayer.UsedBytes.Value, *actual.Rootfs.UsedBytes)
|
||||
assert.Equal(cs.WritableLayer.InodesUsed.Value, *actual.Rootfs.InodesUsed)
|
||||
}
|
||||
|
||||
func checkCRILogsStats(assert *assert.Assertions, actual statsapi.ContainerStats, rootFsInfo *cadvisorapiv2.FsInfo) {
|
||||
assert.Equal(rootFsInfo.Timestamp, actual.Logs.Time.Time)
|
||||
assert.Equal(rootFsInfo.Available, *actual.Logs.AvailableBytes)
|
||||
assert.Equal(rootFsInfo.Capacity, *actual.Logs.CapacityBytes)
|
||||
assert.Equal(*rootFsInfo.InodesFree, *actual.Logs.InodesFree)
|
||||
assert.Equal(*rootFsInfo.Inodes, *actual.Logs.Inodes)
|
||||
assert.Nil(actual.Logs.UsedBytes)
|
||||
assert.Nil(actual.Logs.InodesUsed)
|
||||
}
|
||||
|
||||
func checkEphemeralStorageStats(assert *assert.Assertions, actual statsapi.PodStats, volumes []statsapi.VolumeStats, containers []*runtimeapi.ContainerStats) {
|
||||
var totalUsed, inodesUsed uint64
|
||||
for _, container := range containers {
|
||||
totalUsed = totalUsed + container.WritableLayer.UsedBytes.Value
|
||||
inodesUsed = inodesUsed + container.WritableLayer.InodesUsed.Value
|
||||
}
|
||||
|
||||
for _, volume := range volumes {
|
||||
totalUsed = totalUsed + *volume.FsStats.UsedBytes
|
||||
inodesUsed = inodesUsed + *volume.FsStats.InodesUsed
|
||||
}
|
||||
assert.Equal(int(*actual.EphemeralStorage.UsedBytes), int(totalUsed))
|
||||
assert.Equal(int(*actual.EphemeralStorage.InodesUsed), int(inodesUsed))
|
||||
}
|
||||
|
||||
func checkCRINetworkStats(assert *assert.Assertions, actual *statsapi.NetworkStats, expected *cadvisorapiv2.NetworkStats) {
|
||||
assert.Equal(expected.Interfaces[0].RxBytes, *actual.RxBytes)
|
||||
assert.Equal(expected.Interfaces[0].RxErrors, *actual.RxErrors)
|
||||
assert.Equal(expected.Interfaces[0].TxBytes, *actual.TxBytes)
|
||||
assert.Equal(expected.Interfaces[0].TxErrors, *actual.TxErrors)
|
||||
}
|
295
vendor/k8s.io/kubernetes/pkg/kubelet/stats/helper.go
generated
vendored
Normal file
295
vendor/k8s.io/kubernetes/pkg/kubelet/stats/helper.go
generated
vendored
Normal file
@ -0,0 +1,295 @@
|
||||
/*
|
||||
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 (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
cadvisorapiv1 "github.com/google/cadvisor/info/v1"
|
||||
cadvisorapiv2 "github.com/google/cadvisor/info/v2"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
statsapi "k8s.io/kubernetes/pkg/kubelet/apis/stats/v1alpha1"
|
||||
"k8s.io/kubernetes/pkg/kubelet/cadvisor"
|
||||
"k8s.io/kubernetes/pkg/kubelet/network"
|
||||
)
|
||||
|
||||
func cadvisorInfoToCPUandMemoryStats(info *cadvisorapiv2.ContainerInfo) (*statsapi.CPUStats, *statsapi.MemoryStats) {
|
||||
cstat, found := latestContainerStats(info)
|
||||
if !found {
|
||||
return nil, nil
|
||||
}
|
||||
var cpuStats *statsapi.CPUStats
|
||||
var memoryStats *statsapi.MemoryStats
|
||||
if info.Spec.HasCpu {
|
||||
cpuStats = &statsapi.CPUStats{
|
||||
Time: metav1.NewTime(cstat.Timestamp),
|
||||
}
|
||||
if cstat.CpuInst != nil {
|
||||
cpuStats.UsageNanoCores = &cstat.CpuInst.Usage.Total
|
||||
}
|
||||
if cstat.Cpu != nil {
|
||||
cpuStats.UsageCoreNanoSeconds = &cstat.Cpu.Usage.Total
|
||||
}
|
||||
}
|
||||
if info.Spec.HasMemory {
|
||||
pageFaults := cstat.Memory.ContainerData.Pgfault
|
||||
majorPageFaults := cstat.Memory.ContainerData.Pgmajfault
|
||||
memoryStats = &statsapi.MemoryStats{
|
||||
Time: metav1.NewTime(cstat.Timestamp),
|
||||
UsageBytes: &cstat.Memory.Usage,
|
||||
WorkingSetBytes: &cstat.Memory.WorkingSet,
|
||||
RSSBytes: &cstat.Memory.RSS,
|
||||
PageFaults: &pageFaults,
|
||||
MajorPageFaults: &majorPageFaults,
|
||||
}
|
||||
// availableBytes = memory limit (if known) - workingset
|
||||
if !isMemoryUnlimited(info.Spec.Memory.Limit) {
|
||||
availableBytes := info.Spec.Memory.Limit - cstat.Memory.WorkingSet
|
||||
memoryStats.AvailableBytes = &availableBytes
|
||||
}
|
||||
}
|
||||
return cpuStats, memoryStats
|
||||
}
|
||||
|
||||
// cadvisorInfoToContainerStats returns the statsapi.ContainerStats converted
|
||||
// from the container and filesystem info.
|
||||
func cadvisorInfoToContainerStats(name string, info *cadvisorapiv2.ContainerInfo, rootFs, imageFs *cadvisorapiv2.FsInfo) *statsapi.ContainerStats {
|
||||
result := &statsapi.ContainerStats{
|
||||
StartTime: metav1.NewTime(info.Spec.CreationTime),
|
||||
Name: name,
|
||||
}
|
||||
cstat, found := latestContainerStats(info)
|
||||
if !found {
|
||||
return result
|
||||
}
|
||||
|
||||
cpu, memory := cadvisorInfoToCPUandMemoryStats(info)
|
||||
result.CPU = cpu
|
||||
result.Memory = memory
|
||||
|
||||
if rootFs != nil {
|
||||
// The container logs live on the node rootfs device
|
||||
result.Logs = buildLogsStats(cstat, rootFs)
|
||||
}
|
||||
|
||||
if imageFs != nil {
|
||||
// The container rootFs lives on the imageFs devices (which may not be the node root fs)
|
||||
result.Rootfs = buildRootfsStats(cstat, imageFs)
|
||||
}
|
||||
|
||||
cfs := cstat.Filesystem
|
||||
if cfs != nil {
|
||||
if cfs.BaseUsageBytes != nil {
|
||||
if result.Rootfs != nil {
|
||||
rootfsUsage := *cfs.BaseUsageBytes
|
||||
result.Rootfs.UsedBytes = &rootfsUsage
|
||||
}
|
||||
if cfs.TotalUsageBytes != nil && result.Logs != nil {
|
||||
logsUsage := *cfs.TotalUsageBytes - *cfs.BaseUsageBytes
|
||||
result.Logs.UsedBytes = &logsUsage
|
||||
}
|
||||
}
|
||||
if cfs.InodeUsage != nil && result.Rootfs != nil {
|
||||
rootInodes := *cfs.InodeUsage
|
||||
result.Rootfs.InodesUsed = &rootInodes
|
||||
}
|
||||
}
|
||||
|
||||
for _, acc := range cstat.Accelerators {
|
||||
result.Accelerators = append(result.Accelerators, statsapi.AcceleratorStats{
|
||||
Make: acc.Make,
|
||||
Model: acc.Model,
|
||||
ID: acc.ID,
|
||||
MemoryTotal: acc.MemoryTotal,
|
||||
MemoryUsed: acc.MemoryUsed,
|
||||
DutyCycle: acc.DutyCycle,
|
||||
})
|
||||
}
|
||||
|
||||
result.UserDefinedMetrics = cadvisorInfoToUserDefinedMetrics(info)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// cadvisorInfoToNetworkStats returns the statsapi.NetworkStats converted from
|
||||
// the container info from cadvisor.
|
||||
func cadvisorInfoToNetworkStats(name string, info *cadvisorapiv2.ContainerInfo) *statsapi.NetworkStats {
|
||||
if !info.Spec.HasNetwork {
|
||||
return nil
|
||||
}
|
||||
cstat, found := latestContainerStats(info)
|
||||
if !found {
|
||||
return nil
|
||||
}
|
||||
|
||||
iStats := statsapi.NetworkStats{
|
||||
Time: metav1.NewTime(cstat.Timestamp),
|
||||
}
|
||||
|
||||
for i := range cstat.Network.Interfaces {
|
||||
inter := cstat.Network.Interfaces[i]
|
||||
iStat := statsapi.InterfaceStats{
|
||||
Name: inter.Name,
|
||||
RxBytes: &inter.RxBytes,
|
||||
RxErrors: &inter.RxErrors,
|
||||
TxBytes: &inter.TxBytes,
|
||||
TxErrors: &inter.TxErrors,
|
||||
}
|
||||
|
||||
if inter.Name == network.DefaultInterfaceName {
|
||||
iStats.InterfaceStats = iStat
|
||||
}
|
||||
|
||||
iStats.Interfaces = append(iStats.Interfaces, iStat)
|
||||
}
|
||||
|
||||
return &iStats
|
||||
}
|
||||
|
||||
// cadvisorInfoToUserDefinedMetrics returns the statsapi.UserDefinedMetric
|
||||
// converted from the container info from cadvisor.
|
||||
func cadvisorInfoToUserDefinedMetrics(info *cadvisorapiv2.ContainerInfo) []statsapi.UserDefinedMetric {
|
||||
type specVal struct {
|
||||
ref statsapi.UserDefinedMetricDescriptor
|
||||
valType cadvisorapiv1.DataType
|
||||
time time.Time
|
||||
value float64
|
||||
}
|
||||
udmMap := map[string]*specVal{}
|
||||
for _, spec := range info.Spec.CustomMetrics {
|
||||
udmMap[spec.Name] = &specVal{
|
||||
ref: statsapi.UserDefinedMetricDescriptor{
|
||||
Name: spec.Name,
|
||||
Type: statsapi.UserDefinedMetricType(spec.Type),
|
||||
Units: spec.Units,
|
||||
},
|
||||
valType: spec.Format,
|
||||
}
|
||||
}
|
||||
for _, stat := range info.Stats {
|
||||
for name, values := range stat.CustomMetrics {
|
||||
specVal, ok := udmMap[name]
|
||||
if !ok {
|
||||
glog.Warningf("spec for custom metric %q is missing from cAdvisor output. Spec: %+v, Metrics: %+v", name, info.Spec, stat.CustomMetrics)
|
||||
continue
|
||||
}
|
||||
for _, value := range values {
|
||||
// Pick the most recent value
|
||||
if value.Timestamp.Before(specVal.time) {
|
||||
continue
|
||||
}
|
||||
specVal.time = value.Timestamp
|
||||
specVal.value = value.FloatValue
|
||||
if specVal.valType == cadvisorapiv1.IntType {
|
||||
specVal.value = float64(value.IntValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var udm []statsapi.UserDefinedMetric
|
||||
for _, specVal := range udmMap {
|
||||
udm = append(udm, statsapi.UserDefinedMetric{
|
||||
UserDefinedMetricDescriptor: specVal.ref,
|
||||
Time: metav1.NewTime(specVal.time),
|
||||
Value: specVal.value,
|
||||
})
|
||||
}
|
||||
return udm
|
||||
}
|
||||
|
||||
// latestContainerStats returns the latest container stats from cadvisor, or nil if none exist
|
||||
func latestContainerStats(info *cadvisorapiv2.ContainerInfo) (*cadvisorapiv2.ContainerStats, bool) {
|
||||
stats := info.Stats
|
||||
if len(stats) < 1 {
|
||||
return nil, false
|
||||
}
|
||||
latest := stats[len(stats)-1]
|
||||
if latest == nil {
|
||||
return nil, false
|
||||
}
|
||||
return latest, true
|
||||
}
|
||||
|
||||
func isMemoryUnlimited(v uint64) bool {
|
||||
// Size after which we consider memory to be "unlimited". This is not
|
||||
// MaxInt64 due to rounding by the kernel.
|
||||
// TODO: cadvisor should export this https://github.com/google/cadvisor/blob/master/metrics/prometheus.go#L596
|
||||
const maxMemorySize = uint64(1 << 62)
|
||||
|
||||
return v > maxMemorySize
|
||||
}
|
||||
|
||||
// getCgroupInfo returns the information of the container with the specified
|
||||
// containerName from cadvisor.
|
||||
func getCgroupInfo(cadvisor cadvisor.Interface, containerName string) (*cadvisorapiv2.ContainerInfo, error) {
|
||||
infoMap, err := cadvisor.ContainerInfoV2(containerName, cadvisorapiv2.RequestOptions{
|
||||
IdType: cadvisorapiv2.TypeName,
|
||||
Count: 2, // 2 samples are needed to compute "instantaneous" CPU
|
||||
Recursive: false,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get container info for %q: %v", containerName, err)
|
||||
}
|
||||
if len(infoMap) != 1 {
|
||||
return nil, fmt.Errorf("unexpected number of containers: %v", len(infoMap))
|
||||
}
|
||||
info := infoMap[containerName]
|
||||
return &info, nil
|
||||
}
|
||||
|
||||
// getCgroupStats returns the latest stats of the container having the
|
||||
// specified containerName from cadvisor.
|
||||
func getCgroupStats(cadvisor cadvisor.Interface, containerName string) (*cadvisorapiv2.ContainerStats, error) {
|
||||
info, err := getCgroupInfo(cadvisor, containerName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stats, found := latestContainerStats(info)
|
||||
if !found {
|
||||
return nil, fmt.Errorf("failed to get latest stats from container info for %q", containerName)
|
||||
}
|
||||
return stats, nil
|
||||
}
|
||||
|
||||
func buildLogsStats(cstat *cadvisorapiv2.ContainerStats, rootFs *cadvisorapiv2.FsInfo) *statsapi.FsStats {
|
||||
fsStats := &statsapi.FsStats{
|
||||
Time: metav1.NewTime(cstat.Timestamp),
|
||||
AvailableBytes: &rootFs.Available,
|
||||
CapacityBytes: &rootFs.Capacity,
|
||||
InodesFree: rootFs.InodesFree,
|
||||
Inodes: rootFs.Inodes,
|
||||
}
|
||||
|
||||
if rootFs.Inodes != nil && rootFs.InodesFree != nil {
|
||||
logsInodesUsed := *rootFs.Inodes - *rootFs.InodesFree
|
||||
fsStats.InodesUsed = &logsInodesUsed
|
||||
}
|
||||
return fsStats
|
||||
}
|
||||
|
||||
func buildRootfsStats(cstat *cadvisorapiv2.ContainerStats, imageFs *cadvisorapiv2.FsInfo) *statsapi.FsStats {
|
||||
return &statsapi.FsStats{
|
||||
Time: metav1.NewTime(cstat.Timestamp),
|
||||
AvailableBytes: &imageFs.Available,
|
||||
CapacityBytes: &imageFs.Capacity,
|
||||
InodesFree: imageFs.InodesFree,
|
||||
Inodes: imageFs.Inodes,
|
||||
}
|
||||
}
|
99
vendor/k8s.io/kubernetes/pkg/kubelet/stats/helper_test.go
generated
vendored
Normal file
99
vendor/k8s.io/kubernetes/pkg/kubelet/stats/helper_test.go
generated
vendored
Normal file
@ -0,0 +1,99 @@
|
||||
/*
|
||||
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 (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
cadvisorapiv1 "github.com/google/cadvisor/info/v1"
|
||||
cadvisorapiv2 "github.com/google/cadvisor/info/v2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
statsapi "k8s.io/kubernetes/pkg/kubelet/apis/stats/v1alpha1"
|
||||
)
|
||||
|
||||
func TestCustomMetrics(t *testing.T) {
|
||||
spec := []cadvisorapiv1.MetricSpec{
|
||||
{
|
||||
Name: "qos",
|
||||
Type: cadvisorapiv1.MetricGauge,
|
||||
Format: cadvisorapiv1.IntType,
|
||||
Units: "per second",
|
||||
},
|
||||
{
|
||||
Name: "cpuLoad",
|
||||
Type: cadvisorapiv1.MetricCumulative,
|
||||
Format: cadvisorapiv1.FloatType,
|
||||
Units: "count",
|
||||
},
|
||||
}
|
||||
timestamp1 := time.Now()
|
||||
timestamp2 := time.Now().Add(time.Minute)
|
||||
metrics := map[string][]cadvisorapiv1.MetricVal{
|
||||
"qos": {
|
||||
{
|
||||
Timestamp: timestamp1,
|
||||
IntValue: 10,
|
||||
},
|
||||
{
|
||||
Timestamp: timestamp2,
|
||||
IntValue: 100,
|
||||
},
|
||||
},
|
||||
"cpuLoad": {
|
||||
{
|
||||
Timestamp: timestamp1,
|
||||
FloatValue: 1.2,
|
||||
},
|
||||
{
|
||||
Timestamp: timestamp2,
|
||||
FloatValue: 2.1,
|
||||
},
|
||||
},
|
||||
}
|
||||
cInfo := cadvisorapiv2.ContainerInfo{
|
||||
Spec: cadvisorapiv2.ContainerSpec{
|
||||
CustomMetrics: spec,
|
||||
},
|
||||
Stats: []*cadvisorapiv2.ContainerStats{
|
||||
{
|
||||
CustomMetrics: metrics,
|
||||
},
|
||||
},
|
||||
}
|
||||
assert.Contains(t, cadvisorInfoToUserDefinedMetrics(&cInfo),
|
||||
statsapi.UserDefinedMetric{
|
||||
UserDefinedMetricDescriptor: statsapi.UserDefinedMetricDescriptor{
|
||||
Name: "qos",
|
||||
Type: statsapi.MetricGauge,
|
||||
Units: "per second",
|
||||
},
|
||||
Time: metav1.NewTime(timestamp2),
|
||||
Value: 100,
|
||||
},
|
||||
statsapi.UserDefinedMetric{
|
||||
UserDefinedMetricDescriptor: statsapi.UserDefinedMetricDescriptor{
|
||||
Name: "cpuLoad",
|
||||
Type: statsapi.MetricCumulative,
|
||||
Units: "count",
|
||||
},
|
||||
Time: metav1.NewTime(timestamp2),
|
||||
Value: 2.1,
|
||||
})
|
||||
}
|
169
vendor/k8s.io/kubernetes/pkg/kubelet/stats/stats_provider.go
generated
vendored
Normal file
169
vendor/k8s.io/kubernetes/pkg/kubelet/stats/stats_provider.go
generated
vendored
Normal file
@ -0,0 +1,169 @@
|
||||
/*
|
||||
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 (
|
||||
"fmt"
|
||||
|
||||
cadvisorapiv1 "github.com/google/cadvisor/info/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
internalapi "k8s.io/kubernetes/pkg/kubelet/apis/cri"
|
||||
statsapi "k8s.io/kubernetes/pkg/kubelet/apis/stats/v1alpha1"
|
||||
"k8s.io/kubernetes/pkg/kubelet/cadvisor"
|
||||
kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
|
||||
kubepod "k8s.io/kubernetes/pkg/kubelet/pod"
|
||||
"k8s.io/kubernetes/pkg/kubelet/server/stats"
|
||||
)
|
||||
|
||||
// NewCRIStatsProvider returns a StatsProvider that provides the node stats
|
||||
// from cAdvisor and the container stats from CRI.
|
||||
func NewCRIStatsProvider(
|
||||
cadvisor cadvisor.Interface,
|
||||
resourceAnalyzer stats.ResourceAnalyzer,
|
||||
podManager kubepod.Manager,
|
||||
runtimeCache kubecontainer.RuntimeCache,
|
||||
runtimeService internalapi.RuntimeService,
|
||||
imageService internalapi.ImageManagerService,
|
||||
) *StatsProvider {
|
||||
return newStatsProvider(cadvisor, podManager, runtimeCache, newCRIStatsProvider(cadvisor, resourceAnalyzer, runtimeService, imageService))
|
||||
}
|
||||
|
||||
// NewCadvisorStatsProvider returns a containerStatsProvider that provides both
|
||||
// the node and the container stats from cAdvisor.
|
||||
func NewCadvisorStatsProvider(
|
||||
cadvisor cadvisor.Interface,
|
||||
resourceAnalyzer stats.ResourceAnalyzer,
|
||||
podManager kubepod.Manager,
|
||||
runtimeCache kubecontainer.RuntimeCache,
|
||||
imageService kubecontainer.ImageService,
|
||||
) *StatsProvider {
|
||||
return newStatsProvider(cadvisor, podManager, runtimeCache, newCadvisorStatsProvider(cadvisor, resourceAnalyzer, imageService))
|
||||
}
|
||||
|
||||
// newStatsProvider returns a new StatsProvider that provides node stats from
|
||||
// cAdvisor and the container stats using the containerStatsProvider.
|
||||
func newStatsProvider(
|
||||
cadvisor cadvisor.Interface,
|
||||
podManager kubepod.Manager,
|
||||
runtimeCache kubecontainer.RuntimeCache,
|
||||
containerStatsProvider containerStatsProvider,
|
||||
) *StatsProvider {
|
||||
return &StatsProvider{
|
||||
cadvisor: cadvisor,
|
||||
podManager: podManager,
|
||||
runtimeCache: runtimeCache,
|
||||
containerStatsProvider: containerStatsProvider,
|
||||
}
|
||||
}
|
||||
|
||||
// StatsProvider provides the stats of the node and the pod-managed containers.
|
||||
type StatsProvider struct {
|
||||
cadvisor cadvisor.Interface
|
||||
podManager kubepod.Manager
|
||||
runtimeCache kubecontainer.RuntimeCache
|
||||
containerStatsProvider
|
||||
}
|
||||
|
||||
// containerStatsProvider is an interface that provides the stats of the
|
||||
// containers managed by pods.
|
||||
type containerStatsProvider interface {
|
||||
ListPodStats() ([]statsapi.PodStats, error)
|
||||
ImageFsStats() (*statsapi.FsStats, error)
|
||||
}
|
||||
|
||||
// GetCgroupStats returns the stats of the cgroup with the cgroupName. Note that
|
||||
// this function doesn't generate filesystem stats.
|
||||
func (p *StatsProvider) GetCgroupStats(cgroupName string) (*statsapi.ContainerStats, *statsapi.NetworkStats, error) {
|
||||
info, err := getCgroupInfo(p.cadvisor, cgroupName)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to get cgroup stats for %q: %v", cgroupName, err)
|
||||
}
|
||||
// Rootfs and imagefs doesn't make sense for raw cgroup.
|
||||
s := cadvisorInfoToContainerStats(cgroupName, info, nil, nil)
|
||||
n := cadvisorInfoToNetworkStats(cgroupName, info)
|
||||
return s, n, nil
|
||||
}
|
||||
|
||||
// RootFsStats returns the stats of the node root filesystem.
|
||||
func (p *StatsProvider) RootFsStats() (*statsapi.FsStats, error) {
|
||||
rootFsInfo, err := p.cadvisor.RootFsInfo()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get rootFs info: %v", err)
|
||||
}
|
||||
|
||||
var nodeFsInodesUsed *uint64
|
||||
if rootFsInfo.Inodes != nil && rootFsInfo.InodesFree != nil {
|
||||
nodeFsIU := *rootFsInfo.Inodes - *rootFsInfo.InodesFree
|
||||
nodeFsInodesUsed = &nodeFsIU
|
||||
}
|
||||
|
||||
// Get the root container stats's timestamp, which will be used as the
|
||||
// imageFs stats timestamp.
|
||||
rootStats, err := getCgroupStats(p.cadvisor, "/")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get root container stats: %v", err)
|
||||
}
|
||||
|
||||
return &statsapi.FsStats{
|
||||
Time: metav1.NewTime(rootStats.Timestamp),
|
||||
AvailableBytes: &rootFsInfo.Available,
|
||||
CapacityBytes: &rootFsInfo.Capacity,
|
||||
UsedBytes: &rootFsInfo.Usage,
|
||||
InodesFree: rootFsInfo.InodesFree,
|
||||
Inodes: rootFsInfo.Inodes,
|
||||
InodesUsed: nodeFsInodesUsed,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetContainerInfo returns stats (from cAdvisor) for a container.
|
||||
func (p *StatsProvider) GetContainerInfo(podFullName string, podUID types.UID, containerName string, req *cadvisorapiv1.ContainerInfoRequest) (*cadvisorapiv1.ContainerInfo, error) {
|
||||
// Resolve and type convert back again.
|
||||
// We need the static pod UID but the kubecontainer API works with types.UID.
|
||||
podUID = types.UID(p.podManager.TranslatePodUID(podUID))
|
||||
|
||||
pods, err := p.runtimeCache.GetPods()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pod := kubecontainer.Pods(pods).FindPod(podFullName, podUID)
|
||||
container := pod.FindContainerByName(containerName)
|
||||
if container == nil {
|
||||
return nil, kubecontainer.ErrContainerNotFound
|
||||
}
|
||||
|
||||
ci, err := p.cadvisor.DockerContainer(container.ID.ID, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ci, nil
|
||||
}
|
||||
|
||||
// GetRawContainerInfo returns the stats (from cadvisor) for a non-Kubernetes
|
||||
// container.
|
||||
func (p *StatsProvider) GetRawContainerInfo(containerName string, req *cadvisorapiv1.ContainerInfoRequest, subcontainers bool) (map[string]*cadvisorapiv1.ContainerInfo, error) {
|
||||
if subcontainers {
|
||||
return p.cadvisor.SubcontainerInfo(containerName, req)
|
||||
}
|
||||
containerInfo, err := p.cadvisor.ContainerInfo(containerName, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return map[string]*cadvisorapiv1.ContainerInfo{
|
||||
containerInfo.Name: containerInfo,
|
||||
}, nil
|
||||
}
|
616
vendor/k8s.io/kubernetes/pkg/kubelet/stats/stats_provider_test.go
generated
vendored
Normal file
616
vendor/k8s.io/kubernetes/pkg/kubelet/stats/stats_provider_test.go
generated
vendored
Normal file
@ -0,0 +1,616 @@
|
||||
/*
|
||||
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 (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
cadvisorapiv1 "github.com/google/cadvisor/info/v1"
|
||||
cadvisorapiv2 "github.com/google/cadvisor/info/v2"
|
||||
fuzz "github.com/google/gofuzz"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
statsapi "k8s.io/kubernetes/pkg/kubelet/apis/stats/v1alpha1"
|
||||
cadvisortest "k8s.io/kubernetes/pkg/kubelet/cadvisor/testing"
|
||||
kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
|
||||
kubecontainertest "k8s.io/kubernetes/pkg/kubelet/container/testing"
|
||||
kubepodtest "k8s.io/kubernetes/pkg/kubelet/pod/testing"
|
||||
serverstats "k8s.io/kubernetes/pkg/kubelet/server/stats"
|
||||
kubetypes "k8s.io/kubernetes/pkg/kubelet/types"
|
||||
)
|
||||
|
||||
const (
|
||||
// Offsets from seed value in generated container stats.
|
||||
offsetCPUUsageCores = iota
|
||||
offsetCPUUsageCoreSeconds
|
||||
offsetMemPageFaults
|
||||
offsetMemMajorPageFaults
|
||||
offsetMemUsageBytes
|
||||
offsetMemRSSBytes
|
||||
offsetMemWorkingSetBytes
|
||||
offsetNetRxBytes
|
||||
offsetNetRxErrors
|
||||
offsetNetTxBytes
|
||||
offsetNetTxErrors
|
||||
offsetFsCapacity
|
||||
offsetFsAvailable
|
||||
offsetFsUsage
|
||||
offsetFsInodes
|
||||
offsetFsInodesFree
|
||||
offsetFsTotalUsageBytes
|
||||
offsetFsBaseUsageBytes
|
||||
offsetFsInodeUsage
|
||||
offsetVolume
|
||||
)
|
||||
|
||||
var (
|
||||
timestamp = time.Now()
|
||||
creationTime = timestamp.Add(-5 * time.Minute)
|
||||
)
|
||||
|
||||
func TestGetCgroupStats(t *testing.T) {
|
||||
const (
|
||||
cgroupName = "test-cgroup-name"
|
||||
containerInfoSeed = 1000
|
||||
)
|
||||
var (
|
||||
mockCadvisor = new(cadvisortest.Mock)
|
||||
mockPodManager = new(kubepodtest.MockManager)
|
||||
mockRuntimeCache = new(kubecontainertest.MockRuntimeCache)
|
||||
|
||||
assert = assert.New(t)
|
||||
options = cadvisorapiv2.RequestOptions{IdType: cadvisorapiv2.TypeName, Count: 2, Recursive: false}
|
||||
|
||||
containerInfo = getTestContainerInfo(containerInfoSeed, "test-pod", "test-ns", "test-container")
|
||||
containerInfoMap = map[string]cadvisorapiv2.ContainerInfo{cgroupName: containerInfo}
|
||||
)
|
||||
|
||||
mockCadvisor.On("ContainerInfoV2", cgroupName, options).Return(containerInfoMap, nil)
|
||||
|
||||
provider := newStatsProvider(mockCadvisor, mockPodManager, mockRuntimeCache, fakeContainerStatsProvider{})
|
||||
cs, ns, err := provider.GetCgroupStats(cgroupName)
|
||||
assert.NoError(err)
|
||||
|
||||
checkCPUStats(t, "", containerInfoSeed, cs.CPU)
|
||||
checkMemoryStats(t, "", containerInfoSeed, containerInfo, cs.Memory)
|
||||
checkNetworkStats(t, "", containerInfoSeed, ns)
|
||||
|
||||
assert.Equal(cgroupName, cs.Name)
|
||||
assert.Equal(metav1.NewTime(containerInfo.Spec.CreationTime), cs.StartTime)
|
||||
|
||||
mockCadvisor.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func TestRootFsStats(t *testing.T) {
|
||||
const (
|
||||
rootFsInfoSeed = 1000
|
||||
containerInfoSeed = 2000
|
||||
)
|
||||
var (
|
||||
mockCadvisor = new(cadvisortest.Mock)
|
||||
mockPodManager = new(kubepodtest.MockManager)
|
||||
mockRuntimeCache = new(kubecontainertest.MockRuntimeCache)
|
||||
|
||||
assert = assert.New(t)
|
||||
options = cadvisorapiv2.RequestOptions{IdType: cadvisorapiv2.TypeName, Count: 2, Recursive: false}
|
||||
|
||||
rootFsInfo = getTestFsInfo(rootFsInfoSeed)
|
||||
containerInfo = getTestContainerInfo(containerInfoSeed, "test-pod", "test-ns", "test-container")
|
||||
containerInfoMap = map[string]cadvisorapiv2.ContainerInfo{"/": containerInfo}
|
||||
)
|
||||
|
||||
mockCadvisor.
|
||||
On("RootFsInfo").Return(rootFsInfo, nil).
|
||||
On("ContainerInfoV2", "/", options).Return(containerInfoMap, nil)
|
||||
|
||||
provider := newStatsProvider(mockCadvisor, mockPodManager, mockRuntimeCache, fakeContainerStatsProvider{})
|
||||
stats, err := provider.RootFsStats()
|
||||
assert.NoError(err)
|
||||
|
||||
checkFsStats(t, "", rootFsInfoSeed, stats)
|
||||
|
||||
assert.Equal(metav1.NewTime(containerInfo.Stats[0].Timestamp), stats.Time)
|
||||
assert.Equal(rootFsInfo.Usage, *stats.UsedBytes)
|
||||
assert.Equal(*rootFsInfo.Inodes-*rootFsInfo.InodesFree, *stats.InodesUsed)
|
||||
|
||||
mockCadvisor.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func TestGetContainerInfo(t *testing.T) {
|
||||
cadvisorAPIFailure := fmt.Errorf("cAdvisor failure")
|
||||
runtimeError := fmt.Errorf("List containers error")
|
||||
tests := []struct {
|
||||
name string
|
||||
containerID string
|
||||
containerPath string
|
||||
cadvisorContainerInfo cadvisorapiv1.ContainerInfo
|
||||
runtimeError error
|
||||
podList []*kubecontainer.Pod
|
||||
requestedPodFullName string
|
||||
requestedPodUID types.UID
|
||||
requestedContainerName string
|
||||
expectDockerContainerCall bool
|
||||
mockError error
|
||||
expectedError error
|
||||
expectStats bool
|
||||
}{
|
||||
{
|
||||
name: "get container info",
|
||||
containerID: "ab2cdf",
|
||||
containerPath: "/docker/ab2cdf",
|
||||
cadvisorContainerInfo: cadvisorapiv1.ContainerInfo{
|
||||
ContainerReference: cadvisorapiv1.ContainerReference{
|
||||
Name: "/docker/ab2cdf",
|
||||
},
|
||||
},
|
||||
runtimeError: nil,
|
||||
podList: []*kubecontainer.Pod{
|
||||
{
|
||||
ID: "12345678",
|
||||
Name: "qux",
|
||||
Namespace: "ns",
|
||||
Containers: []*kubecontainer.Container{
|
||||
{
|
||||
Name: "foo",
|
||||
ID: kubecontainer.ContainerID{Type: "test", ID: "ab2cdf"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
requestedPodFullName: "qux_ns",
|
||||
requestedPodUID: "",
|
||||
requestedContainerName: "foo",
|
||||
expectDockerContainerCall: true,
|
||||
mockError: nil,
|
||||
expectedError: nil,
|
||||
expectStats: true,
|
||||
},
|
||||
{
|
||||
name: "get container info when cadvisor failed",
|
||||
containerID: "ab2cdf",
|
||||
containerPath: "/docker/ab2cdf",
|
||||
cadvisorContainerInfo: cadvisorapiv1.ContainerInfo{},
|
||||
runtimeError: nil,
|
||||
podList: []*kubecontainer.Pod{
|
||||
{
|
||||
ID: "uuid",
|
||||
Name: "qux",
|
||||
Namespace: "ns",
|
||||
Containers: []*kubecontainer.Container{
|
||||
{
|
||||
Name: "foo",
|
||||
ID: kubecontainer.ContainerID{Type: "test", ID: "ab2cdf"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
requestedPodFullName: "qux_ns",
|
||||
requestedPodUID: "uuid",
|
||||
requestedContainerName: "foo",
|
||||
expectDockerContainerCall: true,
|
||||
mockError: cadvisorAPIFailure,
|
||||
expectedError: cadvisorAPIFailure,
|
||||
expectStats: false,
|
||||
},
|
||||
{
|
||||
name: "get container info on non-existent container",
|
||||
containerID: "",
|
||||
containerPath: "",
|
||||
cadvisorContainerInfo: cadvisorapiv1.ContainerInfo{},
|
||||
runtimeError: nil,
|
||||
podList: []*kubecontainer.Pod{},
|
||||
requestedPodFullName: "qux",
|
||||
requestedPodUID: "",
|
||||
requestedContainerName: "foo",
|
||||
expectDockerContainerCall: false,
|
||||
mockError: nil,
|
||||
expectedError: kubecontainer.ErrContainerNotFound,
|
||||
expectStats: false,
|
||||
},
|
||||
{
|
||||
name: "get container info when container runtime failed",
|
||||
containerID: "",
|
||||
containerPath: "",
|
||||
cadvisorContainerInfo: cadvisorapiv1.ContainerInfo{},
|
||||
runtimeError: runtimeError,
|
||||
podList: []*kubecontainer.Pod{},
|
||||
requestedPodFullName: "qux",
|
||||
requestedPodUID: "",
|
||||
requestedContainerName: "foo",
|
||||
mockError: nil,
|
||||
expectedError: runtimeError,
|
||||
expectStats: false,
|
||||
},
|
||||
{
|
||||
name: "get container info with no containers",
|
||||
containerID: "",
|
||||
containerPath: "",
|
||||
cadvisorContainerInfo: cadvisorapiv1.ContainerInfo{},
|
||||
runtimeError: nil,
|
||||
podList: []*kubecontainer.Pod{},
|
||||
requestedPodFullName: "qux_ns",
|
||||
requestedPodUID: "",
|
||||
requestedContainerName: "foo",
|
||||
mockError: nil,
|
||||
expectedError: kubecontainer.ErrContainerNotFound,
|
||||
expectStats: false,
|
||||
},
|
||||
{
|
||||
name: "get container info with no matching containers",
|
||||
containerID: "",
|
||||
containerPath: "",
|
||||
cadvisorContainerInfo: cadvisorapiv1.ContainerInfo{},
|
||||
runtimeError: nil,
|
||||
podList: []*kubecontainer.Pod{
|
||||
{
|
||||
ID: "12345678",
|
||||
Name: "qux",
|
||||
Namespace: "ns",
|
||||
Containers: []*kubecontainer.Container{
|
||||
{
|
||||
Name: "bar",
|
||||
ID: kubecontainer.ContainerID{Type: "test", ID: "fakeID"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
requestedPodFullName: "qux_ns",
|
||||
requestedPodUID: "",
|
||||
requestedContainerName: "foo",
|
||||
mockError: nil,
|
||||
expectedError: kubecontainer.ErrContainerNotFound,
|
||||
expectStats: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
var (
|
||||
mockCadvisor = new(cadvisortest.Mock)
|
||||
mockPodManager = new(kubepodtest.MockManager)
|
||||
mockRuntimeCache = new(kubecontainertest.MockRuntimeCache)
|
||||
|
||||
cadvisorReq = &cadvisorapiv1.ContainerInfoRequest{}
|
||||
)
|
||||
|
||||
mockPodManager.On("TranslatePodUID", tc.requestedPodUID).Return(kubetypes.ResolvedPodUID(tc.requestedPodUID))
|
||||
mockRuntimeCache.On("GetPods").Return(tc.podList, tc.runtimeError)
|
||||
if tc.expectDockerContainerCall {
|
||||
mockCadvisor.On("DockerContainer", tc.containerID, cadvisorReq).Return(tc.cadvisorContainerInfo, tc.mockError)
|
||||
}
|
||||
|
||||
provider := newStatsProvider(mockCadvisor, mockPodManager, mockRuntimeCache, fakeContainerStatsProvider{})
|
||||
stats, err := provider.GetContainerInfo(tc.requestedPodFullName, tc.requestedPodUID, tc.requestedContainerName, cadvisorReq)
|
||||
assert.Equal(t, tc.expectedError, err)
|
||||
|
||||
if tc.expectStats {
|
||||
require.NotNil(t, stats)
|
||||
}
|
||||
mockCadvisor.AssertExpectations(t)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetRawContainerInfoRoot(t *testing.T) {
|
||||
var (
|
||||
mockCadvisor = new(cadvisortest.Mock)
|
||||
mockPodManager = new(kubepodtest.MockManager)
|
||||
mockRuntimeCache = new(kubecontainertest.MockRuntimeCache)
|
||||
|
||||
cadvisorReq = &cadvisorapiv1.ContainerInfoRequest{}
|
||||
containerPath = "/"
|
||||
containerInfo = &cadvisorapiv1.ContainerInfo{
|
||||
ContainerReference: cadvisorapiv1.ContainerReference{
|
||||
Name: containerPath,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
mockCadvisor.On("ContainerInfo", containerPath, cadvisorReq).Return(containerInfo, nil)
|
||||
|
||||
provider := newStatsProvider(mockCadvisor, mockPodManager, mockRuntimeCache, fakeContainerStatsProvider{})
|
||||
_, err := provider.GetRawContainerInfo(containerPath, cadvisorReq, false)
|
||||
assert.NoError(t, err)
|
||||
mockCadvisor.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func TestGetRawContainerInfoSubcontainers(t *testing.T) {
|
||||
var (
|
||||
mockCadvisor = new(cadvisortest.Mock)
|
||||
mockPodManager = new(kubepodtest.MockManager)
|
||||
mockRuntimeCache = new(kubecontainertest.MockRuntimeCache)
|
||||
|
||||
cadvisorReq = &cadvisorapiv1.ContainerInfoRequest{}
|
||||
containerPath = "/kubelet"
|
||||
containerInfo = map[string]*cadvisorapiv1.ContainerInfo{
|
||||
containerPath: {
|
||||
ContainerReference: cadvisorapiv1.ContainerReference{
|
||||
Name: containerPath,
|
||||
},
|
||||
},
|
||||
"/kubelet/sub": {
|
||||
ContainerReference: cadvisorapiv1.ContainerReference{
|
||||
Name: "/kubelet/sub",
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
mockCadvisor.On("SubcontainerInfo", containerPath, cadvisorReq).Return(containerInfo, nil)
|
||||
|
||||
provider := newStatsProvider(mockCadvisor, mockPodManager, mockRuntimeCache, fakeContainerStatsProvider{})
|
||||
result, err := provider.GetRawContainerInfo(containerPath, cadvisorReq, true)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, result, 2)
|
||||
mockCadvisor.AssertExpectations(t)
|
||||
}
|
||||
|
||||
func getTerminatedContainerInfo(seed int, podName string, podNamespace string, containerName string) cadvisorapiv2.ContainerInfo {
|
||||
cinfo := getTestContainerInfo(seed, podName, podNamespace, containerName)
|
||||
cinfo.Stats[0].Memory.RSS = 0
|
||||
cinfo.Stats[0].CpuInst.Usage.Total = 0
|
||||
return cinfo
|
||||
}
|
||||
|
||||
func getTestContainerInfo(seed int, podName string, podNamespace string, containerName string) cadvisorapiv2.ContainerInfo {
|
||||
labels := map[string]string{}
|
||||
if podName != "" {
|
||||
labels = map[string]string{
|
||||
"io.kubernetes.pod.name": podName,
|
||||
"io.kubernetes.pod.uid": "UID" + podName,
|
||||
"io.kubernetes.pod.namespace": podNamespace,
|
||||
"io.kubernetes.container.name": containerName,
|
||||
}
|
||||
}
|
||||
// by default, kernel will set memory.limit_in_bytes to 1 << 63 if not bounded
|
||||
unlimitedMemory := uint64(1 << 63)
|
||||
spec := cadvisorapiv2.ContainerSpec{
|
||||
CreationTime: testTime(creationTime, seed),
|
||||
HasCpu: true,
|
||||
HasMemory: true,
|
||||
HasNetwork: true,
|
||||
Labels: labels,
|
||||
Memory: cadvisorapiv2.MemorySpec{
|
||||
Limit: unlimitedMemory,
|
||||
},
|
||||
CustomMetrics: generateCustomMetricSpec(),
|
||||
}
|
||||
|
||||
totalUsageBytes := uint64(seed + offsetFsTotalUsageBytes)
|
||||
baseUsageBytes := uint64(seed + offsetFsBaseUsageBytes)
|
||||
inodeUsage := uint64(seed + offsetFsInodeUsage)
|
||||
|
||||
stats := cadvisorapiv2.ContainerStats{
|
||||
Timestamp: testTime(timestamp, seed),
|
||||
Cpu: &cadvisorapiv1.CpuStats{},
|
||||
CpuInst: &cadvisorapiv2.CpuInstStats{},
|
||||
Memory: &cadvisorapiv1.MemoryStats{
|
||||
Usage: uint64(seed + offsetMemUsageBytes),
|
||||
WorkingSet: uint64(seed + offsetMemWorkingSetBytes),
|
||||
RSS: uint64(seed + offsetMemRSSBytes),
|
||||
ContainerData: cadvisorapiv1.MemoryStatsMemoryData{
|
||||
Pgfault: uint64(seed + offsetMemPageFaults),
|
||||
Pgmajfault: uint64(seed + offsetMemMajorPageFaults),
|
||||
},
|
||||
},
|
||||
Network: &cadvisorapiv2.NetworkStats{
|
||||
Interfaces: []cadvisorapiv1.InterfaceStats{{
|
||||
Name: "eth0",
|
||||
RxBytes: uint64(seed + offsetNetRxBytes),
|
||||
RxErrors: uint64(seed + offsetNetRxErrors),
|
||||
TxBytes: uint64(seed + offsetNetTxBytes),
|
||||
TxErrors: uint64(seed + offsetNetTxErrors),
|
||||
}, {
|
||||
Name: "cbr0",
|
||||
RxBytes: 100,
|
||||
RxErrors: 100,
|
||||
TxBytes: 100,
|
||||
TxErrors: 100,
|
||||
}},
|
||||
},
|
||||
CustomMetrics: generateCustomMetrics(spec.CustomMetrics),
|
||||
Filesystem: &cadvisorapiv2.FilesystemStats{
|
||||
TotalUsageBytes: &totalUsageBytes,
|
||||
BaseUsageBytes: &baseUsageBytes,
|
||||
InodeUsage: &inodeUsage,
|
||||
},
|
||||
}
|
||||
stats.Cpu.Usage.Total = uint64(seed + offsetCPUUsageCoreSeconds)
|
||||
stats.CpuInst.Usage.Total = uint64(seed + offsetCPUUsageCores)
|
||||
return cadvisorapiv2.ContainerInfo{
|
||||
Spec: spec,
|
||||
Stats: []*cadvisorapiv2.ContainerStats{&stats},
|
||||
}
|
||||
}
|
||||
|
||||
func getTestFsInfo(seed int) cadvisorapiv2.FsInfo {
|
||||
var (
|
||||
inodes = uint64(seed + offsetFsInodes)
|
||||
inodesFree = uint64(seed + offsetFsInodesFree)
|
||||
)
|
||||
return cadvisorapiv2.FsInfo{
|
||||
Timestamp: time.Now(),
|
||||
Device: "test-device",
|
||||
Mountpoint: "test-mount-point",
|
||||
Capacity: uint64(seed + offsetFsCapacity),
|
||||
Available: uint64(seed + offsetFsAvailable),
|
||||
Usage: uint64(seed + offsetFsUsage),
|
||||
Inodes: &inodes,
|
||||
InodesFree: &inodesFree,
|
||||
}
|
||||
}
|
||||
|
||||
func getPodVolumeStats(seed int, volumeName string) statsapi.VolumeStats {
|
||||
availableBytes := uint64(seed + offsetFsAvailable)
|
||||
capacityBytes := uint64(seed + offsetFsCapacity)
|
||||
usedBytes := uint64(seed + offsetFsUsage)
|
||||
inodes := uint64(seed + offsetFsInodes)
|
||||
inodesFree := uint64(seed + offsetFsInodesFree)
|
||||
inodesUsed := uint64(seed + offsetFsInodeUsage)
|
||||
fsStats := statsapi.FsStats{
|
||||
Time: metav1.NewTime(time.Now()),
|
||||
AvailableBytes: &availableBytes,
|
||||
CapacityBytes: &capacityBytes,
|
||||
UsedBytes: &usedBytes,
|
||||
Inodes: &inodes,
|
||||
InodesFree: &inodesFree,
|
||||
InodesUsed: &inodesUsed,
|
||||
}
|
||||
return statsapi.VolumeStats{
|
||||
FsStats: fsStats,
|
||||
Name: volumeName,
|
||||
}
|
||||
}
|
||||
|
||||
func generateCustomMetricSpec() []cadvisorapiv1.MetricSpec {
|
||||
f := fuzz.New().NilChance(0).Funcs(
|
||||
func(e *cadvisorapiv1.MetricSpec, c fuzz.Continue) {
|
||||
c.Fuzz(&e.Name)
|
||||
switch c.Intn(3) {
|
||||
case 0:
|
||||
e.Type = cadvisorapiv1.MetricGauge
|
||||
case 1:
|
||||
e.Type = cadvisorapiv1.MetricCumulative
|
||||
case 2:
|
||||
e.Type = cadvisorapiv1.MetricDelta
|
||||
}
|
||||
switch c.Intn(2) {
|
||||
case 0:
|
||||
e.Format = cadvisorapiv1.IntType
|
||||
case 1:
|
||||
e.Format = cadvisorapiv1.FloatType
|
||||
}
|
||||
c.Fuzz(&e.Units)
|
||||
})
|
||||
var ret []cadvisorapiv1.MetricSpec
|
||||
f.Fuzz(&ret)
|
||||
return ret
|
||||
}
|
||||
|
||||
func generateCustomMetrics(spec []cadvisorapiv1.MetricSpec) map[string][]cadvisorapiv1.MetricVal {
|
||||
ret := map[string][]cadvisorapiv1.MetricVal{}
|
||||
for _, metricSpec := range spec {
|
||||
f := fuzz.New().NilChance(0).Funcs(
|
||||
func(e *cadvisorapiv1.MetricVal, c fuzz.Continue) {
|
||||
switch metricSpec.Format {
|
||||
case cadvisorapiv1.IntType:
|
||||
c.Fuzz(&e.IntValue)
|
||||
case cadvisorapiv1.FloatType:
|
||||
c.Fuzz(&e.FloatValue)
|
||||
}
|
||||
})
|
||||
|
||||
var metrics []cadvisorapiv1.MetricVal
|
||||
f.Fuzz(&metrics)
|
||||
ret[metricSpec.Name] = metrics
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func testTime(base time.Time, seed int) time.Time {
|
||||
return base.Add(time.Duration(seed) * time.Second)
|
||||
}
|
||||
|
||||
func checkNetworkStats(t *testing.T, label string, seed int, stats *statsapi.NetworkStats) {
|
||||
assert.NotNil(t, stats)
|
||||
assert.EqualValues(t, testTime(timestamp, seed).Unix(), stats.Time.Time.Unix(), label+".Net.Time")
|
||||
assert.EqualValues(t, "eth0", stats.Name, "default interface name is not eth0")
|
||||
assert.EqualValues(t, seed+offsetNetRxBytes, *stats.RxBytes, label+".Net.RxBytes")
|
||||
assert.EqualValues(t, seed+offsetNetRxErrors, *stats.RxErrors, label+".Net.RxErrors")
|
||||
assert.EqualValues(t, seed+offsetNetTxBytes, *stats.TxBytes, label+".Net.TxBytes")
|
||||
assert.EqualValues(t, seed+offsetNetTxErrors, *stats.TxErrors, label+".Net.TxErrors")
|
||||
|
||||
assert.EqualValues(t, 2, len(stats.Interfaces), "network interfaces should contain 2 elements")
|
||||
|
||||
assert.EqualValues(t, "eth0", stats.Interfaces[0].Name, "default interface name is ont eth0")
|
||||
assert.EqualValues(t, seed+offsetNetRxBytes, *stats.Interfaces[0].RxBytes, label+".Net.TxErrors")
|
||||
assert.EqualValues(t, seed+offsetNetRxErrors, *stats.Interfaces[0].RxErrors, label+".Net.TxErrors")
|
||||
assert.EqualValues(t, seed+offsetNetTxBytes, *stats.Interfaces[0].TxBytes, label+".Net.TxErrors")
|
||||
assert.EqualValues(t, seed+offsetNetTxErrors, *stats.Interfaces[0].TxErrors, label+".Net.TxErrors")
|
||||
|
||||
assert.EqualValues(t, "cbr0", stats.Interfaces[1].Name, "cbr0 interface name is ont cbr0")
|
||||
assert.EqualValues(t, 100, *stats.Interfaces[1].RxBytes, label+".Net.TxErrors")
|
||||
assert.EqualValues(t, 100, *stats.Interfaces[1].RxErrors, label+".Net.TxErrors")
|
||||
assert.EqualValues(t, 100, *stats.Interfaces[1].TxBytes, label+".Net.TxErrors")
|
||||
assert.EqualValues(t, 100, *stats.Interfaces[1].TxErrors, label+".Net.TxErrors")
|
||||
|
||||
}
|
||||
|
||||
func checkCPUStats(t *testing.T, label string, seed int, stats *statsapi.CPUStats) {
|
||||
assert.EqualValues(t, testTime(timestamp, seed).Unix(), stats.Time.Time.Unix(), label+".CPU.Time")
|
||||
assert.EqualValues(t, seed+offsetCPUUsageCores, *stats.UsageNanoCores, label+".CPU.UsageCores")
|
||||
assert.EqualValues(t, seed+offsetCPUUsageCoreSeconds, *stats.UsageCoreNanoSeconds, label+".CPU.UsageCoreSeconds")
|
||||
}
|
||||
|
||||
func checkMemoryStats(t *testing.T, label string, seed int, info cadvisorapiv2.ContainerInfo, stats *statsapi.MemoryStats) {
|
||||
assert.EqualValues(t, testTime(timestamp, seed).Unix(), stats.Time.Time.Unix(), label+".Mem.Time")
|
||||
assert.EqualValues(t, seed+offsetMemUsageBytes, *stats.UsageBytes, label+".Mem.UsageBytes")
|
||||
assert.EqualValues(t, seed+offsetMemWorkingSetBytes, *stats.WorkingSetBytes, label+".Mem.WorkingSetBytes")
|
||||
assert.EqualValues(t, seed+offsetMemRSSBytes, *stats.RSSBytes, label+".Mem.RSSBytes")
|
||||
assert.EqualValues(t, seed+offsetMemPageFaults, *stats.PageFaults, label+".Mem.PageFaults")
|
||||
assert.EqualValues(t, seed+offsetMemMajorPageFaults, *stats.MajorPageFaults, label+".Mem.MajorPageFaults")
|
||||
if !info.Spec.HasMemory || isMemoryUnlimited(info.Spec.Memory.Limit) {
|
||||
assert.Nil(t, stats.AvailableBytes, label+".Mem.AvailableBytes")
|
||||
} else {
|
||||
expected := info.Spec.Memory.Limit - *stats.WorkingSetBytes
|
||||
assert.EqualValues(t, expected, *stats.AvailableBytes, label+".Mem.AvailableBytes")
|
||||
}
|
||||
}
|
||||
|
||||
func checkFsStats(t *testing.T, label string, seed int, stats *statsapi.FsStats) {
|
||||
assert.EqualValues(t, seed+offsetFsCapacity, *stats.CapacityBytes, label+".CapacityBytes")
|
||||
assert.EqualValues(t, seed+offsetFsAvailable, *stats.AvailableBytes, label+".AvailableBytes")
|
||||
assert.EqualValues(t, seed+offsetFsInodes, *stats.Inodes, label+".Inodes")
|
||||
assert.EqualValues(t, seed+offsetFsInodesFree, *stats.InodesFree, label+".InodesFree")
|
||||
}
|
||||
|
||||
func checkEphemeralStats(t *testing.T, label string, containerSeeds []int, volumeSeeds []int, stats *statsapi.FsStats) {
|
||||
var usedBytes, inodeUsage int
|
||||
for _, cseed := range containerSeeds {
|
||||
usedBytes = usedBytes + cseed + offsetFsTotalUsageBytes
|
||||
inodeUsage += cseed + offsetFsInodeUsage
|
||||
}
|
||||
for _, vseed := range volumeSeeds {
|
||||
usedBytes = usedBytes + vseed + offsetFsUsage
|
||||
inodeUsage += vseed + offsetFsInodeUsage
|
||||
}
|
||||
assert.EqualValues(t, usedBytes, int(*stats.UsedBytes), label+".UsedBytes")
|
||||
assert.EqualValues(t, inodeUsage, int(*stats.InodesUsed), label+".InodesUsed")
|
||||
}
|
||||
|
||||
type fakeResourceAnalyzer struct {
|
||||
podVolumeStats serverstats.PodVolumeStats
|
||||
}
|
||||
|
||||
func (o *fakeResourceAnalyzer) Start() {}
|
||||
func (o *fakeResourceAnalyzer) Get() (*statsapi.Summary, error) { return nil, nil }
|
||||
func (o *fakeResourceAnalyzer) GetPodVolumeStats(uid types.UID) (serverstats.PodVolumeStats, bool) {
|
||||
return o.podVolumeStats, true
|
||||
}
|
||||
|
||||
type fakeContainerStatsProvider struct {
|
||||
}
|
||||
|
||||
func (p fakeContainerStatsProvider) ListPodStats() ([]statsapi.PodStats, error) {
|
||||
return nil, fmt.Errorf("not implemented")
|
||||
}
|
||||
func (p fakeContainerStatsProvider) ImageFsStats() (*statsapi.FsStats, error) {
|
||||
return nil, fmt.Errorf("not implemented")
|
||||
}
|
Reference in New Issue
Block a user