Replace godep with dep

This commit is contained in:
Manuel de Brito Fontes 2017-10-06 17:26:14 -03:00
parent 1e7489927c
commit bf5616c65b
14883 changed files with 3937406 additions and 361781 deletions

75
vendor/k8s.io/kubernetes/pkg/kubelet/stats/BUILD generated vendored Normal file
View file

@ -0,0 +1,75 @@
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",
],
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/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/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",
],
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",
],
)

View file

@ -0,0 +1,281 @@
/*
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"
"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"
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 := p.cadvisor.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)
}
}
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)
// 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)
if vstats, found := p.resourceAnalyzer.GetPodVolumeStats(podUID); found {
podStats.VolumeStats = vstats.Volumes
}
result = append(result, *podStats)
}
return result, nil
}
// 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(cinfo *cadvisorapiv2.ContainerInfo) statsapi.PodReference {
podName := kubetypes.GetPodName(cinfo.Spec.Labels)
podNamespace := kubetypes.GetPodNamespace(cinfo.Spec.Labels)
podUID := kubetypes.GetPodUID(cinfo.Spec.Labels)
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
}
// 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),
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
}

View file

@ -0,0 +1,265 @@
/*
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"
)
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
)
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),
}
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)
resourceAnalyzer := &fakeResourceAnalyzer{}
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)
// 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)
}

View file

@ -0,0 +1,281 @@
/*
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"
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"
)
// 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)
}
// Creates container map.
containerMap := make(map[string]*runtimeapi.Container)
containers, err := p.runtimeService.ListContainers(&runtimeapi.ContainerFilter{})
if err != nil {
return nil, fmt.Errorf("failed to list all containers: %v", err)
}
for _, c := range containers {
containerMap[c.Id] = c
}
// 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)
}
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 = p.makePodStats(podSandbox)
sandboxIDToPodStats[podSandboxID] = ps
}
ps.Containers = append(ps.Containers, *p.makeContainerStats(stats, container, &rootFsInfo, uuidToFsInfo))
}
result := make([]statsapi.PodStats, 0, len(sandboxIDToPodStats))
for _, s := range sandboxIDToPodStats {
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
}
func (p *criStatsProvider) makePodStats(podSandbox *runtimeapi.PodSandbox) *statsapi.PodStats {
s := &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)),
// Network stats are not supported by CRI.
}
podUID := types.UID(s.PodRef.UID)
if vstats, found := p.resourceAnalyzer.GetPodVolumeStats(podUID); found {
s.VolumeStats = vstats.Volumes
}
return s
}
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)),
CPU: &statsapi.CPUStats{},
Memory: &statsapi.MemoryStats{},
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
}

View file

@ -0,0 +1,274 @@
/*
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"
"github.com/stretchr/testify/assert"
cadvisorapiv2 "github.com/google/cadvisor/info/v2"
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"
kubepodtest "k8s.io/kubernetes/pkg/kubelet/pod/testing"
)
func TestCRIListPodStats(t *testing.T) {
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, "container0-name")
containerStats0 = makeFakeContainerStats(container0, imageFsStorageUUID)
container1 = makeFakeContainer(sandbox0, "container1-name")
containerStats1 = makeFakeContainerStats(container1, unknownStorageUUID)
sandbox1 = makeFakePodSandbox("sandbox1-name", "sandbox1-uid", "sandbox1-ns")
container2 = makeFakeContainer(sandbox1, "container2-name")
containerStats2 = makeFakeContainerStats(container2, 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("RootFsInfo").Return(rootFsInfo, nil).
On("GetFsInfoByFsUUID", imageFsStorageUUID).Return(imageFsInfo, nil).
On("GetFsInfoByFsUUID", unknownStorageUUID).Return(cadvisorapiv2.FsInfo{}, cadvisorfs.ErrNoSuchDevice)
fakeRuntimeService.SetFakeSandboxes([]*critest.FakePodSandbox{
sandbox0, sandbox1,
})
fakeRuntimeService.SetFakeContainers([]*critest.FakeContainer{
container0, container1, container2,
})
fakeRuntimeService.SetFakeContainerStats([]*runtimeapi.ContainerStats{
containerStats0, containerStats1, containerStats2,
})
provider := NewCRIStatsProvider(
mockCadvisor,
resourceAnalyzer,
mockPodManager,
mockRuntimeCache,
fakeRuntimeService,
fakeImageService)
stats, err := provider.ListPodStats()
assert := assert.New(t)
assert.NoError(err)
assert.Equal(2, 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))
containerStatsMap := make(map[string]statsapi.ContainerStats)
for _, s := range p0.Containers {
containerStatsMap[s.Name] = s
}
c1 := containerStatsMap["container0-name"]
assert.Equal(container0.CreatedAt, c1.StartTime.UnixNano())
checkCRICPUAndMemoryStats(assert, c1, containerStats0)
checkCRIRootfsStats(assert, c1, containerStats0, &imageFsInfo)
checkCRILogsStats(assert, c1, &rootFsInfo)
c2 := containerStatsMap["container1-name"]
assert.Equal(container1.CreatedAt, c2.StartTime.UnixNano())
checkCRICPUAndMemoryStats(assert, c2, containerStats1)
checkCRIRootfsStats(assert, c2, containerStats1, nil)
checkCRILogsStats(assert, c2, &rootFsInfo)
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))
c3 := p1.Containers[0]
assert.Equal("container2-name", c3.Name)
assert.Equal(container2.CreatedAt, c3.StartTime.UnixNano())
checkCRICPUAndMemoryStats(assert, c3, containerStats2)
checkCRIRootfsStats(assert, c3, containerStats2, &imageFsInfo)
checkCRILogsStats(assert, c3, &rootFsInfo)
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) *critest.FakeContainer {
sandboxID := sandbox.PodSandboxStatus.Id
c := &critest.FakeContainer{
SandboxID: sandboxID,
ContainerStatus: runtimeapi.ContainerStatus{
Metadata: &runtimeapi.ContainerMetadata{Name: name},
Image: &runtimeapi.ImageSpec{},
ImageRef: "fake-image-ref",
CreatedAt: time.Now().UnixNano(),
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 {
return &runtimeapi.ContainerStats{
Attributes: &runtimeapi.ContainerAttributes{
Id: container.ContainerStatus.Id,
Metadata: container.ContainerStatus.Metadata,
},
Cpu: &runtimeapi.CpuUsage{
Timestamp: time.Now().UnixNano(),
UsageCoreNanoSeconds: &runtimeapi.UInt64Value{Value: rand.Uint64()},
},
Memory: &runtimeapi.MemoryUsage{
Timestamp: time.Now().UnixNano(),
WorkingSetBytes: &runtimeapi.UInt64Value{Value: rand.Uint64()},
},
WritableLayer: &runtimeapi.FilesystemUsage{
Timestamp: time.Now().UnixNano(),
StorageId: &runtimeapi.StorageIdentifier{Uuid: imageFsUUID},
UsedBytes: &runtimeapi.UInt64Value{Value: rand.Uint64()},
InodesUsed: &runtimeapi.UInt64Value{Value: rand.Uint64()},
},
}
}
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 checkCRICPUAndMemoryStats(assert *assert.Assertions, actual statsapi.ContainerStats, cs *runtimeapi.ContainerStats) {
assert.Equal(cs.Cpu.Timestamp, actual.CPU.Time.UnixNano())
assert.Equal(cs.Cpu.UsageCoreNanoSeconds.Value, *actual.CPU.UsageCoreNanoSeconds)
assert.Nil(actual.CPU.UsageNanoCores)
assert.Equal(cs.Memory.Timestamp, actual.Memory.Time.UnixNano())
assert.Nil(actual.Memory.AvailableBytes)
assert.Nil(actual.Memory.UsageBytes)
assert.Equal(cs.Memory.WorkingSetBytes.Value, *actual.Memory.WorkingSetBytes)
assert.Nil(actual.Memory.RSSBytes)
assert.Nil(actual.Memory.PageFaults)
assert.Nil(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)
}

248
vendor/k8s.io/kubernetes/pkg/kubelet/stats/helper.go generated vendored Normal file
View file

@ -0,0 +1,248 @@
/*
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"
)
// 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
}
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
}
result.CPU = &cpuStats
}
if info.Spec.HasMemory {
pageFaults := cstat.Memory.ContainerData.Pgfault
majorPageFaults := cstat.Memory.ContainerData.Pgmajfault
result.Memory = &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
result.Memory.AvailableBytes = &availableBytes
}
}
// The container logs live on the node rootfs device
result.Logs = &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
result.Logs.InodesUsed = &logsInodesUsed
}
// The container rootFs lives on the imageFs devices (which may not be the node root fs)
result.Rootfs = &statsapi.FsStats{
Time: metav1.NewTime(cstat.Timestamp),
AvailableBytes: &imageFs.Available,
CapacityBytes: &imageFs.Capacity,
InodesFree: imageFs.InodesFree,
Inodes: imageFs.Inodes,
}
cfs := cstat.Filesystem
if cfs != nil {
if cfs.BaseUsageBytes != nil {
rootfsUsage := *cfs.BaseUsageBytes
result.Rootfs.UsedBytes = &rootfsUsage
if cfs.TotalUsageBytes != nil {
logsUsage := *cfs.TotalUsageBytes - *cfs.BaseUsageBytes
result.Logs.UsedBytes = &logsUsage
}
}
if cfs.InodeUsage != nil {
rootInodes := *cfs.InodeUsage
result.Rootfs.InodesUsed = &rootInodes
}
}
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
}
for _, inter := range cstat.Network.Interfaces {
if inter.Name == network.DefaultInterfaceName {
return &statsapi.NetworkStats{
Time: metav1.NewTime(cstat.Timestamp),
RxBytes: &inter.RxBytes,
RxErrors: &inter.RxErrors,
TxBytes: &inter.TxBytes,
TxErrors: &inter.TxErrors,
}
}
}
glog.V(4).Infof("Missing default interface %q for %s", network.DefaultInterfaceName, name)
return nil
}
// 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
}

View 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,
})
}

View file

@ -0,0 +1,175 @@
/*
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.
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)
}
rootFsInfo, err := p.cadvisor.RootFsInfo()
if err != nil {
return nil, nil, fmt.Errorf("failed to get rootFs info: %v", err)
}
imageFsInfo, err := p.cadvisor.ImagesFsInfo()
if err != nil {
return nil, nil, fmt.Errorf("failed to get imageFs info: %v", err)
}
s := cadvisorInfoToContainerStats(cgroupName, info, &rootFsInfo, &imageFsInfo)
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
}

View file

@ -0,0 +1,580 @@
/*
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
)
var (
timestamp = time.Now()
creationTime = timestamp.Add(-5 * time.Minute)
)
func TestGetCgroupStats(t *testing.T) {
const (
cgroupName = "test-cgroup-name"
rootFsInfoSeed = 1000
imageFsInfoSeed = 2000
containerInfoSeed = 3000
)
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)
imageFsInfo = getTestFsInfo(imageFsInfoSeed)
containerInfo = getTestContainerInfo(containerInfoSeed, "test-pod", "test-ns", "test-container")
containerInfoMap = map[string]cadvisorapiv2.ContainerInfo{cgroupName: containerInfo}
)
mockCadvisor.
On("RootFsInfo").Return(rootFsInfo, nil).
On("ImagesFsInfo").Return(imageFsInfo, nil).
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)
checkFsStats(t, "", imageFsInfoSeed, cs.Rootfs)
checkFsStats(t, "", rootFsInfoSeed, cs.Logs)
checkNetworkStats(t, "", containerInfoSeed, ns)
assert.Equal(cgroupName, cs.Name)
assert.Equal(metav1.NewTime(containerInfo.Spec.CreationTime), cs.StartTime)
assert.Equal(metav1.NewTime(containerInfo.Stats[0].Timestamp), cs.Rootfs.Time)
assert.Equal(*containerInfo.Stats[0].Filesystem.BaseUsageBytes, *cs.Rootfs.UsedBytes)
assert.Equal(*containerInfo.Stats[0].Filesystem.InodeUsage, *cs.Rootfs.InodesUsed)
assert.Equal(metav1.NewTime(containerInfo.Stats[0].Timestamp), cs.Logs.Time)
assert.Equal(*containerInfo.Stats[0].Filesystem.TotalUsageBytes-*containerInfo.Stats[0].Filesystem.BaseUsageBytes, *cs.Logs.UsedBytes)
assert.Equal(*rootFsInfo.Inodes-*rootFsInfo.InodesFree, *cs.Logs.InodesUsed)
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 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, 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")
}
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")
}
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")
}