Merge pull request #80105 from ASankaran/dockershim-linux

Add ImageFSInfo, ContainerStats, and ListContainerStats impl for linux to dockershim
This commit is contained in:
Kubernetes Prow Robot 2019-08-06 18:14:51 -07:00 committed by GitHub
commit 7f1ae0e32d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 244 additions and 65 deletions

View File

@ -17,6 +17,7 @@ go_library(
"docker_logs.go",
"docker_sandbox.go",
"docker_service.go",
"docker_stats.go",
"docker_stats_linux.go",
"docker_stats_unsupported.go",
"docker_stats_windows.go",
@ -93,6 +94,7 @@ go_test(
"docker_image_test.go",
"docker_sandbox_test.go",
"docker_service_test.go",
"docker_stats_test.go",
"helpers_linux_test.go",
"helpers_test.go",
"naming_test.go",

View File

@ -20,12 +20,58 @@ package dockershim
import (
"context"
"fmt"
"os"
"path/filepath"
"time"
"k8s.io/klog"
runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
)
// ImageFsInfo returns information of the filesystem that is used to store images.
func (ds *dockerService) ImageFsInfo(_ context.Context, r *runtimeapi.ImageFsInfoRequest) (*runtimeapi.ImageFsInfoResponse, error) {
return nil, fmt.Errorf("not implemented")
func (ds *dockerService) ImageFsInfo(_ context.Context, _ *runtimeapi.ImageFsInfoRequest) (*runtimeapi.ImageFsInfoResponse, error) {
info, err := ds.client.Info()
if err != nil {
klog.Errorf("Failed to get docker info: %v", err)
return nil, err
}
bytes, inodes, err := dirSize(filepath.Join(info.DockerRootDir, "image"))
if err != nil {
return nil, err
}
return &runtimeapi.ImageFsInfoResponse{
ImageFilesystems: []*runtimeapi.FilesystemUsage{
{
Timestamp: time.Now().Unix(),
FsId: &runtimeapi.FilesystemIdentifier{
Mountpoint: info.DockerRootDir,
},
UsedBytes: &runtimeapi.UInt64Value{
Value: uint64(bytes),
},
InodesUsed: &runtimeapi.UInt64Value{
Value: uint64(inodes),
},
},
},
}, nil
}
func dirSize(path string) (int64, int64, error) {
bytes := int64(0)
inodes := int64(0)
err := filepath.Walk(path, func(dir string, info os.FileInfo, err error) error {
if err != nil {
return err
}
inodes += 1
if !info.IsDir() {
bytes += info.Size()
}
return nil
})
return bytes, inodes, err
}

View File

@ -0,0 +1,61 @@
/*
Copyright 2019 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 dockershim
import (
"context"
runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
)
// ContainerStats returns stats for a container stats request based on container id.
func (ds *dockerService) ContainerStats(_ context.Context, r *runtimeapi.ContainerStatsRequest) (*runtimeapi.ContainerStatsResponse, error) {
stats, err := ds.getContainerStats(r.ContainerId)
if err != nil {
return nil, err
}
return &runtimeapi.ContainerStatsResponse{Stats: stats}, nil
}
// ListContainerStats returns stats for a list container stats request based on a filter.
func (ds *dockerService) ListContainerStats(ctx context.Context, r *runtimeapi.ListContainerStatsRequest) (*runtimeapi.ListContainerStatsResponse, error) {
containerStatsFilter := r.GetFilter()
filter := &runtimeapi.ContainerFilter{}
if containerStatsFilter != nil {
filter.Id = containerStatsFilter.Id
filter.PodSandboxId = containerStatsFilter.PodSandboxId
filter.LabelSelector = containerStatsFilter.LabelSelector
}
listResp, err := ds.ListContainers(ctx, &runtimeapi.ListContainersRequest{Filter: filter})
if err != nil {
return nil, err
}
var stats []*runtimeapi.ContainerStats
for _, container := range listResp.Containers {
containerStats, err := ds.getContainerStats(container.Id)
if err != nil {
return nil, err
}
stats = append(stats, containerStats)
}
return &runtimeapi.ListContainerStatsResponse{Stats: stats}, nil
}

View File

@ -20,17 +20,55 @@ package dockershim
import (
"context"
"fmt"
"time"
runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
)
// ContainerStats returns stats for a container stats request based on container id.
func (ds *dockerService) ContainerStats(_ context.Context, r *runtimeapi.ContainerStatsRequest) (*runtimeapi.ContainerStatsResponse, error) {
return nil, fmt.Errorf("not implemented")
}
func (ds *dockerService) getContainerStats(containerID string) (*runtimeapi.ContainerStats, error) {
info, err := ds.client.Info()
if err != nil {
return nil, err
}
// ListContainerStats returns stats for a list container stats request based on a filter.
func (ds *dockerService) ListContainerStats(_ context.Context, r *runtimeapi.ListContainerStatsRequest) (*runtimeapi.ListContainerStatsResponse, error) {
return nil, fmt.Errorf("not implemented")
statsJSON, err := ds.client.GetContainerStats(containerID)
if err != nil {
return nil, err
}
containerJSON, err := ds.client.InspectContainerWithSize(containerID)
if err != nil {
return nil, err
}
statusResp, err := ds.ContainerStatus(context.Background(), &runtimeapi.ContainerStatusRequest{ContainerId: containerID})
if err != nil {
return nil, err
}
status := statusResp.GetStatus()
dockerStats := statsJSON.Stats
timestamp := time.Now().UnixNano()
containerStats := &runtimeapi.ContainerStats{
Attributes: &runtimeapi.ContainerAttributes{
Id: containerID,
Metadata: status.Metadata,
Labels: status.Labels,
Annotations: status.Annotations,
},
Cpu: &runtimeapi.CpuUsage{
Timestamp: timestamp,
UsageCoreNanoSeconds: &runtimeapi.UInt64Value{Value: dockerStats.CPUStats.CPUUsage.TotalUsage},
},
Memory: &runtimeapi.MemoryUsage{
Timestamp: timestamp,
WorkingSetBytes: &runtimeapi.UInt64Value{Value: dockerStats.MemoryStats.PrivateWorkingSet},
},
WritableLayer: &runtimeapi.FilesystemUsage{
Timestamp: timestamp,
FsId: &runtimeapi.FilesystemIdentifier{Mountpoint: info.DockerRootDir},
UsedBytes: &runtimeapi.UInt64Value{Value: uint64(*containerJSON.SizeRw)},
},
}
return containerStats, nil
}

View File

@ -0,0 +1,66 @@
/*
Copyright 2019 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 dockershim
import (
"testing"
dockertypes "github.com/docker/docker/api/types"
"github.com/stretchr/testify/assert"
runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
"k8s.io/kubernetes/pkg/kubelet/dockershim/libdocker"
)
func TestContainerStats(t *testing.T) {
tests := map[string]struct {
containerID string
container *libdocker.FakeContainer
containerStats *dockertypes.StatsJSON
calledDetails []libdocker.CalledDetail
}{
"container exists": {
"fake_container",
&libdocker.FakeContainer{ID: "fake_container"},
&dockertypes.StatsJSON{},
[]libdocker.CalledDetail{
libdocker.NewCalledDetail("get_container_stats", nil),
libdocker.NewCalledDetail("inspect_container_withsize", nil),
libdocker.NewCalledDetail("inspect_container", nil),
libdocker.NewCalledDetail("inspect_image", nil),
},
},
"container doesn't exists": {
"nonexistant_fake_container",
&libdocker.FakeContainer{ID: "fake_container"},
&dockertypes.StatsJSON{},
[]libdocker.CalledDetail{
libdocker.NewCalledDetail("get_container_stats", nil),
},
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
ds, fakeDocker, _ := newTestDockerService()
fakeDocker.SetFakeContainers([]*libdocker.FakeContainer{test.container})
fakeDocker.InjectContainerStats(map[string]*dockertypes.StatsJSON{test.container.ID: test.containerStats})
ds.ContainerStats(getTestCTX(), &runtimeapi.ContainerStatsRequest{ContainerId: test.containerID})
err := fakeDocker.AssertCallDetails(test.calledDetails...)
assert.NoError(t, err)
})
}
}

View File

@ -19,18 +19,11 @@ limitations under the License.
package dockershim
import (
"context"
"fmt"
runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
)
// ContainerStats returns stats for a container stats request based on container id.
func (ds *dockerService) ContainerStats(_ context.Context, r *runtimeapi.ContainerStatsRequest) (*runtimeapi.ContainerStatsResponse, error) {
return nil, fmt.Errorf("not implemented")
}
// ListContainerStats returns stats for a list container stats request based on a filter.
func (ds *dockerService) ListContainerStats(_ context.Context, r *runtimeapi.ListContainerStatsRequest) (*runtimeapi.ListContainerStatsResponse, error) {
func (ds *dockerService) getContainerStats(containerID string) (*runtimeapi.ContainerStats, error) {
return nil, fmt.Errorf("not implemented")
}

View File

@ -25,44 +25,6 @@ import (
runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
)
// ContainerStats returns stats for a container stats request based on container id.
func (ds *dockerService) ContainerStats(_ context.Context, r *runtimeapi.ContainerStatsRequest) (*runtimeapi.ContainerStatsResponse, error) {
stats, err := ds.getContainerStats(r.ContainerId)
if err != nil {
return nil, err
}
return &runtimeapi.ContainerStatsResponse{Stats: stats}, nil
}
// ListContainerStats returns stats for a list container stats request based on a filter.
func (ds *dockerService) ListContainerStats(ctx context.Context, r *runtimeapi.ListContainerStatsRequest) (*runtimeapi.ListContainerStatsResponse, error) {
containerStatsFilter := r.GetFilter()
filter := &runtimeapi.ContainerFilter{}
if containerStatsFilter != nil {
filter.Id = containerStatsFilter.Id
filter.PodSandboxId = containerStatsFilter.PodSandboxId
filter.LabelSelector = containerStatsFilter.LabelSelector
}
listResp, err := ds.ListContainers(ctx, &runtimeapi.ListContainersRequest{Filter: filter})
if err != nil {
return nil, err
}
var stats []*runtimeapi.ContainerStats
for _, container := range listResp.Containers {
containerStats, err := ds.getContainerStats(container.Id)
if err != nil {
return nil, err
}
stats = append(stats, containerStats)
}
return &runtimeapi.ListContainerStatsResponse{Stats: stats}, nil
}
func (ds *dockerService) getContainerStats(containerID string) (*runtimeapi.ContainerStats, error) {
info, err := ds.client.Info()
if err != nil {

View File

@ -71,12 +71,13 @@ type FakeDockerClient struct {
// Images pulled by ref (name or ID).
ImagesPulled []string
VersionInfo dockertypes.Version
Information dockertypes.Info
ExecInspect *dockertypes.ContainerExecInspect
execCmd []string
EnableSleep bool
ImageHistoryMap map[string][]dockerimagetypes.HistoryResponseItem
VersionInfo dockertypes.Version
Information dockertypes.Info
ExecInspect *dockertypes.ContainerExecInspect
execCmd []string
EnableSleep bool
ImageHistoryMap map[string][]dockerimagetypes.HistoryResponseItem
ContainerStatsMap map[string]*dockertypes.StatsJSON
}
const (
@ -913,9 +914,19 @@ func (f *FakeDockerPuller) GetImageRef(image string) (string, error) {
return image, err
}
func (f *FakeDockerClient) InjectContainerStats(data map[string]*dockertypes.StatsJSON) {
f.Lock()
defer f.Unlock()
f.ContainerStatsMap = data
}
func (f *FakeDockerClient) GetContainerStats(id string) (*dockertypes.StatsJSON, error) {
f.Lock()
defer f.Unlock()
f.appendCalled(CalledDetail{name: "getContainerStats"})
return nil, fmt.Errorf("not implemented")
f.appendCalled(CalledDetail{name: "get_container_stats"})
stats, ok := f.ContainerStatsMap[id]
if !ok {
return nil, fmt.Errorf("container %q not found", id)
}
return stats, nil
}