diff --git a/pkg/kubelet/api/v1alpha1/stats/types.go b/pkg/kubelet/api/v1alpha1/stats/types.go index 7fa103ea3813c..90c566d9d3a7d 100644 --- a/pkg/kubelet/api/v1alpha1/stats/types.go +++ b/pkg/kubelet/api/v1alpha1/stats/types.go @@ -46,6 +46,16 @@ type NodeStats struct { // Stats pertaining to total usage of filesystem resources on the rootfs used by node k8s components. // NodeFs.Used is the total bytes used on the filesystem. Fs *FsStats `json:"fs,omitempty"` + // Stats about the underlying container runtime. + Runtime *RuntimeStats `json:"runtime,omitempty"` +} + +// Stats pertaining to the underlying container runtime. +type RuntimeStats struct { + // Stats about the underlying filesystem where container images are stored. + // This filesystem could be the same as the primary (root) filesystem. + // Usage here refers to the total number of bytes occupied by images on the filesystem. + ImageFs *FsStats `json:"imageFs,omitempty"` } const ( diff --git a/pkg/kubelet/container/runtime.go b/pkg/kubelet/container/runtime.go index 1508dc1679b08..cb8af85f3e364 100644 --- a/pkg/kubelet/container/runtime.go +++ b/pkg/kubelet/container/runtime.go @@ -46,6 +46,12 @@ type ImageSpec struct { Image string } +// ImageStats contains statistics about all the images currently available. +type ImageStats struct { + // Total amount of storage consumed by existing images. + TotalStorageBytes uint64 +} + // Runtime interface defines the interfaces that should be implemented // by a container runtime. // Thread safety is required from implementations of this interface. @@ -86,6 +92,8 @@ type Runtime interface { ListImages() ([]Image, error) // Removes the specified image. RemoveImage(image ImageSpec) error + // Returns Image statistics. + ImageStats() (*ImageStats, error) // TODO(vmarmol): Unify pod and containerID args. // GetContainerLogs returns logs of a specific container. By // default, it returns a snapshot of the container log. Set 'follow' to true to diff --git a/pkg/kubelet/container/testing/fake_runtime.go b/pkg/kubelet/container/testing/fake_runtime.go index 1bc56d1825b63..ce38cd2d65e25 100644 --- a/pkg/kubelet/container/testing/fake_runtime.go +++ b/pkg/kubelet/container/testing/fake_runtime.go @@ -353,3 +353,11 @@ func (f *FakeRuntime) GarbageCollect(gcPolicy ContainerGCPolicy) error { f.CalledFunctions = append(f.CalledFunctions, "GarbageCollect") return f.Err } + +func (f *FakeRuntime) ImageStats() (*ImageStats, error) { + f.Lock() + defer f.Unlock() + + f.CalledFunctions = append(f.CalledFunctions, "ImageStats") + return nil, f.Err +} diff --git a/pkg/kubelet/container/testing/runtime_mock.go b/pkg/kubelet/container/testing/runtime_mock.go index d1f799835178c..496e3222ac419 100644 --- a/pkg/kubelet/container/testing/runtime_mock.go +++ b/pkg/kubelet/container/testing/runtime_mock.go @@ -137,3 +137,8 @@ func (r *Mock) GarbageCollect(gcPolicy ContainerGCPolicy) error { args := r.Called(gcPolicy) return args.Error(0) } + +func (r *Mock) ImageStats() (*ImageStats, error) { + args := r.Called() + return args.Get(0).(*ImageStats), args.Error(1) +} diff --git a/pkg/kubelet/dockertools/docker.go b/pkg/kubelet/dockertools/docker.go index 1501565c23d2e..f875bb9b473c0 100644 --- a/pkg/kubelet/dockertools/docker.go +++ b/pkg/kubelet/dockertools/docker.go @@ -67,6 +67,7 @@ type DockerInterface interface { ListImages(opts dockertypes.ImageListOptions) ([]dockertypes.Image, error) PullImage(image string, auth dockertypes.AuthConfig, opts dockertypes.ImagePullOptions) error RemoveImage(image string, opts dockertypes.ImageRemoveOptions) ([]dockertypes.ImageDelete, error) + ImageHistory(id string) ([]dockertypes.ImageHistory, error) Logs(string, dockertypes.ContainerLogsOptions, StreamOptions) error Version() (*dockertypes.Version, error) Info() (*dockertypes.Info, error) diff --git a/pkg/kubelet/dockertools/fake_docker_client.go b/pkg/kubelet/dockertools/fake_docker_client.go index dcb3d964472d7..c0ea8ae03767e 100644 --- a/pkg/kubelet/dockertools/fake_docker_client.go +++ b/pkg/kubelet/dockertools/fake_docker_client.go @@ -46,15 +46,16 @@ type FakeDockerClient struct { pulled []string // Created, Stopped and Removed all container docker ID - Created []string - Stopped []string - Removed []string - RemovedImages sets.String - VersionInfo dockertypes.Version - Information dockertypes.Info - ExecInspect *dockertypes.ContainerExecInspect - execCmd []string - EnableSleep bool + Created []string + Stopped []string + Removed []string + RemovedImages sets.String + VersionInfo dockertypes.Version + Information dockertypes.Info + ExecInspect *dockertypes.ContainerExecInspect + execCmd []string + EnableSleep bool + ImageHistoryMap map[string][]dockertypes.ImageHistory } // We don't check docker version now, just set the docker version of fake docker client to 1.8.1. @@ -482,6 +483,12 @@ func (f *FakeDockerClient) RemoveImage(image string, opts dockertypes.ImageRemov return []dockertypes.ImageDelete{{Deleted: image}}, err } +func (f *FakeDockerClient) InjectImages(images []dockertypes.Image) { + f.Lock() + defer f.Unlock() + f.Images = append(f.Images, images...) +} + func (f *FakeDockerClient) updateContainerStatus(id, status string) { for i := range f.RunningContainerList { if f.RunningContainerList[i].ID == id { @@ -528,6 +535,18 @@ func (f *FakeDockerPuller) IsImagePresent(name string) (bool, error) { } return false, nil } +func (f *FakeDockerClient) ImageHistory(id string) ([]dockertypes.ImageHistory, error) { + f.Lock() + defer f.Unlock() + history := f.ImageHistoryMap[id] + return history, nil +} + +func (f *FakeDockerClient) InjectImageHistory(data map[string][]dockertypes.ImageHistory) { + f.Lock() + defer f.Unlock() + f.ImageHistoryMap = data +} // dockerTimestampToString converts the timestamp to string func dockerTimestampToString(t time.Time) string { diff --git a/pkg/kubelet/dockertools/images.go b/pkg/kubelet/dockertools/images.go new file mode 100644 index 0000000000000..647327fb13c64 --- /dev/null +++ b/pkg/kubelet/dockertools/images.go @@ -0,0 +1,71 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 dockertools + +import ( + "fmt" + + "github.com/golang/glog" + + dockertypes "github.com/docker/engine-api/types" + runtime "k8s.io/kubernetes/pkg/kubelet/container" + "k8s.io/kubernetes/pkg/util/sets" +) + +// imageStatsProvider exposes stats about all images currently available. +type imageStatsProvider struct { + // Docker remote API client + c DockerInterface +} + +func (isp *imageStatsProvider) ImageStats() (*runtime.ImageStats, error) { + images, err := isp.c.ListImages(dockertypes.ImageListOptions{}) + if err != nil { + return nil, fmt.Errorf("failed to list docker images - %v", err) + } + // A map of all the image layers to its corresponding size. + imageMap := sets.NewString() + ret := &runtime.ImageStats{} + for _, image := range images { + // Get information about the various layers of each docker image. + history, err := isp.c.ImageHistory(image.ID) + if err != nil { + glog.V(2).Infof("failed to get history of docker image %v - %v", image, err) + continue + } + // Store size information of each layer. + for _, layer := range history { + // Skip empty layers. + if layer.Size == 0 { + glog.V(10).Infof("skipping image layer %v with size 0", layer) + continue + } + key := layer.ID + // Some of the layers are empty. + // We are hoping that these layers are unique to each image. + // Still keying with the CreatedBy field to be safe. + if key == "" || key == "" { + key = key + layer.CreatedBy + } + if !imageMap.Has(key) { + ret.TotalStorageBytes += uint64(layer.Size) + } + imageMap.Insert(key) + } + } + return ret, nil +} diff --git a/pkg/kubelet/dockertools/images_test.go b/pkg/kubelet/dockertools/images_test.go new file mode 100644 index 0000000000000..2607e0b982d87 --- /dev/null +++ b/pkg/kubelet/dockertools/images_test.go @@ -0,0 +1,103 @@ +/* +Copyright 2016 The Kubernetes Authors All rights reserved. + +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 dockertools + +import ( + "testing" + + dockertypes "github.com/docker/engine-api/types" + "github.com/stretchr/testify/assert" +) + +func TestImageStatsNoImages(t *testing.T) { + fakeDockerClient := NewFakeDockerClientWithVersion("1.2.3", "1.2") + isp := &imageStatsProvider{fakeDockerClient} + st, err := isp.ImageStats() + as := assert.New(t) + as.NoError(err) + as.Equal(st.TotalStorageBytes, uint64(0)) +} + +func TestImageStatsWithImages(t *testing.T) { + fakeDockerClient := NewFakeDockerClientWithVersion("1.2.3", "1.2") + fakeHistoryData := map[string][]dockertypes.ImageHistory{ + "busybox": { + { + ID: "0123456", + CreatedBy: "foo", + Size: 100, + }, + { + ID: "0123457", + CreatedBy: "duplicate", + Size: 200, + }, + { + ID: "", + CreatedBy: "baz", + Size: 300, + }, + }, + "kubelet": { + { + ID: "1123456", + CreatedBy: "foo", + Size: 200, + }, + { + ID: "", + CreatedBy: "1baz", + Size: 400, + }, + }, + "busybox-new": { + { + ID: "01234567", + CreatedBy: "foo", + Size: 100, + }, + { + ID: "0123457", + CreatedBy: "duplicate", + Size: 200, + }, + { + ID: "", + CreatedBy: "baz", + Size: 300, + }, + }, + } + fakeDockerClient.InjectImageHistory(fakeHistoryData) + fakeDockerClient.InjectImages([]dockertypes.Image{ + { + ID: "busybox", + }, + { + ID: "kubelet", + }, + { + ID: "busybox-new", + }, + }) + isp := &imageStatsProvider{fakeDockerClient} + st, err := isp.ImageStats() + as := assert.New(t) + as.NoError(err) + const expectedOutput uint64 = 1300 + as.Equal(expectedOutput, st.TotalStorageBytes, "expected %d, got %d", expectedOutput, st.TotalStorageBytes) +} diff --git a/pkg/kubelet/dockertools/instrumented_docker.go b/pkg/kubelet/dockertools/instrumented_docker.go index dd9355b49197d..2cbaeb9c6e088 100644 --- a/pkg/kubelet/dockertools/instrumented_docker.go +++ b/pkg/kubelet/dockertools/instrumented_docker.go @@ -199,3 +199,12 @@ func (in instrumentedDockerInterface) AttachToContainer(id string, opts dockerty recordError(operation, err) return err } + +func (in instrumentedDockerInterface) ImageHistory(id string) ([]dockertypes.ImageHistory, error) { + const operation = "image_history" + defer recordOperation(operation, time.Now()) + + out, err := in.client.ImageHistory(id) + recordError(operation, err) + return out, err +} diff --git a/pkg/kubelet/dockertools/kube_docker_client.go b/pkg/kubelet/dockertools/kube_docker_client.go index 44c2c248f4741..ad656d54a66e0 100644 --- a/pkg/kubelet/dockertools/kube_docker_client.go +++ b/pkg/kubelet/dockertools/kube_docker_client.go @@ -151,6 +151,10 @@ func (d *kubeDockerClient) InspectImage(image string) (*dockertypes.ImageInspect return &resp, nil } +func (d *kubeDockerClient) ImageHistory(id string) ([]dockertypes.ImageHistory, error) { + return d.client.ImageHistory(getDefaultContext(), id) +} + func (d *kubeDockerClient) ListImages(opts dockertypes.ImageListOptions) ([]dockertypes.Image, error) { images, err := d.client.ImageList(getDefaultContext(), opts) if err != nil { diff --git a/pkg/kubelet/dockertools/manager.go b/pkg/kubelet/dockertools/manager.go index 173cf732cd7a4..07c98da9ae70f 100644 --- a/pkg/kubelet/dockertools/manager.go +++ b/pkg/kubelet/dockertools/manager.go @@ -163,6 +163,9 @@ type DockerManager struct { // The api version cache of docker daemon. versionCache *cache.VersionCache + + // Provides image stats + *imageStatsProvider } // A subset of the pod.Manager interface extracted for testing purposes. @@ -240,6 +243,7 @@ func NewDockerManager( cpuCFSQuota: cpuCFSQuota, enableCustomMetrics: enableCustomMetrics, configureHairpinMode: hairpinMode, + imageStatsProvider: &imageStatsProvider{client}, } dm.runner = lifecycle.NewHandlerRunner(httpClient, dm, dm) if serializeImagePulls { diff --git a/pkg/kubelet/kubelet.go b/pkg/kubelet/kubelet.go index 3c548c2b043b3..cc15d2f0acafb 100644 --- a/pkg/kubelet/kubelet.go +++ b/pkg/kubelet/kubelet.go @@ -342,8 +342,6 @@ func NewMainKubelet( enableCustomMetrics: enableCustomMetrics, babysitDaemons: babysitDaemons, } - // TODO: Factor out "StatsProvider" from Kubelet so we don't have a cyclic dependency - klet.resourceAnalyzer = stats.NewResourceAnalyzer(klet, volumeStatsAggPeriod) if klet.flannelExperimentalOverlay { glog.Infof("Flannel is in charge of podCIDR and overlay networking.") @@ -440,6 +438,9 @@ func NewMainKubelet( return nil, fmt.Errorf("unsupported container runtime %q specified", containerRuntime) } + // TODO: Factor out "StatsProvider" from Kubelet so we don't have a cyclic dependency + klet.resourceAnalyzer = stats.NewResourceAnalyzer(klet, volumeStatsAggPeriod, klet.containerRuntime) + klet.pleg = pleg.NewGenericPLEG(klet.containerRuntime, plegChannelCapacity, plegRelistPeriod, klet.podCache, util.RealClock{}) klet.runtimeState = newRuntimeState(maxWaitForContainerRuntime, configureCBR0) klet.updatePodCIDR(podCIDR) @@ -3579,11 +3580,11 @@ func (kl *Kubelet) GetCachedMachineInfo() (*cadvisorapi.MachineInfo, error) { } func (kl *Kubelet) ListenAndServe(address net.IP, port uint, tlsOptions *server.TLSOptions, auth server.AuthInterface, enableDebuggingHandlers bool) { - server.ListenAndServeKubeletServer(kl, kl.resourceAnalyzer, address, port, tlsOptions, auth, enableDebuggingHandlers) + server.ListenAndServeKubeletServer(kl, kl.resourceAnalyzer, address, port, tlsOptions, auth, enableDebuggingHandlers, kl.containerRuntime) } func (kl *Kubelet) ListenAndServeReadOnly(address net.IP, port uint) { - server.ListenAndServeKubeletReadOnlyServer(kl, kl.resourceAnalyzer, address, port) + server.ListenAndServeKubeletReadOnlyServer(kl, kl.resourceAnalyzer, address, port, kl.containerRuntime) } // GetRuntime returns the current Runtime implementation in use by the kubelet. This func diff --git a/pkg/kubelet/rkt/rkt.go b/pkg/kubelet/rkt/rkt.go index d9af816d047de..5b091b7540170 100644 --- a/pkg/kubelet/rkt/rkt.go +++ b/pkg/kubelet/rkt/rkt.go @@ -1695,3 +1695,8 @@ func (r *Runtime) GetPodStatus(uid types.UID, name, namespace string) (*kubecont return podStatus, nil } + +// FIXME: I need to be implemented. +func (r *Runtime) ImageStats() (*kubecontainer.ImageStats, error) { + return &kubecontainer.ImageStats{}, nil +} diff --git a/pkg/kubelet/server/server.go b/pkg/kubelet/server/server.go index 15bd523a04d23..163ecf57b64ff 100644 --- a/pkg/kubelet/server/server.go +++ b/pkg/kubelet/server/server.go @@ -67,6 +67,7 @@ type Server struct { host HostInterface restfulCont containerInterface resourceAnalyzer stats.ResourceAnalyzer + runtime kubecontainer.Runtime } type TLSOptions struct { @@ -104,9 +105,17 @@ func (a *filteringContainer) RegisteredHandlePaths() []string { } // ListenAndServeKubeletServer initializes a server to respond to HTTP network requests on the Kubelet. -func ListenAndServeKubeletServer(host HostInterface, resourceAnalyzer stats.ResourceAnalyzer, address net.IP, port uint, tlsOptions *TLSOptions, auth AuthInterface, enableDebuggingHandlers bool) { +func ListenAndServeKubeletServer( + host HostInterface, + resourceAnalyzer stats.ResourceAnalyzer, + address net.IP, + port uint, + tlsOptions *TLSOptions, + auth AuthInterface, + enableDebuggingHandlers bool, + runtime kubecontainer.Runtime) { glog.Infof("Starting to listen on %s:%d", address, port) - handler := NewServer(host, resourceAnalyzer, auth, enableDebuggingHandlers) + handler := NewServer(host, resourceAnalyzer, auth, enableDebuggingHandlers, runtime) s := &http.Server{ Addr: net.JoinHostPort(address.String(), strconv.FormatUint(uint64(port), 10)), Handler: &handler, @@ -121,9 +130,9 @@ func ListenAndServeKubeletServer(host HostInterface, resourceAnalyzer stats.Reso } // ListenAndServeKubeletReadOnlyServer initializes a server to respond to HTTP network requests on the Kubelet. -func ListenAndServeKubeletReadOnlyServer(host HostInterface, resourceAnalyzer stats.ResourceAnalyzer, address net.IP, port uint) { +func ListenAndServeKubeletReadOnlyServer(host HostInterface, resourceAnalyzer stats.ResourceAnalyzer, address net.IP, port uint, runtime kubecontainer.Runtime) { glog.V(1).Infof("Starting to listen read-only on %s:%d", address, port) - s := NewServer(host, resourceAnalyzer, nil, false) + s := NewServer(host, resourceAnalyzer, nil, false, runtime) server := &http.Server{ Addr: net.JoinHostPort(address.String(), strconv.FormatUint(uint64(port), 10)), @@ -169,12 +178,18 @@ type HostInterface interface { } // NewServer initializes and configures a kubelet.Server object to handle HTTP requests. -func NewServer(host HostInterface, resourceAnalyzer stats.ResourceAnalyzer, auth AuthInterface, enableDebuggingHandlers bool) Server { +func NewServer( + host HostInterface, + resourceAnalyzer stats.ResourceAnalyzer, + auth AuthInterface, + enableDebuggingHandlers bool, + runtime kubecontainer.Runtime) Server { server := Server{ host: host, resourceAnalyzer: resourceAnalyzer, auth: auth, restfulCont: &filteringContainer{Container: restful.NewContainer()}, + runtime: runtime, } if auth != nil { server.InstallAuthFilter() diff --git a/pkg/kubelet/server/server_test.go b/pkg/kubelet/server/server_test.go index c52413b6f9f89..48882642f5a8e 100644 --- a/pkg/kubelet/server/server_test.go +++ b/pkg/kubelet/server/server_test.go @@ -41,6 +41,7 @@ import ( "k8s.io/kubernetes/pkg/auth/user" "k8s.io/kubernetes/pkg/kubelet/cm" kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" + kubecontainertesting "k8s.io/kubernetes/pkg/kubelet/container/testing" "k8s.io/kubernetes/pkg/kubelet/server/stats" kubetypes "k8s.io/kubernetes/pkg/kubelet/types" "k8s.io/kubernetes/pkg/types" @@ -208,9 +209,10 @@ func newServerTest() *serverTestFramework { } server := NewServer( fw.fakeKubelet, - stats.NewResourceAnalyzer(fw.fakeKubelet, time.Minute), + stats.NewResourceAnalyzer(fw.fakeKubelet, time.Minute, &kubecontainertesting.FakeRuntime{}), fw.fakeAuth, - true) + true, + &kubecontainertesting.Mock{}) fw.serverUnderTest = &server // TODO: Close() this when fix #19254 fw.testHTTPServer = httptest.NewServer(fw.serverUnderTest) diff --git a/pkg/kubelet/server/stats/resource_analyzer.go b/pkg/kubelet/server/stats/resource_analyzer.go index 594611d76d52a..a9e9dbbf0aced 100644 --- a/pkg/kubelet/server/stats/resource_analyzer.go +++ b/pkg/kubelet/server/stats/resource_analyzer.go @@ -16,7 +16,11 @@ limitations under the License. package stats -import "time" +import ( + "time" + + "k8s.io/kubernetes/pkg/kubelet/container" +) // ResourceAnalyzer provides statistics on node resource consumption type ResourceAnalyzer interface { @@ -35,9 +39,9 @@ type resourceAnalyzer struct { var _ ResourceAnalyzer = &resourceAnalyzer{} // NewResourceAnalyzer returns a new ResourceAnalyzer -func NewResourceAnalyzer(statsProvider StatsProvider, calVolumeFrequency time.Duration) ResourceAnalyzer { +func NewResourceAnalyzer(statsProvider StatsProvider, calVolumeFrequency time.Duration, runtime container.Runtime) ResourceAnalyzer { fsAnalyzer := newFsResourceAnalyzer(statsProvider, calVolumeFrequency) - summaryProvider := NewSummaryProvider(statsProvider, fsAnalyzer) + summaryProvider := NewSummaryProvider(statsProvider, fsAnalyzer, runtime) return &resourceAnalyzer{fsAnalyzer, summaryProvider} } diff --git a/pkg/kubelet/server/stats/summary.go b/pkg/kubelet/server/stats/summary.go index e519cd6951d1d..5a1ee279738bf 100644 --- a/pkg/kubelet/server/stats/summary.go +++ b/pkg/kubelet/server/stats/summary.go @@ -26,6 +26,7 @@ import ( "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/stats" "k8s.io/kubernetes/pkg/kubelet/cm" + "k8s.io/kubernetes/pkg/kubelet/container" "k8s.io/kubernetes/pkg/kubelet/dockertools" "k8s.io/kubernetes/pkg/kubelet/leaky" "k8s.io/kubernetes/pkg/kubelet/network" @@ -45,15 +46,16 @@ type SummaryProvider interface { type summaryProviderImpl struct { provider StatsProvider fsResourceAnalyzer fsResourceAnalyzerInterface + runtime container.Runtime } var _ SummaryProvider = &summaryProviderImpl{} // NewSummaryProvider returns a new SummaryProvider -func NewSummaryProvider(statsProvider StatsProvider, fsResourceAnalyzer fsResourceAnalyzerInterface) SummaryProvider { +func NewSummaryProvider(statsProvider StatsProvider, fsResourceAnalyzer fsResourceAnalyzerInterface, cruntime container.Runtime) SummaryProvider { stackBuff := []byte{} runtime.Stack(stackBuff, false) - return &summaryProviderImpl{statsProvider, fsResourceAnalyzer} + return &summaryProviderImpl{statsProvider, fsResourceAnalyzer, cruntime} } // Get implements the SummaryProvider interface @@ -83,8 +85,11 @@ func (sp *summaryProviderImpl) Get() (*stats.Summary, error) { if err != nil { return nil, err } - - sb := &summaryBuilder{sp.fsResourceAnalyzer, node, nodeConfig, rootFsInfo, imageFsInfo, infos} + imageStats, err := sp.runtime.ImageStats() + if err != nil || imageStats == nil { + return nil, err + } + sb := &summaryBuilder{sp.fsResourceAnalyzer, node, nodeConfig, rootFsInfo, imageFsInfo, *imageStats, infos} return sb.build() } @@ -95,6 +100,7 @@ type summaryBuilder struct { nodeConfig cm.NodeConfig rootFsInfo cadvisorapiv2.FsInfo imageFsInfo cadvisorapiv2.FsInfo + imageStats container.ImageStats infos map[string]cadvisorapiv2.ContainerInfo } @@ -116,6 +122,13 @@ func (sb *summaryBuilder) build() (*stats.Summary, error) { CapacityBytes: &sb.rootFsInfo.Capacity, UsedBytes: &sb.rootFsInfo.Usage}, StartTime: rootStats.StartTime, + Runtime: &stats.RuntimeStats{ + ImageFs: &stats.FsStats{ + AvailableBytes: &sb.imageFsInfo.Available, + CapacityBytes: &sb.imageFsInfo.Capacity, + UsedBytes: &sb.imageStats.TotalStorageBytes, + }, + }, } systemContainers := map[string]string{ @@ -152,7 +165,6 @@ func (sb *summaryBuilder) containerInfoV2FsStats( AvailableBytes: &sb.imageFsInfo.Available, CapacityBytes: &sb.imageFsInfo.Capacity, } - lcs, found := sb.latestContainerStats(info) if !found { return diff --git a/pkg/kubelet/server/stats/summary_test.go b/pkg/kubelet/server/stats/summary_test.go index f75b1f35f2387..a90031c0806fb 100644 --- a/pkg/kubelet/server/stats/summary_test.go +++ b/pkg/kubelet/server/stats/summary_test.go @@ -29,6 +29,7 @@ import ( "k8s.io/kubernetes/pkg/api/unversioned" kubestats "k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/stats" "k8s.io/kubernetes/pkg/kubelet/cm" + "k8s.io/kubernetes/pkg/kubelet/container" "k8s.io/kubernetes/pkg/kubelet/leaky" ) @@ -127,7 +128,7 @@ func TestBuildSummary(t *testing.T) { } sb := &summaryBuilder{ - newFsResourceAnalyzer(&MockStatsProvider{}, time.Minute*5), &node, nodeConfig, rootfs, imagefs, infos} + newFsResourceAnalyzer(&MockStatsProvider{}, time.Minute*5), &node, nodeConfig, rootfs, imagefs, container.ImageStats{}, infos} summary, err := sb.build() assert.NoError(t, err) diff --git a/test/e2e_node/kubelet_test.go b/test/e2e_node/kubelet_test.go index aecf97e3a4b90..cc74c2ab13003 100644 --- a/test/e2e_node/kubelet_test.go +++ b/test/e2e_node/kubelet_test.go @@ -203,6 +203,16 @@ var _ = Describe("Kubelet", func() { Expect(summary.Node.Fs.UsedBytes).NotTo(BeNil()) Expect(*summary.Node.Fs.UsedBytes).NotTo(BeZero()) + By("Having container runtime's image storage information") + Expect(summary.Node.Runtime).NotTo(BeNil()) + Expect(summary.Node.Runtime.ImageFs).NotTo(BeNil()) + Expect(summary.Node.Runtime.ImageFs.AvailableBytes).NotTo(BeNil()) + Expect(*summary.Node.Runtime.ImageFs.AvailableBytes).NotTo(BeZero()) + Expect(summary.Node.Runtime.ImageFs.CapacityBytes).NotTo(BeNil()) + Expect(*summary.Node.Runtime.ImageFs.CapacityBytes).NotTo(BeZero()) + Expect(summary.Node.Runtime.ImageFs.UsedBytes).NotTo(BeNil()) + Expect(*summary.Node.Runtime.ImageFs.UsedBytes).NotTo(BeZero()) + By("Having resources for kubelet and runtime system containers") sysContainers := map[string]stats.ContainerStats{} sysContainersList := []string{}