Add generated file
This PR adds generated files under pkg/client and vendor folder.
This commit is contained in:
59
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/libdocker/BUILD
generated
vendored
Normal file
59
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/libdocker/BUILD
generated
vendored
Normal file
@@ -0,0 +1,59 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"helpers_test.go",
|
||||
"kube_docker_client_test.go",
|
||||
],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//vendor/github.com/docker/docker/api/types:go_default_library",
|
||||
"//vendor/github.com/stretchr/testify/assert:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"client.go",
|
||||
"fake_client.go",
|
||||
"helpers.go",
|
||||
"instrumented_client.go",
|
||||
"kube_docker_client.go",
|
||||
],
|
||||
importpath = "k8s.io/kubernetes/pkg/kubelet/dockershim/libdocker",
|
||||
deps = [
|
||||
"//pkg/kubelet/dockershim/metrics:go_default_library",
|
||||
"//vendor/github.com/docker/distribution/reference:go_default_library",
|
||||
"//vendor/github.com/docker/docker/api/types:go_default_library",
|
||||
"//vendor/github.com/docker/docker/api/types/container:go_default_library",
|
||||
"//vendor/github.com/docker/docker/api/types/image:go_default_library",
|
||||
"//vendor/github.com/docker/docker/client:go_default_library",
|
||||
"//vendor/github.com/docker/docker/pkg/jsonmessage:go_default_library",
|
||||
"//vendor/github.com/docker/docker/pkg/stdcopy:go_default_library",
|
||||
"//vendor/github.com/golang/glog:go_default_library",
|
||||
"//vendor/github.com/opencontainers/go-digest:go_default_library",
|
||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/clock:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
106
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/libdocker/client.go
generated
vendored
Normal file
106
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/libdocker/client.go
generated
vendored
Normal file
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
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 libdocker
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
dockertypes "github.com/docker/docker/api/types"
|
||||
dockercontainer "github.com/docker/docker/api/types/container"
|
||||
dockerimagetypes "github.com/docker/docker/api/types/image"
|
||||
dockerapi "github.com/docker/docker/client"
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
const (
|
||||
// https://docs.docker.com/engine/reference/api/docker_remote_api/
|
||||
// docker version should be at least 1.11.x
|
||||
MinimumDockerAPIVersion = "1.23.0"
|
||||
|
||||
// Status of a container returned by ListContainers.
|
||||
StatusRunningPrefix = "Up"
|
||||
StatusCreatedPrefix = "Created"
|
||||
StatusExitedPrefix = "Exited"
|
||||
|
||||
// Fake docker endpoint
|
||||
FakeDockerEndpoint = "fake://"
|
||||
)
|
||||
|
||||
// Interface is an abstract interface for testability. It abstracts the interface of docker client.
|
||||
type Interface interface {
|
||||
ListContainers(options dockertypes.ContainerListOptions) ([]dockertypes.Container, error)
|
||||
InspectContainer(id string) (*dockertypes.ContainerJSON, error)
|
||||
InspectContainerWithSize(id string) (*dockertypes.ContainerJSON, error)
|
||||
CreateContainer(dockertypes.ContainerCreateConfig) (*dockercontainer.ContainerCreateCreatedBody, error)
|
||||
StartContainer(id string) error
|
||||
StopContainer(id string, timeout time.Duration) error
|
||||
UpdateContainerResources(id string, updateConfig dockercontainer.UpdateConfig) error
|
||||
RemoveContainer(id string, opts dockertypes.ContainerRemoveOptions) error
|
||||
InspectImageByRef(imageRef string) (*dockertypes.ImageInspect, error)
|
||||
InspectImageByID(imageID string) (*dockertypes.ImageInspect, error)
|
||||
ListImages(opts dockertypes.ImageListOptions) ([]dockertypes.ImageSummary, error)
|
||||
PullImage(image string, auth dockertypes.AuthConfig, opts dockertypes.ImagePullOptions) error
|
||||
RemoveImage(image string, opts dockertypes.ImageRemoveOptions) ([]dockertypes.ImageDeleteResponseItem, error)
|
||||
ImageHistory(id string) ([]dockerimagetypes.HistoryResponseItem, error)
|
||||
Logs(string, dockertypes.ContainerLogsOptions, StreamOptions) error
|
||||
Version() (*dockertypes.Version, error)
|
||||
Info() (*dockertypes.Info, error)
|
||||
CreateExec(string, dockertypes.ExecConfig) (*dockertypes.IDResponse, error)
|
||||
StartExec(string, dockertypes.ExecStartCheck, StreamOptions) error
|
||||
InspectExec(id string) (*dockertypes.ContainerExecInspect, error)
|
||||
AttachToContainer(string, dockertypes.ContainerAttachOptions, StreamOptions) error
|
||||
ResizeContainerTTY(id string, height, width uint) error
|
||||
ResizeExecTTY(id string, height, width uint) error
|
||||
GetContainerStats(id string) (*dockertypes.StatsJSON, error)
|
||||
}
|
||||
|
||||
// Get a *dockerapi.Client, either using the endpoint passed in, or using
|
||||
// DOCKER_HOST, DOCKER_TLS_VERIFY, and DOCKER_CERT path per their spec
|
||||
func getDockerClient(dockerEndpoint string) (*dockerapi.Client, error) {
|
||||
if len(dockerEndpoint) > 0 {
|
||||
glog.Infof("Connecting to docker on %s", dockerEndpoint)
|
||||
return dockerapi.NewClient(dockerEndpoint, "", nil, nil)
|
||||
}
|
||||
return dockerapi.NewEnvClient()
|
||||
}
|
||||
|
||||
// ConnectToDockerOrDie creates docker client connecting to docker daemon.
|
||||
// If the endpoint passed in is "fake://", a fake docker client
|
||||
// will be returned. The program exits if error occurs. The requestTimeout
|
||||
// is the timeout for docker requests. If timeout is exceeded, the request
|
||||
// will be cancelled and throw out an error. If requestTimeout is 0, a default
|
||||
// value will be applied.
|
||||
func ConnectToDockerOrDie(dockerEndpoint string, requestTimeout, imagePullProgressDeadline time.Duration,
|
||||
withTraceDisabled bool, enableSleep bool) Interface {
|
||||
if dockerEndpoint == FakeDockerEndpoint {
|
||||
fakeClient := NewFakeDockerClient()
|
||||
if withTraceDisabled {
|
||||
fakeClient = fakeClient.WithTraceDisabled()
|
||||
}
|
||||
|
||||
if enableSleep {
|
||||
fakeClient.EnableSleep = true
|
||||
}
|
||||
return fakeClient
|
||||
}
|
||||
client, err := getDockerClient(dockerEndpoint)
|
||||
if err != nil {
|
||||
glog.Fatalf("Couldn't connect to docker: %v", err)
|
||||
}
|
||||
glog.Infof("Start docker client with request timeout=%v", requestTimeout)
|
||||
return newKubeDockerClient(client, requestTimeout, imagePullProgressDeadline)
|
||||
}
|
921
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/libdocker/fake_client.go
generated
vendored
Normal file
921
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/libdocker/fake_client.go
generated
vendored
Normal file
@@ -0,0 +1,921 @@
|
||||
/*
|
||||
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 libdocker
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"math/rand"
|
||||
"os"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
dockertypes "github.com/docker/docker/api/types"
|
||||
dockercontainer "github.com/docker/docker/api/types/container"
|
||||
dockerimagetypes "github.com/docker/docker/api/types/image"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/util/clock"
|
||||
)
|
||||
|
||||
type calledDetail struct {
|
||||
name string
|
||||
arguments []interface{}
|
||||
}
|
||||
|
||||
// NewCalledDetail create a new call detail item.
|
||||
func NewCalledDetail(name string, arguments []interface{}) calledDetail {
|
||||
return calledDetail{name: name, arguments: arguments}
|
||||
}
|
||||
|
||||
// FakeDockerClient is a simple fake docker client, so that kubelet can be run for testing without requiring a real docker setup.
|
||||
type FakeDockerClient struct {
|
||||
sync.Mutex
|
||||
Clock clock.Clock
|
||||
RunningContainerList []dockertypes.Container
|
||||
ExitedContainerList []dockertypes.Container
|
||||
ContainerMap map[string]*dockertypes.ContainerJSON
|
||||
ImageInspects map[string]*dockertypes.ImageInspect
|
||||
Images []dockertypes.ImageSummary
|
||||
ImageIDsNeedingAuth map[string]dockertypes.AuthConfig
|
||||
Errors map[string]error
|
||||
called []calledDetail
|
||||
pulled []string
|
||||
EnableTrace bool
|
||||
RandGenerator *rand.Rand
|
||||
|
||||
// Created, Started, Stopped and Removed all contain container docker ID
|
||||
Created []string
|
||||
Started []string
|
||||
Stopped []string
|
||||
Removed []string
|
||||
// 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
|
||||
}
|
||||
|
||||
const (
|
||||
// Notice that if someday we also have minimum docker version requirement, this should also be updated.
|
||||
fakeDockerVersion = "1.11.2"
|
||||
|
||||
fakeImageSize = 1024
|
||||
|
||||
// Docker prepends '/' to the container name.
|
||||
dockerNamePrefix = "/"
|
||||
)
|
||||
|
||||
func NewFakeDockerClient() *FakeDockerClient {
|
||||
return &FakeDockerClient{
|
||||
// Docker's API version does not include the patch number.
|
||||
VersionInfo: dockertypes.Version{Version: fakeDockerVersion, APIVersion: strings.TrimSuffix(MinimumDockerAPIVersion, ".0")},
|
||||
Errors: make(map[string]error),
|
||||
ContainerMap: make(map[string]*dockertypes.ContainerJSON),
|
||||
Clock: clock.RealClock{},
|
||||
// default this to true, so that we trace calls, image pulls and container lifecycle
|
||||
EnableTrace: true,
|
||||
ImageInspects: make(map[string]*dockertypes.ImageInspect),
|
||||
ImageIDsNeedingAuth: make(map[string]dockertypes.AuthConfig),
|
||||
RandGenerator: rand.New(rand.NewSource(time.Now().UnixNano())),
|
||||
}
|
||||
}
|
||||
|
||||
func (f *FakeDockerClient) WithClock(c clock.Clock) *FakeDockerClient {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
f.Clock = c
|
||||
return f
|
||||
}
|
||||
|
||||
func (f *FakeDockerClient) WithVersion(version, apiVersion string) *FakeDockerClient {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
f.VersionInfo = dockertypes.Version{Version: version, APIVersion: apiVersion}
|
||||
return f
|
||||
}
|
||||
|
||||
func (f *FakeDockerClient) WithTraceDisabled() *FakeDockerClient {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
f.EnableTrace = false
|
||||
return f
|
||||
}
|
||||
|
||||
func (f *FakeDockerClient) WithRandSource(source rand.Source) *FakeDockerClient {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
f.RandGenerator = rand.New(source)
|
||||
return f
|
||||
}
|
||||
|
||||
func (f *FakeDockerClient) appendCalled(callDetail calledDetail) {
|
||||
if f.EnableTrace {
|
||||
f.called = append(f.called, callDetail)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *FakeDockerClient) appendPulled(pull string) {
|
||||
if f.EnableTrace {
|
||||
f.pulled = append(f.pulled, pull)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *FakeDockerClient) appendContainerTrace(traceCategory string, containerName string) {
|
||||
if !f.EnableTrace {
|
||||
return
|
||||
}
|
||||
switch traceCategory {
|
||||
case "Created":
|
||||
f.Created = append(f.Created, containerName)
|
||||
case "Started":
|
||||
f.Started = append(f.Started, containerName)
|
||||
case "Stopped":
|
||||
f.Stopped = append(f.Stopped, containerName)
|
||||
case "Removed":
|
||||
f.Removed = append(f.Removed, containerName)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *FakeDockerClient) InjectError(fn string, err error) {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
f.Errors[fn] = err
|
||||
}
|
||||
|
||||
func (f *FakeDockerClient) InjectErrors(errs map[string]error) {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
for fn, err := range errs {
|
||||
f.Errors[fn] = err
|
||||
}
|
||||
}
|
||||
|
||||
func (f *FakeDockerClient) ClearErrors() {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
f.Errors = map[string]error{}
|
||||
}
|
||||
|
||||
func (f *FakeDockerClient) ClearCalls() {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
f.called = []calledDetail{}
|
||||
f.pulled = []string{}
|
||||
f.Created = []string{}
|
||||
f.Started = []string{}
|
||||
f.Stopped = []string{}
|
||||
f.Removed = []string{}
|
||||
}
|
||||
|
||||
func (f *FakeDockerClient) getCalledNames() []string {
|
||||
names := []string{}
|
||||
for _, detail := range f.called {
|
||||
names = append(names, detail.name)
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
// Because the new data type returned by engine-api is too complex to manually initialize, we need a
|
||||
// fake container which is easier to initialize.
|
||||
type FakeContainer struct {
|
||||
ID string
|
||||
Name string
|
||||
Running bool
|
||||
ExitCode int
|
||||
Pid int
|
||||
CreatedAt time.Time
|
||||
StartedAt time.Time
|
||||
FinishedAt time.Time
|
||||
Config *dockercontainer.Config
|
||||
HostConfig *dockercontainer.HostConfig
|
||||
}
|
||||
|
||||
// convertFakeContainer converts the fake container to real container
|
||||
func convertFakeContainer(f *FakeContainer) *dockertypes.ContainerJSON {
|
||||
if f.Config == nil {
|
||||
f.Config = &dockercontainer.Config{}
|
||||
}
|
||||
if f.HostConfig == nil {
|
||||
f.HostConfig = &dockercontainer.HostConfig{}
|
||||
}
|
||||
return &dockertypes.ContainerJSON{
|
||||
ContainerJSONBase: &dockertypes.ContainerJSONBase{
|
||||
ID: f.ID,
|
||||
Name: f.Name,
|
||||
Image: f.Config.Image,
|
||||
State: &dockertypes.ContainerState{
|
||||
Running: f.Running,
|
||||
ExitCode: f.ExitCode,
|
||||
Pid: f.Pid,
|
||||
StartedAt: dockerTimestampToString(f.StartedAt),
|
||||
FinishedAt: dockerTimestampToString(f.FinishedAt),
|
||||
},
|
||||
Created: dockerTimestampToString(f.CreatedAt),
|
||||
HostConfig: f.HostConfig,
|
||||
},
|
||||
Config: f.Config,
|
||||
NetworkSettings: &dockertypes.NetworkSettings{},
|
||||
}
|
||||
}
|
||||
|
||||
func (f *FakeDockerClient) SetFakeContainers(containers []*FakeContainer) {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
// Reset the lists and the map.
|
||||
f.ContainerMap = map[string]*dockertypes.ContainerJSON{}
|
||||
f.RunningContainerList = []dockertypes.Container{}
|
||||
f.ExitedContainerList = []dockertypes.Container{}
|
||||
|
||||
for i := range containers {
|
||||
c := containers[i]
|
||||
f.ContainerMap[c.ID] = convertFakeContainer(c)
|
||||
container := dockertypes.Container{
|
||||
Names: []string{c.Name},
|
||||
ID: c.ID,
|
||||
}
|
||||
if c.Config != nil {
|
||||
container.Labels = c.Config.Labels
|
||||
}
|
||||
if c.Running {
|
||||
f.RunningContainerList = append(f.RunningContainerList, container)
|
||||
} else {
|
||||
f.ExitedContainerList = append(f.ExitedContainerList, container)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (f *FakeDockerClient) SetFakeRunningContainers(containers []*FakeContainer) {
|
||||
for _, c := range containers {
|
||||
c.Running = true
|
||||
}
|
||||
f.SetFakeContainers(containers)
|
||||
}
|
||||
|
||||
func (f *FakeDockerClient) AssertCalls(calls []string) (err error) {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
|
||||
if !reflect.DeepEqual(calls, f.getCalledNames()) {
|
||||
err = fmt.Errorf("expected %#v, got %#v", calls, f.getCalledNames())
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (f *FakeDockerClient) AssertCallDetails(calls ...calledDetail) (err error) {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
|
||||
if !reflect.DeepEqual(calls, f.called) {
|
||||
err = fmt.Errorf("expected %#v, got %#v", calls, f.called)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// idsToNames converts container ids into names. The caller must hold the lock.
|
||||
func (f *FakeDockerClient) idsToNames(ids []string) ([]string, error) {
|
||||
names := []string{}
|
||||
for _, id := range ids {
|
||||
names = append(names, strings.TrimPrefix(f.ContainerMap[id].Name, dockerNamePrefix))
|
||||
}
|
||||
return names, nil
|
||||
}
|
||||
|
||||
func (f *FakeDockerClient) AssertCreatedByNameWithOrder(created []string) error {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
actualCreated, err := f.idsToNames(f.Created)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !reflect.DeepEqual(created, actualCreated) {
|
||||
return fmt.Errorf("expected %#v, got %#v", created, actualCreated)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *FakeDockerClient) AssertCreatedByName(created []string) error {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
|
||||
actualCreated, err := f.idsToNames(f.Created)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return sortedStringSlicesEqual(created, actualCreated)
|
||||
}
|
||||
|
||||
func (f *FakeDockerClient) AssertStoppedByName(stopped []string) error {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
actualStopped, err := f.idsToNames(f.Stopped)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return sortedStringSlicesEqual(stopped, actualStopped)
|
||||
}
|
||||
|
||||
func (f *FakeDockerClient) AssertStopped(stopped []string) error {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
// Copy stopped to avoid modifying it.
|
||||
actualStopped := append([]string{}, f.Stopped...)
|
||||
return sortedStringSlicesEqual(stopped, actualStopped)
|
||||
}
|
||||
|
||||
func (f *FakeDockerClient) AssertImagesPulled(pulled []string) error {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
// Copy pulled to avoid modifying it.
|
||||
actualPulled := append([]string{}, f.ImagesPulled...)
|
||||
return sortedStringSlicesEqual(pulled, actualPulled)
|
||||
}
|
||||
|
||||
func (f *FakeDockerClient) AssertImagesPulledMsgs(expected []string) error {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
// Copy pulled to avoid modifying it.
|
||||
actual := append([]string{}, f.pulled...)
|
||||
return sortedStringSlicesEqual(expected, actual)
|
||||
}
|
||||
|
||||
func sortedStringSlicesEqual(expected, actual []string) error {
|
||||
sort.StringSlice(expected).Sort()
|
||||
sort.StringSlice(actual).Sort()
|
||||
if !reflect.DeepEqual(expected, actual) {
|
||||
return fmt.Errorf("expected %#v, got %#v", expected, actual)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *FakeDockerClient) popError(op string) error {
|
||||
if f.Errors == nil {
|
||||
return nil
|
||||
}
|
||||
err, ok := f.Errors[op]
|
||||
if ok {
|
||||
delete(f.Errors, op)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListContainers is a test-spy implementation of Interface.ListContainers.
|
||||
// It adds an entry "list" to the internal method call record.
|
||||
func (f *FakeDockerClient) ListContainers(options dockertypes.ContainerListOptions) ([]dockertypes.Container, error) {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
f.appendCalled(calledDetail{name: "list"})
|
||||
err := f.popError("list")
|
||||
containerList := append([]dockertypes.Container{}, f.RunningContainerList...)
|
||||
if options.All {
|
||||
// Although the container is not sorted, but the container with the same name should be in order,
|
||||
// that is enough for us now.
|
||||
// TODO(random-liu): Is a fully sorted array needed?
|
||||
containerList = append(containerList, f.ExitedContainerList...)
|
||||
}
|
||||
// Filters containers with id, only support 1 id.
|
||||
idFilters := options.Filters.Get("id")
|
||||
if len(idFilters) != 0 {
|
||||
var filtered []dockertypes.Container
|
||||
for _, container := range containerList {
|
||||
for _, idFilter := range idFilters {
|
||||
if container.ID == idFilter {
|
||||
filtered = append(filtered, container)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
containerList = filtered
|
||||
}
|
||||
// Filters containers with status, only support 1 status.
|
||||
statusFilters := options.Filters.Get("status")
|
||||
if len(statusFilters) == 1 {
|
||||
var filtered []dockertypes.Container
|
||||
for _, container := range containerList {
|
||||
for _, statusFilter := range statusFilters {
|
||||
if toDockerContainerStatus(container.Status) == statusFilter {
|
||||
filtered = append(filtered, container)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
containerList = filtered
|
||||
}
|
||||
// Filters containers with label filter.
|
||||
labelFilters := options.Filters.Get("label")
|
||||
if len(labelFilters) != 0 {
|
||||
var filtered []dockertypes.Container
|
||||
for _, container := range containerList {
|
||||
match := true
|
||||
for _, labelFilter := range labelFilters {
|
||||
kv := strings.Split(labelFilter, "=")
|
||||
if len(kv) != 2 {
|
||||
return nil, fmt.Errorf("invalid label filter %q", labelFilter)
|
||||
}
|
||||
if container.Labels[kv[0]] != kv[1] {
|
||||
match = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if match {
|
||||
filtered = append(filtered, container)
|
||||
}
|
||||
}
|
||||
containerList = filtered
|
||||
}
|
||||
return containerList, err
|
||||
}
|
||||
|
||||
func toDockerContainerStatus(state string) string {
|
||||
switch {
|
||||
case strings.HasPrefix(state, StatusCreatedPrefix):
|
||||
return "created"
|
||||
case strings.HasPrefix(state, StatusRunningPrefix):
|
||||
return "running"
|
||||
case strings.HasPrefix(state, StatusExitedPrefix):
|
||||
return "exited"
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
// InspectContainer is a test-spy implementation of Interface.InspectContainer.
|
||||
// It adds an entry "inspect" to the internal method call record.
|
||||
func (f *FakeDockerClient) InspectContainer(id string) (*dockertypes.ContainerJSON, error) {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
f.appendCalled(calledDetail{name: "inspect_container"})
|
||||
err := f.popError("inspect_container")
|
||||
if container, ok := f.ContainerMap[id]; ok {
|
||||
return container, err
|
||||
}
|
||||
if err != nil {
|
||||
// Use the custom error if it exists.
|
||||
return nil, err
|
||||
}
|
||||
return nil, fmt.Errorf("container %q not found", id)
|
||||
}
|
||||
|
||||
// InspectContainerWithSize is a test-spy implementation of Interface.InspectContainerWithSize.
|
||||
// It adds an entry "inspect" to the internal method call record.
|
||||
func (f *FakeDockerClient) InspectContainerWithSize(id string) (*dockertypes.ContainerJSON, error) {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
f.appendCalled(calledDetail{name: "inspect_container_withsize"})
|
||||
err := f.popError("inspect_container_withsize")
|
||||
if container, ok := f.ContainerMap[id]; ok {
|
||||
return container, err
|
||||
}
|
||||
if err != nil {
|
||||
// Use the custom error if it exists.
|
||||
return nil, err
|
||||
}
|
||||
return nil, fmt.Errorf("container %q not found", id)
|
||||
}
|
||||
|
||||
// InspectImageByRef is a test-spy implementation of Interface.InspectImageByRef.
|
||||
// It adds an entry "inspect" to the internal method call record.
|
||||
func (f *FakeDockerClient) InspectImageByRef(name string) (*dockertypes.ImageInspect, error) {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
f.appendCalled(calledDetail{name: "inspect_image"})
|
||||
if err := f.popError("inspect_image"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if result, ok := f.ImageInspects[name]; ok {
|
||||
return result, nil
|
||||
}
|
||||
return nil, ImageNotFoundError{name}
|
||||
}
|
||||
|
||||
// InspectImageByID is a test-spy implementation of Interface.InspectImageByID.
|
||||
// It adds an entry "inspect" to the internal method call record.
|
||||
func (f *FakeDockerClient) InspectImageByID(name string) (*dockertypes.ImageInspect, error) {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
f.appendCalled(calledDetail{name: "inspect_image"})
|
||||
if err := f.popError("inspect_image"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if result, ok := f.ImageInspects[name]; ok {
|
||||
return result, nil
|
||||
}
|
||||
return nil, ImageNotFoundError{name}
|
||||
}
|
||||
|
||||
// Sleeps random amount of time with the normal distribution with given mean and stddev
|
||||
// (in milliseconds), we never sleep less than cutOffMillis
|
||||
func (f *FakeDockerClient) normalSleep(mean, stdDev, cutOffMillis int) {
|
||||
if !f.EnableSleep {
|
||||
return
|
||||
}
|
||||
cutoff := (time.Duration)(cutOffMillis) * time.Millisecond
|
||||
delay := (time.Duration)(rand.NormFloat64()*float64(stdDev)+float64(mean)) * time.Millisecond
|
||||
if delay < cutoff {
|
||||
delay = cutoff
|
||||
}
|
||||
time.Sleep(delay)
|
||||
}
|
||||
|
||||
// GetFakeContainerID generates a fake container id from container name with a hash.
|
||||
func GetFakeContainerID(name string) string {
|
||||
hash := fnv.New64a()
|
||||
hash.Write([]byte(name))
|
||||
return strconv.FormatUint(hash.Sum64(), 16)
|
||||
}
|
||||
|
||||
// CreateContainer is a test-spy implementation of Interface.CreateContainer.
|
||||
// It adds an entry "create" to the internal method call record.
|
||||
func (f *FakeDockerClient) CreateContainer(c dockertypes.ContainerCreateConfig) (*dockercontainer.ContainerCreateCreatedBody, error) {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
f.appendCalled(calledDetail{name: "create"})
|
||||
if err := f.popError("create"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// This is not a very good fake. We'll just add this container's name to the list.
|
||||
name := dockerNamePrefix + c.Name
|
||||
id := GetFakeContainerID(name)
|
||||
f.appendContainerTrace("Created", id)
|
||||
timestamp := f.Clock.Now()
|
||||
// The newest container should be in front, because we assume so in GetPodStatus()
|
||||
f.RunningContainerList = append([]dockertypes.Container{
|
||||
{ID: id, Names: []string{name}, Image: c.Config.Image, Created: timestamp.Unix(), State: StatusCreatedPrefix, Labels: c.Config.Labels},
|
||||
}, f.RunningContainerList...)
|
||||
f.ContainerMap[id] = convertFakeContainer(&FakeContainer{
|
||||
ID: id, Name: name, Config: c.Config, HostConfig: c.HostConfig, CreatedAt: timestamp})
|
||||
|
||||
f.normalSleep(100, 25, 25)
|
||||
|
||||
return &dockercontainer.ContainerCreateCreatedBody{ID: id}, nil
|
||||
}
|
||||
|
||||
// StartContainer is a test-spy implementation of Interface.StartContainer.
|
||||
// It adds an entry "start" to the internal method call record.
|
||||
func (f *FakeDockerClient) StartContainer(id string) error {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
f.appendCalled(calledDetail{name: "start"})
|
||||
if err := f.popError("start"); err != nil {
|
||||
return err
|
||||
}
|
||||
f.appendContainerTrace("Started", id)
|
||||
container, ok := f.ContainerMap[id]
|
||||
if container.HostConfig.NetworkMode.IsContainer() {
|
||||
hostContainerID := container.HostConfig.NetworkMode.ConnectedContainer()
|
||||
found := false
|
||||
for _, container := range f.RunningContainerList {
|
||||
if container.ID == hostContainerID {
|
||||
found = true
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return fmt.Errorf("failed to start container \"%s\": Error response from daemon: cannot join network of a non running container: %s", id, hostContainerID)
|
||||
}
|
||||
}
|
||||
timestamp := f.Clock.Now()
|
||||
if !ok {
|
||||
container = convertFakeContainer(&FakeContainer{ID: id, Name: id, CreatedAt: timestamp})
|
||||
}
|
||||
container.State.Running = true
|
||||
container.State.Pid = os.Getpid()
|
||||
container.State.StartedAt = dockerTimestampToString(timestamp)
|
||||
r := f.RandGenerator.Uint32()
|
||||
container.NetworkSettings.IPAddress = fmt.Sprintf("10.%d.%d.%d", byte(r>>16), byte(r>>8), byte(r))
|
||||
f.ContainerMap[id] = container
|
||||
f.updateContainerStatus(id, StatusRunningPrefix)
|
||||
f.normalSleep(200, 50, 50)
|
||||
return nil
|
||||
}
|
||||
|
||||
// StopContainer is a test-spy implementation of Interface.StopContainer.
|
||||
// It adds an entry "stop" to the internal method call record.
|
||||
func (f *FakeDockerClient) StopContainer(id string, timeout time.Duration) error {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
f.appendCalled(calledDetail{name: "stop"})
|
||||
if err := f.popError("stop"); err != nil {
|
||||
return err
|
||||
}
|
||||
f.appendContainerTrace("Stopped", id)
|
||||
// Container status should be Updated before container moved to ExitedContainerList
|
||||
f.updateContainerStatus(id, StatusExitedPrefix)
|
||||
var newList []dockertypes.Container
|
||||
for _, container := range f.RunningContainerList {
|
||||
if container.ID == id {
|
||||
// The newest exited container should be in front. Because we assume so in GetPodStatus()
|
||||
f.ExitedContainerList = append([]dockertypes.Container{container}, f.ExitedContainerList...)
|
||||
continue
|
||||
}
|
||||
newList = append(newList, container)
|
||||
}
|
||||
f.RunningContainerList = newList
|
||||
container, ok := f.ContainerMap[id]
|
||||
if !ok {
|
||||
container = convertFakeContainer(&FakeContainer{
|
||||
ID: id,
|
||||
Name: id,
|
||||
Running: false,
|
||||
StartedAt: time.Now().Add(-time.Second),
|
||||
FinishedAt: time.Now(),
|
||||
})
|
||||
} else {
|
||||
container.State.FinishedAt = dockerTimestampToString(f.Clock.Now())
|
||||
container.State.Running = false
|
||||
}
|
||||
f.ContainerMap[id] = container
|
||||
f.normalSleep(200, 50, 50)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *FakeDockerClient) RemoveContainer(id string, opts dockertypes.ContainerRemoveOptions) error {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
f.appendCalled(calledDetail{name: "remove"})
|
||||
err := f.popError("remove")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i := range f.ExitedContainerList {
|
||||
if f.ExitedContainerList[i].ID == id {
|
||||
delete(f.ContainerMap, id)
|
||||
f.ExitedContainerList = append(f.ExitedContainerList[:i], f.ExitedContainerList[i+1:]...)
|
||||
f.appendContainerTrace("Removed", id)
|
||||
return nil
|
||||
}
|
||||
|
||||
}
|
||||
for i := range f.RunningContainerList {
|
||||
// allow removal of running containers which are not running
|
||||
if f.RunningContainerList[i].ID == id && !f.ContainerMap[id].State.Running {
|
||||
delete(f.ContainerMap, id)
|
||||
f.RunningContainerList = append(f.RunningContainerList[:i], f.RunningContainerList[i+1:]...)
|
||||
f.appendContainerTrace("Removed", id)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
// To be a good fake, report error if container is not stopped.
|
||||
return fmt.Errorf("container not stopped")
|
||||
}
|
||||
|
||||
func (f *FakeDockerClient) UpdateContainerResources(id string, updateConfig dockercontainer.UpdateConfig) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Logs is a test-spy implementation of Interface.Logs.
|
||||
// It adds an entry "logs" to the internal method call record.
|
||||
func (f *FakeDockerClient) Logs(id string, opts dockertypes.ContainerLogsOptions, sopts StreamOptions) error {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
f.appendCalled(calledDetail{name: "logs"})
|
||||
return f.popError("logs")
|
||||
}
|
||||
|
||||
func (f *FakeDockerClient) isAuthorizedForImage(image string, auth dockertypes.AuthConfig) bool {
|
||||
if reqd, exists := f.ImageIDsNeedingAuth[image]; !exists {
|
||||
return true // no auth needed
|
||||
} else {
|
||||
return auth.Username == reqd.Username && auth.Password == reqd.Password
|
||||
}
|
||||
}
|
||||
|
||||
// PullImage is a test-spy implementation of Interface.PullImage.
|
||||
// It adds an entry "pull" to the internal method call record.
|
||||
func (f *FakeDockerClient) PullImage(image string, auth dockertypes.AuthConfig, opts dockertypes.ImagePullOptions) error {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
f.appendCalled(calledDetail{name: "pull"})
|
||||
err := f.popError("pull")
|
||||
if err == nil {
|
||||
if !f.isAuthorizedForImage(image, auth) {
|
||||
return ImageNotFoundError{ID: image}
|
||||
}
|
||||
|
||||
authJson, _ := json.Marshal(auth)
|
||||
inspect := createImageInspectFromRef(image)
|
||||
f.ImageInspects[image] = inspect
|
||||
f.appendPulled(fmt.Sprintf("%s using %s", image, string(authJson)))
|
||||
f.Images = append(f.Images, *createImageFromImageInspect(*inspect))
|
||||
f.ImagesPulled = append(f.ImagesPulled, image)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *FakeDockerClient) Version() (*dockertypes.Version, error) {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
v := f.VersionInfo
|
||||
return &v, f.popError("version")
|
||||
}
|
||||
|
||||
func (f *FakeDockerClient) Info() (*dockertypes.Info, error) {
|
||||
return &f.Information, nil
|
||||
}
|
||||
|
||||
func (f *FakeDockerClient) CreateExec(id string, opts dockertypes.ExecConfig) (*dockertypes.IDResponse, error) {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
f.execCmd = opts.Cmd
|
||||
f.appendCalled(calledDetail{name: "create_exec"})
|
||||
return &dockertypes.IDResponse{ID: "12345678"}, nil
|
||||
}
|
||||
|
||||
func (f *FakeDockerClient) StartExec(startExec string, opts dockertypes.ExecStartCheck, sopts StreamOptions) error {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
f.appendCalled(calledDetail{name: "start_exec"})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *FakeDockerClient) AttachToContainer(id string, opts dockertypes.ContainerAttachOptions, sopts StreamOptions) error {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
f.appendCalled(calledDetail{name: "attach"})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *FakeDockerClient) InspectExec(id string) (*dockertypes.ContainerExecInspect, error) {
|
||||
return f.ExecInspect, f.popError("inspect_exec")
|
||||
}
|
||||
|
||||
func (f *FakeDockerClient) ListImages(opts dockertypes.ImageListOptions) ([]dockertypes.ImageSummary, error) {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
f.appendCalled(calledDetail{name: "list_images"})
|
||||
err := f.popError("list_images")
|
||||
return f.Images, err
|
||||
}
|
||||
|
||||
func (f *FakeDockerClient) RemoveImage(image string, opts dockertypes.ImageRemoveOptions) ([]dockertypes.ImageDeleteResponseItem, error) {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
f.appendCalled(calledDetail{name: "remove_image", arguments: []interface{}{image, opts}})
|
||||
err := f.popError("remove_image")
|
||||
if err == nil {
|
||||
for i := range f.Images {
|
||||
if f.Images[i].ID == image {
|
||||
f.Images = append(f.Images[:i], f.Images[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return []dockertypes.ImageDeleteResponseItem{{Deleted: image}}, err
|
||||
}
|
||||
|
||||
func (f *FakeDockerClient) InjectImages(images []dockertypes.ImageSummary) {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
f.Images = append(f.Images, images...)
|
||||
for _, i := range images {
|
||||
f.ImageInspects[i.ID] = createImageInspectFromImage(i)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *FakeDockerClient) MakeImagesPrivate(images []dockertypes.ImageSummary, auth dockertypes.AuthConfig) {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
for _, i := range images {
|
||||
f.ImageIDsNeedingAuth[i.ID] = auth
|
||||
}
|
||||
}
|
||||
|
||||
func (f *FakeDockerClient) ResetImages() {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
f.Images = []dockertypes.ImageSummary{}
|
||||
f.ImageInspects = make(map[string]*dockertypes.ImageInspect)
|
||||
f.ImageIDsNeedingAuth = make(map[string]dockertypes.AuthConfig)
|
||||
}
|
||||
|
||||
func (f *FakeDockerClient) InjectImageInspects(inspects []dockertypes.ImageInspect) {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
for _, i := range inspects {
|
||||
f.Images = append(f.Images, *createImageFromImageInspect(i))
|
||||
f.ImageInspects[i.ID] = &i
|
||||
}
|
||||
}
|
||||
|
||||
func (f *FakeDockerClient) updateContainerStatus(id, status string) {
|
||||
for i := range f.RunningContainerList {
|
||||
if f.RunningContainerList[i].ID == id {
|
||||
f.RunningContainerList[i].Status = status
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (f *FakeDockerClient) ResizeExecTTY(id string, height, width uint) error {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
f.appendCalled(calledDetail{name: "resize_exec"})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *FakeDockerClient) ResizeContainerTTY(id string, height, width uint) error {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
f.appendCalled(calledDetail{name: "resize_container"})
|
||||
return nil
|
||||
}
|
||||
|
||||
func createImageInspectFromRef(ref string) *dockertypes.ImageInspect {
|
||||
return &dockertypes.ImageInspect{
|
||||
ID: ref,
|
||||
RepoTags: []string{ref},
|
||||
// Image size is required to be non-zero for CRI integration.
|
||||
VirtualSize: fakeImageSize,
|
||||
Size: fakeImageSize,
|
||||
Config: &dockercontainer.Config{},
|
||||
}
|
||||
}
|
||||
|
||||
func createImageInspectFromImage(image dockertypes.ImageSummary) *dockertypes.ImageInspect {
|
||||
return &dockertypes.ImageInspect{
|
||||
ID: image.ID,
|
||||
RepoTags: image.RepoTags,
|
||||
// Image size is required to be non-zero for CRI integration.
|
||||
VirtualSize: fakeImageSize,
|
||||
Size: fakeImageSize,
|
||||
Config: &dockercontainer.Config{},
|
||||
}
|
||||
}
|
||||
|
||||
func createImageFromImageInspect(inspect dockertypes.ImageInspect) *dockertypes.ImageSummary {
|
||||
return &dockertypes.ImageSummary{
|
||||
ID: inspect.ID,
|
||||
RepoTags: inspect.RepoTags,
|
||||
// Image size is required to be non-zero for CRI integration.
|
||||
VirtualSize: fakeImageSize,
|
||||
Size: fakeImageSize,
|
||||
}
|
||||
}
|
||||
|
||||
// dockerTimestampToString converts the timestamp to string
|
||||
func dockerTimestampToString(t time.Time) string {
|
||||
return t.Format(time.RFC3339Nano)
|
||||
}
|
||||
|
||||
func (f *FakeDockerClient) ImageHistory(id string) ([]dockerimagetypes.HistoryResponseItem, error) {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
f.appendCalled(calledDetail{name: "image_history"})
|
||||
history := f.ImageHistoryMap[id]
|
||||
return history, nil
|
||||
}
|
||||
|
||||
func (f *FakeDockerClient) InjectImageHistory(data map[string][]dockerimagetypes.HistoryResponseItem) {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
f.ImageHistoryMap = data
|
||||
}
|
||||
|
||||
// FakeDockerPuller is meant to be a simple wrapper around FakeDockerClient.
|
||||
// Please do not add more functionalities to it.
|
||||
type FakeDockerPuller struct {
|
||||
client Interface
|
||||
}
|
||||
|
||||
func (f *FakeDockerPuller) Pull(image string, _ []v1.Secret) error {
|
||||
return f.client.PullImage(image, dockertypes.AuthConfig{}, dockertypes.ImagePullOptions{})
|
||||
}
|
||||
|
||||
func (f *FakeDockerPuller) GetImageRef(image string) (string, error) {
|
||||
_, err := f.client.InspectImageByRef(image)
|
||||
if err != nil && IsImageNotFoundError(err) {
|
||||
return "", nil
|
||||
}
|
||||
return image, err
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
172
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/libdocker/helpers.go
generated
vendored
Normal file
172
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/libdocker/helpers.go
generated
vendored
Normal file
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
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 libdocker
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
dockerref "github.com/docker/distribution/reference"
|
||||
dockertypes "github.com/docker/docker/api/types"
|
||||
"github.com/golang/glog"
|
||||
godigest "github.com/opencontainers/go-digest"
|
||||
)
|
||||
|
||||
// ParseDockerTimestamp parses the timestamp returned by Interface from string to time.Time
|
||||
func ParseDockerTimestamp(s string) (time.Time, error) {
|
||||
// Timestamp returned by Docker is in time.RFC3339Nano format.
|
||||
return time.Parse(time.RFC3339Nano, s)
|
||||
}
|
||||
|
||||
// matchImageTagOrSHA checks if the given image specifier is a valid image ref,
|
||||
// and that it matches the given image. It should fail on things like image IDs
|
||||
// (config digests) and other digest-only references, but succeed on image names
|
||||
// (`foo`), tag references (`foo:bar`), and manifest digest references
|
||||
// (`foo@sha256:xyz`).
|
||||
func matchImageTagOrSHA(inspected dockertypes.ImageInspect, image string) bool {
|
||||
// The image string follows the grammar specified here
|
||||
// https://github.com/docker/distribution/blob/master/reference/reference.go#L4
|
||||
named, err := dockerref.ParseNormalizedNamed(image)
|
||||
if err != nil {
|
||||
glog.V(4).Infof("couldn't parse image reference %q: %v", image, err)
|
||||
return false
|
||||
}
|
||||
_, isTagged := named.(dockerref.Tagged)
|
||||
digest, isDigested := named.(dockerref.Digested)
|
||||
if !isTagged && !isDigested {
|
||||
// No Tag or SHA specified, so just return what we have
|
||||
return true
|
||||
}
|
||||
|
||||
if isTagged {
|
||||
// Check the RepoTags for a match.
|
||||
for _, tag := range inspected.RepoTags {
|
||||
// An image name (without the tag/digest) can be [hostname '/'] component ['/' component]*
|
||||
// Because either the RepoTag or the name *may* contain the
|
||||
// hostname or not, we only check for the suffix match.
|
||||
if strings.HasSuffix(image, tag) || strings.HasSuffix(tag, image) {
|
||||
return true
|
||||
} else {
|
||||
// TODO: We need to remove this hack when project atomic based
|
||||
// docker distro(s) like centos/fedora/rhel image fix problems on
|
||||
// their end.
|
||||
// Say the tag is "docker.io/busybox:latest"
|
||||
// and the image is "docker.io/library/busybox:latest"
|
||||
t, err := dockerref.ParseNormalizedNamed(tag)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
// the parsed/normalized tag will look like
|
||||
// reference.taggedReference {
|
||||
// namedRepository: reference.repository {
|
||||
// domain: "docker.io",
|
||||
// path: "library/busybox"
|
||||
// },
|
||||
// tag: "latest"
|
||||
// }
|
||||
// If it does not have tags then we bail out
|
||||
t2, ok := t.(dockerref.Tagged)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
// normalized tag would look like "docker.io/library/busybox:latest"
|
||||
// note the library get added in the string
|
||||
normalizedTag := t2.String()
|
||||
if normalizedTag == "" {
|
||||
continue
|
||||
}
|
||||
if strings.HasSuffix(image, normalizedTag) || strings.HasSuffix(normalizedTag, image) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if isDigested {
|
||||
for _, repoDigest := range inspected.RepoDigests {
|
||||
named, err := dockerref.ParseNormalizedNamed(repoDigest)
|
||||
if err != nil {
|
||||
glog.V(4).Infof("couldn't parse image RepoDigest reference %q: %v", repoDigest, err)
|
||||
continue
|
||||
}
|
||||
if d, isDigested := named.(dockerref.Digested); isDigested {
|
||||
if digest.Digest().Algorithm().String() == d.Digest().Algorithm().String() &&
|
||||
digest.Digest().Hex() == d.Digest().Hex() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// process the ID as a digest
|
||||
id, err := godigest.Parse(inspected.ID)
|
||||
if err != nil {
|
||||
glog.V(4).Infof("couldn't parse image ID reference %q: %v", id, err)
|
||||
return false
|
||||
}
|
||||
if digest.Digest().Algorithm().String() == id.Algorithm().String() && digest.Digest().Hex() == id.Hex() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
glog.V(4).Infof("Inspected image (%q) does not match %s", inspected.ID, image)
|
||||
return false
|
||||
}
|
||||
|
||||
// matchImageIDOnly checks that the given image specifier is a digest-only
|
||||
// reference, and that it matches the given image.
|
||||
func matchImageIDOnly(inspected dockertypes.ImageInspect, image string) bool {
|
||||
// If the image ref is literally equal to the inspected image's ID,
|
||||
// just return true here (this might be the case for Docker 1.9,
|
||||
// where we won't have a digest for the ID)
|
||||
if inspected.ID == image {
|
||||
return true
|
||||
}
|
||||
|
||||
// Otherwise, we should try actual parsing to be more correct
|
||||
ref, err := dockerref.Parse(image)
|
||||
if err != nil {
|
||||
glog.V(4).Infof("couldn't parse image reference %q: %v", image, err)
|
||||
return false
|
||||
}
|
||||
|
||||
digest, isDigested := ref.(dockerref.Digested)
|
||||
if !isDigested {
|
||||
glog.V(4).Infof("the image reference %q was not a digest reference", image)
|
||||
return false
|
||||
}
|
||||
|
||||
id, err := godigest.Parse(inspected.ID)
|
||||
if err != nil {
|
||||
glog.V(4).Infof("couldn't parse image ID reference %q: %v", id, err)
|
||||
return false
|
||||
}
|
||||
|
||||
if digest.Digest().Algorithm().String() == id.Algorithm().String() && digest.Digest().Hex() == id.Hex() {
|
||||
return true
|
||||
}
|
||||
|
||||
glog.V(4).Infof("The reference %s does not directly refer to the given image's ID (%q)", image, inspected.ID)
|
||||
return false
|
||||
}
|
||||
|
||||
// isImageNotFoundError returns whether the err is caused by image not found in docker
|
||||
// TODO: Use native error tester once ImageNotFoundError is supported in docker-engine client(eg. ImageRemove())
|
||||
func isImageNotFoundError(err error) bool {
|
||||
if err != nil {
|
||||
return strings.Contains(err.Error(), "No such image:")
|
||||
}
|
||||
return false
|
||||
}
|
270
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/libdocker/helpers_test.go
generated
vendored
Normal file
270
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/libdocker/helpers_test.go
generated
vendored
Normal file
@@ -0,0 +1,270 @@
|
||||
/*
|
||||
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 libdocker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
dockertypes "github.com/docker/docker/api/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestMatchImageTagOrSHA(t *testing.T) {
|
||||
for i, testCase := range []struct {
|
||||
Inspected dockertypes.ImageInspect
|
||||
Image string
|
||||
Output bool
|
||||
}{
|
||||
{
|
||||
Inspected: dockertypes.ImageInspect{RepoTags: []string{"ubuntu:latest"}},
|
||||
Image: "ubuntu",
|
||||
Output: true,
|
||||
},
|
||||
{
|
||||
Inspected: dockertypes.ImageInspect{RepoTags: []string{"ubuntu:14.04"}},
|
||||
Image: "ubuntu:latest",
|
||||
Output: false,
|
||||
},
|
||||
{
|
||||
Inspected: dockertypes.ImageInspect{RepoTags: []string{"colemickens/hyperkube-amd64:217.9beff63"}},
|
||||
Image: "colemickens/hyperkube-amd64:217.9beff63",
|
||||
Output: true,
|
||||
},
|
||||
{
|
||||
Inspected: dockertypes.ImageInspect{RepoTags: []string{"colemickens/hyperkube-amd64:217.9beff63"}},
|
||||
Image: "docker.io/colemickens/hyperkube-amd64:217.9beff63",
|
||||
Output: true,
|
||||
},
|
||||
{
|
||||
Inspected: dockertypes.ImageInspect{RepoTags: []string{"docker.io/kubernetes/pause:latest"}},
|
||||
Image: "kubernetes/pause:latest",
|
||||
Output: true,
|
||||
},
|
||||
{
|
||||
Inspected: dockertypes.ImageInspect{
|
||||
ID: "sha256:2208f7a29005d226d1ee33a63e33af1f47af6156c740d7d23c7948e8d282d53d",
|
||||
},
|
||||
Image: "myimage@sha256:2208f7a29005d226d1ee33a63e33af1f47af6156c740d7d23c7948e8d282d53d",
|
||||
Output: true,
|
||||
},
|
||||
{
|
||||
Inspected: dockertypes.ImageInspect{
|
||||
ID: "sha256:2208f7a29005d226d1ee33a63e33af1f47af6156c740d7d23c7948e8d282d53d",
|
||||
},
|
||||
Image: "myimage@sha256:2208f7a29005",
|
||||
Output: false,
|
||||
},
|
||||
{
|
||||
Inspected: dockertypes.ImageInspect{
|
||||
ID: "sha256:2208f7a29005d226d1ee33a63e33af1f47af6156c740d7d23c7948e8d282d53d",
|
||||
},
|
||||
Image: "myimage@sha256:2208",
|
||||
Output: false,
|
||||
},
|
||||
{
|
||||
// mismatched ID is ignored
|
||||
Inspected: dockertypes.ImageInspect{
|
||||
ID: "sha256:2208f7a29005d226d1ee33a63e33af1f47af6156c740d7d23c7948e8d282d53d",
|
||||
},
|
||||
Image: "myimage@sha256:0000f7a29005d226d1ee33a63e33af1f47af6156c740d7d23c7948e8d282d53d",
|
||||
Output: false,
|
||||
},
|
||||
{
|
||||
// invalid digest is ignored
|
||||
Inspected: dockertypes.ImageInspect{
|
||||
ID: "sha256:unparseable",
|
||||
},
|
||||
Image: "myimage@sha256:unparseable",
|
||||
Output: false,
|
||||
},
|
||||
{
|
||||
// v1 schema images can be pulled in one format and returned in another
|
||||
Inspected: dockertypes.ImageInspect{
|
||||
ID: "sha256:9bbdf247c91345f0789c10f50a57e36a667af1189687ad1de88a6243d05a2227",
|
||||
RepoDigests: []string{"centos/ruby-23-centos7@sha256:940584acbbfb0347272112d2eb95574625c0c60b4e2fdadb139de5859cf754bf"},
|
||||
},
|
||||
Image: "centos/ruby-23-centos7@sha256:940584acbbfb0347272112d2eb95574625c0c60b4e2fdadb139de5859cf754bf",
|
||||
Output: true,
|
||||
},
|
||||
{
|
||||
Inspected: dockertypes.ImageInspect{
|
||||
ID: "sha256:9bbdf247c91345f0789c10f50a57e36a667af1189687ad1de88a6243d05a2227",
|
||||
RepoTags: []string{"docker.io/busybox:latest"},
|
||||
},
|
||||
Image: "docker.io/library/busybox:latest",
|
||||
Output: true,
|
||||
},
|
||||
{
|
||||
// RepoDigest match is is required
|
||||
Inspected: dockertypes.ImageInspect{
|
||||
ID: "",
|
||||
RepoDigests: []string{"docker.io/centos/ruby-23-centos7@sha256:000084acbbfb0347272112d2eb95574625c0c60b4e2fdadb139de5859cf754bf"},
|
||||
},
|
||||
Image: "centos/ruby-23-centos7@sha256:940584acbbfb0347272112d2eb95574625c0c60b4e2fdadb139de5859cf754bf",
|
||||
Output: false,
|
||||
},
|
||||
{
|
||||
// RepoDigest match is allowed
|
||||
Inspected: dockertypes.ImageInspect{
|
||||
ID: "sha256:9bbdf247c91345f0789c10f50a57e36a667af1189687ad1de88a6243d05a2227",
|
||||
RepoDigests: []string{"docker.io/centos/ruby-23-centos7@sha256:940584acbbfb0347272112d2eb95574625c0c60b4e2fdadb139de5859cf754bf"},
|
||||
},
|
||||
Image: "centos/ruby-23-centos7@sha256:940584acbbfb0347272112d2eb95574625c0c60b4e2fdadb139de5859cf754bf",
|
||||
Output: true,
|
||||
},
|
||||
{
|
||||
// RepoDigest and ID are checked
|
||||
Inspected: dockertypes.ImageInspect{
|
||||
ID: "sha256:940584acbbfb0347272112d2eb95574625c0c60b4e2fdadb139de5859cf754bf",
|
||||
RepoDigests: []string{"docker.io/centos/ruby-23-centos7@sha256:9bbdf247c91345f0789c10f50a57e36a667af1189687ad1de88a6243d05a2227"},
|
||||
},
|
||||
Image: "centos/ruby-23-centos7@sha256:940584acbbfb0347272112d2eb95574625c0c60b4e2fdadb139de5859cf754bf",
|
||||
Output: true,
|
||||
},
|
||||
{
|
||||
// unparseable RepoDigests are skipped
|
||||
Inspected: dockertypes.ImageInspect{
|
||||
ID: "sha256:9bbdf247c91345f0789c10f50a57e36a667af1189687ad1de88a6243d05a2227",
|
||||
RepoDigests: []string{
|
||||
"centos/ruby-23-centos7@sha256:unparseable",
|
||||
"docker.io/centos/ruby-23-centos7@sha256:940584acbbfb0347272112d2eb95574625c0c60b4e2fdadb139de5859cf754bf",
|
||||
},
|
||||
},
|
||||
Image: "centos/ruby-23-centos7@sha256:940584acbbfb0347272112d2eb95574625c0c60b4e2fdadb139de5859cf754bf",
|
||||
Output: true,
|
||||
},
|
||||
{
|
||||
// unparseable RepoDigest is ignored
|
||||
Inspected: dockertypes.ImageInspect{
|
||||
ID: "sha256:9bbdf247c91345f0789c10f50a57e36a667af1189687ad1de88a6243d05a2227",
|
||||
RepoDigests: []string{"docker.io/centos/ruby-23-centos7@sha256:unparseable"},
|
||||
},
|
||||
Image: "centos/ruby-23-centos7@sha256:940584acbbfb0347272112d2eb95574625c0c60b4e2fdadb139de5859cf754bf",
|
||||
Output: false,
|
||||
},
|
||||
{
|
||||
// unparseable image digest is ignored
|
||||
Inspected: dockertypes.ImageInspect{
|
||||
ID: "sha256:9bbdf247c91345f0789c10f50a57e36a667af1189687ad1de88a6243d05a2227",
|
||||
RepoDigests: []string{"docker.io/centos/ruby-23-centos7@sha256:unparseable"},
|
||||
},
|
||||
Image: "centos/ruby-23-centos7@sha256:unparseable",
|
||||
Output: false,
|
||||
},
|
||||
{
|
||||
// prefix match is rejected for ID and RepoDigest
|
||||
Inspected: dockertypes.ImageInspect{
|
||||
ID: "sha256:unparseable",
|
||||
RepoDigests: []string{"docker.io/centos/ruby-23-centos7@sha256:unparseable"},
|
||||
},
|
||||
Image: "sha256:unparseable",
|
||||
Output: false,
|
||||
},
|
||||
{
|
||||
// possible SHA prefix match is rejected for ID and RepoDigest because it is not in the named format
|
||||
Inspected: dockertypes.ImageInspect{
|
||||
ID: "sha256:0000f247c91345f0789c10f50a57e36a667af1189687ad1de88a6243d05a2227",
|
||||
RepoDigests: []string{"docker.io/centos/ruby-23-centos7@sha256:0000f247c91345f0789c10f50a57e36a667af1189687ad1de88a6243d05a2227"},
|
||||
},
|
||||
Image: "sha256:0000",
|
||||
Output: false,
|
||||
},
|
||||
} {
|
||||
match := matchImageTagOrSHA(testCase.Inspected, testCase.Image)
|
||||
assert.Equal(t, testCase.Output, match, testCase.Image+fmt.Sprintf(" is not a match (%d)", i))
|
||||
}
|
||||
}
|
||||
|
||||
func TestMatchImageIDOnly(t *testing.T) {
|
||||
for i, testCase := range []struct {
|
||||
Inspected dockertypes.ImageInspect
|
||||
Image string
|
||||
Output bool
|
||||
}{
|
||||
// shouldn't match names or tagged names
|
||||
{
|
||||
Inspected: dockertypes.ImageInspect{RepoTags: []string{"ubuntu:latest"}},
|
||||
Image: "ubuntu",
|
||||
Output: false,
|
||||
},
|
||||
{
|
||||
Inspected: dockertypes.ImageInspect{RepoTags: []string{"colemickens/hyperkube-amd64:217.9beff63"}},
|
||||
Image: "colemickens/hyperkube-amd64:217.9beff63",
|
||||
Output: false,
|
||||
},
|
||||
// should match name@digest refs if they refer to the image ID (but only the full ID)
|
||||
{
|
||||
Inspected: dockertypes.ImageInspect{
|
||||
ID: "sha256:2208f7a29005d226d1ee33a63e33af1f47af6156c740d7d23c7948e8d282d53d",
|
||||
},
|
||||
Image: "myimage@sha256:2208f7a29005d226d1ee33a63e33af1f47af6156c740d7d23c7948e8d282d53d",
|
||||
Output: true,
|
||||
},
|
||||
{
|
||||
Inspected: dockertypes.ImageInspect{
|
||||
ID: "sha256:2208f7a29005d226d1ee33a63e33af1f47af6156c740d7d23c7948e8d282d53d",
|
||||
},
|
||||
Image: "myimage@sha256:2208f7a29005",
|
||||
Output: false,
|
||||
},
|
||||
{
|
||||
Inspected: dockertypes.ImageInspect{
|
||||
ID: "sha256:2208f7a29005d226d1ee33a63e33af1f47af6156c740d7d23c7948e8d282d53d",
|
||||
},
|
||||
Image: "myimage@sha256:2208",
|
||||
Output: false,
|
||||
},
|
||||
// should match when the IDs are literally the same
|
||||
{
|
||||
Inspected: dockertypes.ImageInspect{
|
||||
ID: "foobar",
|
||||
},
|
||||
Image: "foobar",
|
||||
Output: true,
|
||||
},
|
||||
// shouldn't match mismatched IDs
|
||||
{
|
||||
Inspected: dockertypes.ImageInspect{
|
||||
ID: "sha256:2208f7a29005d226d1ee33a63e33af1f47af6156c740d7d23c7948e8d282d53d",
|
||||
},
|
||||
Image: "myimage@sha256:0000f7a29005d226d1ee33a63e33af1f47af6156c740d7d23c7948e8d282d53d",
|
||||
Output: false,
|
||||
},
|
||||
// shouldn't match invalid IDs or refs
|
||||
{
|
||||
Inspected: dockertypes.ImageInspect{
|
||||
ID: "sha256:unparseable",
|
||||
},
|
||||
Image: "myimage@sha256:unparseable",
|
||||
Output: false,
|
||||
},
|
||||
// shouldn't match against repo digests
|
||||
{
|
||||
Inspected: dockertypes.ImageInspect{
|
||||
ID: "sha256:9bbdf247c91345f0789c10f50a57e36a667af1189687ad1de88a6243d05a2227",
|
||||
RepoDigests: []string{"centos/ruby-23-centos7@sha256:940584acbbfb0347272112d2eb95574625c0c60b4e2fdadb139de5859cf754bf"},
|
||||
},
|
||||
Image: "centos/ruby-23-centos7@sha256:940584acbbfb0347272112d2eb95574625c0c60b4e2fdadb139de5859cf754bf",
|
||||
Output: false,
|
||||
},
|
||||
} {
|
||||
match := matchImageIDOnly(testCase.Inspected, testCase.Image)
|
||||
assert.Equal(t, testCase.Output, match, fmt.Sprintf("%s is not a match (%d)", testCase.Image, i))
|
||||
}
|
||||
|
||||
}
|
272
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/libdocker/instrumented_client.go
generated
vendored
Normal file
272
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/libdocker/instrumented_client.go
generated
vendored
Normal file
@@ -0,0 +1,272 @@
|
||||
/*
|
||||
Copyright 2015 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 libdocker
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
dockertypes "github.com/docker/docker/api/types"
|
||||
dockercontainer "github.com/docker/docker/api/types/container"
|
||||
dockerimagetypes "github.com/docker/docker/api/types/image"
|
||||
|
||||
"k8s.io/kubernetes/pkg/kubelet/dockershim/metrics"
|
||||
)
|
||||
|
||||
// instrumentedInterface wraps the Interface and records the operations
|
||||
// and errors metrics.
|
||||
type instrumentedInterface struct {
|
||||
client Interface
|
||||
}
|
||||
|
||||
// NewInstrumentedInterface creates an instrumented Interface from an existing Interface.
|
||||
func NewInstrumentedInterface(dockerClient Interface) Interface {
|
||||
return instrumentedInterface{
|
||||
client: dockerClient,
|
||||
}
|
||||
}
|
||||
|
||||
// recordOperation records the duration of the operation.
|
||||
func recordOperation(operation string, start time.Time) {
|
||||
metrics.DockerOperations.WithLabelValues(operation).Inc()
|
||||
metrics.DockerOperationsLatency.WithLabelValues(operation).Observe(metrics.SinceInMicroseconds(start))
|
||||
}
|
||||
|
||||
// recordError records error for metric if an error occurred.
|
||||
func recordError(operation string, err error) {
|
||||
if err != nil {
|
||||
if _, ok := err.(operationTimeout); ok {
|
||||
metrics.DockerOperationsTimeout.WithLabelValues(operation).Inc()
|
||||
}
|
||||
// Docker operation timeout error is also a docker error, so we don't add else here.
|
||||
metrics.DockerOperationsErrors.WithLabelValues(operation).Inc()
|
||||
}
|
||||
}
|
||||
|
||||
func (in instrumentedInterface) ListContainers(options dockertypes.ContainerListOptions) ([]dockertypes.Container, error) {
|
||||
const operation = "list_containers"
|
||||
defer recordOperation(operation, time.Now())
|
||||
|
||||
out, err := in.client.ListContainers(options)
|
||||
recordError(operation, err)
|
||||
return out, err
|
||||
}
|
||||
|
||||
func (in instrumentedInterface) InspectContainer(id string) (*dockertypes.ContainerJSON, error) {
|
||||
const operation = "inspect_container"
|
||||
defer recordOperation(operation, time.Now())
|
||||
|
||||
out, err := in.client.InspectContainer(id)
|
||||
recordError(operation, err)
|
||||
return out, err
|
||||
}
|
||||
|
||||
func (in instrumentedInterface) InspectContainerWithSize(id string) (*dockertypes.ContainerJSON, error) {
|
||||
const operation = "inspect_container_withsize"
|
||||
defer recordOperation(operation, time.Now())
|
||||
|
||||
out, err := in.client.InspectContainerWithSize(id)
|
||||
recordError(operation, err)
|
||||
return out, err
|
||||
}
|
||||
|
||||
func (in instrumentedInterface) CreateContainer(opts dockertypes.ContainerCreateConfig) (*dockercontainer.ContainerCreateCreatedBody, error) {
|
||||
const operation = "create_container"
|
||||
defer recordOperation(operation, time.Now())
|
||||
|
||||
out, err := in.client.CreateContainer(opts)
|
||||
recordError(operation, err)
|
||||
return out, err
|
||||
}
|
||||
|
||||
func (in instrumentedInterface) StartContainer(id string) error {
|
||||
const operation = "start_container"
|
||||
defer recordOperation(operation, time.Now())
|
||||
|
||||
err := in.client.StartContainer(id)
|
||||
recordError(operation, err)
|
||||
return err
|
||||
}
|
||||
|
||||
func (in instrumentedInterface) StopContainer(id string, timeout time.Duration) error {
|
||||
const operation = "stop_container"
|
||||
defer recordOperation(operation, time.Now())
|
||||
|
||||
err := in.client.StopContainer(id, timeout)
|
||||
recordError(operation, err)
|
||||
return err
|
||||
}
|
||||
|
||||
func (in instrumentedInterface) RemoveContainer(id string, opts dockertypes.ContainerRemoveOptions) error {
|
||||
const operation = "remove_container"
|
||||
defer recordOperation(operation, time.Now())
|
||||
|
||||
err := in.client.RemoveContainer(id, opts)
|
||||
recordError(operation, err)
|
||||
return err
|
||||
}
|
||||
|
||||
func (in instrumentedInterface) UpdateContainerResources(id string, updateConfig dockercontainer.UpdateConfig) error {
|
||||
const operation = "update_container"
|
||||
defer recordOperation(operation, time.Now())
|
||||
|
||||
err := in.client.UpdateContainerResources(id, updateConfig)
|
||||
recordError(operation, err)
|
||||
return err
|
||||
}
|
||||
|
||||
func (in instrumentedInterface) InspectImageByRef(image string) (*dockertypes.ImageInspect, error) {
|
||||
const operation = "inspect_image"
|
||||
defer recordOperation(operation, time.Now())
|
||||
|
||||
out, err := in.client.InspectImageByRef(image)
|
||||
recordError(operation, err)
|
||||
return out, err
|
||||
}
|
||||
|
||||
func (in instrumentedInterface) InspectImageByID(image string) (*dockertypes.ImageInspect, error) {
|
||||
const operation = "inspect_image"
|
||||
defer recordOperation(operation, time.Now())
|
||||
|
||||
out, err := in.client.InspectImageByID(image)
|
||||
recordError(operation, err)
|
||||
return out, err
|
||||
}
|
||||
|
||||
func (in instrumentedInterface) ListImages(opts dockertypes.ImageListOptions) ([]dockertypes.ImageSummary, error) {
|
||||
const operation = "list_images"
|
||||
defer recordOperation(operation, time.Now())
|
||||
|
||||
out, err := in.client.ListImages(opts)
|
||||
recordError(operation, err)
|
||||
return out, err
|
||||
}
|
||||
|
||||
func (in instrumentedInterface) PullImage(imageID string, auth dockertypes.AuthConfig, opts dockertypes.ImagePullOptions) error {
|
||||
const operation = "pull_image"
|
||||
defer recordOperation(operation, time.Now())
|
||||
err := in.client.PullImage(imageID, auth, opts)
|
||||
recordError(operation, err)
|
||||
return err
|
||||
}
|
||||
|
||||
func (in instrumentedInterface) RemoveImage(image string, opts dockertypes.ImageRemoveOptions) ([]dockertypes.ImageDeleteResponseItem, error) {
|
||||
const operation = "remove_image"
|
||||
defer recordOperation(operation, time.Now())
|
||||
|
||||
imageDelete, err := in.client.RemoveImage(image, opts)
|
||||
recordError(operation, err)
|
||||
return imageDelete, err
|
||||
}
|
||||
|
||||
func (in instrumentedInterface) Logs(id string, opts dockertypes.ContainerLogsOptions, sopts StreamOptions) error {
|
||||
const operation = "logs"
|
||||
defer recordOperation(operation, time.Now())
|
||||
|
||||
err := in.client.Logs(id, opts, sopts)
|
||||
recordError(operation, err)
|
||||
return err
|
||||
}
|
||||
|
||||
func (in instrumentedInterface) Version() (*dockertypes.Version, error) {
|
||||
const operation = "version"
|
||||
defer recordOperation(operation, time.Now())
|
||||
|
||||
out, err := in.client.Version()
|
||||
recordError(operation, err)
|
||||
return out, err
|
||||
}
|
||||
|
||||
func (in instrumentedInterface) Info() (*dockertypes.Info, error) {
|
||||
const operation = "info"
|
||||
defer recordOperation(operation, time.Now())
|
||||
|
||||
out, err := in.client.Info()
|
||||
recordError(operation, err)
|
||||
return out, err
|
||||
}
|
||||
|
||||
func (in instrumentedInterface) CreateExec(id string, opts dockertypes.ExecConfig) (*dockertypes.IDResponse, error) {
|
||||
const operation = "create_exec"
|
||||
defer recordOperation(operation, time.Now())
|
||||
|
||||
out, err := in.client.CreateExec(id, opts)
|
||||
recordError(operation, err)
|
||||
return out, err
|
||||
}
|
||||
|
||||
func (in instrumentedInterface) StartExec(startExec string, opts dockertypes.ExecStartCheck, sopts StreamOptions) error {
|
||||
const operation = "start_exec"
|
||||
defer recordOperation(operation, time.Now())
|
||||
|
||||
err := in.client.StartExec(startExec, opts, sopts)
|
||||
recordError(operation, err)
|
||||
return err
|
||||
}
|
||||
|
||||
func (in instrumentedInterface) InspectExec(id string) (*dockertypes.ContainerExecInspect, error) {
|
||||
const operation = "inspect_exec"
|
||||
defer recordOperation(operation, time.Now())
|
||||
|
||||
out, err := in.client.InspectExec(id)
|
||||
recordError(operation, err)
|
||||
return out, err
|
||||
}
|
||||
|
||||
func (in instrumentedInterface) AttachToContainer(id string, opts dockertypes.ContainerAttachOptions, sopts StreamOptions) error {
|
||||
const operation = "attach"
|
||||
defer recordOperation(operation, time.Now())
|
||||
|
||||
err := in.client.AttachToContainer(id, opts, sopts)
|
||||
recordError(operation, err)
|
||||
return err
|
||||
}
|
||||
|
||||
func (in instrumentedInterface) ImageHistory(id string) ([]dockerimagetypes.HistoryResponseItem, error) {
|
||||
const operation = "image_history"
|
||||
defer recordOperation(operation, time.Now())
|
||||
|
||||
out, err := in.client.ImageHistory(id)
|
||||
recordError(operation, err)
|
||||
return out, err
|
||||
}
|
||||
|
||||
func (in instrumentedInterface) ResizeExecTTY(id string, height, width uint) error {
|
||||
const operation = "resize_exec"
|
||||
defer recordOperation(operation, time.Now())
|
||||
|
||||
err := in.client.ResizeExecTTY(id, height, width)
|
||||
recordError(operation, err)
|
||||
return err
|
||||
}
|
||||
|
||||
func (in instrumentedInterface) ResizeContainerTTY(id string, height, width uint) error {
|
||||
const operation = "resize_container"
|
||||
defer recordOperation(operation, time.Now())
|
||||
|
||||
err := in.client.ResizeContainerTTY(id, height, width)
|
||||
recordError(operation, err)
|
||||
return err
|
||||
}
|
||||
|
||||
func (in instrumentedInterface) GetContainerStats(id string) (*dockertypes.StatsJSON, error) {
|
||||
const operation = "stats"
|
||||
defer recordOperation(operation, time.Now())
|
||||
|
||||
out, err := in.client.GetContainerStats(id)
|
||||
recordError(operation, err)
|
||||
return out, err
|
||||
}
|
678
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/libdocker/kube_docker_client.go
generated
vendored
Normal file
678
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/libdocker/kube_docker_client.go
generated
vendored
Normal file
@@ -0,0 +1,678 @@
|
||||
/*
|
||||
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 libdocker
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"regexp"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
dockertypes "github.com/docker/docker/api/types"
|
||||
dockercontainer "github.com/docker/docker/api/types/container"
|
||||
dockerimagetypes "github.com/docker/docker/api/types/image"
|
||||
dockerapi "github.com/docker/docker/client"
|
||||
dockermessage "github.com/docker/docker/pkg/jsonmessage"
|
||||
dockerstdcopy "github.com/docker/docker/pkg/stdcopy"
|
||||
)
|
||||
|
||||
// kubeDockerClient is a wrapped layer of docker client for kubelet internal use. This layer is added to:
|
||||
// 1) Redirect stream for exec and attach operations.
|
||||
// 2) Wrap the context in this layer to make the Interface cleaner.
|
||||
type kubeDockerClient struct {
|
||||
// timeout is the timeout of short running docker operations.
|
||||
timeout time.Duration
|
||||
// If no pulling progress is made before imagePullProgressDeadline, the image pulling will be cancelled.
|
||||
// Docker reports image progress for every 512kB block, so normally there shouldn't be too long interval
|
||||
// between progress updates.
|
||||
imagePullProgressDeadline time.Duration
|
||||
client *dockerapi.Client
|
||||
}
|
||||
|
||||
// Make sure that kubeDockerClient implemented the Interface.
|
||||
var _ Interface = &kubeDockerClient{}
|
||||
|
||||
// There are 2 kinds of docker operations categorized by running time:
|
||||
// * Long running operation: The long running operation could run for arbitrary long time, and the running time
|
||||
// usually depends on some uncontrollable factors. These operations include: PullImage, Logs, StartExec, AttachToContainer.
|
||||
// * Non-long running operation: Given the maximum load of the system, the non-long running operation should finish
|
||||
// in expected and usually short time. These include all other operations.
|
||||
// kubeDockerClient only applies timeout on non-long running operations.
|
||||
const (
|
||||
// defaultTimeout is the default timeout of short running docker operations.
|
||||
// Value is slightly offset from 2 minutes to make timeouts due to this
|
||||
// constant recognizable.
|
||||
defaultTimeout = 2*time.Minute - 1*time.Second
|
||||
|
||||
// defaultShmSize is the default ShmSize to use (in bytes) if not specified.
|
||||
defaultShmSize = int64(1024 * 1024 * 64)
|
||||
|
||||
// defaultImagePullingProgressReportInterval is the default interval of image pulling progress reporting.
|
||||
defaultImagePullingProgressReportInterval = 10 * time.Second
|
||||
)
|
||||
|
||||
// newKubeDockerClient creates an kubeDockerClient from an existing docker client. If requestTimeout is 0,
|
||||
// defaultTimeout will be applied.
|
||||
func newKubeDockerClient(dockerClient *dockerapi.Client, requestTimeout, imagePullProgressDeadline time.Duration) Interface {
|
||||
if requestTimeout == 0 {
|
||||
requestTimeout = defaultTimeout
|
||||
}
|
||||
|
||||
k := &kubeDockerClient{
|
||||
client: dockerClient,
|
||||
timeout: requestTimeout,
|
||||
imagePullProgressDeadline: imagePullProgressDeadline,
|
||||
}
|
||||
// Notice that this assumes that docker is running before kubelet is started.
|
||||
v, err := k.Version()
|
||||
if err != nil {
|
||||
glog.Errorf("failed to retrieve docker version: %v", err)
|
||||
glog.Warningf("Using empty version for docker client, this may sometimes cause compatibility issue.")
|
||||
} else {
|
||||
// Update client version with real api version.
|
||||
dockerClient.NegotiateAPIVersionPing(dockertypes.Ping{APIVersion: v.APIVersion})
|
||||
}
|
||||
return k
|
||||
}
|
||||
|
||||
func (d *kubeDockerClient) ListContainers(options dockertypes.ContainerListOptions) ([]dockertypes.Container, error) {
|
||||
ctx, cancel := d.getTimeoutContext()
|
||||
defer cancel()
|
||||
containers, err := d.client.ContainerList(ctx, options)
|
||||
if ctxErr := contextError(ctx); ctxErr != nil {
|
||||
return nil, ctxErr
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return containers, nil
|
||||
}
|
||||
|
||||
func (d *kubeDockerClient) InspectContainer(id string) (*dockertypes.ContainerJSON, error) {
|
||||
ctx, cancel := d.getTimeoutContext()
|
||||
defer cancel()
|
||||
containerJSON, err := d.client.ContainerInspect(ctx, id)
|
||||
if ctxErr := contextError(ctx); ctxErr != nil {
|
||||
return nil, ctxErr
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &containerJSON, nil
|
||||
}
|
||||
|
||||
// InspectContainerWithSize is currently only used for Windows container stats
|
||||
func (d *kubeDockerClient) InspectContainerWithSize(id string) (*dockertypes.ContainerJSON, error) {
|
||||
ctx, cancel := d.getTimeoutContext()
|
||||
defer cancel()
|
||||
// Inspects the container including the fields SizeRw and SizeRootFs.
|
||||
containerJSON, _, err := d.client.ContainerInspectWithRaw(ctx, id, true)
|
||||
if ctxErr := contextError(ctx); ctxErr != nil {
|
||||
return nil, ctxErr
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &containerJSON, nil
|
||||
}
|
||||
|
||||
func (d *kubeDockerClient) CreateContainer(opts dockertypes.ContainerCreateConfig) (*dockercontainer.ContainerCreateCreatedBody, error) {
|
||||
ctx, cancel := d.getTimeoutContext()
|
||||
defer cancel()
|
||||
// we provide an explicit default shm size as to not depend on docker daemon.
|
||||
// TODO: evaluate exposing this as a knob in the API
|
||||
if opts.HostConfig != nil && opts.HostConfig.ShmSize <= 0 {
|
||||
opts.HostConfig.ShmSize = defaultShmSize
|
||||
}
|
||||
createResp, err := d.client.ContainerCreate(ctx, opts.Config, opts.HostConfig, opts.NetworkingConfig, opts.Name)
|
||||
if ctxErr := contextError(ctx); ctxErr != nil {
|
||||
return nil, ctxErr
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &createResp, nil
|
||||
}
|
||||
|
||||
func (d *kubeDockerClient) StartContainer(id string) error {
|
||||
ctx, cancel := d.getTimeoutContext()
|
||||
defer cancel()
|
||||
err := d.client.ContainerStart(ctx, id, dockertypes.ContainerStartOptions{})
|
||||
if ctxErr := contextError(ctx); ctxErr != nil {
|
||||
return ctxErr
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Stopping an already stopped container will not cause an error in dockerapi.
|
||||
func (d *kubeDockerClient) StopContainer(id string, timeout time.Duration) error {
|
||||
ctx, cancel := d.getCustomTimeoutContext(timeout)
|
||||
defer cancel()
|
||||
err := d.client.ContainerStop(ctx, id, &timeout)
|
||||
if ctxErr := contextError(ctx); ctxErr != nil {
|
||||
return ctxErr
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *kubeDockerClient) RemoveContainer(id string, opts dockertypes.ContainerRemoveOptions) error {
|
||||
ctx, cancel := d.getTimeoutContext()
|
||||
defer cancel()
|
||||
err := d.client.ContainerRemove(ctx, id, opts)
|
||||
if ctxErr := contextError(ctx); ctxErr != nil {
|
||||
return ctxErr
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *kubeDockerClient) UpdateContainerResources(id string, updateConfig dockercontainer.UpdateConfig) error {
|
||||
ctx, cancel := d.getTimeoutContext()
|
||||
defer cancel()
|
||||
_, err := d.client.ContainerUpdate(ctx, id, updateConfig)
|
||||
if ctxErr := contextError(ctx); ctxErr != nil {
|
||||
return ctxErr
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *kubeDockerClient) inspectImageRaw(ref string) (*dockertypes.ImageInspect, error) {
|
||||
ctx, cancel := d.getTimeoutContext()
|
||||
defer cancel()
|
||||
resp, _, err := d.client.ImageInspectWithRaw(ctx, ref)
|
||||
if ctxErr := contextError(ctx); ctxErr != nil {
|
||||
return nil, ctxErr
|
||||
}
|
||||
if err != nil {
|
||||
if dockerapi.IsErrImageNotFound(err) {
|
||||
err = ImageNotFoundError{ID: ref}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
func (d *kubeDockerClient) InspectImageByID(imageID string) (*dockertypes.ImageInspect, error) {
|
||||
resp, err := d.inspectImageRaw(imageID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !matchImageIDOnly(*resp, imageID) {
|
||||
return nil, ImageNotFoundError{ID: imageID}
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (d *kubeDockerClient) InspectImageByRef(imageRef string) (*dockertypes.ImageInspect, error) {
|
||||
resp, err := d.inspectImageRaw(imageRef)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !matchImageTagOrSHA(*resp, imageRef) {
|
||||
return nil, ImageNotFoundError{ID: imageRef}
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (d *kubeDockerClient) ImageHistory(id string) ([]dockerimagetypes.HistoryResponseItem, error) {
|
||||
ctx, cancel := d.getTimeoutContext()
|
||||
defer cancel()
|
||||
resp, err := d.client.ImageHistory(ctx, id)
|
||||
if ctxErr := contextError(ctx); ctxErr != nil {
|
||||
return nil, ctxErr
|
||||
}
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func (d *kubeDockerClient) ListImages(opts dockertypes.ImageListOptions) ([]dockertypes.ImageSummary, error) {
|
||||
ctx, cancel := d.getTimeoutContext()
|
||||
defer cancel()
|
||||
images, err := d.client.ImageList(ctx, opts)
|
||||
if ctxErr := contextError(ctx); ctxErr != nil {
|
||||
return nil, ctxErr
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return images, nil
|
||||
}
|
||||
|
||||
func base64EncodeAuth(auth dockertypes.AuthConfig) (string, error) {
|
||||
var buf bytes.Buffer
|
||||
if err := json.NewEncoder(&buf).Encode(auth); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return base64.URLEncoding.EncodeToString(buf.Bytes()), nil
|
||||
}
|
||||
|
||||
// progress is a wrapper of dockermessage.JSONMessage with a lock protecting it.
|
||||
type progress struct {
|
||||
sync.RWMutex
|
||||
// message stores the latest docker json message.
|
||||
message *dockermessage.JSONMessage
|
||||
// timestamp of the latest update.
|
||||
timestamp time.Time
|
||||
}
|
||||
|
||||
func newProgress() *progress {
|
||||
return &progress{timestamp: time.Now()}
|
||||
}
|
||||
|
||||
func (p *progress) set(msg *dockermessage.JSONMessage) {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
p.message = msg
|
||||
p.timestamp = time.Now()
|
||||
}
|
||||
|
||||
func (p *progress) get() (string, time.Time) {
|
||||
p.RLock()
|
||||
defer p.RUnlock()
|
||||
if p.message == nil {
|
||||
return "No progress", p.timestamp
|
||||
}
|
||||
// The following code is based on JSONMessage.Display
|
||||
var prefix string
|
||||
if p.message.ID != "" {
|
||||
prefix = fmt.Sprintf("%s: ", p.message.ID)
|
||||
}
|
||||
if p.message.Progress == nil {
|
||||
return fmt.Sprintf("%s%s", prefix, p.message.Status), p.timestamp
|
||||
}
|
||||
return fmt.Sprintf("%s%s %s", prefix, p.message.Status, p.message.Progress.String()), p.timestamp
|
||||
}
|
||||
|
||||
// progressReporter keeps the newest image pulling progress and periodically report the newest progress.
|
||||
type progressReporter struct {
|
||||
*progress
|
||||
image string
|
||||
cancel context.CancelFunc
|
||||
stopCh chan struct{}
|
||||
imagePullProgressDeadline time.Duration
|
||||
}
|
||||
|
||||
// newProgressReporter creates a new progressReporter for specific image with specified reporting interval
|
||||
func newProgressReporter(image string, cancel context.CancelFunc, imagePullProgressDeadline time.Duration) *progressReporter {
|
||||
return &progressReporter{
|
||||
progress: newProgress(),
|
||||
image: image,
|
||||
cancel: cancel,
|
||||
stopCh: make(chan struct{}),
|
||||
imagePullProgressDeadline: imagePullProgressDeadline,
|
||||
}
|
||||
}
|
||||
|
||||
// start starts the progressReporter
|
||||
func (p *progressReporter) start() {
|
||||
go func() {
|
||||
ticker := time.NewTicker(defaultImagePullingProgressReportInterval)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
// TODO(random-liu): Report as events.
|
||||
select {
|
||||
case <-ticker.C:
|
||||
progress, timestamp := p.progress.get()
|
||||
// If there is no progress for p.imagePullProgressDeadline, cancel the operation.
|
||||
if time.Since(timestamp) > p.imagePullProgressDeadline {
|
||||
glog.Errorf("Cancel pulling image %q because of no progress for %v, latest progress: %q", p.image, p.imagePullProgressDeadline, progress)
|
||||
p.cancel()
|
||||
return
|
||||
}
|
||||
glog.V(2).Infof("Pulling image %q: %q", p.image, progress)
|
||||
case <-p.stopCh:
|
||||
progress, _ := p.progress.get()
|
||||
glog.V(2).Infof("Stop pulling image %q: %q", p.image, progress)
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// stop stops the progressReporter
|
||||
func (p *progressReporter) stop() {
|
||||
close(p.stopCh)
|
||||
}
|
||||
|
||||
func (d *kubeDockerClient) PullImage(image string, auth dockertypes.AuthConfig, opts dockertypes.ImagePullOptions) error {
|
||||
// RegistryAuth is the base64 encoded credentials for the registry
|
||||
base64Auth, err := base64EncodeAuth(auth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
opts.RegistryAuth = base64Auth
|
||||
ctx, cancel := d.getCancelableContext()
|
||||
defer cancel()
|
||||
resp, err := d.client.ImagePull(ctx, image, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Close()
|
||||
reporter := newProgressReporter(image, cancel, d.imagePullProgressDeadline)
|
||||
reporter.start()
|
||||
defer reporter.stop()
|
||||
decoder := json.NewDecoder(resp)
|
||||
for {
|
||||
var msg dockermessage.JSONMessage
|
||||
err := decoder.Decode(&msg)
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if msg.Error != nil {
|
||||
return msg.Error
|
||||
}
|
||||
reporter.set(&msg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *kubeDockerClient) RemoveImage(image string, opts dockertypes.ImageRemoveOptions) ([]dockertypes.ImageDeleteResponseItem, error) {
|
||||
ctx, cancel := d.getTimeoutContext()
|
||||
defer cancel()
|
||||
resp, err := d.client.ImageRemove(ctx, image, opts)
|
||||
if ctxErr := contextError(ctx); ctxErr != nil {
|
||||
return nil, ctxErr
|
||||
}
|
||||
if isImageNotFoundError(err) {
|
||||
return nil, ImageNotFoundError{ID: image}
|
||||
}
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func (d *kubeDockerClient) Logs(id string, opts dockertypes.ContainerLogsOptions, sopts StreamOptions) error {
|
||||
ctx, cancel := d.getCancelableContext()
|
||||
defer cancel()
|
||||
resp, err := d.client.ContainerLogs(ctx, id, opts)
|
||||
if ctxErr := contextError(ctx); ctxErr != nil {
|
||||
return ctxErr
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Close()
|
||||
return d.redirectResponseToOutputStream(sopts.RawTerminal, sopts.OutputStream, sopts.ErrorStream, resp)
|
||||
}
|
||||
|
||||
func (d *kubeDockerClient) Version() (*dockertypes.Version, error) {
|
||||
ctx, cancel := d.getTimeoutContext()
|
||||
defer cancel()
|
||||
resp, err := d.client.ServerVersion(ctx)
|
||||
if ctxErr := contextError(ctx); ctxErr != nil {
|
||||
return nil, ctxErr
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
func (d *kubeDockerClient) Info() (*dockertypes.Info, error) {
|
||||
ctx, cancel := d.getTimeoutContext()
|
||||
defer cancel()
|
||||
resp, err := d.client.Info(ctx)
|
||||
if ctxErr := contextError(ctx); ctxErr != nil {
|
||||
return nil, ctxErr
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
// TODO(random-liu): Add unit test for exec and attach functions, just like what go-dockerclient did.
|
||||
func (d *kubeDockerClient) CreateExec(id string, opts dockertypes.ExecConfig) (*dockertypes.IDResponse, error) {
|
||||
ctx, cancel := d.getTimeoutContext()
|
||||
defer cancel()
|
||||
resp, err := d.client.ContainerExecCreate(ctx, id, opts)
|
||||
if ctxErr := contextError(ctx); ctxErr != nil {
|
||||
return nil, ctxErr
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
func (d *kubeDockerClient) StartExec(startExec string, opts dockertypes.ExecStartCheck, sopts StreamOptions) error {
|
||||
ctx, cancel := d.getCancelableContext()
|
||||
defer cancel()
|
||||
if opts.Detach {
|
||||
err := d.client.ContainerExecStart(ctx, startExec, opts)
|
||||
if ctxErr := contextError(ctx); ctxErr != nil {
|
||||
return ctxErr
|
||||
}
|
||||
return err
|
||||
}
|
||||
resp, err := d.client.ContainerExecAttach(ctx, startExec, dockertypes.ExecConfig{
|
||||
Detach: opts.Detach,
|
||||
Tty: opts.Tty,
|
||||
})
|
||||
if ctxErr := contextError(ctx); ctxErr != nil {
|
||||
return ctxErr
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Close()
|
||||
|
||||
if sopts.ExecStarted != nil {
|
||||
// Send a message to the channel indicating that the exec has started. This is needed so
|
||||
// interactive execs can handle resizing correctly - the request to resize the TTY has to happen
|
||||
// after the call to d.client.ContainerExecAttach, and because d.holdHijackedConnection below
|
||||
// blocks, we use sopts.ExecStarted to signal the caller that it's ok to resize.
|
||||
sopts.ExecStarted <- struct{}{}
|
||||
}
|
||||
|
||||
return d.holdHijackedConnection(sopts.RawTerminal || opts.Tty, sopts.InputStream, sopts.OutputStream, sopts.ErrorStream, resp)
|
||||
}
|
||||
|
||||
func (d *kubeDockerClient) InspectExec(id string) (*dockertypes.ContainerExecInspect, error) {
|
||||
ctx, cancel := d.getTimeoutContext()
|
||||
defer cancel()
|
||||
resp, err := d.client.ContainerExecInspect(ctx, id)
|
||||
if ctxErr := contextError(ctx); ctxErr != nil {
|
||||
return nil, ctxErr
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
func (d *kubeDockerClient) AttachToContainer(id string, opts dockertypes.ContainerAttachOptions, sopts StreamOptions) error {
|
||||
ctx, cancel := d.getCancelableContext()
|
||||
defer cancel()
|
||||
resp, err := d.client.ContainerAttach(ctx, id, opts)
|
||||
if ctxErr := contextError(ctx); ctxErr != nil {
|
||||
return ctxErr
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Close()
|
||||
return d.holdHijackedConnection(sopts.RawTerminal, sopts.InputStream, sopts.OutputStream, sopts.ErrorStream, resp)
|
||||
}
|
||||
|
||||
func (d *kubeDockerClient) ResizeExecTTY(id string, height, width uint) error {
|
||||
ctx, cancel := d.getCancelableContext()
|
||||
defer cancel()
|
||||
return d.client.ContainerExecResize(ctx, id, dockertypes.ResizeOptions{
|
||||
Height: height,
|
||||
Width: width,
|
||||
})
|
||||
}
|
||||
|
||||
func (d *kubeDockerClient) ResizeContainerTTY(id string, height, width uint) error {
|
||||
ctx, cancel := d.getCancelableContext()
|
||||
defer cancel()
|
||||
return d.client.ContainerResize(ctx, id, dockertypes.ResizeOptions{
|
||||
Height: height,
|
||||
Width: width,
|
||||
})
|
||||
}
|
||||
|
||||
// GetContainerStats is currently only used for Windows container stats
|
||||
func (d *kubeDockerClient) GetContainerStats(id string) (*dockertypes.StatsJSON, error) {
|
||||
ctx, cancel := d.getCancelableContext()
|
||||
defer cancel()
|
||||
|
||||
response, err := d.client.ContainerStats(ctx, id, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dec := json.NewDecoder(response.Body)
|
||||
var stats dockertypes.StatsJSON
|
||||
err = dec.Decode(&stats)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer response.Body.Close()
|
||||
return &stats, nil
|
||||
}
|
||||
|
||||
// redirectResponseToOutputStream redirect the response stream to stdout and stderr. When tty is true, all stream will
|
||||
// only be redirected to stdout.
|
||||
func (d *kubeDockerClient) redirectResponseToOutputStream(tty bool, outputStream, errorStream io.Writer, resp io.Reader) error {
|
||||
if outputStream == nil {
|
||||
outputStream = ioutil.Discard
|
||||
}
|
||||
if errorStream == nil {
|
||||
errorStream = ioutil.Discard
|
||||
}
|
||||
var err error
|
||||
if tty {
|
||||
_, err = io.Copy(outputStream, resp)
|
||||
} else {
|
||||
_, err = dockerstdcopy.StdCopy(outputStream, errorStream, resp)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// holdHijackedConnection hold the HijackedResponse, redirect the inputStream to the connection, and redirect the response
|
||||
// stream to stdout and stderr. NOTE: If needed, we could also add context in this function.
|
||||
func (d *kubeDockerClient) holdHijackedConnection(tty bool, inputStream io.Reader, outputStream, errorStream io.Writer, resp dockertypes.HijackedResponse) error {
|
||||
receiveStdout := make(chan error)
|
||||
if outputStream != nil || errorStream != nil {
|
||||
go func() {
|
||||
receiveStdout <- d.redirectResponseToOutputStream(tty, outputStream, errorStream, resp.Reader)
|
||||
}()
|
||||
}
|
||||
|
||||
stdinDone := make(chan struct{})
|
||||
go func() {
|
||||
if inputStream != nil {
|
||||
io.Copy(resp.Conn, inputStream)
|
||||
}
|
||||
resp.CloseWrite()
|
||||
close(stdinDone)
|
||||
}()
|
||||
|
||||
select {
|
||||
case err := <-receiveStdout:
|
||||
return err
|
||||
case <-stdinDone:
|
||||
if outputStream != nil || errorStream != nil {
|
||||
return <-receiveStdout
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// getCancelableContext returns a new cancelable context. For long running requests without timeout, we use cancelable
|
||||
// context to avoid potential resource leak, although the current implementation shouldn't leak resource.
|
||||
func (d *kubeDockerClient) getCancelableContext() (context.Context, context.CancelFunc) {
|
||||
return context.WithCancel(context.Background())
|
||||
}
|
||||
|
||||
// getTimeoutContext returns a new context with default request timeout
|
||||
func (d *kubeDockerClient) getTimeoutContext() (context.Context, context.CancelFunc) {
|
||||
return context.WithTimeout(context.Background(), d.timeout)
|
||||
}
|
||||
|
||||
// getCustomTimeoutContext returns a new context with a specific request timeout
|
||||
func (d *kubeDockerClient) getCustomTimeoutContext(timeout time.Duration) (context.Context, context.CancelFunc) {
|
||||
// Pick the larger of the two
|
||||
if d.timeout > timeout {
|
||||
timeout = d.timeout
|
||||
}
|
||||
return context.WithTimeout(context.Background(), timeout)
|
||||
}
|
||||
|
||||
// contextError checks the context, and returns error if the context is timeout.
|
||||
func contextError(ctx context.Context) error {
|
||||
if ctx.Err() == context.DeadlineExceeded {
|
||||
return operationTimeout{err: ctx.Err()}
|
||||
}
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
// StreamOptions are the options used to configure the stream redirection
|
||||
type StreamOptions struct {
|
||||
RawTerminal bool
|
||||
InputStream io.Reader
|
||||
OutputStream io.Writer
|
||||
ErrorStream io.Writer
|
||||
ExecStarted chan struct{}
|
||||
}
|
||||
|
||||
// operationTimeout is the error returned when the docker operations are timeout.
|
||||
type operationTimeout struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (e operationTimeout) Error() string {
|
||||
return fmt.Sprintf("operation timeout: %v", e.err)
|
||||
}
|
||||
|
||||
// containerNotFoundErrorRegx is the regexp of container not found error message.
|
||||
var containerNotFoundErrorRegx = regexp.MustCompile(`No such container: [0-9a-z]+`)
|
||||
|
||||
// IsContainerNotFoundError checks whether the error is container not found error.
|
||||
func IsContainerNotFoundError(err error) bool {
|
||||
return containerNotFoundErrorRegx.MatchString(err.Error())
|
||||
}
|
||||
|
||||
// ImageNotFoundError is the error returned by InspectImage when image not found.
|
||||
// Expose this to inject error in dockershim for testing.
|
||||
type ImageNotFoundError struct {
|
||||
ID string
|
||||
}
|
||||
|
||||
func (e ImageNotFoundError) Error() string {
|
||||
return fmt.Sprintf("no such image: %q", e.ID)
|
||||
}
|
||||
|
||||
// IsImageNotFoundError checks whether the error is image not found error. This is exposed
|
||||
// to share with dockershim.
|
||||
func IsImageNotFoundError(err error) bool {
|
||||
_, ok := err.(ImageNotFoundError)
|
||||
return ok
|
||||
}
|
33
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/libdocker/kube_docker_client_test.go
generated
vendored
Normal file
33
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/libdocker/kube_docker_client_test.go
generated
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
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 libdocker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestIsContainerNotFoundError(t *testing.T) {
|
||||
// Expected error message from docker.
|
||||
containerNotFoundError := fmt.Errorf("Error response from daemon: No such container: 96e914f31579e44fe49b239266385330a9b2125abeb9254badd9fca74580c95a")
|
||||
otherError := fmt.Errorf("Error response from daemon: Other errors")
|
||||
|
||||
assert.True(t, IsContainerNotFoundError(containerNotFoundError))
|
||||
assert.False(t, IsContainerNotFoundError(otherError))
|
||||
}
|
Reference in New Issue
Block a user