vendor files

This commit is contained in:
Serguei Bezverkhi
2018-01-09 13:57:14 -05:00
parent 558bc6c02a
commit 7b24313bd6
16547 changed files with 4527373 additions and 0 deletions

View File

@ -0,0 +1,68 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"fs_resource_analyzer.go",
"handler.go",
"resource_analyzer.go",
"summary.go",
"volume_stat_calculator.go",
],
importpath = "k8s.io/kubernetes/pkg/kubelet/server/stats",
visibility = ["//visibility:public"],
deps = [
"//pkg/kubelet/apis/stats/v1alpha1:go_default_library",
"//pkg/kubelet/cm:go_default_library",
"//pkg/kubelet/container:go_default_library",
"//pkg/kubelet/metrics:go_default_library",
"//pkg/kubelet/util/format:go_default_library",
"//pkg/volume:go_default_library",
"//vendor/github.com/emicklei/go-restful:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/github.com/google/cadvisor/info/v1:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = [
"summary_test.go",
"volume_stat_calculator_test.go",
],
importpath = "k8s.io/kubernetes/pkg/kubelet/server/stats",
library = ":go_default_library",
deps = [
"//pkg/kubelet/apis/stats/v1alpha1:go_default_library",
"//pkg/kubelet/cm:go_default_library",
"//pkg/kubelet/server/stats/testing:go_default_library",
"//pkg/volume:go_default_library",
"//vendor/github.com/google/gofuzz:go_default_library",
"//vendor/github.com/stretchr/testify/assert:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//pkg/kubelet/server/stats/testing:all-srcs",
],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@ -0,0 +1,20 @@
/*
Copyright 2014 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 handles exporting Kubelet and container stats.
// NOTE: We intend to move this functionality into a standalone pod, so this package should be very
// loosely coupled to the rest of the Kubelet.
package stats // import "k8s.io/kubernetes/pkg/kubelet/server/stats"

View File

@ -0,0 +1,107 @@
/*
Copyright 2016 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 (
"sync"
"sync/atomic"
"time"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
"github.com/golang/glog"
)
// Map to PodVolumeStats pointers since the addresses for map values are not constant and can cause pain
// if we need ever to get a pointer to one of the values (e.g. you can't)
type Cache map[types.UID]*volumeStatCalculator
// fsResourceAnalyzerInterface is for embedding fs functions into ResourceAnalyzer
type fsResourceAnalyzerInterface interface {
GetPodVolumeStats(uid types.UID) (PodVolumeStats, bool)
}
// fsResourceAnalyzer provides stats about fs resource usage
type fsResourceAnalyzer struct {
statsProvider StatsProvider
calcPeriod time.Duration
cachedVolumeStats atomic.Value
startOnce sync.Once
}
var _ fsResourceAnalyzerInterface = &fsResourceAnalyzer{}
// newFsResourceAnalyzer returns a new fsResourceAnalyzer implementation
func newFsResourceAnalyzer(statsProvider StatsProvider, calcVolumePeriod time.Duration) *fsResourceAnalyzer {
r := &fsResourceAnalyzer{
statsProvider: statsProvider,
calcPeriod: calcVolumePeriod,
}
r.cachedVolumeStats.Store(make(Cache))
return r
}
// Start eager background caching of volume stats.
func (s *fsResourceAnalyzer) Start() {
s.startOnce.Do(func() {
if s.calcPeriod <= 0 {
glog.Info("Volume stats collection disabled.")
return
}
glog.Info("Starting FS ResourceAnalyzer")
go wait.Forever(func() { s.updateCachedPodVolumeStats() }, s.calcPeriod)
})
}
// updateCachedPodVolumeStats calculates and caches the PodVolumeStats for every Pod known to the kubelet.
func (s *fsResourceAnalyzer) updateCachedPodVolumeStats() {
oldCache := s.cachedVolumeStats.Load().(Cache)
newCache := make(Cache)
// Copy existing entries to new map, creating/starting new entries for pods missing from the cache
for _, pod := range s.statsProvider.GetPods() {
if value, found := oldCache[pod.GetUID()]; !found {
newCache[pod.GetUID()] = newVolumeStatCalculator(s.statsProvider, s.calcPeriod, pod).StartOnce()
} else {
newCache[pod.GetUID()] = value
}
}
// Stop entries for pods that have been deleted
for uid, entry := range oldCache {
if _, found := newCache[uid]; !found {
entry.StopOnce()
}
}
// Update the cache reference
s.cachedVolumeStats.Store(newCache)
}
// GetPodVolumeStats returns the PodVolumeStats for a given pod. Results are looked up from a cache that
// is eagerly populated in the background, and never calculated on the fly.
func (s *fsResourceAnalyzer) GetPodVolumeStats(uid types.UID) (PodVolumeStats, bool) {
cache := s.cachedVolumeStats.Load().(Cache)
if statCalc, found := cache[uid]; !found {
// TODO: Differentiate between stats being empty
// See issue #20679
return PodVolumeStats{}, false
} else {
return statCalc.GetLatest()
}
}

View File

@ -0,0 +1,279 @@
/*
Copyright 2016 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 (
"encoding/json"
"fmt"
"io"
"net/http"
"path"
"time"
restful "github.com/emicklei/go-restful"
"github.com/golang/glog"
cadvisorapi "github.com/google/cadvisor/info/v1"
"k8s.io/api/core/v1"
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/cm"
kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
"k8s.io/kubernetes/pkg/volume"
)
// Host methods required by stats handlers.
type StatsProvider interface {
// The following stats are provided by either CRI or cAdvisor.
//
// ListPodStats returns the stats of all the containers managed by pods.
ListPodStats() ([]statsapi.PodStats, error)
// ImageFsStats returns the stats of the image filesystem.
ImageFsStats() (*statsapi.FsStats, error)
// The following stats are provided by cAdvisor.
//
// GetCgroupStats returns the stats and the networking usage of the cgroup
// with the specified cgroupName.
GetCgroupStats(cgroupName string) (*statsapi.ContainerStats, *statsapi.NetworkStats, error)
// RootFsStats returns the stats of the node root filesystem.
RootFsStats() (*statsapi.FsStats, error)
// The following stats are provided by cAdvisor for legacy usage.
//
// GetContainerInfo returns the information of the container with the
// containerName managed by the pod with the uid.
GetContainerInfo(podFullName string, uid types.UID, containerName string, req *cadvisorapi.ContainerInfoRequest) (*cadvisorapi.ContainerInfo, error)
// GetRawContainerInfo returns the information of the container with the
// containerName. If subcontainers is true, this function will return the
// information of all the sub-containers as well.
GetRawContainerInfo(containerName string, req *cadvisorapi.ContainerInfoRequest, subcontainers bool) (map[string]*cadvisorapi.ContainerInfo, error)
// The following information is provided by Kubelet.
//
// GetPodByName returns the spec of the pod with the name in the specified
// namespace.
GetPodByName(namespace, name string) (*v1.Pod, bool)
// GetNode returns the spec of the local node.
GetNode() (*v1.Node, error)
// GetNodeConfig returns the configuration of the local node.
GetNodeConfig() cm.NodeConfig
// ListVolumesForPod returns the stats of the volume used by the pod with
// the podUID.
ListVolumesForPod(podUID types.UID) (map[string]volume.Volume, bool)
// GetPods returns the specs of all the pods running on this node.
GetPods() []*v1.Pod
}
type handler struct {
provider StatsProvider
summaryProvider SummaryProvider
}
func CreateHandlers(rootPath string, provider StatsProvider, summaryProvider SummaryProvider) *restful.WebService {
h := &handler{provider, summaryProvider}
ws := &restful.WebService{}
ws.Path(rootPath).
Produces(restful.MIME_JSON)
endpoints := []struct {
path string
handler restful.RouteFunction
}{
{"", h.handleStats},
{"/summary", h.handleSummary},
{"/container", h.handleSystemContainer},
{"/{podName}/{containerName}", h.handlePodContainer},
{"/{namespace}/{podName}/{uid}/{containerName}", h.handlePodContainer},
}
for _, e := range endpoints {
for _, method := range []string{"GET", "POST"} {
ws.Route(ws.
Method(method).
Path(e.path).
To(e.handler))
}
}
return ws
}
type StatsRequest struct {
// The name of the container for which to request stats.
// Default: /
// +optional
ContainerName string `json:"containerName,omitempty"`
// Max number of stats to return.
// If start and end time are specified this limit is ignored.
// Default: 60
// +optional
NumStats int `json:"num_stats,omitempty"`
// Start time for which to query information.
// If omitted, the beginning of time is assumed.
// +optional
Start time.Time `json:"start,omitempty"`
// End time for which to query information.
// If omitted, current time is assumed.
// +optional
End time.Time `json:"end,omitempty"`
// Whether to also include information from subcontainers.
// Default: false.
// +optional
Subcontainers bool `json:"subcontainers,omitempty"`
}
func (r *StatsRequest) cadvisorRequest() *cadvisorapi.ContainerInfoRequest {
return &cadvisorapi.ContainerInfoRequest{
NumStats: r.NumStats,
Start: r.Start,
End: r.End,
}
}
func parseStatsRequest(request *restful.Request) (StatsRequest, error) {
// Default request.
query := StatsRequest{
NumStats: 60,
}
err := json.NewDecoder(request.Request.Body).Decode(&query)
if err != nil && err != io.EOF {
return query, err
}
return query, nil
}
// Handles root container stats requests to /stats
func (h *handler) handleStats(request *restful.Request, response *restful.Response) {
query, err := parseStatsRequest(request)
if err != nil {
handleError(response, "/stats", err)
return
}
// Root container stats.
statsMap, err := h.provider.GetRawContainerInfo("/", query.cadvisorRequest(), false)
if err != nil {
handleError(response, fmt.Sprintf("/stats %v", query), err)
return
}
writeResponse(response, statsMap["/"])
}
// Handles stats summary requests to /stats/summary
func (h *handler) handleSummary(request *restful.Request, response *restful.Response) {
summary, err := h.summaryProvider.Get()
if err != nil {
handleError(response, "/stats/summary", err)
} else {
writeResponse(response, summary)
}
}
// Handles non-kubernetes container stats requests to /stats/container/
func (h *handler) handleSystemContainer(request *restful.Request, response *restful.Response) {
query, err := parseStatsRequest(request)
if err != nil {
handleError(response, "/stats/container", err)
return
}
// Non-Kubernetes container stats.
containerName := path.Join("/", query.ContainerName)
stats, err := h.provider.GetRawContainerInfo(
containerName, query.cadvisorRequest(), query.Subcontainers)
if err != nil {
if _, ok := stats[containerName]; ok {
// If the failure is partial, log it and return a best-effort response.
glog.Errorf("Partial failure issuing GetRawContainerInfo(%v): %v", query, err)
} else {
handleError(response, fmt.Sprintf("/stats/container %v", query), err)
return
}
}
writeResponse(response, stats)
}
// Handles kubernetes pod/container stats requests to:
// /stats/<pod name>/<container name>
// /stats/<namespace>/<pod name>/<uid>/<container name>
func (h *handler) handlePodContainer(request *restful.Request, response *restful.Response) {
query, err := parseStatsRequest(request)
if err != nil {
handleError(response, request.Request.URL.String(), err)
return
}
// Default parameters.
params := map[string]string{
"namespace": metav1.NamespaceDefault,
"uid": "",
}
for k, v := range request.PathParameters() {
params[k] = v
}
if params["podName"] == "" || params["containerName"] == "" {
response.WriteErrorString(http.StatusBadRequest,
fmt.Sprintf("Invalid pod container request: %v", params))
return
}
pod, ok := h.provider.GetPodByName(params["namespace"], params["podName"])
if !ok {
glog.V(4).Infof("Container not found: %v", params)
response.WriteError(http.StatusNotFound, kubecontainer.ErrContainerNotFound)
return
}
stats, err := h.provider.GetContainerInfo(
kubecontainer.GetPodFullName(pod),
types.UID(params["uid"]),
params["containerName"],
query.cadvisorRequest())
if err != nil {
handleError(response, fmt.Sprintf("%s %v", request.Request.URL.String(), query), err)
return
}
writeResponse(response, stats)
}
func writeResponse(response *restful.Response, stats interface{}) {
if err := response.WriteAsJson(stats); err != nil {
glog.Errorf("Error writing response: %v", err)
}
}
// handleError serializes an error object into an HTTP response.
// request is provided for logging.
func handleError(response *restful.Response, request string, err error) {
switch err {
case kubecontainer.ErrContainerNotFound:
response.WriteError(http.StatusNotFound, err)
default:
msg := fmt.Sprintf("Internal Error: %v", err)
glog.Errorf("HTTP InternalServerError serving %s: %s", request, msg)
response.WriteErrorString(http.StatusInternalServerError, msg)
}
}

View File

@ -0,0 +1,49 @@
/*
Copyright 2016 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 (
"time"
)
// ResourceAnalyzer provides statistics on node resource consumption
type ResourceAnalyzer interface {
Start()
fsResourceAnalyzerInterface
SummaryProvider
}
// resourceAnalyzer implements ResourceAnalyzer
type resourceAnalyzer struct {
*fsResourceAnalyzer
SummaryProvider
}
var _ ResourceAnalyzer = &resourceAnalyzer{}
// NewResourceAnalyzer returns a new ResourceAnalyzer
func NewResourceAnalyzer(statsProvider StatsProvider, calVolumeFrequency time.Duration) ResourceAnalyzer {
fsAnalyzer := newFsResourceAnalyzer(statsProvider, calVolumeFrequency)
summaryProvider := NewSummaryProvider(statsProvider)
return &resourceAnalyzer{fsAnalyzer, summaryProvider}
}
// Start starts background functions necessary for the ResourceAnalyzer to function
func (ra *resourceAnalyzer) Start() {
ra.fsResourceAnalyzer.Start()
}

View File

@ -0,0 +1,106 @@
/*
Copyright 2016 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"
"github.com/golang/glog"
statsapi "k8s.io/kubernetes/pkg/kubelet/apis/stats/v1alpha1"
)
type SummaryProvider interface {
Get() (*statsapi.Summary, error)
}
// summaryProviderImpl implements the SummaryProvider interface.
type summaryProviderImpl struct {
provider StatsProvider
}
var _ SummaryProvider = &summaryProviderImpl{}
// NewSummaryProvider returns a SummaryProvider using the stats provided by the
// specified statsProvider.
func NewSummaryProvider(statsProvider StatsProvider) SummaryProvider {
return &summaryProviderImpl{statsProvider}
}
// Get provides a new Summary with the stats from Kubelet.
func (sp *summaryProviderImpl) Get() (*statsapi.Summary, error) {
// TODO(timstclair): Consider returning a best-effort response if any of
// the following errors occur.
node, err := sp.provider.GetNode()
if err != nil {
return nil, fmt.Errorf("failed to get node info: %v", err)
}
nodeConfig := sp.provider.GetNodeConfig()
rootStats, networkStats, err := sp.provider.GetCgroupStats("/")
if err != nil {
return nil, fmt.Errorf("failed to get root cgroup stats: %v", err)
}
rootFsStats, err := sp.provider.RootFsStats()
if err != nil {
return nil, fmt.Errorf("failed to get rootFs stats: %v", err)
}
imageFsStats, err := sp.provider.ImageFsStats()
if err != nil {
return nil, fmt.Errorf("failed to get imageFs stats: %v", err)
}
podStats, err := sp.provider.ListPodStats()
if err != nil {
return nil, fmt.Errorf("failed to list pod stats: %v", err)
}
nodeStats := statsapi.NodeStats{
NodeName: node.Name,
CPU: rootStats.CPU,
Memory: rootStats.Memory,
Network: networkStats,
StartTime: rootStats.StartTime,
Fs: rootFsStats,
Runtime: &statsapi.RuntimeStats{ImageFs: imageFsStats},
}
systemContainers := map[string]string{
statsapi.SystemContainerKubelet: nodeConfig.KubeletCgroupsName,
statsapi.SystemContainerRuntime: nodeConfig.RuntimeCgroupsName,
statsapi.SystemContainerMisc: nodeConfig.SystemCgroupsName,
}
for sys, name := range systemContainers {
// skip if cgroup name is undefined (not all system containers are required)
if name == "" {
continue
}
s, _, err := sp.provider.GetCgroupStats(name)
if err != nil {
glog.Errorf("Failed to get system container stats for %q: %v", name, err)
continue
}
// System containers don't have a filesystem associated with them.
s.Logs, s.Rootfs = nil, nil
s.Name = sys
nodeStats.SystemContainers = append(nodeStats.SystemContainers, *s)
}
summary := statsapi.Summary{
Node: nodeStats,
Pods: podStats,
}
return &summary, nil
}

View File

@ -0,0 +1,143 @@
/*
Copyright 2016 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"
fuzz "github.com/google/gofuzz"
"github.com/stretchr/testify/assert"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
statsapi "k8s.io/kubernetes/pkg/kubelet/apis/stats/v1alpha1"
"k8s.io/kubernetes/pkg/kubelet/cm"
statstest "k8s.io/kubernetes/pkg/kubelet/server/stats/testing"
)
func TestSummaryProvider(t *testing.T) {
var (
podStats = []statsapi.PodStats{
{
PodRef: statsapi.PodReference{Name: "test-pod", Namespace: "test-namespace", UID: "UID_test-pod"},
StartTime: metav1.NewTime(time.Now()),
Containers: []statsapi.ContainerStats{*getContainerStats()},
Network: getNetworkStats(),
VolumeStats: []statsapi.VolumeStats{*getVolumeStats()},
},
}
imageFsStats = getFsStats()
rootFsStats = getFsStats()
node = &v1.Node{ObjectMeta: metav1.ObjectMeta{Name: "test-node"}}
nodeConfig = cm.NodeConfig{
RuntimeCgroupsName: "/runtime",
SystemCgroupsName: "/misc",
KubeletCgroupsName: "/kubelet",
}
cgroupStatsMap = map[string]struct {
cs *statsapi.ContainerStats
ns *statsapi.NetworkStats
}{
"/": {cs: getContainerStats(), ns: getNetworkStats()},
"/runtime": {cs: getContainerStats(), ns: getNetworkStats()},
"/misc": {cs: getContainerStats(), ns: getNetworkStats()},
"/kubelet": {cs: getContainerStats(), ns: getNetworkStats()},
}
)
assert := assert.New(t)
mockStatsProvider := new(statstest.StatsProvider)
mockStatsProvider.
On("GetNode").Return(node, nil).
On("GetNodeConfig").Return(nodeConfig).
On("ListPodStats").Return(podStats, nil).
On("ImageFsStats").Return(imageFsStats, nil).
On("RootFsStats").Return(rootFsStats, nil).
On("GetCgroupStats", "/").Return(cgroupStatsMap["/"].cs, cgroupStatsMap["/"].ns, nil).
On("GetCgroupStats", "/runtime").Return(cgroupStatsMap["/runtime"].cs, cgroupStatsMap["/runtime"].ns, nil).
On("GetCgroupStats", "/misc").Return(cgroupStatsMap["/misc"].cs, cgroupStatsMap["/misc"].ns, nil).
On("GetCgroupStats", "/kubelet").Return(cgroupStatsMap["/kubelet"].cs, cgroupStatsMap["/kubelet"].ns, nil)
provider := NewSummaryProvider(mockStatsProvider)
summary, err := provider.Get()
assert.NoError(err)
assert.Equal(summary.Node.NodeName, "test-node")
assert.Equal(summary.Node.StartTime, cgroupStatsMap["/"].cs.StartTime)
assert.Equal(summary.Node.CPU, cgroupStatsMap["/"].cs.CPU)
assert.Equal(summary.Node.Memory, cgroupStatsMap["/"].cs.Memory)
assert.Equal(summary.Node.Network, cgroupStatsMap["/"].ns)
assert.Equal(summary.Node.Fs, rootFsStats)
assert.Equal(summary.Node.Runtime, &statsapi.RuntimeStats{ImageFs: imageFsStats})
assert.Equal(len(summary.Node.SystemContainers), 3)
assert.Contains(summary.Node.SystemContainers, statsapi.ContainerStats{
Name: "kubelet",
StartTime: cgroupStatsMap["/kubelet"].cs.StartTime,
CPU: cgroupStatsMap["/kubelet"].cs.CPU,
Memory: cgroupStatsMap["/kubelet"].cs.Memory,
Accelerators: cgroupStatsMap["/kubelet"].cs.Accelerators,
UserDefinedMetrics: cgroupStatsMap["/kubelet"].cs.UserDefinedMetrics,
})
assert.Contains(summary.Node.SystemContainers, statsapi.ContainerStats{
Name: "misc",
StartTime: cgroupStatsMap["/misc"].cs.StartTime,
CPU: cgroupStatsMap["/misc"].cs.CPU,
Memory: cgroupStatsMap["/misc"].cs.Memory,
Accelerators: cgroupStatsMap["/misc"].cs.Accelerators,
UserDefinedMetrics: cgroupStatsMap["/misc"].cs.UserDefinedMetrics,
})
assert.Contains(summary.Node.SystemContainers, statsapi.ContainerStats{
Name: "runtime",
StartTime: cgroupStatsMap["/runtime"].cs.StartTime,
CPU: cgroupStatsMap["/runtime"].cs.CPU,
Memory: cgroupStatsMap["/runtime"].cs.Memory,
Accelerators: cgroupStatsMap["/runtime"].cs.Accelerators,
UserDefinedMetrics: cgroupStatsMap["/runtime"].cs.UserDefinedMetrics,
})
assert.Equal(summary.Pods, podStats)
}
func getFsStats() *statsapi.FsStats {
f := fuzz.New().NilChance(0)
v := &statsapi.FsStats{}
f.Fuzz(v)
return v
}
func getContainerStats() *statsapi.ContainerStats {
f := fuzz.New().NilChance(0)
v := &statsapi.ContainerStats{}
f.Fuzz(v)
return v
}
func getVolumeStats() *statsapi.VolumeStats {
f := fuzz.New().NilChance(0)
v := &statsapi.VolumeStats{}
f.Fuzz(v)
return v
}
func getNetworkStats() *statsapi.NetworkStats {
f := fuzz.New().NilChance(0)
v := &statsapi.NetworkStats{}
f.Fuzz(v)
return v
}

View File

@ -0,0 +1,31 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = ["mock_stats_provider.go"],
importpath = "k8s.io/kubernetes/pkg/kubelet/server/stats/testing",
visibility = ["//visibility:public"],
deps = [
"//pkg/kubelet/apis/stats/v1alpha1:go_default_library",
"//pkg/kubelet/cm:go_default_library",
"//pkg/volume:go_default_library",
"//vendor/github.com/google/cadvisor/info/v1:go_default_library",
"//vendor/github.com/stretchr/testify/mock:go_default_library",
"//vendor/k8s.io/api/core/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"],
)

View File

@ -0,0 +1,280 @@
/*
Copyright 2016 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 testing
import cm "k8s.io/kubernetes/pkg/kubelet/cm"
import corev1 "k8s.io/api/core/v1"
import mock "github.com/stretchr/testify/mock"
import types "k8s.io/apimachinery/pkg/types"
import v1 "github.com/google/cadvisor/info/v1"
import v1alpha1 "k8s.io/kubernetes/pkg/kubelet/apis/stats/v1alpha1"
import volume "k8s.io/kubernetes/pkg/volume"
// DO NOT EDIT
// GENERATED BY mockery
// StatsProvider is an autogenerated mock type for the StatsProvider type
type StatsProvider struct {
mock.Mock
}
// GetCgroupStats provides a mock function with given fields: cgroupName
func (_m *StatsProvider) GetCgroupStats(cgroupName string) (*v1alpha1.ContainerStats, *v1alpha1.NetworkStats, error) {
ret := _m.Called(cgroupName)
var r0 *v1alpha1.ContainerStats
if rf, ok := ret.Get(0).(func(string) *v1alpha1.ContainerStats); ok {
r0 = rf(cgroupName)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*v1alpha1.ContainerStats)
}
}
var r1 *v1alpha1.NetworkStats
if rf, ok := ret.Get(1).(func(string) *v1alpha1.NetworkStats); ok {
r1 = rf(cgroupName)
} else {
if ret.Get(1) != nil {
r1 = ret.Get(1).(*v1alpha1.NetworkStats)
}
}
var r2 error
if rf, ok := ret.Get(2).(func(string) error); ok {
r2 = rf(cgroupName)
} else {
r2 = ret.Error(2)
}
return r0, r1, r2
}
// GetContainerInfo provides a mock function with given fields: podFullName, uid, containerName, req
func (_m *StatsProvider) GetContainerInfo(podFullName string, uid types.UID, containerName string, req *v1.ContainerInfoRequest) (*v1.ContainerInfo, error) {
ret := _m.Called(podFullName, uid, containerName, req)
var r0 *v1.ContainerInfo
if rf, ok := ret.Get(0).(func(string, types.UID, string, *v1.ContainerInfoRequest) *v1.ContainerInfo); ok {
r0 = rf(podFullName, uid, containerName, req)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*v1.ContainerInfo)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string, types.UID, string, *v1.ContainerInfoRequest) error); ok {
r1 = rf(podFullName, uid, containerName, req)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetNode provides a mock function with given fields:
func (_m *StatsProvider) GetNode() (*corev1.Node, error) {
ret := _m.Called()
var r0 *corev1.Node
if rf, ok := ret.Get(0).(func() *corev1.Node); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*corev1.Node)
}
}
var r1 error
if rf, ok := ret.Get(1).(func() error); ok {
r1 = rf()
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetNodeConfig provides a mock function with given fields:
func (_m *StatsProvider) GetNodeConfig() cm.NodeConfig {
ret := _m.Called()
var r0 cm.NodeConfig
if rf, ok := ret.Get(0).(func() cm.NodeConfig); ok {
r0 = rf()
} else {
r0 = ret.Get(0).(cm.NodeConfig)
}
return r0
}
// GetPodByName provides a mock function with given fields: namespace, name
func (_m *StatsProvider) GetPodByName(namespace string, name string) (*corev1.Pod, bool) {
ret := _m.Called(namespace, name)
var r0 *corev1.Pod
if rf, ok := ret.Get(0).(func(string, string) *corev1.Pod); ok {
r0 = rf(namespace, name)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*corev1.Pod)
}
}
var r1 bool
if rf, ok := ret.Get(1).(func(string, string) bool); ok {
r1 = rf(namespace, name)
} else {
r1 = ret.Get(1).(bool)
}
return r0, r1
}
// GetPods provides a mock function with given fields:
func (_m *StatsProvider) GetPods() []*corev1.Pod {
ret := _m.Called()
var r0 []*corev1.Pod
if rf, ok := ret.Get(0).(func() []*corev1.Pod); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*corev1.Pod)
}
}
return r0
}
// GetRawContainerInfo provides a mock function with given fields: containerName, req, subcontainers
func (_m *StatsProvider) GetRawContainerInfo(containerName string, req *v1.ContainerInfoRequest, subcontainers bool) (map[string]*v1.ContainerInfo, error) {
ret := _m.Called(containerName, req, subcontainers)
var r0 map[string]*v1.ContainerInfo
if rf, ok := ret.Get(0).(func(string, *v1.ContainerInfoRequest, bool) map[string]*v1.ContainerInfo); ok {
r0 = rf(containerName, req, subcontainers)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(map[string]*v1.ContainerInfo)
}
}
var r1 error
if rf, ok := ret.Get(1).(func(string, *v1.ContainerInfoRequest, bool) error); ok {
r1 = rf(containerName, req, subcontainers)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// ImageFsStats provides a mock function with given fields:
func (_m *StatsProvider) ImageFsStats() (*v1alpha1.FsStats, error) {
ret := _m.Called()
var r0 *v1alpha1.FsStats
if rf, ok := ret.Get(0).(func() *v1alpha1.FsStats); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*v1alpha1.FsStats)
}
}
var r1 error
if rf, ok := ret.Get(1).(func() error); ok {
r1 = rf()
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// ListPodStats provides a mock function with given fields:
func (_m *StatsProvider) ListPodStats() ([]v1alpha1.PodStats, error) {
ret := _m.Called()
var r0 []v1alpha1.PodStats
if rf, ok := ret.Get(0).(func() []v1alpha1.PodStats); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]v1alpha1.PodStats)
}
}
var r1 error
if rf, ok := ret.Get(1).(func() error); ok {
r1 = rf()
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// ListVolumesForPod provides a mock function with given fields: podUID
func (_m *StatsProvider) ListVolumesForPod(podUID types.UID) (map[string]volume.Volume, bool) {
ret := _m.Called(podUID)
var r0 map[string]volume.Volume
if rf, ok := ret.Get(0).(func(types.UID) map[string]volume.Volume); ok {
r0 = rf(podUID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(map[string]volume.Volume)
}
}
var r1 bool
if rf, ok := ret.Get(1).(func(types.UID) bool); ok {
r1 = rf(podUID)
} else {
r1 = ret.Get(1).(bool)
}
return r0, r1
}
// RootFsStats provides a mock function with given fields:
func (_m *StatsProvider) RootFsStats() (*v1alpha1.FsStats, error) {
ret := _m.Called()
var r0 *v1alpha1.FsStats
if rf, ok := ret.Get(0).(func() *v1alpha1.FsStats); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*v1alpha1.FsStats)
}
}
var r1 error
if rf, ok := ret.Get(1).(func() error); ok {
r1 = rf()
} else {
r1 = ret.Error(1)
}
return r0, r1
}

View File

@ -0,0 +1,175 @@
/*
Copyright 2016 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 (
"sync"
"sync/atomic"
"time"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/wait"
stats "k8s.io/kubernetes/pkg/kubelet/apis/stats/v1alpha1"
"k8s.io/kubernetes/pkg/kubelet/metrics"
"k8s.io/kubernetes/pkg/kubelet/util/format"
"k8s.io/kubernetes/pkg/volume"
"github.com/golang/glog"
)
// volumeStatCalculator calculates volume metrics for a given pod periodically in the background and caches the result
type volumeStatCalculator struct {
statsProvider StatsProvider
jitterPeriod time.Duration
pod *v1.Pod
stopChannel chan struct{}
startO sync.Once
stopO sync.Once
latest atomic.Value
}
// PodVolumeStats encapsulates the VolumeStats for a pod.
// It consists of two lists, for local ephemeral volumes, and for persistent volumes respectively.
type PodVolumeStats struct {
EphemeralVolumes []stats.VolumeStats
PersistentVolumes []stats.VolumeStats
}
// newVolumeStatCalculator creates a new VolumeStatCalculator
func newVolumeStatCalculator(statsProvider StatsProvider, jitterPeriod time.Duration, pod *v1.Pod) *volumeStatCalculator {
return &volumeStatCalculator{
statsProvider: statsProvider,
jitterPeriod: jitterPeriod,
pod: pod,
stopChannel: make(chan struct{}),
}
}
// StartOnce starts pod volume calc that will occur periodically in the background until s.StopOnce is called
func (s *volumeStatCalculator) StartOnce() *volumeStatCalculator {
s.startO.Do(func() {
go wait.JitterUntil(func() {
s.calcAndStoreStats()
}, s.jitterPeriod, 1.0, true, s.stopChannel)
})
return s
}
// StopOnce stops background pod volume calculation. Will not stop a currently executing calculations until
// they complete their current iteration.
func (s *volumeStatCalculator) StopOnce() *volumeStatCalculator {
s.stopO.Do(func() {
close(s.stopChannel)
})
return s
}
// getLatest returns the most recent PodVolumeStats from the cache
func (s *volumeStatCalculator) GetLatest() (PodVolumeStats, bool) {
if result := s.latest.Load(); result == nil {
return PodVolumeStats{}, false
} else {
return result.(PodVolumeStats), true
}
}
// calcAndStoreStats calculates PodVolumeStats for a given pod and writes the result to the s.latest cache.
// If the pod references PVCs, the prometheus metrics for those are updated with the result.
func (s *volumeStatCalculator) calcAndStoreStats() {
// Find all Volumes for the Pod
volumes, found := s.statsProvider.ListVolumesForPod(s.pod.UID)
if !found {
return
}
// Get volume specs for the pod - key'd by volume name
volumesSpec := make(map[string]v1.Volume)
for _, v := range s.pod.Spec.Volumes {
volumesSpec[v.Name] = v
}
// Call GetMetrics on each Volume and copy the result to a new VolumeStats.FsStats
ephemeralStats := []stats.VolumeStats{}
persistentStats := []stats.VolumeStats{}
for name, v := range volumes {
metric, err := v.GetMetrics()
if err != nil {
// Expected for Volumes that don't support Metrics
if !volume.IsNotSupported(err) {
glog.V(4).Infof("Failed to calculate volume metrics for pod %s volume %s: %+v", format.Pod(s.pod), name, err)
}
continue
}
// Lookup the volume spec and add a 'PVCReference' for volumes that reference a PVC
volSpec := volumesSpec[name]
var pvcRef *stats.PVCReference
if pvcSource := volSpec.PersistentVolumeClaim; pvcSource != nil {
pvcRef = &stats.PVCReference{
Name: pvcSource.ClaimName,
Namespace: s.pod.GetNamespace(),
}
// Set the PVC's prometheus metrics
s.setPVCMetrics(pvcRef, metric)
}
volumeStats := s.parsePodVolumeStats(name, pvcRef, metric, volSpec)
if isVolumeEphemeral(volSpec) {
ephemeralStats = append(ephemeralStats, volumeStats)
} else {
persistentStats = append(persistentStats, volumeStats)
}
}
// Store the new stats
s.latest.Store(PodVolumeStats{EphemeralVolumes: ephemeralStats,
PersistentVolumes: persistentStats})
}
// parsePodVolumeStats converts (internal) volume.Metrics to (external) stats.VolumeStats structures
func (s *volumeStatCalculator) parsePodVolumeStats(podName string, pvcRef *stats.PVCReference, metric *volume.Metrics, volSpec v1.Volume) stats.VolumeStats {
available := uint64(metric.Available.Value())
capacity := uint64(metric.Capacity.Value())
used := uint64(metric.Used.Value())
inodes := uint64(metric.Inodes.Value())
inodesFree := uint64(metric.InodesFree.Value())
inodesUsed := uint64(metric.InodesUsed.Value())
return stats.VolumeStats{
Name: podName,
PVCRef: pvcRef,
FsStats: stats.FsStats{Time: metric.Time, AvailableBytes: &available, CapacityBytes: &capacity,
UsedBytes: &used, Inodes: &inodes, InodesFree: &inodesFree, InodesUsed: &inodesUsed},
}
}
func isVolumeEphemeral(volume v1.Volume) bool {
if (volume.EmptyDir != nil && volume.EmptyDir.Medium == v1.StorageMediumDefault) ||
volume.ConfigMap != nil || volume.GitRepo != nil {
return true
}
return false
}
// setPVCMetrics sets the given PVC's prometheus metrics to match the given volume.Metrics
func (s *volumeStatCalculator) setPVCMetrics(pvcRef *stats.PVCReference, metric *volume.Metrics) {
metrics.VolumeStatsAvailableBytes.WithLabelValues(pvcRef.Namespace, pvcRef.Name).Set(float64(metric.Available.Value()))
metrics.VolumeStatsCapacityBytes.WithLabelValues(pvcRef.Namespace, pvcRef.Name).Set(float64(metric.Capacity.Value()))
metrics.VolumeStatsUsedBytes.WithLabelValues(pvcRef.Namespace, pvcRef.Name).Set(float64(metric.Used.Value()))
metrics.VolumeStatsInodes.WithLabelValues(pvcRef.Namespace, pvcRef.Name).Set(float64(metric.Inodes.Value()))
metrics.VolumeStatsInodesFree.WithLabelValues(pvcRef.Namespace, pvcRef.Name).Set(float64(metric.InodesFree.Value()))
metrics.VolumeStatsInodesUsed.WithLabelValues(pvcRef.Namespace, pvcRef.Name).Set(float64(metric.InodesUsed.Value()))
}

View File

@ -0,0 +1,143 @@
/*
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"
"github.com/stretchr/testify/assert"
k8sv1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
kubestats "k8s.io/kubernetes/pkg/kubelet/apis/stats/v1alpha1"
statstest "k8s.io/kubernetes/pkg/kubelet/server/stats/testing"
"k8s.io/kubernetes/pkg/volume"
)
const (
namespace0 = "test0"
pName0 = "pod0"
capacity = int64(10000000)
available = int64(5000000)
inodesTotal = int64(2000)
inodesFree = int64(1000)
vol0 = "vol0"
vol1 = "vol1"
pvcClaimName = "pvc-fake"
)
func TestPVCRef(t *testing.T) {
// Create pod spec to test against
podVolumes := []k8sv1.Volume{
{
Name: vol0,
VolumeSource: k8sv1.VolumeSource{
GCEPersistentDisk: &k8sv1.GCEPersistentDiskVolumeSource{
PDName: "fake-device1",
},
},
},
{
Name: vol1,
VolumeSource: k8sv1.VolumeSource{
PersistentVolumeClaim: &k8sv1.PersistentVolumeClaimVolumeSource{
ClaimName: pvcClaimName,
},
},
},
}
fakePod := &k8sv1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: pName0,
Namespace: namespace0,
UID: "UID" + pName0,
},
Spec: k8sv1.PodSpec{
Volumes: podVolumes,
},
}
// Setup mock stats provider
mockStats := new(statstest.StatsProvider)
volumes := map[string]volume.Volume{vol0: &fakeVolume{}, vol1: &fakeVolume{}}
mockStats.On("ListVolumesForPod", fakePod.UID).Return(volumes, true)
// Calculate stats for pod
statsCalculator := newVolumeStatCalculator(mockStats, time.Minute, fakePod)
statsCalculator.calcAndStoreStats()
vs, _ := statsCalculator.GetLatest()
assert.Len(t, append(vs.EphemeralVolumes, vs.PersistentVolumes...), 2)
// Verify 'vol0' doesn't have a PVC reference
assert.Contains(t, append(vs.EphemeralVolumes, vs.PersistentVolumes...), kubestats.VolumeStats{
Name: vol0,
FsStats: expectedFSStats(),
})
// Verify 'vol1' has a PVC reference
assert.Contains(t, append(vs.EphemeralVolumes, vs.PersistentVolumes...), kubestats.VolumeStats{
Name: vol1,
PVCRef: &kubestats.PVCReference{
Name: pvcClaimName,
Namespace: namespace0,
},
FsStats: expectedFSStats(),
})
}
// Fake volume/metrics provider
var _ volume.Volume = &fakeVolume{}
type fakeVolume struct{}
func (v *fakeVolume) GetPath() string { return "" }
func (v *fakeVolume) GetMetrics() (*volume.Metrics, error) {
return expectedMetrics(), nil
}
func expectedMetrics() *volume.Metrics {
return &volume.Metrics{
Available: resource.NewQuantity(available, resource.BinarySI),
Capacity: resource.NewQuantity(capacity, resource.BinarySI),
Used: resource.NewQuantity(available-capacity, resource.BinarySI),
Inodes: resource.NewQuantity(inodesTotal, resource.BinarySI),
InodesFree: resource.NewQuantity(inodesFree, resource.BinarySI),
InodesUsed: resource.NewQuantity(inodesTotal-inodesFree, resource.BinarySI),
}
}
func expectedFSStats() kubestats.FsStats {
metric := expectedMetrics()
available := uint64(metric.Available.Value())
capacity := uint64(metric.Capacity.Value())
used := uint64(metric.Used.Value())
inodes := uint64(metric.Inodes.Value())
inodesFree := uint64(metric.InodesFree.Value())
inodesUsed := uint64(metric.InodesUsed.Value())
return kubestats.FsStats{
AvailableBytes: &available,
CapacityBytes: &capacity,
UsedBytes: &used,
Inodes: &inodes,
InodesFree: &inodesFree,
InodesUsed: &inodesUsed,
}
}