mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-10-24 00:31:24 +00:00
Implement CRI stats for dockershim using docker stats. This enables use of the summary api to get container metrics on Windows where CRI stats are enabled.
879 lines
26 KiB
Go
879 lines
26 KiB
Go
/*
|
|
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
|
|
|
|
// 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),
|
|
}
|
|
}
|
|
|
|
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) 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
|
|
} else {
|
|
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 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
|
|
}
|
|
|
|
// 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]
|
|
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)
|
|
container.NetworkSettings.IPAddress = "2.3.4.5"
|
|
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
|
|
}
|
|
|
|
}
|
|
// 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")
|
|
}
|