mirror of
https://github.com/ceph/ceph-csi.git
synced 2025-06-13 18:43:34 +00:00
vendor files
This commit is contained in:
148
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/BUILD
generated
vendored
Normal file
148
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/BUILD
generated
vendored
Normal file
@ -0,0 +1,148 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"convert.go",
|
||||
"doc.go",
|
||||
"docker_checkpoint.go",
|
||||
"docker_container.go",
|
||||
"docker_image.go",
|
||||
"docker_image_unsupported.go",
|
||||
"docker_sandbox.go",
|
||||
"docker_service.go",
|
||||
"docker_stats_unsupported.go",
|
||||
"docker_streaming.go",
|
||||
"exec.go",
|
||||
"helpers.go",
|
||||
"helpers_unsupported.go",
|
||||
"naming.go",
|
||||
"security_context.go",
|
||||
"selinux_util.go",
|
||||
] + select({
|
||||
"@io_bazel_rules_go//go/platform:linux_amd64": [
|
||||
"docker_image_linux.go",
|
||||
"docker_stats_linux.go",
|
||||
"helpers_linux.go",
|
||||
],
|
||||
"@io_bazel_rules_go//go/platform:windows_amd64": [
|
||||
"docker_image_windows.go",
|
||||
"docker_stats_windows.go",
|
||||
"helpers_windows.go",
|
||||
],
|
||||
"//conditions:default": [],
|
||||
}),
|
||||
importpath = "k8s.io/kubernetes/pkg/kubelet/dockershim",
|
||||
deps = [
|
||||
"//pkg/credentialprovider:go_default_library",
|
||||
"//pkg/kubelet/apis/cri:go_default_library",
|
||||
"//pkg/kubelet/apis/cri/v1alpha1/runtime:go_default_library",
|
||||
"//pkg/kubelet/apis/kubeletconfig:go_default_library",
|
||||
"//pkg/kubelet/cm:go_default_library",
|
||||
"//pkg/kubelet/container:go_default_library",
|
||||
"//pkg/kubelet/dockershim/cm:go_default_library",
|
||||
"//pkg/kubelet/dockershim/libdocker:go_default_library",
|
||||
"//pkg/kubelet/dockershim/metrics:go_default_library",
|
||||
"//pkg/kubelet/leaky:go_default_library",
|
||||
"//pkg/kubelet/network:go_default_library",
|
||||
"//pkg/kubelet/network/cni:go_default_library",
|
||||
"//pkg/kubelet/network/hostport:go_default_library",
|
||||
"//pkg/kubelet/network/kubenet:go_default_library",
|
||||
"//pkg/kubelet/qos:go_default_library",
|
||||
"//pkg/kubelet/server/streaming:go_default_library",
|
||||
"//pkg/kubelet/types:go_default_library",
|
||||
"//pkg/kubelet/util/cache:go_default_library",
|
||||
"//pkg/kubelet/util/ioutils:go_default_library",
|
||||
"//pkg/kubelet/util/store:go_default_library",
|
||||
"//pkg/security/apparmor:go_default_library",
|
||||
"//pkg/util/filesystem:go_default_library",
|
||||
"//pkg/util/hash:go_default_library",
|
||||
"//pkg/util/parsers:go_default_library",
|
||||
"//vendor/github.com/armon/circbuf:go_default_library",
|
||||
"//vendor/github.com/blang/semver: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/filters:go_default_library",
|
||||
"//vendor/github.com/docker/docker/api/types/strslice:go_default_library",
|
||||
"//vendor/github.com/docker/docker/pkg/jsonmessage:go_default_library",
|
||||
"//vendor/github.com/docker/go-connections/nat:go_default_library",
|
||||
"//vendor/github.com/golang/glog:go_default_library",
|
||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library",
|
||||
"//vendor/k8s.io/client-go/tools/remotecommand:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"convert_test.go",
|
||||
"docker_checkpoint_test.go",
|
||||
"docker_container_test.go",
|
||||
"docker_image_test.go",
|
||||
"docker_sandbox_test.go",
|
||||
"docker_service_test.go",
|
||||
"helpers_test.go",
|
||||
"naming_test.go",
|
||||
"security_context_test.go",
|
||||
"selinux_util_test.go",
|
||||
] + select({
|
||||
"@io_bazel_rules_go//go/platform:linux_amd64": [
|
||||
"helpers_linux_test.go",
|
||||
],
|
||||
"//conditions:default": [],
|
||||
}),
|
||||
data = [
|
||||
],
|
||||
importpath = "k8s.io/kubernetes/pkg/kubelet/dockershim",
|
||||
library = ":go_default_library",
|
||||
deps = [
|
||||
"//pkg/kubelet/apis/cri/v1alpha1/runtime:go_default_library",
|
||||
"//pkg/kubelet/container:go_default_library",
|
||||
"//pkg/kubelet/container/testing:go_default_library",
|
||||
"//pkg/kubelet/dockershim/libdocker:go_default_library",
|
||||
"//pkg/kubelet/dockershim/testing:go_default_library",
|
||||
"//pkg/kubelet/network:go_default_library",
|
||||
"//pkg/kubelet/network/testing:go_default_library",
|
||||
"//pkg/kubelet/types:go_default_library",
|
||||
"//pkg/kubelet/util/cache:go_default_library",
|
||||
"//pkg/security/apparmor:go_default_library",
|
||||
"//vendor/github.com/blang/semver: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/pkg/jsonmessage:go_default_library",
|
||||
"//vendor/github.com/docker/go-connections/nat:go_default_library",
|
||||
"//vendor/github.com/golang/mock/gomock:go_default_library",
|
||||
"//vendor/github.com/stretchr/testify/assert:go_default_library",
|
||||
"//vendor/github.com/stretchr/testify/require: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",
|
||||
"//pkg/kubelet/dockershim/cm:all-srcs",
|
||||
"//pkg/kubelet/dockershim/libdocker:all-srcs",
|
||||
"//pkg/kubelet/dockershim/metrics:all-srcs",
|
||||
"//pkg/kubelet/dockershim/remote:all-srcs",
|
||||
"//pkg/kubelet/dockershim/testing:all-srcs",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
)
|
50
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/cm/BUILD
generated
vendored
Normal file
50
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/cm/BUILD
generated
vendored
Normal file
@ -0,0 +1,50 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"container_manager.go",
|
||||
"container_manager_unsupported.go",
|
||||
] + select({
|
||||
"@io_bazel_rules_go//go/platform:linux_amd64": [
|
||||
"container_manager_linux.go",
|
||||
],
|
||||
"@io_bazel_rules_go//go/platform:windows_amd64": [
|
||||
"container_manager_windows.go",
|
||||
],
|
||||
"//conditions:default": [],
|
||||
}),
|
||||
importpath = "k8s.io/kubernetes/pkg/kubelet/dockershim/cm",
|
||||
deps = [
|
||||
"//pkg/kubelet/dockershim/libdocker:go_default_library",
|
||||
] + select({
|
||||
"@io_bazel_rules_go//go/platform:linux_amd64": [
|
||||
"//pkg/kubelet/cm:go_default_library",
|
||||
"//pkg/kubelet/qos:go_default_library",
|
||||
"//pkg/util/version:go_default_library",
|
||||
"//vendor/github.com/golang/glog:go_default_library",
|
||||
"//vendor/github.com/opencontainers/runc/libcontainer/cgroups/fs:go_default_library",
|
||||
"//vendor/github.com/opencontainers/runc/libcontainer/configs:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
|
||||
],
|
||||
"//conditions:default": [],
|
||||
}),
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
21
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/cm/container_manager.go
generated
vendored
Normal file
21
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/cm/container_manager.go
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
/*
|
||||
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 cm
|
||||
|
||||
type ContainerManager interface {
|
||||
Start() error
|
||||
}
|
148
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/cm/container_manager_linux.go
generated
vendored
Normal file
148
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/cm/container_manager_linux.go
generated
vendored
Normal file
@ -0,0 +1,148 @@
|
||||
// +build linux
|
||||
|
||||
/*
|
||||
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 cm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/opencontainers/runc/libcontainer/cgroups/fs"
|
||||
"github.com/opencontainers/runc/libcontainer/configs"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
kubecm "k8s.io/kubernetes/pkg/kubelet/cm"
|
||||
"k8s.io/kubernetes/pkg/kubelet/qos"
|
||||
utilversion "k8s.io/kubernetes/pkg/util/version"
|
||||
|
||||
"k8s.io/kubernetes/pkg/kubelet/dockershim/libdocker"
|
||||
)
|
||||
|
||||
const (
|
||||
// The percent of the machine memory capacity.
|
||||
dockerMemoryLimitThresholdPercent = kubecm.DockerMemoryLimitThresholdPercent
|
||||
|
||||
// The minimum memory limit allocated to docker container.
|
||||
minDockerMemoryLimit = kubecm.MinDockerMemoryLimit
|
||||
|
||||
// The Docker OOM score adjustment.
|
||||
dockerOOMScoreAdj = qos.DockerOOMScoreAdj
|
||||
)
|
||||
|
||||
var (
|
||||
memoryCapacityRegexp = regexp.MustCompile(`MemTotal:\s*([0-9]+) kB`)
|
||||
)
|
||||
|
||||
func NewContainerManager(cgroupsName string, client libdocker.Interface) ContainerManager {
|
||||
return &containerManager{
|
||||
cgroupsName: cgroupsName,
|
||||
client: client,
|
||||
}
|
||||
}
|
||||
|
||||
type containerManager struct {
|
||||
// Docker client.
|
||||
client libdocker.Interface
|
||||
// Name of the cgroups.
|
||||
cgroupsName string
|
||||
// Manager for the cgroups.
|
||||
cgroupsManager *fs.Manager
|
||||
}
|
||||
|
||||
func (m *containerManager) Start() error {
|
||||
// TODO: check if the required cgroups are mounted.
|
||||
if len(m.cgroupsName) != 0 {
|
||||
manager, err := createCgroupManager(m.cgroupsName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.cgroupsManager = manager
|
||||
}
|
||||
go wait.Until(m.doWork, 5*time.Minute, wait.NeverStop)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *containerManager) doWork() {
|
||||
v, err := m.client.Version()
|
||||
if err != nil {
|
||||
glog.Errorf("Unable to get docker version: %v", err)
|
||||
return
|
||||
}
|
||||
version, err := utilversion.ParseGeneric(v.APIVersion)
|
||||
if err != nil {
|
||||
glog.Errorf("Unable to parse docker version %q: %v", v.APIVersion, err)
|
||||
return
|
||||
}
|
||||
// EnsureDockerInContainer does two things.
|
||||
// 1. Ensure processes run in the cgroups if m.cgroupsManager is not nil.
|
||||
// 2. Ensure processes have the OOM score applied.
|
||||
if err := kubecm.EnsureDockerInContainer(version, dockerOOMScoreAdj, m.cgroupsManager); err != nil {
|
||||
glog.Errorf("Unable to ensure the docker processes run in the desired containers")
|
||||
}
|
||||
}
|
||||
|
||||
func createCgroupManager(name string) (*fs.Manager, error) {
|
||||
var memoryLimit uint64
|
||||
memoryCapacity, err := getMemoryCapacity()
|
||||
if err != nil || memoryCapacity*dockerMemoryLimitThresholdPercent/100 < minDockerMemoryLimit {
|
||||
memoryLimit = minDockerMemoryLimit
|
||||
}
|
||||
glog.V(2).Infof("Configure resource-only container %q with memory limit: %d", name, memoryLimit)
|
||||
|
||||
allowAllDevices := true
|
||||
cm := &fs.Manager{
|
||||
Cgroups: &configs.Cgroup{
|
||||
Parent: "/",
|
||||
Name: name,
|
||||
Resources: &configs.Resources{
|
||||
Memory: int64(memoryLimit),
|
||||
MemorySwap: -1,
|
||||
AllowAllDevices: &allowAllDevices,
|
||||
},
|
||||
},
|
||||
}
|
||||
return cm, nil
|
||||
}
|
||||
|
||||
// getMemoryCapacity returns the memory capacity on the machine in bytes.
|
||||
func getMemoryCapacity() (uint64, error) {
|
||||
out, err := ioutil.ReadFile("/proc/meminfo")
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return parseCapacity(out, memoryCapacityRegexp)
|
||||
}
|
||||
|
||||
// parseCapacity matches a Regexp in a []byte, returning the resulting value in bytes.
|
||||
// Assumes that the value matched by the Regexp is in KB.
|
||||
func parseCapacity(b []byte, r *regexp.Regexp) (uint64, error) {
|
||||
matches := r.FindSubmatch(b)
|
||||
if len(matches) != 2 {
|
||||
return 0, fmt.Errorf("failed to match regexp in output: %q", string(b))
|
||||
}
|
||||
m, err := strconv.ParseUint(string(matches[1]), 10, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Convert to bytes.
|
||||
return m * 1024, err
|
||||
}
|
36
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/cm/container_manager_unsupported.go
generated
vendored
Normal file
36
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/cm/container_manager_unsupported.go
generated
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
// +build !linux,!windows
|
||||
|
||||
/*
|
||||
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 cm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"k8s.io/kubernetes/pkg/kubelet/dockershim/libdocker"
|
||||
)
|
||||
|
||||
type unsupportedContainerManager struct {
|
||||
}
|
||||
|
||||
func NewContainerManager(_ string, _ libdocker.Interface) ContainerManager {
|
||||
return &unsupportedContainerManager{}
|
||||
}
|
||||
|
||||
func (m *unsupportedContainerManager) Start() error {
|
||||
return fmt.Errorf("Container Manager is unsupported in this build")
|
||||
}
|
35
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/cm/container_manager_windows.go
generated
vendored
Normal file
35
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/cm/container_manager_windows.go
generated
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
// +build windows
|
||||
|
||||
/*
|
||||
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 cm
|
||||
|
||||
import (
|
||||
"k8s.io/kubernetes/pkg/kubelet/dockershim/libdocker"
|
||||
)
|
||||
|
||||
// no-op
|
||||
type containerManager struct {
|
||||
}
|
||||
|
||||
func NewContainerManager(_ string, _ libdocker.Interface) ContainerManager {
|
||||
return &containerManager{}
|
||||
}
|
||||
|
||||
func (m *containerManager) Start() error {
|
||||
return nil
|
||||
}
|
177
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/convert.go
generated
vendored
Normal file
177
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/convert.go
generated
vendored
Normal file
@ -0,0 +1,177 @@
|
||||
/*
|
||||
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 dockershim
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
dockertypes "github.com/docker/docker/api/types"
|
||||
|
||||
runtimeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
|
||||
"k8s.io/kubernetes/pkg/kubelet/dockershim/libdocker"
|
||||
)
|
||||
|
||||
// This file contains helper functions to convert docker API types to runtime
|
||||
// API types, or vice versa.
|
||||
|
||||
func imageToRuntimeAPIImage(image *dockertypes.ImageSummary) (*runtimeapi.Image, error) {
|
||||
if image == nil {
|
||||
return nil, fmt.Errorf("unable to convert a nil pointer to a runtime API image")
|
||||
}
|
||||
|
||||
size := uint64(image.VirtualSize)
|
||||
return &runtimeapi.Image{
|
||||
Id: image.ID,
|
||||
RepoTags: image.RepoTags,
|
||||
RepoDigests: image.RepoDigests,
|
||||
Size_: size,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func imageInspectToRuntimeAPIImage(image *dockertypes.ImageInspect) (*runtimeapi.Image, error) {
|
||||
if image == nil || image.Config == nil {
|
||||
return nil, fmt.Errorf("unable to convert a nil pointer to a runtime API image")
|
||||
}
|
||||
|
||||
size := uint64(image.VirtualSize)
|
||||
runtimeImage := &runtimeapi.Image{
|
||||
Id: image.ID,
|
||||
RepoTags: image.RepoTags,
|
||||
RepoDigests: image.RepoDigests,
|
||||
Size_: size,
|
||||
}
|
||||
|
||||
uid, username := getUserFromImageUser(image.Config.User)
|
||||
if uid != nil {
|
||||
runtimeImage.Uid = &runtimeapi.Int64Value{Value: *uid}
|
||||
}
|
||||
runtimeImage.Username = username
|
||||
return runtimeImage, nil
|
||||
}
|
||||
|
||||
func toPullableImageID(id string, image *dockertypes.ImageInspect) string {
|
||||
// Default to the image ID, but if RepoDigests is not empty, use
|
||||
// the first digest instead.
|
||||
imageID := DockerImageIDPrefix + id
|
||||
if len(image.RepoDigests) > 0 {
|
||||
imageID = DockerPullableImageIDPrefix + image.RepoDigests[0]
|
||||
}
|
||||
return imageID
|
||||
}
|
||||
|
||||
func toRuntimeAPIContainer(c *dockertypes.Container) (*runtimeapi.Container, error) {
|
||||
state := toRuntimeAPIContainerState(c.Status)
|
||||
if len(c.Names) == 0 {
|
||||
return nil, fmt.Errorf("unexpected empty container name: %+v", c)
|
||||
}
|
||||
metadata, err := parseContainerName(c.Names[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
labels, annotations := extractLabels(c.Labels)
|
||||
sandboxID := c.Labels[sandboxIDLabelKey]
|
||||
// The timestamp in dockertypes.Container is in seconds.
|
||||
createdAt := c.Created * int64(time.Second)
|
||||
return &runtimeapi.Container{
|
||||
Id: c.ID,
|
||||
PodSandboxId: sandboxID,
|
||||
Metadata: metadata,
|
||||
Image: &runtimeapi.ImageSpec{Image: c.Image},
|
||||
ImageRef: c.ImageID,
|
||||
State: state,
|
||||
CreatedAt: createdAt,
|
||||
Labels: labels,
|
||||
Annotations: annotations,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func toDockerContainerStatus(state runtimeapi.ContainerState) string {
|
||||
switch state {
|
||||
case runtimeapi.ContainerState_CONTAINER_CREATED:
|
||||
return "created"
|
||||
case runtimeapi.ContainerState_CONTAINER_RUNNING:
|
||||
return "running"
|
||||
case runtimeapi.ContainerState_CONTAINER_EXITED:
|
||||
return "exited"
|
||||
case runtimeapi.ContainerState_CONTAINER_UNKNOWN:
|
||||
fallthrough
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
func toRuntimeAPIContainerState(state string) runtimeapi.ContainerState {
|
||||
// Parse the state string in dockertypes.Container. This could break when
|
||||
// we upgrade docker.
|
||||
switch {
|
||||
case strings.HasPrefix(state, libdocker.StatusRunningPrefix):
|
||||
return runtimeapi.ContainerState_CONTAINER_RUNNING
|
||||
case strings.HasPrefix(state, libdocker.StatusExitedPrefix):
|
||||
return runtimeapi.ContainerState_CONTAINER_EXITED
|
||||
case strings.HasPrefix(state, libdocker.StatusCreatedPrefix):
|
||||
return runtimeapi.ContainerState_CONTAINER_CREATED
|
||||
default:
|
||||
return runtimeapi.ContainerState_CONTAINER_UNKNOWN
|
||||
}
|
||||
}
|
||||
|
||||
func toRuntimeAPISandboxState(state string) runtimeapi.PodSandboxState {
|
||||
// Parse the state string in dockertypes.Container. This could break when
|
||||
// we upgrade docker.
|
||||
switch {
|
||||
case strings.HasPrefix(state, libdocker.StatusRunningPrefix):
|
||||
return runtimeapi.PodSandboxState_SANDBOX_READY
|
||||
default:
|
||||
return runtimeapi.PodSandboxState_SANDBOX_NOTREADY
|
||||
}
|
||||
}
|
||||
|
||||
func containerToRuntimeAPISandbox(c *dockertypes.Container) (*runtimeapi.PodSandbox, error) {
|
||||
state := toRuntimeAPISandboxState(c.Status)
|
||||
if len(c.Names) == 0 {
|
||||
return nil, fmt.Errorf("unexpected empty sandbox name: %+v", c)
|
||||
}
|
||||
metadata, err := parseSandboxName(c.Names[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
labels, annotations := extractLabels(c.Labels)
|
||||
// The timestamp in dockertypes.Container is in seconds.
|
||||
createdAt := c.Created * int64(time.Second)
|
||||
return &runtimeapi.PodSandbox{
|
||||
Id: c.ID,
|
||||
Metadata: metadata,
|
||||
State: state,
|
||||
CreatedAt: createdAt,
|
||||
Labels: labels,
|
||||
Annotations: annotations,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func checkpointToRuntimeAPISandbox(id string, checkpoint *PodSandboxCheckpoint) *runtimeapi.PodSandbox {
|
||||
state := runtimeapi.PodSandboxState_SANDBOX_NOTREADY
|
||||
return &runtimeapi.PodSandbox{
|
||||
Id: id,
|
||||
Metadata: &runtimeapi.PodSandboxMetadata{
|
||||
Name: checkpoint.Name,
|
||||
Namespace: checkpoint.Namespace,
|
||||
},
|
||||
State: state,
|
||||
}
|
||||
}
|
71
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/convert_test.go
generated
vendored
Normal file
71
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/convert_test.go
generated
vendored
Normal file
@ -0,0 +1,71 @@
|
||||
/*
|
||||
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 dockershim
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
dockertypes "github.com/docker/docker/api/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
runtimeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
|
||||
)
|
||||
|
||||
func TestConvertDockerStatusToRuntimeAPIState(t *testing.T) {
|
||||
testCases := []struct {
|
||||
input string
|
||||
expected runtimeapi.ContainerState
|
||||
}{
|
||||
{input: "Up 5 hours", expected: runtimeapi.ContainerState_CONTAINER_RUNNING},
|
||||
{input: "Exited (0) 2 hours ago", expected: runtimeapi.ContainerState_CONTAINER_EXITED},
|
||||
{input: "Created", expected: runtimeapi.ContainerState_CONTAINER_CREATED},
|
||||
{input: "Random string", expected: runtimeapi.ContainerState_CONTAINER_UNKNOWN},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
actual := toRuntimeAPIContainerState(test.input)
|
||||
assert.Equal(t, test.expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConvertToPullableImageID(t *testing.T) {
|
||||
testCases := []struct {
|
||||
id string
|
||||
image *dockertypes.ImageInspect
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
id: "image-1",
|
||||
image: &dockertypes.ImageInspect{
|
||||
RepoDigests: []string{"digest-1"},
|
||||
},
|
||||
expected: DockerPullableImageIDPrefix + "digest-1",
|
||||
},
|
||||
{
|
||||
id: "image-2",
|
||||
image: &dockertypes.ImageInspect{
|
||||
RepoDigests: []string{},
|
||||
},
|
||||
expected: DockerImageIDPrefix + "image-2",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range testCases {
|
||||
actual := toPullableImageID(test.id, test.image)
|
||||
assert.Equal(t, test.expected, actual)
|
||||
}
|
||||
}
|
18
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/doc.go
generated
vendored
Normal file
18
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/doc.go
generated
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
// Docker integration using pkg/kubelet/apis/cri/v1alpha1/runtime/api.pb.go
|
||||
package dockershim
|
153
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/docker_checkpoint.go
generated
vendored
Normal file
153
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/docker_checkpoint.go
generated
vendored
Normal file
@ -0,0 +1,153 @@
|
||||
/*
|
||||
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 dockershim
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/golang/glog"
|
||||
utilstore "k8s.io/kubernetes/pkg/kubelet/util/store"
|
||||
utilfs "k8s.io/kubernetes/pkg/util/filesystem"
|
||||
hashutil "k8s.io/kubernetes/pkg/util/hash"
|
||||
)
|
||||
|
||||
const (
|
||||
// default directory to store pod sandbox checkpoint files
|
||||
sandboxCheckpointDir = "sandbox"
|
||||
protocolTCP = Protocol("tcp")
|
||||
protocolUDP = Protocol("udp")
|
||||
schemaVersion = "v1"
|
||||
)
|
||||
|
||||
type Protocol string
|
||||
|
||||
// PortMapping is the port mapping configurations of a sandbox.
|
||||
type PortMapping struct {
|
||||
// Protocol of the port mapping.
|
||||
Protocol *Protocol `json:"protocol,omitempty"`
|
||||
// Port number within the container.
|
||||
ContainerPort *int32 `json:"container_port,omitempty"`
|
||||
// Port number on the host.
|
||||
HostPort *int32 `json:"host_port,omitempty"`
|
||||
}
|
||||
|
||||
// CheckpointData contains all types of data that can be stored in the checkpoint.
|
||||
type CheckpointData struct {
|
||||
PortMappings []*PortMapping `json:"port_mappings,omitempty"`
|
||||
HostNetwork bool `json:"host_network,omitempty"`
|
||||
}
|
||||
|
||||
// PodSandboxCheckpoint is the checkpoint structure for a sandbox
|
||||
type PodSandboxCheckpoint struct {
|
||||
// Version of the pod sandbox checkpoint schema.
|
||||
Version string `json:"version"`
|
||||
// Pod name of the sandbox. Same as the pod name in the PodSpec.
|
||||
Name string `json:"name"`
|
||||
// Pod namespace of the sandbox. Same as the pod namespace in the PodSpec.
|
||||
Namespace string `json:"namespace"`
|
||||
// Data to checkpoint for pod sandbox.
|
||||
Data *CheckpointData `json:"data,omitempty"`
|
||||
// Checksum is calculated with fnv hash of the checkpoint object with checksum field set to be zero
|
||||
CheckSum uint64 `json:"checksum"`
|
||||
}
|
||||
|
||||
// CheckpointHandler provides the interface to manage PodSandbox checkpoint
|
||||
type CheckpointHandler interface {
|
||||
// CreateCheckpoint persists sandbox checkpoint in CheckpointStore.
|
||||
CreateCheckpoint(podSandboxID string, checkpoint *PodSandboxCheckpoint) error
|
||||
// GetCheckpoint retrieves sandbox checkpoint from CheckpointStore.
|
||||
GetCheckpoint(podSandboxID string) (*PodSandboxCheckpoint, error)
|
||||
// RemoveCheckpoint removes sandbox checkpoint form CheckpointStore.
|
||||
// WARNING: RemoveCheckpoint will not return error if checkpoint does not exist.
|
||||
RemoveCheckpoint(podSandboxID string) error
|
||||
// ListCheckpoint returns the list of existing checkpoints.
|
||||
ListCheckpoints() ([]string, error)
|
||||
}
|
||||
|
||||
// PersistentCheckpointHandler is an implementation of CheckpointHandler. It persists checkpoint in CheckpointStore
|
||||
type PersistentCheckpointHandler struct {
|
||||
store utilstore.Store
|
||||
}
|
||||
|
||||
func NewPersistentCheckpointHandler(dockershimRootDir string) (CheckpointHandler, error) {
|
||||
fstore, err := utilstore.NewFileStore(filepath.Join(dockershimRootDir, sandboxCheckpointDir), utilfs.DefaultFs{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &PersistentCheckpointHandler{store: fstore}, nil
|
||||
}
|
||||
|
||||
func (handler *PersistentCheckpointHandler) CreateCheckpoint(podSandboxID string, checkpoint *PodSandboxCheckpoint) error {
|
||||
checkpoint.CheckSum = calculateChecksum(*checkpoint)
|
||||
blob, err := json.Marshal(checkpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return handler.store.Write(podSandboxID, blob)
|
||||
}
|
||||
|
||||
func (handler *PersistentCheckpointHandler) GetCheckpoint(podSandboxID string) (*PodSandboxCheckpoint, error) {
|
||||
blob, err := handler.store.Read(podSandboxID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var checkpoint PodSandboxCheckpoint
|
||||
//TODO: unmarhsal into a struct with just Version, check version, unmarshal into versioned type.
|
||||
err = json.Unmarshal(blob, &checkpoint)
|
||||
if err != nil {
|
||||
glog.Errorf("Failed to unmarshal checkpoint %q, removing checkpoint. Checkpoint content: %q. ErrMsg: %v", podSandboxID, string(blob), err)
|
||||
handler.RemoveCheckpoint(podSandboxID)
|
||||
return nil, fmt.Errorf("failed to unmarshal checkpoint")
|
||||
}
|
||||
if checkpoint.CheckSum != calculateChecksum(checkpoint) {
|
||||
glog.Errorf("Checksum of checkpoint %q is not valid, removing checkpoint", podSandboxID)
|
||||
handler.RemoveCheckpoint(podSandboxID)
|
||||
return nil, fmt.Errorf("checkpoint is corrupted")
|
||||
}
|
||||
return &checkpoint, nil
|
||||
}
|
||||
|
||||
func (handler *PersistentCheckpointHandler) RemoveCheckpoint(podSandboxID string) error {
|
||||
return handler.store.Delete(podSandboxID)
|
||||
}
|
||||
|
||||
func (handler *PersistentCheckpointHandler) ListCheckpoints() ([]string, error) {
|
||||
keys, err := handler.store.List()
|
||||
if err != nil {
|
||||
return []string{}, fmt.Errorf("failed to list checkpoint store: %v", err)
|
||||
}
|
||||
return keys, nil
|
||||
}
|
||||
|
||||
func NewPodSandboxCheckpoint(namespace, name string) *PodSandboxCheckpoint {
|
||||
return &PodSandboxCheckpoint{
|
||||
Version: schemaVersion,
|
||||
Namespace: namespace,
|
||||
Name: name,
|
||||
Data: &CheckpointData{},
|
||||
}
|
||||
}
|
||||
|
||||
func calculateChecksum(checkpoint PodSandboxCheckpoint) uint64 {
|
||||
checkpoint.CheckSum = 0
|
||||
hash := fnv.New32a()
|
||||
hashutil.DeepHashObject(hash, checkpoint)
|
||||
return uint64(hash.Sum32())
|
||||
}
|
102
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/docker_checkpoint_test.go
generated
vendored
Normal file
102
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/docker_checkpoint_test.go
generated
vendored
Normal file
@ -0,0 +1,102 @@
|
||||
/*
|
||||
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 dockershim
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
utilstore "k8s.io/kubernetes/pkg/kubelet/dockershim/testing"
|
||||
)
|
||||
|
||||
func NewTestPersistentCheckpointHandler() CheckpointHandler {
|
||||
return &PersistentCheckpointHandler{store: utilstore.NewMemStore()}
|
||||
}
|
||||
|
||||
func TestPersistentCheckpointHandler(t *testing.T) {
|
||||
var err error
|
||||
handler := NewTestPersistentCheckpointHandler()
|
||||
port80 := int32(80)
|
||||
port443 := int32(443)
|
||||
proto := protocolTCP
|
||||
|
||||
checkpoint1 := NewPodSandboxCheckpoint("ns1", "sandbox1")
|
||||
checkpoint1.Data.PortMappings = []*PortMapping{
|
||||
{
|
||||
&proto,
|
||||
&port80,
|
||||
&port80,
|
||||
},
|
||||
{
|
||||
&proto,
|
||||
&port443,
|
||||
&port443,
|
||||
},
|
||||
}
|
||||
checkpoint1.Data.HostNetwork = true
|
||||
|
||||
checkpoints := []struct {
|
||||
podSandboxID string
|
||||
checkpoint *PodSandboxCheckpoint
|
||||
expectHostNetwork bool
|
||||
}{
|
||||
{
|
||||
"id1",
|
||||
checkpoint1,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"id2",
|
||||
NewPodSandboxCheckpoint("ns2", "sandbox2"),
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range checkpoints {
|
||||
// Test CreateCheckpoints
|
||||
err = handler.CreateCheckpoint(tc.podSandboxID, tc.checkpoint)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Test GetCheckpoints
|
||||
checkpoint, err := handler.GetCheckpoint(tc.podSandboxID)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, *checkpoint, *tc.checkpoint)
|
||||
assert.Equal(t, checkpoint.Data.HostNetwork, tc.expectHostNetwork)
|
||||
}
|
||||
// Test ListCheckpoints
|
||||
keys, err := handler.ListCheckpoints()
|
||||
assert.NoError(t, err)
|
||||
sort.Strings(keys)
|
||||
assert.Equal(t, keys, []string{"id1", "id2"})
|
||||
|
||||
// Test RemoveCheckpoints
|
||||
err = handler.RemoveCheckpoint("id1")
|
||||
assert.NoError(t, err)
|
||||
// Test Remove Nonexisted Checkpoints
|
||||
err = handler.RemoveCheckpoint("id1")
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Test ListCheckpoints
|
||||
keys, err = handler.ListCheckpoints()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, keys, []string{"id2"})
|
||||
|
||||
// Test Get NonExisted Checkpoint
|
||||
_, err = handler.GetCheckpoint("id1")
|
||||
assert.Error(t, err)
|
||||
}
|
413
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/docker_container.go
generated
vendored
Normal file
413
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/docker_container.go
generated
vendored
Normal file
@ -0,0 +1,413 @@
|
||||
/*
|
||||
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 dockershim
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
dockertypes "github.com/docker/docker/api/types"
|
||||
dockercontainer "github.com/docker/docker/api/types/container"
|
||||
dockerfilters "github.com/docker/docker/api/types/filters"
|
||||
dockerstrslice "github.com/docker/docker/api/types/strslice"
|
||||
"github.com/golang/glog"
|
||||
|
||||
runtimeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
|
||||
"k8s.io/kubernetes/pkg/kubelet/dockershim/libdocker"
|
||||
)
|
||||
|
||||
// ListContainers lists all containers matching the filter.
|
||||
func (ds *dockerService) ListContainers(filter *runtimeapi.ContainerFilter) ([]*runtimeapi.Container, error) {
|
||||
opts := dockertypes.ContainerListOptions{All: true}
|
||||
|
||||
opts.Filters = dockerfilters.NewArgs()
|
||||
f := newDockerFilter(&opts.Filters)
|
||||
// Add filter to get *only* (non-sandbox) containers.
|
||||
f.AddLabel(containerTypeLabelKey, containerTypeLabelContainer)
|
||||
|
||||
if filter != nil {
|
||||
if filter.Id != "" {
|
||||
f.Add("id", filter.Id)
|
||||
}
|
||||
if filter.State != nil {
|
||||
f.Add("status", toDockerContainerStatus(filter.GetState().State))
|
||||
}
|
||||
if filter.PodSandboxId != "" {
|
||||
f.AddLabel(sandboxIDLabelKey, filter.PodSandboxId)
|
||||
}
|
||||
|
||||
if filter.LabelSelector != nil {
|
||||
for k, v := range filter.LabelSelector {
|
||||
f.AddLabel(k, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
containers, err := ds.client.ListContainers(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Convert docker to runtime api containers.
|
||||
result := []*runtimeapi.Container{}
|
||||
for i := range containers {
|
||||
c := containers[i]
|
||||
|
||||
converted, err := toRuntimeAPIContainer(&c)
|
||||
if err != nil {
|
||||
glog.V(4).Infof("Unable to convert docker to runtime API container: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
result = append(result, converted)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// CreateContainer creates a new container in the given PodSandbox
|
||||
// Docker cannot store the log to an arbitrary location (yet), so we create an
|
||||
// symlink at LogPath, linking to the actual path of the log.
|
||||
// TODO: check if the default values returned by the runtime API are ok.
|
||||
func (ds *dockerService) CreateContainer(podSandboxID string, config *runtimeapi.ContainerConfig, sandboxConfig *runtimeapi.PodSandboxConfig) (string, error) {
|
||||
if config == nil {
|
||||
return "", fmt.Errorf("container config is nil")
|
||||
}
|
||||
if sandboxConfig == nil {
|
||||
return "", fmt.Errorf("sandbox config is nil for container %q", config.Metadata.Name)
|
||||
}
|
||||
|
||||
labels := makeLabels(config.GetLabels(), config.GetAnnotations())
|
||||
// Apply a the container type label.
|
||||
labels[containerTypeLabelKey] = containerTypeLabelContainer
|
||||
// Write the container log path in the labels.
|
||||
labels[containerLogPathLabelKey] = filepath.Join(sandboxConfig.LogDirectory, config.LogPath)
|
||||
// Write the sandbox ID in the labels.
|
||||
labels[sandboxIDLabelKey] = podSandboxID
|
||||
|
||||
apiVersion, err := ds.getDockerAPIVersion()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("unable to get the docker API version: %v", err)
|
||||
}
|
||||
securityOptSep := getSecurityOptSeparator(apiVersion)
|
||||
|
||||
image := ""
|
||||
if iSpec := config.GetImage(); iSpec != nil {
|
||||
image = iSpec.Image
|
||||
}
|
||||
createConfig := dockertypes.ContainerCreateConfig{
|
||||
Name: makeContainerName(sandboxConfig, config),
|
||||
Config: &dockercontainer.Config{
|
||||
// TODO: set User.
|
||||
Entrypoint: dockerstrslice.StrSlice(config.Command),
|
||||
Cmd: dockerstrslice.StrSlice(config.Args),
|
||||
Env: generateEnvList(config.GetEnvs()),
|
||||
Image: image,
|
||||
WorkingDir: config.WorkingDir,
|
||||
Labels: labels,
|
||||
// Interactive containers:
|
||||
OpenStdin: config.Stdin,
|
||||
StdinOnce: config.StdinOnce,
|
||||
Tty: config.Tty,
|
||||
// Disable Docker's health check until we officially support it
|
||||
// (https://github.com/kubernetes/kubernetes/issues/25829).
|
||||
Healthcheck: &dockercontainer.HealthConfig{
|
||||
Test: []string{"NONE"},
|
||||
},
|
||||
},
|
||||
HostConfig: &dockercontainer.HostConfig{
|
||||
Binds: generateMountBindings(config.GetMounts()),
|
||||
},
|
||||
}
|
||||
|
||||
hc := createConfig.HostConfig
|
||||
ds.updateCreateConfig(&createConfig, config, sandboxConfig, podSandboxID, securityOptSep, apiVersion)
|
||||
// Set devices for container.
|
||||
devices := make([]dockercontainer.DeviceMapping, len(config.Devices))
|
||||
for i, device := range config.Devices {
|
||||
devices[i] = dockercontainer.DeviceMapping{
|
||||
PathOnHost: device.HostPath,
|
||||
PathInContainer: device.ContainerPath,
|
||||
CgroupPermissions: device.Permissions,
|
||||
}
|
||||
}
|
||||
hc.Resources.Devices = devices
|
||||
|
||||
securityOpts, err := ds.getSecurityOpts(config.GetLinux().GetSecurityContext().GetSeccompProfilePath(), securityOptSep)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to generate security options for container %q: %v", config.Metadata.Name, err)
|
||||
}
|
||||
|
||||
hc.SecurityOpt = append(hc.SecurityOpt, securityOpts...)
|
||||
|
||||
createResp, err := ds.client.CreateContainer(createConfig)
|
||||
if err != nil {
|
||||
createResp, err = recoverFromCreationConflictIfNeeded(ds.client, createConfig, err)
|
||||
}
|
||||
|
||||
if createResp != nil {
|
||||
return createResp.ID, err
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
|
||||
// getContainerLogPath returns the container log path specified by kubelet and the real
|
||||
// path where docker stores the container log.
|
||||
func (ds *dockerService) getContainerLogPath(containerID string) (string, string, error) {
|
||||
info, err := ds.client.InspectContainer(containerID)
|
||||
if err != nil {
|
||||
return "", "", fmt.Errorf("failed to inspect container %q: %v", containerID, err)
|
||||
}
|
||||
return info.Config.Labels[containerLogPathLabelKey], info.LogPath, nil
|
||||
}
|
||||
|
||||
// createContainerLogSymlink creates the symlink for docker container log.
|
||||
func (ds *dockerService) createContainerLogSymlink(containerID string) error {
|
||||
path, realPath, err := ds.getContainerLogPath(containerID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get container %q log path: %v", containerID, err)
|
||||
}
|
||||
|
||||
if path == "" {
|
||||
glog.V(5).Infof("Container %s log path isn't specified, will not create the symlink", containerID)
|
||||
return nil
|
||||
}
|
||||
|
||||
if realPath != "" {
|
||||
// Only create the symlink when container log path is specified and log file exists.
|
||||
// Delete possibly existing file first
|
||||
if err = ds.os.Remove(path); err == nil {
|
||||
glog.Warningf("Deleted previously existing symlink file: %q", path)
|
||||
}
|
||||
if err = ds.os.Symlink(realPath, path); err != nil {
|
||||
return fmt.Errorf("failed to create symbolic link %q to the container log file %q for container %q: %v",
|
||||
path, realPath, containerID, err)
|
||||
}
|
||||
} else {
|
||||
supported, err := ds.IsCRISupportedLogDriver()
|
||||
if err != nil {
|
||||
glog.Warningf("Failed to check supported logging driver by CRI: %v", err)
|
||||
return nil
|
||||
}
|
||||
|
||||
if supported {
|
||||
glog.Warningf("Cannot create symbolic link because container log file doesn't exist!")
|
||||
} else {
|
||||
glog.V(5).Infof("Unsupported logging driver by CRI")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// removeContainerLogSymlink removes the symlink for docker container log.
|
||||
func (ds *dockerService) removeContainerLogSymlink(containerID string) error {
|
||||
path, _, err := ds.getContainerLogPath(containerID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get container %q log path: %v", containerID, err)
|
||||
}
|
||||
if path != "" {
|
||||
// Only remove the symlink when container log path is specified.
|
||||
err := ds.os.Remove(path)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return fmt.Errorf("failed to remove container %q log symlink %q: %v", containerID, path, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// StartContainer starts the container.
|
||||
func (ds *dockerService) StartContainer(containerID string) error {
|
||||
err := ds.client.StartContainer(containerID)
|
||||
|
||||
// Create container log symlink for all containers (including failed ones).
|
||||
if linkError := ds.createContainerLogSymlink(containerID); linkError != nil {
|
||||
// Do not stop the container if we failed to create symlink because:
|
||||
// 1. This is not a critical failure.
|
||||
// 2. We don't have enough information to properly stop container here.
|
||||
// Kubelet will surface this error to user via an event.
|
||||
return linkError
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
err = transformStartContainerError(err)
|
||||
return fmt.Errorf("failed to start container %q: %v", containerID, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// StopContainer stops a running container with a grace period (i.e., timeout).
|
||||
func (ds *dockerService) StopContainer(containerID string, timeout int64) error {
|
||||
return ds.client.StopContainer(containerID, time.Duration(timeout)*time.Second)
|
||||
}
|
||||
|
||||
// RemoveContainer removes the container.
|
||||
func (ds *dockerService) RemoveContainer(containerID string) error {
|
||||
// Ideally, log lifecycle should be independent of container lifecycle.
|
||||
// However, docker will remove container log after container is removed,
|
||||
// we can't prevent that now, so we also clean up the symlink here.
|
||||
err := ds.removeContainerLogSymlink(containerID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = ds.client.RemoveContainer(containerID, dockertypes.ContainerRemoveOptions{RemoveVolumes: true, Force: true})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to remove container %q: %v", containerID, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getContainerTimestamps(r *dockertypes.ContainerJSON) (time.Time, time.Time, time.Time, error) {
|
||||
var createdAt, startedAt, finishedAt time.Time
|
||||
var err error
|
||||
|
||||
createdAt, err = libdocker.ParseDockerTimestamp(r.Created)
|
||||
if err != nil {
|
||||
return createdAt, startedAt, finishedAt, err
|
||||
}
|
||||
startedAt, err = libdocker.ParseDockerTimestamp(r.State.StartedAt)
|
||||
if err != nil {
|
||||
return createdAt, startedAt, finishedAt, err
|
||||
}
|
||||
finishedAt, err = libdocker.ParseDockerTimestamp(r.State.FinishedAt)
|
||||
if err != nil {
|
||||
return createdAt, startedAt, finishedAt, err
|
||||
}
|
||||
return createdAt, startedAt, finishedAt, nil
|
||||
}
|
||||
|
||||
// ContainerStatus inspects the docker container and returns the status.
|
||||
func (ds *dockerService) ContainerStatus(containerID string) (*runtimeapi.ContainerStatus, error) {
|
||||
r, err := ds.client.InspectContainer(containerID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Parse the timestamps.
|
||||
createdAt, startedAt, finishedAt, err := getContainerTimestamps(r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse timestamp for container %q: %v", containerID, err)
|
||||
}
|
||||
|
||||
// Convert the image id to a pullable id.
|
||||
ir, err := ds.client.InspectImageByID(r.Image)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to inspect docker image %q while inspecting docker container %q: %v", r.Image, containerID, err)
|
||||
}
|
||||
imageID := toPullableImageID(r.Image, ir)
|
||||
|
||||
// Convert the mounts.
|
||||
mounts := make([]*runtimeapi.Mount, 0, len(r.Mounts))
|
||||
for i := range r.Mounts {
|
||||
m := r.Mounts[i]
|
||||
readonly := !m.RW
|
||||
mounts = append(mounts, &runtimeapi.Mount{
|
||||
HostPath: m.Source,
|
||||
ContainerPath: m.Destination,
|
||||
Readonly: readonly,
|
||||
// Note: Can't set SeLinuxRelabel
|
||||
})
|
||||
}
|
||||
// Interpret container states.
|
||||
var state runtimeapi.ContainerState
|
||||
var reason, message string
|
||||
if r.State.Running {
|
||||
// Container is running.
|
||||
state = runtimeapi.ContainerState_CONTAINER_RUNNING
|
||||
} else {
|
||||
// Container is *not* running. We need to get more details.
|
||||
// * Case 1: container has run and exited with non-zero finishedAt
|
||||
// time.
|
||||
// * Case 2: container has failed to start; it has a zero finishedAt
|
||||
// time, but a non-zero exit code.
|
||||
// * Case 3: container has been created, but not started (yet).
|
||||
if !finishedAt.IsZero() { // Case 1
|
||||
state = runtimeapi.ContainerState_CONTAINER_EXITED
|
||||
switch {
|
||||
case r.State.OOMKilled:
|
||||
// TODO: consider exposing OOMKilled via the runtimeAPI.
|
||||
// Note: if an application handles OOMKilled gracefully, the
|
||||
// exit code could be zero.
|
||||
reason = "OOMKilled"
|
||||
case r.State.ExitCode == 0:
|
||||
reason = "Completed"
|
||||
default:
|
||||
reason = "Error"
|
||||
}
|
||||
} else if r.State.ExitCode != 0 { // Case 2
|
||||
state = runtimeapi.ContainerState_CONTAINER_EXITED
|
||||
// Adjust finshedAt and startedAt time to createdAt time to avoid
|
||||
// the confusion.
|
||||
finishedAt, startedAt = createdAt, createdAt
|
||||
reason = "ContainerCannotRun"
|
||||
} else { // Case 3
|
||||
state = runtimeapi.ContainerState_CONTAINER_CREATED
|
||||
}
|
||||
message = r.State.Error
|
||||
}
|
||||
|
||||
// Convert to unix timestamps.
|
||||
ct, st, ft := createdAt.UnixNano(), startedAt.UnixNano(), finishedAt.UnixNano()
|
||||
exitCode := int32(r.State.ExitCode)
|
||||
|
||||
metadata, err := parseContainerName(r.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
labels, annotations := extractLabels(r.Config.Labels)
|
||||
imageName := r.Config.Image
|
||||
if len(ir.RepoTags) > 0 {
|
||||
imageName = ir.RepoTags[0]
|
||||
}
|
||||
return &runtimeapi.ContainerStatus{
|
||||
Id: r.ID,
|
||||
Metadata: metadata,
|
||||
Image: &runtimeapi.ImageSpec{Image: imageName},
|
||||
ImageRef: imageID,
|
||||
Mounts: mounts,
|
||||
ExitCode: exitCode,
|
||||
State: state,
|
||||
CreatedAt: ct,
|
||||
StartedAt: st,
|
||||
FinishedAt: ft,
|
||||
Reason: reason,
|
||||
Message: message,
|
||||
Labels: labels,
|
||||
Annotations: annotations,
|
||||
LogPath: r.Config.Labels[containerLogPathLabelKey],
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (ds *dockerService) UpdateContainerResources(containerID string, resources *runtimeapi.LinuxContainerResources) error {
|
||||
updateConfig := dockercontainer.UpdateConfig{
|
||||
Resources: dockercontainer.Resources{
|
||||
CPUPeriod: resources.CpuPeriod,
|
||||
CPUQuota: resources.CpuQuota,
|
||||
CPUShares: resources.CpuShares,
|
||||
Memory: resources.MemoryLimitInBytes,
|
||||
CpusetCpus: resources.CpusetCpus,
|
||||
CpusetMems: resources.CpusetMems,
|
||||
},
|
||||
}
|
||||
|
||||
err := ds.client.UpdateContainerResources(containerID, updateConfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update container %q: %v", containerID, err)
|
||||
}
|
||||
return nil
|
||||
}
|
292
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/docker_container_test.go
generated
vendored
Normal file
292
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/docker_container_test.go
generated
vendored
Normal file
@ -0,0 +1,292 @@
|
||||
/*
|
||||
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 dockershim
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
dockertypes "github.com/docker/docker/api/types"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
runtimeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
|
||||
containertest "k8s.io/kubernetes/pkg/kubelet/container/testing"
|
||||
)
|
||||
|
||||
// A helper to create a basic config.
|
||||
func makeContainerConfig(sConfig *runtimeapi.PodSandboxConfig, name, image string, attempt uint32, labels, annotations map[string]string) *runtimeapi.ContainerConfig {
|
||||
return &runtimeapi.ContainerConfig{
|
||||
Metadata: &runtimeapi.ContainerMetadata{
|
||||
Name: name,
|
||||
Attempt: attempt,
|
||||
},
|
||||
Image: &runtimeapi.ImageSpec{Image: image},
|
||||
Labels: labels,
|
||||
Annotations: annotations,
|
||||
}
|
||||
}
|
||||
|
||||
// TestListContainers creates several containers and then list them to check
|
||||
// whether the correct metadatas, states, and labels are returned.
|
||||
func TestListContainers(t *testing.T) {
|
||||
ds, _, fakeClock := newTestDockerService()
|
||||
podName, namespace := "foo", "bar"
|
||||
containerName, image := "sidecar", "logger"
|
||||
|
||||
configs := []*runtimeapi.ContainerConfig{}
|
||||
sConfigs := []*runtimeapi.PodSandboxConfig{}
|
||||
for i := 0; i < 3; i++ {
|
||||
s := makeSandboxConfig(fmt.Sprintf("%s%d", podName, i),
|
||||
fmt.Sprintf("%s%d", namespace, i), fmt.Sprintf("%d", i), 0)
|
||||
labels := map[string]string{"abc.xyz": fmt.Sprintf("label%d", i)}
|
||||
annotations := map[string]string{"foo.bar.baz": fmt.Sprintf("annotation%d", i)}
|
||||
c := makeContainerConfig(s, fmt.Sprintf("%s%d", containerName, i),
|
||||
fmt.Sprintf("%s:v%d", image, i), uint32(i), labels, annotations)
|
||||
sConfigs = append(sConfigs, s)
|
||||
configs = append(configs, c)
|
||||
}
|
||||
|
||||
expected := []*runtimeapi.Container{}
|
||||
state := runtimeapi.ContainerState_CONTAINER_RUNNING
|
||||
var createdAt int64 = fakeClock.Now().UnixNano()
|
||||
for i := range configs {
|
||||
// We don't care about the sandbox id; pass a bogus one.
|
||||
sandboxID := fmt.Sprintf("sandboxid%d", i)
|
||||
id, err := ds.CreateContainer(sandboxID, configs[i], sConfigs[i])
|
||||
assert.NoError(t, err)
|
||||
err = ds.StartContainer(id)
|
||||
assert.NoError(t, err)
|
||||
|
||||
imageRef := "" // FakeDockerClient doesn't populate ImageRef yet.
|
||||
// Prepend to the expected list because ListContainers returns
|
||||
// the most recent containers first.
|
||||
expected = append([]*runtimeapi.Container{{
|
||||
Metadata: configs[i].Metadata,
|
||||
Id: id,
|
||||
PodSandboxId: sandboxID,
|
||||
State: state,
|
||||
CreatedAt: createdAt,
|
||||
Image: configs[i].Image,
|
||||
ImageRef: imageRef,
|
||||
Labels: configs[i].Labels,
|
||||
Annotations: configs[i].Annotations,
|
||||
}}, expected...)
|
||||
}
|
||||
containers, err := ds.ListContainers(nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, containers, len(expected))
|
||||
assert.Equal(t, expected, containers)
|
||||
}
|
||||
|
||||
// TestContainerStatus tests the basic lifecycle operations and verify that
|
||||
// the status returned reflects the operations performed.
|
||||
func TestContainerStatus(t *testing.T) {
|
||||
ds, fDocker, fClock := newTestDockerService()
|
||||
sConfig := makeSandboxConfig("foo", "bar", "1", 0)
|
||||
labels := map[string]string{"abc.xyz": "foo"}
|
||||
annotations := map[string]string{"foo.bar.baz": "abc"}
|
||||
imageName := "iamimage"
|
||||
config := makeContainerConfig(sConfig, "pause", imageName, 0, labels, annotations)
|
||||
|
||||
var defaultTime time.Time
|
||||
dt := defaultTime.UnixNano()
|
||||
ct, st, ft := dt, dt, dt
|
||||
state := runtimeapi.ContainerState_CONTAINER_CREATED
|
||||
imageRef := DockerImageIDPrefix + imageName
|
||||
// The following variables are not set in FakeDockerClient.
|
||||
exitCode := int32(0)
|
||||
var reason, message string
|
||||
|
||||
expected := &runtimeapi.ContainerStatus{
|
||||
State: state,
|
||||
CreatedAt: ct,
|
||||
StartedAt: st,
|
||||
FinishedAt: ft,
|
||||
Metadata: config.Metadata,
|
||||
Image: config.Image,
|
||||
ImageRef: imageRef,
|
||||
ExitCode: exitCode,
|
||||
Reason: reason,
|
||||
Message: message,
|
||||
Mounts: []*runtimeapi.Mount{},
|
||||
Labels: config.Labels,
|
||||
Annotations: config.Annotations,
|
||||
}
|
||||
|
||||
fDocker.InjectImages([]dockertypes.ImageSummary{{ID: imageName}})
|
||||
|
||||
// Create the container.
|
||||
fClock.SetTime(time.Now().Add(-1 * time.Hour))
|
||||
expected.CreatedAt = fClock.Now().UnixNano()
|
||||
const sandboxId = "sandboxid"
|
||||
id, err := ds.CreateContainer(sandboxId, config, sConfig)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Check internal labels
|
||||
c, err := fDocker.InspectContainer(id)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, c.Config.Labels[containerTypeLabelKey], containerTypeLabelContainer)
|
||||
assert.Equal(t, c.Config.Labels[sandboxIDLabelKey], sandboxId)
|
||||
|
||||
// Set the id manually since we don't know the id until it's created.
|
||||
expected.Id = id
|
||||
assert.NoError(t, err)
|
||||
status, err := ds.ContainerStatus(id)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expected, status)
|
||||
|
||||
// Advance the clock and start the container.
|
||||
fClock.SetTime(time.Now())
|
||||
expected.StartedAt = fClock.Now().UnixNano()
|
||||
expected.State = runtimeapi.ContainerState_CONTAINER_RUNNING
|
||||
|
||||
err = ds.StartContainer(id)
|
||||
assert.NoError(t, err)
|
||||
status, err = ds.ContainerStatus(id)
|
||||
assert.Equal(t, expected, status)
|
||||
|
||||
// Advance the clock and stop the container.
|
||||
fClock.SetTime(time.Now().Add(1 * time.Hour))
|
||||
expected.FinishedAt = fClock.Now().UnixNano()
|
||||
expected.State = runtimeapi.ContainerState_CONTAINER_EXITED
|
||||
expected.Reason = "Completed"
|
||||
|
||||
err = ds.StopContainer(id, 0)
|
||||
assert.NoError(t, err)
|
||||
status, err = ds.ContainerStatus(id)
|
||||
assert.Equal(t, expected, status)
|
||||
|
||||
// Remove the container.
|
||||
err = ds.RemoveContainer(id)
|
||||
assert.NoError(t, err)
|
||||
status, err = ds.ContainerStatus(id)
|
||||
assert.Error(t, err, fmt.Sprintf("status of container: %+v", status))
|
||||
}
|
||||
|
||||
// TestContainerLogPath tests the container log creation logic.
|
||||
func TestContainerLogPath(t *testing.T) {
|
||||
ds, fDocker, _ := newTestDockerService()
|
||||
podLogPath := "/pod/1"
|
||||
containerLogPath := "0"
|
||||
kubeletContainerLogPath := filepath.Join(podLogPath, containerLogPath)
|
||||
sConfig := makeSandboxConfig("foo", "bar", "1", 0)
|
||||
sConfig.LogDirectory = podLogPath
|
||||
config := makeContainerConfig(sConfig, "pause", "iamimage", 0, nil, nil)
|
||||
config.LogPath = containerLogPath
|
||||
|
||||
const sandboxId = "sandboxid"
|
||||
id, err := ds.CreateContainer(sandboxId, config, sConfig)
|
||||
|
||||
// Check internal container log label
|
||||
c, err := fDocker.InspectContainer(id)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, c.Config.Labels[containerLogPathLabelKey], kubeletContainerLogPath)
|
||||
|
||||
// Set docker container log path
|
||||
dockerContainerLogPath := "/docker/container/log"
|
||||
c.LogPath = dockerContainerLogPath
|
||||
|
||||
// Verify container log symlink creation
|
||||
fakeOS := ds.os.(*containertest.FakeOS)
|
||||
fakeOS.SymlinkFn = func(oldname, newname string) error {
|
||||
assert.Equal(t, dockerContainerLogPath, oldname)
|
||||
assert.Equal(t, kubeletContainerLogPath, newname)
|
||||
return nil
|
||||
}
|
||||
err = ds.StartContainer(id)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = ds.StopContainer(id, 0)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Verify container log symlink deletion
|
||||
// symlink is also tentatively deleted at startup
|
||||
err = ds.RemoveContainer(id)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []string{kubeletContainerLogPath, kubeletContainerLogPath}, fakeOS.Removes)
|
||||
}
|
||||
|
||||
// TestContainerCreationConflict tests the logic to work around docker container
|
||||
// creation naming conflict bug.
|
||||
func TestContainerCreationConflict(t *testing.T) {
|
||||
sConfig := makeSandboxConfig("foo", "bar", "1", 0)
|
||||
config := makeContainerConfig(sConfig, "pause", "iamimage", 0, map[string]string{}, map[string]string{})
|
||||
containerName := makeContainerName(sConfig, config)
|
||||
const sandboxId = "sandboxid"
|
||||
const containerId = "containerid"
|
||||
conflictError := fmt.Errorf("Error response from daemon: Conflict. The name \"/%s\" is already in use by container %s. You have to remove (or rename) that container to be able to reuse that name.",
|
||||
containerName, containerId)
|
||||
noContainerError := fmt.Errorf("Error response from daemon: No such container: %s", containerId)
|
||||
randomError := fmt.Errorf("random error")
|
||||
|
||||
for desc, test := range map[string]struct {
|
||||
createError error
|
||||
removeError error
|
||||
expectError error
|
||||
expectCalls []string
|
||||
expectFields int
|
||||
}{
|
||||
"no create error": {
|
||||
expectCalls: []string{"create"},
|
||||
expectFields: 6,
|
||||
},
|
||||
"random create error": {
|
||||
createError: randomError,
|
||||
expectError: randomError,
|
||||
expectCalls: []string{"create"},
|
||||
},
|
||||
"conflict create error with successful remove": {
|
||||
createError: conflictError,
|
||||
expectError: conflictError,
|
||||
expectCalls: []string{"create", "remove"},
|
||||
},
|
||||
"conflict create error with random remove error": {
|
||||
createError: conflictError,
|
||||
removeError: randomError,
|
||||
expectError: conflictError,
|
||||
expectCalls: []string{"create", "remove"},
|
||||
},
|
||||
"conflict create error with no such container remove error": {
|
||||
createError: conflictError,
|
||||
removeError: noContainerError,
|
||||
expectCalls: []string{"create", "remove", "create"},
|
||||
expectFields: 7,
|
||||
},
|
||||
} {
|
||||
t.Logf("TestCase: %s", desc)
|
||||
ds, fDocker, _ := newTestDockerService()
|
||||
|
||||
if test.createError != nil {
|
||||
fDocker.InjectError("create", test.createError)
|
||||
}
|
||||
if test.removeError != nil {
|
||||
fDocker.InjectError("remove", test.removeError)
|
||||
}
|
||||
id, err := ds.CreateContainer(sandboxId, config, sConfig)
|
||||
require.Equal(t, test.expectError, err)
|
||||
assert.NoError(t, fDocker.AssertCalls(test.expectCalls))
|
||||
if err == nil {
|
||||
c, err := fDocker.InspectContainer(id)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, strings.Split(c.Name, nameDelimiter), test.expectFields)
|
||||
}
|
||||
}
|
||||
}
|
151
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/docker_image.go
generated
vendored
Normal file
151
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/docker_image.go
generated
vendored
Normal file
@ -0,0 +1,151 @@
|
||||
/*
|
||||
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 dockershim
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
dockertypes "github.com/docker/docker/api/types"
|
||||
dockerfilters "github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/pkg/jsonmessage"
|
||||
|
||||
runtimeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
|
||||
"k8s.io/kubernetes/pkg/kubelet/dockershim/libdocker"
|
||||
)
|
||||
|
||||
// This file implements methods in ImageManagerService.
|
||||
|
||||
// ListImages lists existing images.
|
||||
func (ds *dockerService) ListImages(filter *runtimeapi.ImageFilter) ([]*runtimeapi.Image, error) {
|
||||
opts := dockertypes.ImageListOptions{}
|
||||
if filter != nil {
|
||||
if filter.GetImage().GetImage() != "" {
|
||||
opts.Filters = dockerfilters.NewArgs()
|
||||
opts.Filters.Add("reference", filter.GetImage().GetImage())
|
||||
}
|
||||
}
|
||||
|
||||
images, err := ds.client.ListImages(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make([]*runtimeapi.Image, 0, len(images))
|
||||
for _, i := range images {
|
||||
apiImage, err := imageToRuntimeAPIImage(&i)
|
||||
if err != nil {
|
||||
// TODO: log an error message?
|
||||
continue
|
||||
}
|
||||
result = append(result, apiImage)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// ImageStatus returns the status of the image, returns nil if the image doesn't present.
|
||||
func (ds *dockerService) ImageStatus(image *runtimeapi.ImageSpec) (*runtimeapi.Image, error) {
|
||||
imageInspect, err := ds.client.InspectImageByRef(image.Image)
|
||||
if err != nil {
|
||||
if libdocker.IsImageNotFoundError(err) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return imageInspectToRuntimeAPIImage(imageInspect)
|
||||
}
|
||||
|
||||
// PullImage pulls an image with authentication config.
|
||||
func (ds *dockerService) PullImage(image *runtimeapi.ImageSpec, auth *runtimeapi.AuthConfig) (string, error) {
|
||||
authConfig := dockertypes.AuthConfig{}
|
||||
if auth != nil {
|
||||
authConfig.Username = auth.Username
|
||||
authConfig.Password = auth.Password
|
||||
authConfig.ServerAddress = auth.ServerAddress
|
||||
authConfig.IdentityToken = auth.IdentityToken
|
||||
authConfig.RegistryToken = auth.RegistryToken
|
||||
}
|
||||
err := ds.client.PullImage(image.Image,
|
||||
authConfig,
|
||||
dockertypes.ImagePullOptions{},
|
||||
)
|
||||
if err != nil {
|
||||
return "", filterHTTPError(err, image.Image)
|
||||
}
|
||||
|
||||
return getImageRef(ds.client, image.Image)
|
||||
}
|
||||
|
||||
// RemoveImage removes the image.
|
||||
func (ds *dockerService) RemoveImage(image *runtimeapi.ImageSpec) error {
|
||||
// If the image has multiple tags, we need to remove all the tags
|
||||
// TODO: We assume image.Image is image ID here, which is true in the current implementation
|
||||
// of kubelet, but we should still clarify this in CRI.
|
||||
imageInspect, err := ds.client.InspectImageByID(image.Image)
|
||||
if err == nil && imageInspect != nil && len(imageInspect.RepoTags) > 1 {
|
||||
for _, tag := range imageInspect.RepoTags {
|
||||
if _, err := ds.client.RemoveImage(tag, dockertypes.ImageRemoveOptions{PruneChildren: true}); err != nil && !libdocker.IsImageNotFoundError(err) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
// dockerclient.InspectImageByID doesn't work with digest and repoTags,
|
||||
// it is safe to continue removing it since there is another check below.
|
||||
if err != nil && !libdocker.IsImageNotFoundError(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = ds.client.RemoveImage(image.Image, dockertypes.ImageRemoveOptions{PruneChildren: true})
|
||||
if err != nil && !libdocker.IsImageNotFoundError(err) {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// getImageRef returns the image digest if exists, or else returns the image ID.
|
||||
func getImageRef(client libdocker.Interface, image string) (string, error) {
|
||||
img, err := client.InspectImageByRef(image)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if img == nil {
|
||||
return "", fmt.Errorf("unable to inspect image %s", image)
|
||||
}
|
||||
|
||||
// Returns the digest if it exist.
|
||||
if len(img.RepoDigests) > 0 {
|
||||
return img.RepoDigests[0], nil
|
||||
}
|
||||
|
||||
return img.ID, nil
|
||||
}
|
||||
|
||||
func filterHTTPError(err error, image string) error {
|
||||
// docker/docker/pull/11314 prints detailed error info for docker pull.
|
||||
// When it hits 502, it returns a verbose html output including an inline svg,
|
||||
// which makes the output of kubectl get pods much harder to parse.
|
||||
// Here converts such verbose output to a concise one.
|
||||
jerr, ok := err.(*jsonmessage.JSONError)
|
||||
if ok && (jerr.Code == http.StatusBadGateway ||
|
||||
jerr.Code == http.StatusServiceUnavailable ||
|
||||
jerr.Code == http.StatusGatewayTimeout) {
|
||||
return fmt.Errorf("RegistryUnavailable: %v", err)
|
||||
}
|
||||
return err
|
||||
|
||||
}
|
30
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/docker_image_linux.go
generated
vendored
Normal file
30
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/docker_image_linux.go
generated
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
// +build linux
|
||||
|
||||
/*
|
||||
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 dockershim
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
runtimeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
|
||||
)
|
||||
|
||||
// ImageFsInfo returns information of the filesystem that is used to store images.
|
||||
func (ds *dockerService) ImageFsInfo() ([]*runtimeapi.FilesystemUsage, error) {
|
||||
return nil, fmt.Errorf("not implemented")
|
||||
}
|
74
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/docker_image_test.go
generated
vendored
Normal file
74
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/docker_image_test.go
generated
vendored
Normal file
@ -0,0 +1,74 @@
|
||||
/*
|
||||
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 dockershim
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
dockertypes "github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/pkg/jsonmessage"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
runtimeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
|
||||
"k8s.io/kubernetes/pkg/kubelet/dockershim/libdocker"
|
||||
)
|
||||
|
||||
func TestRemoveImage(t *testing.T) {
|
||||
ds, fakeDocker, _ := newTestDockerService()
|
||||
id := "1111"
|
||||
fakeDocker.InjectImageInspects([]dockertypes.ImageInspect{{ID: id, RepoTags: []string{"foo"}}})
|
||||
ds.RemoveImage(&runtimeapi.ImageSpec{Image: id})
|
||||
fakeDocker.AssertCallDetails(libdocker.NewCalledDetail("inspect_image", nil),
|
||||
libdocker.NewCalledDetail("remove_image", []interface{}{id, dockertypes.ImageRemoveOptions{PruneChildren: true}}))
|
||||
}
|
||||
|
||||
func TestRemoveImageWithMultipleTags(t *testing.T) {
|
||||
ds, fakeDocker, _ := newTestDockerService()
|
||||
id := "1111"
|
||||
fakeDocker.InjectImageInspects([]dockertypes.ImageInspect{{ID: id, RepoTags: []string{"foo", "bar"}}})
|
||||
ds.RemoveImage(&runtimeapi.ImageSpec{Image: id})
|
||||
fakeDocker.AssertCallDetails(libdocker.NewCalledDetail("inspect_image", nil),
|
||||
libdocker.NewCalledDetail("remove_image", []interface{}{"foo", dockertypes.ImageRemoveOptions{PruneChildren: true}}),
|
||||
libdocker.NewCalledDetail("remove_image", []interface{}{"bar", dockertypes.ImageRemoveOptions{PruneChildren: true}}))
|
||||
}
|
||||
|
||||
func TestPullWithJSONError(t *testing.T) {
|
||||
ds, fakeDocker, _ := newTestDockerService()
|
||||
tests := map[string]struct {
|
||||
image *runtimeapi.ImageSpec
|
||||
err error
|
||||
expectedError string
|
||||
}{
|
||||
"Json error": {
|
||||
&runtimeapi.ImageSpec{Image: "ubuntu"},
|
||||
&jsonmessage.JSONError{Code: 50, Message: "Json error"},
|
||||
"Json error",
|
||||
},
|
||||
"Bad gateway": {
|
||||
&runtimeapi.ImageSpec{Image: "ubuntu"},
|
||||
&jsonmessage.JSONError{Code: 502, Message: "<!doctype html>\n<html class=\"no-js\" lang=\"\">\n <head>\n </head>\n <body>\n <h1>Oops, there was an error!</h1>\n <p>We have been contacted of this error, feel free to check out <a href=\"http://status.docker.com/\">status.docker.com</a>\n to see if there is a bigger issue.</p>\n\n </body>\n</html>"},
|
||||
"RegistryUnavailable",
|
||||
},
|
||||
}
|
||||
for key, test := range tests {
|
||||
fakeDocker.InjectError("pull", test.err)
|
||||
_, err := ds.PullImage(test.image, &runtimeapi.AuthConfig{})
|
||||
assert.Error(t, err, fmt.Sprintf("TestCase [%s]", key))
|
||||
assert.Contains(t, err.Error(), test.expectedError)
|
||||
}
|
||||
}
|
30
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/docker_image_unsupported.go
generated
vendored
Normal file
30
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/docker_image_unsupported.go
generated
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
// +build !linux,!windows
|
||||
|
||||
/*
|
||||
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 dockershim
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
runtimeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
|
||||
)
|
||||
|
||||
// ImageFsInfo returns information of the filesystem that is used to store images.
|
||||
func (ds *dockerService) ImageFsInfo() ([]*runtimeapi.FilesystemUsage, error) {
|
||||
return nil, fmt.Errorf("not implemented")
|
||||
}
|
39
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/docker_image_windows.go
generated
vendored
Normal file
39
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/docker_image_windows.go
generated
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
// +build windows
|
||||
|
||||
/*
|
||||
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 dockershim
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
runtimeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
|
||||
)
|
||||
|
||||
// ImageFsInfo returns information of the filesystem that is used to store images.
|
||||
func (ds *dockerService) ImageFsInfo() ([]*runtimeapi.FilesystemUsage, error) {
|
||||
// For Windows Stats to work correctly, a file system must be provided. For now, provide a fake filesystem.
|
||||
filesystems := []*runtimeapi.FilesystemUsage{
|
||||
{
|
||||
Timestamp: time.Now().UnixNano(),
|
||||
UsedBytes: &runtimeapi.UInt64Value{Value: 0},
|
||||
InodesUsed: &runtimeapi.UInt64Value{Value: 0},
|
||||
},
|
||||
}
|
||||
|
||||
return filesystems, nil
|
||||
}
|
677
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/docker_sandbox.go
generated
vendored
Normal file
677
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/docker_sandbox.go
generated
vendored
Normal file
@ -0,0 +1,677 @@
|
||||
/*
|
||||
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 dockershim
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
dockertypes "github.com/docker/docker/api/types"
|
||||
dockercontainer "github.com/docker/docker/api/types/container"
|
||||
dockerfilters "github.com/docker/docker/api/types/filters"
|
||||
"github.com/golang/glog"
|
||||
|
||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||
runtimeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
|
||||
kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
|
||||
"k8s.io/kubernetes/pkg/kubelet/dockershim/libdocker"
|
||||
"k8s.io/kubernetes/pkg/kubelet/qos"
|
||||
"k8s.io/kubernetes/pkg/kubelet/types"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultSandboxImage = "gcr.io/google_containers/pause-amd64:3.0"
|
||||
|
||||
// Various default sandbox resources requests/limits.
|
||||
defaultSandboxCPUshares int64 = 2
|
||||
|
||||
// Name of the underlying container runtime
|
||||
runtimeName = "docker"
|
||||
)
|
||||
|
||||
var (
|
||||
// Termination grace period
|
||||
defaultSandboxGracePeriod = time.Duration(10) * time.Second
|
||||
)
|
||||
|
||||
// Returns whether the sandbox network is ready, and whether the sandbox is known
|
||||
func (ds *dockerService) getNetworkReady(podSandboxID string) (bool, bool) {
|
||||
ds.networkReadyLock.Lock()
|
||||
defer ds.networkReadyLock.Unlock()
|
||||
ready, ok := ds.networkReady[podSandboxID]
|
||||
return ready, ok
|
||||
}
|
||||
|
||||
func (ds *dockerService) setNetworkReady(podSandboxID string, ready bool) {
|
||||
ds.networkReadyLock.Lock()
|
||||
defer ds.networkReadyLock.Unlock()
|
||||
ds.networkReady[podSandboxID] = ready
|
||||
}
|
||||
|
||||
func (ds *dockerService) clearNetworkReady(podSandboxID string) {
|
||||
ds.networkReadyLock.Lock()
|
||||
defer ds.networkReadyLock.Unlock()
|
||||
delete(ds.networkReady, podSandboxID)
|
||||
}
|
||||
|
||||
// RunPodSandbox creates and starts a pod-level sandbox. Runtimes should ensure
|
||||
// the sandbox is in ready state.
|
||||
// For docker, PodSandbox is implemented by a container holding the network
|
||||
// namespace for the pod.
|
||||
// Note: docker doesn't use LogDirectory (yet).
|
||||
func (ds *dockerService) RunPodSandbox(config *runtimeapi.PodSandboxConfig) (id string, err error) {
|
||||
// Step 1: Pull the image for the sandbox.
|
||||
image := defaultSandboxImage
|
||||
podSandboxImage := ds.podSandboxImage
|
||||
if len(podSandboxImage) != 0 {
|
||||
image = podSandboxImage
|
||||
}
|
||||
|
||||
// NOTE: To use a custom sandbox image in a private repository, users need to configure the nodes with credentials properly.
|
||||
// see: http://kubernetes.io/docs/user-guide/images/#configuring-nodes-to-authenticate-to-a-private-repository
|
||||
// Only pull sandbox image when it's not present - v1.PullIfNotPresent.
|
||||
if err := ensureSandboxImageExists(ds.client, image); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Step 2: Create the sandbox container.
|
||||
createConfig, err := ds.makeSandboxDockerConfig(config, image)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to make sandbox docker config for pod %q: %v", config.Metadata.Name, err)
|
||||
}
|
||||
createResp, err := ds.client.CreateContainer(*createConfig)
|
||||
if err != nil {
|
||||
createResp, err = recoverFromCreationConflictIfNeeded(ds.client, *createConfig, err)
|
||||
}
|
||||
|
||||
if err != nil || createResp == nil {
|
||||
return "", fmt.Errorf("failed to create a sandbox for pod %q: %v", config.Metadata.Name, err)
|
||||
}
|
||||
|
||||
ds.setNetworkReady(createResp.ID, false)
|
||||
defer func(e *error) {
|
||||
// Set networking ready depending on the error return of
|
||||
// the parent function
|
||||
if *e == nil {
|
||||
ds.setNetworkReady(createResp.ID, true)
|
||||
}
|
||||
}(&err)
|
||||
|
||||
// Step 3: Create Sandbox Checkpoint.
|
||||
if err = ds.checkpointHandler.CreateCheckpoint(createResp.ID, constructPodSandboxCheckpoint(config)); err != nil {
|
||||
return createResp.ID, err
|
||||
}
|
||||
|
||||
// Step 4: Start the sandbox container.
|
||||
// Assume kubelet's garbage collector would remove the sandbox later, if
|
||||
// startContainer failed.
|
||||
err = ds.client.StartContainer(createResp.ID)
|
||||
if err != nil {
|
||||
return createResp.ID, fmt.Errorf("failed to start sandbox container for pod %q: %v", config.Metadata.Name, err)
|
||||
}
|
||||
|
||||
// Rewrite resolv.conf file generated by docker.
|
||||
// NOTE: cluster dns settings aren't passed anymore to docker api in all cases,
|
||||
// not only for pods with host network: the resolver conf will be overwritten
|
||||
// after sandbox creation to override docker's behaviour. This resolv.conf
|
||||
// file is shared by all containers of the same pod, and needs to be modified
|
||||
// only once per pod.
|
||||
if dnsConfig := config.GetDnsConfig(); dnsConfig != nil {
|
||||
containerInfo, err := ds.client.InspectContainer(createResp.ID)
|
||||
if err != nil {
|
||||
return createResp.ID, fmt.Errorf("failed to inspect sandbox container for pod %q: %v", config.Metadata.Name, err)
|
||||
}
|
||||
|
||||
if err := rewriteResolvFile(containerInfo.ResolvConfPath, dnsConfig.Servers, dnsConfig.Searches, dnsConfig.Options); err != nil {
|
||||
return createResp.ID, fmt.Errorf("rewrite resolv.conf failed for pod %q: %v", config.Metadata.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Do not invoke network plugins if in hostNetwork mode.
|
||||
if nsOptions := config.GetLinux().GetSecurityContext().GetNamespaceOptions(); nsOptions != nil && nsOptions.HostNetwork {
|
||||
return createResp.ID, nil
|
||||
}
|
||||
|
||||
// Step 5: Setup networking for the sandbox.
|
||||
// All pod networking is setup by a CNI plugin discovered at startup time.
|
||||
// This plugin assigns the pod ip, sets up routes inside the sandbox,
|
||||
// creates interfaces etc. In theory, its jurisdiction ends with pod
|
||||
// sandbox networking, but it might insert iptables rules or open ports
|
||||
// on the host as well, to satisfy parts of the pod spec that aren't
|
||||
// recognized by the CNI standard yet.
|
||||
cID := kubecontainer.BuildContainerID(runtimeName, createResp.ID)
|
||||
err = ds.network.SetUpPod(config.GetMetadata().Namespace, config.GetMetadata().Name, cID, config.Annotations)
|
||||
if err != nil {
|
||||
// TODO(random-liu): Do we need to teardown network here?
|
||||
if err := ds.client.StopContainer(createResp.ID, defaultSandboxGracePeriod); err != nil {
|
||||
glog.Warningf("Failed to stop sandbox container %q for pod %q: %v", createResp.ID, config.Metadata.Name, err)
|
||||
}
|
||||
}
|
||||
return createResp.ID, err
|
||||
}
|
||||
|
||||
// StopPodSandbox stops the sandbox. If there are any running containers in the
|
||||
// sandbox, they should be force terminated.
|
||||
// TODO: This function blocks sandbox teardown on networking teardown. Is it
|
||||
// better to cut our losses assuming an out of band GC routine will cleanup
|
||||
// after us?
|
||||
func (ds *dockerService) StopPodSandbox(podSandboxID string) error {
|
||||
var namespace, name string
|
||||
var hostNetwork bool
|
||||
var checkpointErr, statusErr error
|
||||
|
||||
// Try to retrieve sandbox information from docker daemon or sandbox checkpoint
|
||||
status, statusErr := ds.PodSandboxStatus(podSandboxID)
|
||||
if statusErr == nil {
|
||||
nsOpts := status.GetLinux().GetNamespaces().GetOptions()
|
||||
hostNetwork = nsOpts != nil && nsOpts.HostNetwork
|
||||
m := status.GetMetadata()
|
||||
namespace = m.Namespace
|
||||
name = m.Name
|
||||
} else {
|
||||
var checkpoint *PodSandboxCheckpoint
|
||||
checkpoint, checkpointErr = ds.checkpointHandler.GetCheckpoint(podSandboxID)
|
||||
|
||||
// Proceed if both sandbox container and checkpoint could not be found. This means that following
|
||||
// actions will only have sandbox ID and not have pod namespace and name information.
|
||||
// Return error if encounter any unexpected error.
|
||||
if checkpointErr != nil {
|
||||
if libdocker.IsContainerNotFoundError(statusErr) {
|
||||
glog.Warningf("Both sandbox container and checkpoint for id %q could not be found. "+
|
||||
"Proceed without further sandbox information.", podSandboxID)
|
||||
} else {
|
||||
return utilerrors.NewAggregate([]error{
|
||||
fmt.Errorf("failed to get checkpoint for sandbox %q: %v", podSandboxID, checkpointErr),
|
||||
fmt.Errorf("failed to get sandbox status: %v", statusErr)})
|
||||
}
|
||||
} else {
|
||||
namespace = checkpoint.Namespace
|
||||
name = checkpoint.Name
|
||||
hostNetwork = checkpoint.Data != nil && checkpoint.Data.HostNetwork
|
||||
}
|
||||
}
|
||||
|
||||
// WARNING: The following operations made the following assumption:
|
||||
// 1. kubelet will retry on any error returned by StopPodSandbox.
|
||||
// 2. tearing down network and stopping sandbox container can succeed in any sequence.
|
||||
// This depends on the implementation detail of network plugin and proper error handling.
|
||||
// For kubenet, if tearing down network failed and sandbox container is stopped, kubelet
|
||||
// will retry. On retry, kubenet will not be able to retrieve network namespace of the sandbox
|
||||
// since it is stopped. With empty network namespcae, CNI bridge plugin will conduct best
|
||||
// effort clean up and will not return error.
|
||||
errList := []error{}
|
||||
ready, ok := ds.getNetworkReady(podSandboxID)
|
||||
if !hostNetwork && (ready || !ok) {
|
||||
// Only tear down the pod network if we haven't done so already
|
||||
cID := kubecontainer.BuildContainerID(runtimeName, podSandboxID)
|
||||
err := ds.network.TearDownPod(namespace, name, cID)
|
||||
if err == nil {
|
||||
ds.setNetworkReady(podSandboxID, false)
|
||||
} else {
|
||||
errList = append(errList, err)
|
||||
}
|
||||
}
|
||||
if err := ds.client.StopContainer(podSandboxID, defaultSandboxGracePeriod); err != nil {
|
||||
// Do not return error if the container does not exist
|
||||
if !libdocker.IsContainerNotFoundError(err) {
|
||||
glog.Errorf("Failed to stop sandbox %q: %v", podSandboxID, err)
|
||||
errList = append(errList, err)
|
||||
} else {
|
||||
// remove the checkpoint for any sandbox that is not found in the runtime
|
||||
ds.checkpointHandler.RemoveCheckpoint(podSandboxID)
|
||||
}
|
||||
}
|
||||
return utilerrors.NewAggregate(errList)
|
||||
// TODO: Stop all running containers in the sandbox.
|
||||
}
|
||||
|
||||
// RemovePodSandbox removes the sandbox. If there are running containers in the
|
||||
// sandbox, they should be forcibly removed.
|
||||
func (ds *dockerService) RemovePodSandbox(podSandboxID string) error {
|
||||
var errs []error
|
||||
opts := dockertypes.ContainerListOptions{All: true}
|
||||
|
||||
opts.Filters = dockerfilters.NewArgs()
|
||||
f := newDockerFilter(&opts.Filters)
|
||||
f.AddLabel(sandboxIDLabelKey, podSandboxID)
|
||||
|
||||
containers, err := ds.client.ListContainers(opts)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
|
||||
// Remove all containers in the sandbox.
|
||||
for i := range containers {
|
||||
if err := ds.RemoveContainer(containers[i].ID); err != nil && !libdocker.IsContainerNotFoundError(err) {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the sandbox container.
|
||||
err = ds.client.RemoveContainer(podSandboxID, dockertypes.ContainerRemoveOptions{RemoveVolumes: true, Force: true})
|
||||
if err == nil || libdocker.IsContainerNotFoundError(err) {
|
||||
// Only clear network ready when the sandbox has actually been
|
||||
// removed from docker or doesn't exist
|
||||
ds.clearNetworkReady(podSandboxID)
|
||||
} else {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
|
||||
// Remove the checkpoint of the sandbox.
|
||||
if err := ds.checkpointHandler.RemoveCheckpoint(podSandboxID); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
return utilerrors.NewAggregate(errs)
|
||||
}
|
||||
|
||||
// getIPFromPlugin interrogates the network plugin for an IP.
|
||||
func (ds *dockerService) getIPFromPlugin(sandbox *dockertypes.ContainerJSON) (string, error) {
|
||||
metadata, err := parseSandboxName(sandbox.Name)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
msg := fmt.Sprintf("Couldn't find network status for %s/%s through plugin", metadata.Namespace, metadata.Name)
|
||||
cID := kubecontainer.BuildContainerID(runtimeName, sandbox.ID)
|
||||
networkStatus, err := ds.network.GetPodNetworkStatus(metadata.Namespace, metadata.Name, cID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if networkStatus == nil {
|
||||
return "", fmt.Errorf("%v: invalid network status for", msg)
|
||||
}
|
||||
return networkStatus.IP.String(), nil
|
||||
}
|
||||
|
||||
// getIP returns the ip given the output of `docker inspect` on a pod sandbox,
|
||||
// first interrogating any registered plugins, then simply trusting the ip
|
||||
// in the sandbox itself. We look for an ipv4 address before ipv6.
|
||||
func (ds *dockerService) getIP(podSandboxID string, sandbox *dockertypes.ContainerJSON) string {
|
||||
if sandbox.NetworkSettings == nil {
|
||||
return ""
|
||||
}
|
||||
if sharesHostNetwork(sandbox) {
|
||||
// For sandboxes using host network, the shim is not responsible for
|
||||
// reporting the IP.
|
||||
return ""
|
||||
}
|
||||
|
||||
// Don't bother getting IP if the pod is known and networking isn't ready
|
||||
ready, ok := ds.getNetworkReady(podSandboxID)
|
||||
if ok && !ready {
|
||||
return ""
|
||||
}
|
||||
|
||||
ip, err := ds.getIPFromPlugin(sandbox)
|
||||
if err == nil {
|
||||
return ip
|
||||
}
|
||||
|
||||
// TODO: trusting the docker ip is not a great idea. However docker uses
|
||||
// eth0 by default and so does CNI, so if we find a docker IP here, we
|
||||
// conclude that the plugin must have failed setup, or forgotten its ip.
|
||||
// This is not a sensible assumption for plugins across the board, but if
|
||||
// a plugin doesn't want this behavior, it can throw an error.
|
||||
if sandbox.NetworkSettings.IPAddress != "" {
|
||||
return sandbox.NetworkSettings.IPAddress
|
||||
}
|
||||
if sandbox.NetworkSettings.GlobalIPv6Address != "" {
|
||||
return sandbox.NetworkSettings.GlobalIPv6Address
|
||||
}
|
||||
|
||||
// If all else fails, warn but don't return an error, as pod status
|
||||
// should generally not return anything except fatal errors
|
||||
// FIXME: handle network errors by restarting the pod somehow?
|
||||
glog.Warningf("failed to read pod IP from plugin/docker: %v", err)
|
||||
return ""
|
||||
}
|
||||
|
||||
// PodSandboxStatus returns the status of the PodSandbox.
|
||||
func (ds *dockerService) PodSandboxStatus(podSandboxID string) (*runtimeapi.PodSandboxStatus, error) {
|
||||
// Inspect the container.
|
||||
r, err := ds.client.InspectContainer(podSandboxID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Parse the timestamps.
|
||||
createdAt, _, _, err := getContainerTimestamps(r)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse timestamp for container %q: %v", podSandboxID, err)
|
||||
}
|
||||
ct := createdAt.UnixNano()
|
||||
|
||||
// Translate container to sandbox state.
|
||||
state := runtimeapi.PodSandboxState_SANDBOX_NOTREADY
|
||||
if r.State.Running {
|
||||
state = runtimeapi.PodSandboxState_SANDBOX_READY
|
||||
}
|
||||
|
||||
var IP string
|
||||
// TODO: Remove this when sandbox is available on windows
|
||||
// This is a workaround for windows, where sandbox is not in use, and pod IP is determined through containers belonging to the Pod.
|
||||
if IP = ds.determinePodIPBySandboxID(podSandboxID); IP == "" {
|
||||
IP = ds.getIP(podSandboxID, r)
|
||||
}
|
||||
hostNetwork := sharesHostNetwork(r)
|
||||
|
||||
metadata, err := parseSandboxName(r.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
labels, annotations := extractLabels(r.Config.Labels)
|
||||
return &runtimeapi.PodSandboxStatus{
|
||||
Id: r.ID,
|
||||
State: state,
|
||||
CreatedAt: ct,
|
||||
Metadata: metadata,
|
||||
Labels: labels,
|
||||
Annotations: annotations,
|
||||
Network: &runtimeapi.PodSandboxNetworkStatus{
|
||||
Ip: IP,
|
||||
},
|
||||
Linux: &runtimeapi.LinuxPodSandboxStatus{
|
||||
Namespaces: &runtimeapi.Namespace{
|
||||
Options: &runtimeapi.NamespaceOption{
|
||||
HostNetwork: hostNetwork,
|
||||
HostPid: sharesHostPid(r),
|
||||
HostIpc: sharesHostIpc(r),
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ListPodSandbox returns a list of Sandbox.
|
||||
func (ds *dockerService) ListPodSandbox(filter *runtimeapi.PodSandboxFilter) ([]*runtimeapi.PodSandbox, error) {
|
||||
// By default, list all containers whether they are running or not.
|
||||
opts := dockertypes.ContainerListOptions{All: true}
|
||||
filterOutReadySandboxes := false
|
||||
|
||||
opts.Filters = dockerfilters.NewArgs()
|
||||
f := newDockerFilter(&opts.Filters)
|
||||
// Add filter to select only sandbox containers.
|
||||
f.AddLabel(containerTypeLabelKey, containerTypeLabelSandbox)
|
||||
|
||||
if filter != nil {
|
||||
if filter.Id != "" {
|
||||
f.Add("id", filter.Id)
|
||||
}
|
||||
if filter.State != nil {
|
||||
if filter.GetState().State == runtimeapi.PodSandboxState_SANDBOX_READY {
|
||||
// Only list running containers.
|
||||
opts.All = false
|
||||
} else {
|
||||
// runtimeapi.PodSandboxState_SANDBOX_NOTREADY can mean the
|
||||
// container is in any of the non-running state (e.g., created,
|
||||
// exited). We can't tell docker to filter out running
|
||||
// containers directly, so we'll need to filter them out
|
||||
// ourselves after getting the results.
|
||||
filterOutReadySandboxes = true
|
||||
}
|
||||
}
|
||||
|
||||
if filter.LabelSelector != nil {
|
||||
for k, v := range filter.LabelSelector {
|
||||
f.AddLabel(k, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure we get the list of checkpoints first so that we don't include
|
||||
// new PodSandboxes that are being created right now.
|
||||
var err error
|
||||
checkpoints := []string{}
|
||||
if filter == nil {
|
||||
checkpoints, err = ds.checkpointHandler.ListCheckpoints()
|
||||
if err != nil {
|
||||
glog.Errorf("Failed to list checkpoints: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
containers, err := ds.client.ListContainers(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Convert docker containers to runtime api sandboxes.
|
||||
result := []*runtimeapi.PodSandbox{}
|
||||
// using map as set
|
||||
sandboxIDs := make(map[string]bool)
|
||||
for i := range containers {
|
||||
c := containers[i]
|
||||
converted, err := containerToRuntimeAPISandbox(&c)
|
||||
if err != nil {
|
||||
glog.V(4).Infof("Unable to convert docker to runtime API sandbox %+v: %v", c, err)
|
||||
continue
|
||||
}
|
||||
if filterOutReadySandboxes && converted.State == runtimeapi.PodSandboxState_SANDBOX_READY {
|
||||
continue
|
||||
}
|
||||
sandboxIDs[converted.Id] = true
|
||||
result = append(result, converted)
|
||||
}
|
||||
|
||||
// Include sandbox that could only be found with its checkpoint if no filter is applied
|
||||
// These PodSandbox will only include PodSandboxID, Name, Namespace.
|
||||
// These PodSandbox will be in PodSandboxState_SANDBOX_NOTREADY state.
|
||||
for _, id := range checkpoints {
|
||||
if _, ok := sandboxIDs[id]; ok {
|
||||
continue
|
||||
}
|
||||
checkpoint, err := ds.checkpointHandler.GetCheckpoint(id)
|
||||
if err != nil {
|
||||
glog.Errorf("Failed to retrieve checkpoint for sandbox %q: %v", id, err)
|
||||
continue
|
||||
}
|
||||
result = append(result, checkpointToRuntimeAPISandbox(id, checkpoint))
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// applySandboxLinuxOptions applies LinuxPodSandboxConfig to dockercontainer.HostConfig and dockercontainer.ContainerCreateConfig.
|
||||
func (ds *dockerService) applySandboxLinuxOptions(hc *dockercontainer.HostConfig, lc *runtimeapi.LinuxPodSandboxConfig, createConfig *dockertypes.ContainerCreateConfig, image string, separator rune) error {
|
||||
if lc == nil {
|
||||
return nil
|
||||
}
|
||||
// Apply security context.
|
||||
if err := applySandboxSecurityContext(lc, createConfig.Config, hc, ds.network, separator); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Set sysctls.
|
||||
hc.Sysctls = lc.Sysctls
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ds *dockerService) applySandboxResources(hc *dockercontainer.HostConfig, lc *runtimeapi.LinuxPodSandboxConfig) error {
|
||||
hc.Resources = dockercontainer.Resources{
|
||||
MemorySwap: DefaultMemorySwap(),
|
||||
CPUShares: defaultSandboxCPUshares,
|
||||
// Use docker's default cpu quota/period.
|
||||
}
|
||||
|
||||
if lc != nil {
|
||||
// Apply Cgroup options.
|
||||
cgroupParent, err := ds.GenerateExpectedCgroupParent(lc.CgroupParent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hc.CgroupParent = cgroupParent
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// makeSandboxDockerConfig returns dockertypes.ContainerCreateConfig based on runtimeapi.PodSandboxConfig.
|
||||
func (ds *dockerService) makeSandboxDockerConfig(c *runtimeapi.PodSandboxConfig, image string) (*dockertypes.ContainerCreateConfig, error) {
|
||||
// Merge annotations and labels because docker supports only labels.
|
||||
labels := makeLabels(c.GetLabels(), c.GetAnnotations())
|
||||
// Apply a label to distinguish sandboxes from regular containers.
|
||||
labels[containerTypeLabelKey] = containerTypeLabelSandbox
|
||||
// Apply a container name label for infra container. This is used in summary v1.
|
||||
// TODO(random-liu): Deprecate this label once container metrics is directly got from CRI.
|
||||
labels[types.KubernetesContainerNameLabel] = sandboxContainerName
|
||||
|
||||
apiVersion, err := ds.getDockerAPIVersion()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to get the docker API version: %v", err)
|
||||
}
|
||||
securityOptSep := getSecurityOptSeparator(apiVersion)
|
||||
|
||||
hc := &dockercontainer.HostConfig{}
|
||||
createConfig := &dockertypes.ContainerCreateConfig{
|
||||
Name: makeSandboxName(c),
|
||||
Config: &dockercontainer.Config{
|
||||
Hostname: c.Hostname,
|
||||
// TODO: Handle environment variables.
|
||||
Image: image,
|
||||
Labels: labels,
|
||||
},
|
||||
HostConfig: hc,
|
||||
}
|
||||
|
||||
// Apply linux-specific options.
|
||||
if err := ds.applySandboxLinuxOptions(hc, c.GetLinux(), createConfig, image, securityOptSep); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Set port mappings.
|
||||
exposedPorts, portBindings := makePortsAndBindings(c.GetPortMappings())
|
||||
createConfig.Config.ExposedPorts = exposedPorts
|
||||
hc.PortBindings = portBindings
|
||||
|
||||
// TODO: Get rid of the dependency on kubelet internal package.
|
||||
hc.OomScoreAdj = qos.PodInfraOOMAdj
|
||||
|
||||
// Apply resource options.
|
||||
if err := ds.applySandboxResources(hc, c.GetLinux()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Set security options.
|
||||
securityOpts, err := ds.getSecurityOpts(c.GetLinux().GetSecurityContext().GetSeccompProfilePath(), securityOptSep)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to generate sandbox security options for sandbox %q: %v", c.Metadata.Name, err)
|
||||
}
|
||||
hc.SecurityOpt = append(hc.SecurityOpt, securityOpts...)
|
||||
return createConfig, nil
|
||||
}
|
||||
|
||||
// sharesHostNetwork returns true if the given container is sharing the host's
|
||||
// network namespace.
|
||||
func sharesHostNetwork(container *dockertypes.ContainerJSON) bool {
|
||||
if container != nil && container.HostConfig != nil {
|
||||
return string(container.HostConfig.NetworkMode) == namespaceModeHost
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// sharesHostPid returns true if the given container is sharing the host's pid
|
||||
// namespace.
|
||||
func sharesHostPid(container *dockertypes.ContainerJSON) bool {
|
||||
if container != nil && container.HostConfig != nil {
|
||||
return string(container.HostConfig.PidMode) == namespaceModeHost
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// sharesHostIpc returns true if the given container is sharing the host's ipc
|
||||
// namespace.
|
||||
func sharesHostIpc(container *dockertypes.ContainerJSON) bool {
|
||||
if container != nil && container.HostConfig != nil {
|
||||
return string(container.HostConfig.IpcMode) == namespaceModeHost
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func constructPodSandboxCheckpoint(config *runtimeapi.PodSandboxConfig) *PodSandboxCheckpoint {
|
||||
checkpoint := NewPodSandboxCheckpoint(config.Metadata.Namespace, config.Metadata.Name)
|
||||
for _, pm := range config.GetPortMappings() {
|
||||
proto := toCheckpointProtocol(pm.Protocol)
|
||||
checkpoint.Data.PortMappings = append(checkpoint.Data.PortMappings, &PortMapping{
|
||||
HostPort: &pm.HostPort,
|
||||
ContainerPort: &pm.ContainerPort,
|
||||
Protocol: &proto,
|
||||
})
|
||||
}
|
||||
if nsOptions := config.GetLinux().GetSecurityContext().GetNamespaceOptions(); nsOptions != nil {
|
||||
checkpoint.Data.HostNetwork = nsOptions.HostNetwork
|
||||
}
|
||||
return checkpoint
|
||||
}
|
||||
|
||||
func toCheckpointProtocol(protocol runtimeapi.Protocol) Protocol {
|
||||
switch protocol {
|
||||
case runtimeapi.Protocol_TCP:
|
||||
return protocolTCP
|
||||
case runtimeapi.Protocol_UDP:
|
||||
return protocolUDP
|
||||
}
|
||||
glog.Warningf("Unknown protocol %q: defaulting to TCP", protocol)
|
||||
return protocolTCP
|
||||
}
|
||||
|
||||
// rewriteResolvFile rewrites resolv.conf file generated by docker.
|
||||
func rewriteResolvFile(resolvFilePath string, dns []string, dnsSearch []string, dnsOptions []string) error {
|
||||
if len(resolvFilePath) == 0 {
|
||||
glog.Errorf("ResolvConfPath is empty.")
|
||||
return nil
|
||||
}
|
||||
|
||||
if _, err := os.Stat(resolvFilePath); os.IsNotExist(err) {
|
||||
return fmt.Errorf("ResolvConfPath %q does not exist", resolvFilePath)
|
||||
}
|
||||
|
||||
var resolvFileContent []string
|
||||
for _, srv := range dns {
|
||||
resolvFileContent = append(resolvFileContent, "nameserver "+srv)
|
||||
}
|
||||
|
||||
if len(dnsSearch) > 0 {
|
||||
resolvFileContent = append(resolvFileContent, "search "+strings.Join(dnsSearch, " "))
|
||||
}
|
||||
|
||||
if len(dnsOptions) > 0 {
|
||||
resolvFileContent = append(resolvFileContent, "options "+strings.Join(dnsOptions, " "))
|
||||
}
|
||||
|
||||
if len(resolvFileContent) > 0 {
|
||||
resolvFileContentStr := strings.Join(resolvFileContent, "\n")
|
||||
resolvFileContentStr += "\n"
|
||||
|
||||
glog.V(4).Infof("Will attempt to re-write config file %s with: \n%s", resolvFilePath, resolvFileContent)
|
||||
if err := rewriteFile(resolvFilePath, resolvFileContentStr); err != nil {
|
||||
glog.Errorf("resolv.conf could not be updated: %v", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func rewriteFile(filePath, stringToWrite string) error {
|
||||
f, err := os.OpenFile(filePath, os.O_TRUNC|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
_, err = f.WriteString(stringToWrite)
|
||||
return err
|
||||
}
|
294
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/docker_sandbox_test.go
generated
vendored
Normal file
294
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/docker_sandbox_test.go
generated
vendored
Normal file
@ -0,0 +1,294 @@
|
||||
/*
|
||||
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 dockershim
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
runtimeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
|
||||
kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
|
||||
"k8s.io/kubernetes/pkg/kubelet/dockershim/libdocker"
|
||||
"k8s.io/kubernetes/pkg/kubelet/network"
|
||||
"k8s.io/kubernetes/pkg/kubelet/types"
|
||||
)
|
||||
|
||||
// A helper to create a basic config.
|
||||
func makeSandboxConfig(name, namespace, uid string, attempt uint32) *runtimeapi.PodSandboxConfig {
|
||||
return makeSandboxConfigWithLabelsAndAnnotations(name, namespace, uid, attempt, map[string]string{}, map[string]string{})
|
||||
}
|
||||
|
||||
func makeSandboxConfigWithLabelsAndAnnotations(name, namespace, uid string, attempt uint32, labels, annotations map[string]string) *runtimeapi.PodSandboxConfig {
|
||||
return &runtimeapi.PodSandboxConfig{
|
||||
Metadata: &runtimeapi.PodSandboxMetadata{
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
Uid: uid,
|
||||
Attempt: attempt,
|
||||
},
|
||||
Labels: labels,
|
||||
Annotations: annotations,
|
||||
}
|
||||
}
|
||||
|
||||
// TestListSandboxes creates several sandboxes and then list them to check
|
||||
// whether the correct metadatas, states, and labels are returned.
|
||||
func TestListSandboxes(t *testing.T) {
|
||||
ds, _, fakeClock := newTestDockerService()
|
||||
name, namespace := "foo", "bar"
|
||||
configs := []*runtimeapi.PodSandboxConfig{}
|
||||
for i := 0; i < 3; i++ {
|
||||
c := makeSandboxConfigWithLabelsAndAnnotations(fmt.Sprintf("%s%d", name, i),
|
||||
fmt.Sprintf("%s%d", namespace, i), fmt.Sprintf("%d", i), 0,
|
||||
map[string]string{"label": fmt.Sprintf("foo%d", i)},
|
||||
map[string]string{"annotation": fmt.Sprintf("bar%d", i)},
|
||||
)
|
||||
configs = append(configs, c)
|
||||
}
|
||||
|
||||
expected := []*runtimeapi.PodSandbox{}
|
||||
state := runtimeapi.PodSandboxState_SANDBOX_READY
|
||||
var createdAt int64 = fakeClock.Now().UnixNano()
|
||||
for i := range configs {
|
||||
id, err := ds.RunPodSandbox(configs[i])
|
||||
assert.NoError(t, err)
|
||||
// Prepend to the expected list because ListPodSandbox returns
|
||||
// the most recent sandbox first.
|
||||
expected = append([]*runtimeapi.PodSandbox{{
|
||||
Metadata: configs[i].Metadata,
|
||||
Id: id,
|
||||
State: state,
|
||||
CreatedAt: createdAt,
|
||||
Labels: configs[i].Labels,
|
||||
Annotations: configs[i].Annotations,
|
||||
}}, expected...)
|
||||
}
|
||||
sandboxes, err := ds.ListPodSandbox(nil)
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, sandboxes, len(expected))
|
||||
assert.Equal(t, expected, sandboxes)
|
||||
}
|
||||
|
||||
// TestSandboxStatus tests the basic lifecycle operations and verify that
|
||||
// the status returned reflects the operations performed.
|
||||
func TestSandboxStatus(t *testing.T) {
|
||||
ds, fDocker, fClock := newTestDockerService()
|
||||
labels := map[string]string{"label": "foobar1"}
|
||||
annotations := map[string]string{"annotation": "abc"}
|
||||
config := makeSandboxConfigWithLabelsAndAnnotations("foo", "bar", "1", 0, labels, annotations)
|
||||
|
||||
// TODO: The following variables depend on the internal
|
||||
// implementation of FakeDockerClient, and should be fixed.
|
||||
fakeIP := "2.3.4.5"
|
||||
|
||||
state := runtimeapi.PodSandboxState_SANDBOX_READY
|
||||
ct := int64(0)
|
||||
hostNetwork := false
|
||||
expected := &runtimeapi.PodSandboxStatus{
|
||||
State: state,
|
||||
CreatedAt: ct,
|
||||
Metadata: config.Metadata,
|
||||
Network: &runtimeapi.PodSandboxNetworkStatus{Ip: fakeIP},
|
||||
Linux: &runtimeapi.LinuxPodSandboxStatus{Namespaces: &runtimeapi.Namespace{Options: &runtimeapi.NamespaceOption{HostNetwork: hostNetwork}}},
|
||||
Labels: labels,
|
||||
Annotations: annotations,
|
||||
}
|
||||
|
||||
// Create the sandbox.
|
||||
fClock.SetTime(time.Now())
|
||||
expected.CreatedAt = fClock.Now().UnixNano()
|
||||
id, err := ds.RunPodSandbox(config)
|
||||
|
||||
// Check internal labels
|
||||
c, err := fDocker.InspectContainer(id)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, c.Config.Labels[containerTypeLabelKey], containerTypeLabelSandbox)
|
||||
assert.Equal(t, c.Config.Labels[types.KubernetesContainerNameLabel], sandboxContainerName)
|
||||
|
||||
expected.Id = id // ID is only known after the creation.
|
||||
status, err := ds.PodSandboxStatus(id)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expected, status)
|
||||
|
||||
// Stop the sandbox.
|
||||
expected.State = runtimeapi.PodSandboxState_SANDBOX_NOTREADY
|
||||
err = ds.StopPodSandbox(id)
|
||||
assert.NoError(t, err)
|
||||
// IP not valid after sandbox stop
|
||||
expected.Network.Ip = ""
|
||||
status, err = ds.PodSandboxStatus(id)
|
||||
assert.Equal(t, expected, status)
|
||||
|
||||
// Remove the container.
|
||||
err = ds.RemovePodSandbox(id)
|
||||
assert.NoError(t, err)
|
||||
status, err = ds.PodSandboxStatus(id)
|
||||
assert.Error(t, err, fmt.Sprintf("status of sandbox: %+v", status))
|
||||
}
|
||||
|
||||
// TestSandboxStatusAfterRestart tests that retrieving sandbox status returns
|
||||
// an IP address even if RunPodSandbox() was not yet called for this pod, as
|
||||
// would happen on kubelet restart
|
||||
func TestSandboxStatusAfterRestart(t *testing.T) {
|
||||
ds, _, fClock := newTestDockerService()
|
||||
config := makeSandboxConfig("foo", "bar", "1", 0)
|
||||
|
||||
// TODO: The following variables depend on the internal
|
||||
// implementation of FakeDockerClient, and should be fixed.
|
||||
fakeIP := "2.3.4.5"
|
||||
|
||||
state := runtimeapi.PodSandboxState_SANDBOX_READY
|
||||
ct := int64(0)
|
||||
hostNetwork := false
|
||||
expected := &runtimeapi.PodSandboxStatus{
|
||||
State: state,
|
||||
CreatedAt: ct,
|
||||
Metadata: config.Metadata,
|
||||
Network: &runtimeapi.PodSandboxNetworkStatus{Ip: fakeIP},
|
||||
Linux: &runtimeapi.LinuxPodSandboxStatus{Namespaces: &runtimeapi.Namespace{Options: &runtimeapi.NamespaceOption{HostNetwork: hostNetwork}}},
|
||||
Labels: map[string]string{},
|
||||
Annotations: map[string]string{},
|
||||
}
|
||||
|
||||
// Create the sandbox.
|
||||
fClock.SetTime(time.Now())
|
||||
expected.CreatedAt = fClock.Now().UnixNano()
|
||||
|
||||
createConfig, err := ds.makeSandboxDockerConfig(config, defaultSandboxImage)
|
||||
assert.NoError(t, err)
|
||||
|
||||
createResp, err := ds.client.CreateContainer(*createConfig)
|
||||
assert.NoError(t, err)
|
||||
err = ds.client.StartContainer(createResp.ID)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Check status without RunPodSandbox() having set up networking
|
||||
expected.Id = createResp.ID // ID is only known after the creation.
|
||||
status, err := ds.PodSandboxStatus(createResp.ID)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, expected, status)
|
||||
}
|
||||
|
||||
// TestNetworkPluginInvocation checks that the right SetUpPod and TearDownPod
|
||||
// calls are made when we run/stop a sandbox.
|
||||
func TestNetworkPluginInvocation(t *testing.T) {
|
||||
ds, _, _ := newTestDockerService()
|
||||
mockPlugin := newTestNetworkPlugin(t)
|
||||
ds.network = network.NewPluginManager(mockPlugin)
|
||||
defer mockPlugin.Finish()
|
||||
|
||||
name := "foo0"
|
||||
ns := "bar0"
|
||||
c := makeSandboxConfigWithLabelsAndAnnotations(
|
||||
name, ns, "0", 0,
|
||||
map[string]string{"label": name},
|
||||
map[string]string{"annotation": ns},
|
||||
)
|
||||
cID := kubecontainer.ContainerID{Type: runtimeName, ID: libdocker.GetFakeContainerID(fmt.Sprintf("/%v", makeSandboxName(c)))}
|
||||
|
||||
mockPlugin.EXPECT().Name().Return("mockNetworkPlugin").AnyTimes()
|
||||
setup := mockPlugin.EXPECT().SetUpPod(ns, name, cID)
|
||||
// StopPodSandbox performs a lookup on status to figure out if the sandbox
|
||||
// is running with hostnetworking, as all its given is the ID.
|
||||
mockPlugin.EXPECT().GetPodNetworkStatus(ns, name, cID)
|
||||
mockPlugin.EXPECT().TearDownPod(ns, name, cID).After(setup)
|
||||
|
||||
_, err := ds.RunPodSandbox(c)
|
||||
assert.NoError(t, err)
|
||||
err = ds.StopPodSandbox(cID.ID)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
// TestHostNetworkPluginInvocation checks that *no* SetUp/TearDown calls happen
|
||||
// for host network sandboxes.
|
||||
func TestHostNetworkPluginInvocation(t *testing.T) {
|
||||
ds, _, _ := newTestDockerService()
|
||||
mockPlugin := newTestNetworkPlugin(t)
|
||||
ds.network = network.NewPluginManager(mockPlugin)
|
||||
defer mockPlugin.Finish()
|
||||
|
||||
name := "foo0"
|
||||
ns := "bar0"
|
||||
c := makeSandboxConfigWithLabelsAndAnnotations(
|
||||
name, ns, "0", 0,
|
||||
map[string]string{"label": name},
|
||||
map[string]string{"annotation": ns},
|
||||
)
|
||||
hostNetwork := true
|
||||
c.Linux = &runtimeapi.LinuxPodSandboxConfig{
|
||||
SecurityContext: &runtimeapi.LinuxSandboxSecurityContext{
|
||||
NamespaceOptions: &runtimeapi.NamespaceOption{
|
||||
HostNetwork: hostNetwork,
|
||||
},
|
||||
},
|
||||
}
|
||||
cID := kubecontainer.ContainerID{Type: runtimeName, ID: libdocker.GetFakeContainerID(fmt.Sprintf("/%v", makeSandboxName(c)))}
|
||||
|
||||
// No calls to network plugin are expected
|
||||
_, err := ds.RunPodSandbox(c)
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, ds.StopPodSandbox(cID.ID))
|
||||
}
|
||||
|
||||
// TestSetUpPodFailure checks that the sandbox should be not ready when it
|
||||
// hits a SetUpPod failure.
|
||||
func TestSetUpPodFailure(t *testing.T) {
|
||||
ds, _, _ := newTestDockerService()
|
||||
mockPlugin := newTestNetworkPlugin(t)
|
||||
ds.network = network.NewPluginManager(mockPlugin)
|
||||
defer mockPlugin.Finish()
|
||||
|
||||
name := "foo0"
|
||||
ns := "bar0"
|
||||
c := makeSandboxConfigWithLabelsAndAnnotations(
|
||||
name, ns, "0", 0,
|
||||
map[string]string{"label": name},
|
||||
map[string]string{"annotation": ns},
|
||||
)
|
||||
cID := kubecontainer.ContainerID{Type: runtimeName, ID: libdocker.GetFakeContainerID(fmt.Sprintf("/%v", makeSandboxName(c)))}
|
||||
mockPlugin.EXPECT().Name().Return("mockNetworkPlugin").AnyTimes()
|
||||
mockPlugin.EXPECT().SetUpPod(ns, name, cID).Return(errors.New("setup pod error")).AnyTimes()
|
||||
// Assume network plugin doesn't return error, dockershim should still be able to return not ready correctly.
|
||||
mockPlugin.EXPECT().GetPodNetworkStatus(ns, name, cID).Return(&network.PodNetworkStatus{IP: net.IP("127.0.0.01")}, nil).AnyTimes()
|
||||
|
||||
t.Logf("RunPodSandbox should return error")
|
||||
_, err := ds.RunPodSandbox(c)
|
||||
assert.Error(t, err)
|
||||
|
||||
t.Logf("PodSandboxStatus should be not ready")
|
||||
status, err := ds.PodSandboxStatus(cID.ID)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, runtimeapi.PodSandboxState_SANDBOX_NOTREADY, status.State)
|
||||
|
||||
t.Logf("ListPodSandbox should also show not ready")
|
||||
sandboxes, err := ds.ListPodSandbox(nil)
|
||||
assert.NoError(t, err)
|
||||
var sandbox *runtimeapi.PodSandbox
|
||||
for _, s := range sandboxes {
|
||||
if s.Id == cID.ID {
|
||||
sandbox = s
|
||||
break
|
||||
}
|
||||
}
|
||||
assert.NotNil(t, sandbox)
|
||||
assert.Equal(t, runtimeapi.PodSandboxState_SANDBOX_NOTREADY, sandbox.State)
|
||||
}
|
609
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/docker_service.go
generated
vendored
Normal file
609
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/docker_service.go
generated
vendored
Normal file
@ -0,0 +1,609 @@
|
||||
/*
|
||||
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 dockershim
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/armon/circbuf"
|
||||
"github.com/blang/semver"
|
||||
dockertypes "github.com/docker/docker/api/types"
|
||||
"github.com/golang/glog"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
kubetypes "k8s.io/apimachinery/pkg/types"
|
||||
internalapi "k8s.io/kubernetes/pkg/kubelet/apis/cri"
|
||||
runtimeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
|
||||
"k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig"
|
||||
kubecm "k8s.io/kubernetes/pkg/kubelet/cm"
|
||||
kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
|
||||
"k8s.io/kubernetes/pkg/kubelet/dockershim/cm"
|
||||
"k8s.io/kubernetes/pkg/kubelet/network"
|
||||
"k8s.io/kubernetes/pkg/kubelet/network/cni"
|
||||
"k8s.io/kubernetes/pkg/kubelet/network/hostport"
|
||||
"k8s.io/kubernetes/pkg/kubelet/network/kubenet"
|
||||
"k8s.io/kubernetes/pkg/kubelet/server/streaming"
|
||||
"k8s.io/kubernetes/pkg/kubelet/util/cache"
|
||||
utilstore "k8s.io/kubernetes/pkg/kubelet/util/store"
|
||||
|
||||
"k8s.io/kubernetes/pkg/kubelet/dockershim/libdocker"
|
||||
"k8s.io/kubernetes/pkg/kubelet/dockershim/metrics"
|
||||
)
|
||||
|
||||
const (
|
||||
dockerRuntimeName = "docker"
|
||||
kubeAPIVersion = "0.1.0"
|
||||
|
||||
// String used to detect docker host mode for various namespaces (e.g.
|
||||
// networking). Must match the value returned by docker inspect -f
|
||||
// '{{.HostConfig.NetworkMode}}'.
|
||||
namespaceModeHost = "host"
|
||||
|
||||
dockerNetNSFmt = "/proc/%v/ns/net"
|
||||
|
||||
defaultSeccompProfile = "unconfined"
|
||||
|
||||
// Internal docker labels used to identify whether a container is a sandbox
|
||||
// or a regular container.
|
||||
// TODO: This is not backward compatible with older containers. We will
|
||||
// need to add filtering based on names.
|
||||
containerTypeLabelKey = "io.kubernetes.docker.type"
|
||||
containerTypeLabelSandbox = "podsandbox"
|
||||
containerTypeLabelContainer = "container"
|
||||
containerLogPathLabelKey = "io.kubernetes.container.logpath"
|
||||
sandboxIDLabelKey = "io.kubernetes.sandbox.id"
|
||||
|
||||
// The expiration time of version cache.
|
||||
versionCacheTTL = 60 * time.Second
|
||||
|
||||
defaultCgroupDriver = "cgroupfs"
|
||||
|
||||
// TODO: https://github.com/kubernetes/kubernetes/pull/31169 provides experimental
|
||||
// defaulting of host user namespace that may be enabled when the docker daemon
|
||||
// is using remapped UIDs.
|
||||
// Dockershim should provide detection support for a remapping environment .
|
||||
// This should be included in the feature proposal. Defaulting may still occur according
|
||||
// to kubelet behavior and system settings in addition to any API flags that may be introduced.
|
||||
)
|
||||
|
||||
// NetworkPluginSettings is the subset of kubelet runtime args we pass
|
||||
// to the container runtime shim so it can probe for network plugins.
|
||||
// In the future we will feed these directly to a standalone container
|
||||
// runtime process.
|
||||
type NetworkPluginSettings struct {
|
||||
// HairpinMode is best described by comments surrounding the kubelet arg
|
||||
HairpinMode kubeletconfig.HairpinMode
|
||||
// NonMasqueradeCIDR is the range of ips which should *not* be included
|
||||
// in any MASQUERADE rules applied by the plugin
|
||||
NonMasqueradeCIDR string
|
||||
// PluginName is the name of the plugin, runtime shim probes for
|
||||
PluginName string
|
||||
// PluginBinDir is the directory in which the binaries for the plugin with
|
||||
// PluginName is kept. The admin is responsible for provisioning these
|
||||
// binaries before-hand.
|
||||
PluginBinDir string
|
||||
// PluginConfDir is the directory in which the admin places a CNI conf.
|
||||
// Depending on the plugin, this may be an optional field, eg: kubenet
|
||||
// generates its own plugin conf.
|
||||
PluginConfDir string
|
||||
// MTU is the desired MTU for network devices created by the plugin.
|
||||
MTU int
|
||||
|
||||
// RuntimeHost is an interface that serves as a trap-door from plugin back
|
||||
// into the kubelet.
|
||||
// TODO: This shouldn't be required, remove once we move host ports into CNI
|
||||
// and figure out bandwidth shaping. See corresponding comments above
|
||||
// network.Host interface.
|
||||
LegacyRuntimeHost network.LegacyHost
|
||||
}
|
||||
|
||||
// namespaceGetter is a wrapper around the dockerService that implements
|
||||
// the network.NamespaceGetter interface.
|
||||
type namespaceGetter struct {
|
||||
ds *dockerService
|
||||
}
|
||||
|
||||
func (n *namespaceGetter) GetNetNS(containerID string) (string, error) {
|
||||
return n.ds.GetNetNS(containerID)
|
||||
}
|
||||
|
||||
// portMappingGetter is a wrapper around the dockerService that implements
|
||||
// the network.PortMappingGetter interface.
|
||||
type portMappingGetter struct {
|
||||
ds *dockerService
|
||||
}
|
||||
|
||||
func (p *portMappingGetter) GetPodPortMappings(containerID string) ([]*hostport.PortMapping, error) {
|
||||
return p.ds.GetPodPortMappings(containerID)
|
||||
}
|
||||
|
||||
// dockerNetworkHost implements network.Host by wrapping the legacy host passed in by the kubelet
|
||||
// and dockerServices which implementes the rest of the network host interfaces.
|
||||
// The legacy host methods are slated for deletion.
|
||||
type dockerNetworkHost struct {
|
||||
network.LegacyHost
|
||||
*namespaceGetter
|
||||
*portMappingGetter
|
||||
}
|
||||
|
||||
var internalLabelKeys []string = []string{containerTypeLabelKey, containerLogPathLabelKey, sandboxIDLabelKey}
|
||||
|
||||
// ClientConfig is parameters used to initialize docker client
|
||||
type ClientConfig struct {
|
||||
DockerEndpoint string
|
||||
RuntimeRequestTimeout time.Duration
|
||||
ImagePullProgressDeadline time.Duration
|
||||
|
||||
// Configuration for fake docker client
|
||||
EnableSleep bool
|
||||
WithTraceDisabled bool
|
||||
}
|
||||
|
||||
// NewDockerClientFromConfig create a docker client from given configure
|
||||
// return nil if nil configure is given.
|
||||
func NewDockerClientFromConfig(config *ClientConfig) libdocker.Interface {
|
||||
if config != nil {
|
||||
// Create docker client.
|
||||
client := libdocker.ConnectToDockerOrDie(
|
||||
config.DockerEndpoint,
|
||||
config.RuntimeRequestTimeout,
|
||||
config.ImagePullProgressDeadline,
|
||||
config.WithTraceDisabled,
|
||||
config.EnableSleep,
|
||||
)
|
||||
return client
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NOTE: Anything passed to DockerService should be eventually handled in another way when we switch to running the shim as a different process.
|
||||
func NewDockerService(config *ClientConfig, podSandboxImage string, streamingConfig *streaming.Config,
|
||||
pluginSettings *NetworkPluginSettings, cgroupsName string, kubeCgroupDriver string, dockershimRootDir string, disableSharedPID bool) (DockerService, error) {
|
||||
|
||||
client := NewDockerClientFromConfig(config)
|
||||
|
||||
c := libdocker.NewInstrumentedInterface(client)
|
||||
checkpointHandler, err := NewPersistentCheckpointHandler(dockershimRootDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ds := &dockerService{
|
||||
client: c,
|
||||
os: kubecontainer.RealOS{},
|
||||
podSandboxImage: podSandboxImage,
|
||||
streamingRuntime: &streamingRuntime{
|
||||
client: client,
|
||||
execHandler: &NativeExecHandler{},
|
||||
},
|
||||
containerManager: cm.NewContainerManager(cgroupsName, client),
|
||||
checkpointHandler: checkpointHandler,
|
||||
disableSharedPID: disableSharedPID,
|
||||
networkReady: make(map[string]bool),
|
||||
}
|
||||
|
||||
// check docker version compatibility.
|
||||
if err = ds.checkVersionCompatibility(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// create streaming server if configured.
|
||||
if streamingConfig != nil {
|
||||
var err error
|
||||
ds.streamingServer, err = streaming.NewServer(*streamingConfig, ds.streamingRuntime)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
// dockershim currently only supports CNI plugins.
|
||||
cniPlugins := cni.ProbeNetworkPlugins(pluginSettings.PluginConfDir, pluginSettings.PluginBinDir)
|
||||
cniPlugins = append(cniPlugins, kubenet.NewPlugin(pluginSettings.PluginBinDir))
|
||||
netHost := &dockerNetworkHost{
|
||||
pluginSettings.LegacyRuntimeHost,
|
||||
&namespaceGetter{ds},
|
||||
&portMappingGetter{ds},
|
||||
}
|
||||
plug, err := network.InitNetworkPlugin(cniPlugins, pluginSettings.PluginName, netHost, pluginSettings.HairpinMode, pluginSettings.NonMasqueradeCIDR, pluginSettings.MTU)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("didn't find compatible CNI plugin with given settings %+v: %v", pluginSettings, err)
|
||||
}
|
||||
ds.network = network.NewPluginManager(plug)
|
||||
glog.Infof("Docker cri networking managed by %v", plug.Name())
|
||||
|
||||
// NOTE: cgroup driver is only detectable in docker 1.11+
|
||||
cgroupDriver := defaultCgroupDriver
|
||||
dockerInfo, err := ds.client.Info()
|
||||
glog.Infof("Docker Info: %+v", dockerInfo)
|
||||
if err != nil {
|
||||
glog.Errorf("Failed to execute Info() call to the Docker client: %v", err)
|
||||
glog.Warningf("Falling back to use the default driver: %q", cgroupDriver)
|
||||
} else if len(dockerInfo.CgroupDriver) == 0 {
|
||||
glog.Warningf("No cgroup driver is set in Docker")
|
||||
glog.Warningf("Falling back to use the default driver: %q", cgroupDriver)
|
||||
} else {
|
||||
cgroupDriver = dockerInfo.CgroupDriver
|
||||
}
|
||||
if len(kubeCgroupDriver) != 0 && kubeCgroupDriver != cgroupDriver {
|
||||
return nil, fmt.Errorf("misconfiguration: kubelet cgroup driver: %q is different from docker cgroup driver: %q", kubeCgroupDriver, cgroupDriver)
|
||||
}
|
||||
glog.Infof("Setting cgroupDriver to %s", cgroupDriver)
|
||||
ds.cgroupDriver = cgroupDriver
|
||||
ds.versionCache = cache.NewObjectCache(
|
||||
func() (interface{}, error) {
|
||||
return ds.getDockerVersion()
|
||||
},
|
||||
versionCacheTTL,
|
||||
)
|
||||
|
||||
// Register prometheus metrics.
|
||||
metrics.Register()
|
||||
|
||||
return ds, nil
|
||||
}
|
||||
|
||||
// DockerService is an interface that embeds the new RuntimeService and
|
||||
// ImageService interfaces.
|
||||
type DockerService interface {
|
||||
internalapi.RuntimeService
|
||||
internalapi.ImageManagerService
|
||||
Start() error
|
||||
// For serving streaming calls.
|
||||
http.Handler
|
||||
|
||||
// IsCRISupportedLogDriver checks whether the logging driver used by docker is
|
||||
// suppoted by native CRI integration.
|
||||
// TODO(resouer): remove this when deprecating unsupported log driver
|
||||
IsCRISupportedLogDriver() (bool, error)
|
||||
|
||||
// NewDockerLegacyService created docker legacy service when log driver is not supported.
|
||||
// TODO(resouer): remove this when deprecating unsupported log driver
|
||||
NewDockerLegacyService() DockerLegacyService
|
||||
}
|
||||
|
||||
type dockerService struct {
|
||||
client libdocker.Interface
|
||||
os kubecontainer.OSInterface
|
||||
podSandboxImage string
|
||||
streamingRuntime *streamingRuntime
|
||||
streamingServer streaming.Server
|
||||
|
||||
network *network.PluginManager
|
||||
// Map of podSandboxID :: network-is-ready
|
||||
networkReady map[string]bool
|
||||
networkReadyLock sync.Mutex
|
||||
|
||||
containerManager cm.ContainerManager
|
||||
// cgroup driver used by Docker runtime.
|
||||
cgroupDriver string
|
||||
checkpointHandler CheckpointHandler
|
||||
// caches the version of the runtime.
|
||||
// To be compatible with multiple docker versions, we need to perform
|
||||
// version checking for some operations. Use this cache to avoid querying
|
||||
// the docker daemon every time we need to do such checks.
|
||||
versionCache *cache.ObjectCache
|
||||
// This option provides an escape hatch to override the new default behavior for Docker under
|
||||
// the CRI to use a shared PID namespace for all pods. It is temporary and will be removed.
|
||||
// See proposals/pod-pid-namespace.md for details.
|
||||
// TODO: Remove once the escape hatch is no longer used (https://issues.k8s.io/41938)
|
||||
disableSharedPID bool
|
||||
}
|
||||
|
||||
// Version returns the runtime name, runtime version and runtime API version
|
||||
func (ds *dockerService) Version(_ string) (*runtimeapi.VersionResponse, error) {
|
||||
v, err := ds.getDockerVersion()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &runtimeapi.VersionResponse{
|
||||
Version: kubeAPIVersion,
|
||||
RuntimeName: dockerRuntimeName,
|
||||
RuntimeVersion: v.Version,
|
||||
RuntimeApiVersion: v.APIVersion,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// dockerVersion gets the version information from docker.
|
||||
func (ds *dockerService) getDockerVersion() (*dockertypes.Version, error) {
|
||||
v, err := ds.client.Version()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get docker version: %v", err)
|
||||
}
|
||||
// Docker API version (e.g., 1.23) is not semver compatible. Add a ".0"
|
||||
// suffix to remedy this.
|
||||
v.APIVersion = fmt.Sprintf("%s.0", v.APIVersion)
|
||||
return v, nil
|
||||
}
|
||||
|
||||
// UpdateRuntimeConfig updates the runtime config. Currently only handles podCIDR updates.
|
||||
func (ds *dockerService) UpdateRuntimeConfig(runtimeConfig *runtimeapi.RuntimeConfig) (err error) {
|
||||
if runtimeConfig == nil {
|
||||
return
|
||||
}
|
||||
glog.Infof("docker cri received runtime config %+v", runtimeConfig)
|
||||
if ds.network != nil && runtimeConfig.NetworkConfig.PodCidr != "" {
|
||||
event := make(map[string]interface{})
|
||||
event[network.NET_PLUGIN_EVENT_POD_CIDR_CHANGE_DETAIL_CIDR] = runtimeConfig.NetworkConfig.PodCidr
|
||||
ds.network.Event(network.NET_PLUGIN_EVENT_POD_CIDR_CHANGE, event)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetNetNS returns the network namespace of the given containerID. The ID
|
||||
// supplied is typically the ID of a pod sandbox. This getter doesn't try
|
||||
// to map non-sandbox IDs to their respective sandboxes.
|
||||
func (ds *dockerService) GetNetNS(podSandboxID string) (string, error) {
|
||||
r, err := ds.client.InspectContainer(podSandboxID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return getNetworkNamespace(r)
|
||||
}
|
||||
|
||||
// GetPodPortMappings returns the port mappings of the given podSandbox ID.
|
||||
func (ds *dockerService) GetPodPortMappings(podSandboxID string) ([]*hostport.PortMapping, error) {
|
||||
// TODO: get portmappings from docker labels for backward compatibility
|
||||
checkpoint, err := ds.checkpointHandler.GetCheckpoint(podSandboxID)
|
||||
// Return empty portMappings if checkpoint is not found
|
||||
if err != nil {
|
||||
if err == utilstore.ErrKeyNotFound {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
portMappings := make([]*hostport.PortMapping, 0, len(checkpoint.Data.PortMappings))
|
||||
for _, pm := range checkpoint.Data.PortMappings {
|
||||
proto := toAPIProtocol(*pm.Protocol)
|
||||
portMappings = append(portMappings, &hostport.PortMapping{
|
||||
HostPort: *pm.HostPort,
|
||||
ContainerPort: *pm.ContainerPort,
|
||||
Protocol: proto,
|
||||
})
|
||||
}
|
||||
return portMappings, nil
|
||||
}
|
||||
|
||||
// Start initializes and starts components in dockerService.
|
||||
func (ds *dockerService) Start() error {
|
||||
// Initialize the legacy cleanup flag.
|
||||
return ds.containerManager.Start()
|
||||
}
|
||||
|
||||
// Status returns the status of the runtime.
|
||||
// TODO(random-liu): Set network condition accordingly here.
|
||||
func (ds *dockerService) Status() (*runtimeapi.RuntimeStatus, error) {
|
||||
runtimeReady := &runtimeapi.RuntimeCondition{
|
||||
Type: runtimeapi.RuntimeReady,
|
||||
Status: true,
|
||||
}
|
||||
networkReady := &runtimeapi.RuntimeCondition{
|
||||
Type: runtimeapi.NetworkReady,
|
||||
Status: true,
|
||||
}
|
||||
conditions := []*runtimeapi.RuntimeCondition{runtimeReady, networkReady}
|
||||
if _, err := ds.client.Version(); err != nil {
|
||||
runtimeReady.Status = false
|
||||
runtimeReady.Reason = "DockerDaemonNotReady"
|
||||
runtimeReady.Message = fmt.Sprintf("docker: failed to get docker version: %v", err)
|
||||
}
|
||||
if err := ds.network.Status(); err != nil {
|
||||
networkReady.Status = false
|
||||
networkReady.Reason = "NetworkPluginNotReady"
|
||||
networkReady.Message = fmt.Sprintf("docker: network plugin is not ready: %v", err)
|
||||
}
|
||||
return &runtimeapi.RuntimeStatus{Conditions: conditions}, nil
|
||||
}
|
||||
|
||||
func (ds *dockerService) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if ds.streamingServer != nil {
|
||||
ds.streamingServer.ServeHTTP(w, r)
|
||||
} else {
|
||||
http.NotFound(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
// GenerateExpectedCgroupParent returns cgroup parent in syntax expected by cgroup driver
|
||||
func (ds *dockerService) GenerateExpectedCgroupParent(cgroupParent string) (string, error) {
|
||||
if len(cgroupParent) > 0 {
|
||||
// if docker uses the systemd cgroup driver, it expects *.slice style names for cgroup parent.
|
||||
// if we configured kubelet to use --cgroup-driver=cgroupfs, and docker is configured to use systemd driver
|
||||
// docker will fail to launch the container because the name we provide will not be a valid slice.
|
||||
// this is a very good thing.
|
||||
if ds.cgroupDriver == "systemd" {
|
||||
systemdCgroupParent, err := kubecm.ConvertCgroupFsNameToSystemd(cgroupParent)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
cgroupParent = systemdCgroupParent
|
||||
}
|
||||
}
|
||||
glog.V(3).Infof("Setting cgroup parent to: %q", cgroupParent)
|
||||
return cgroupParent, nil
|
||||
}
|
||||
|
||||
// checkVersionCompatibility verifies whether docker is in a compatible version.
|
||||
func (ds *dockerService) checkVersionCompatibility() error {
|
||||
apiVersion, err := ds.getDockerAPIVersion()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
minAPIVersion, err := semver.Parse(libdocker.MinimumDockerAPIVersion)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Verify the docker version.
|
||||
result := apiVersion.Compare(minAPIVersion)
|
||||
if result < 0 {
|
||||
return fmt.Errorf("docker API version is older than %s", libdocker.MinimumDockerAPIVersion)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// getDockerAPIVersion gets the semver-compatible docker api version.
|
||||
func (ds *dockerService) getDockerAPIVersion() (*semver.Version, error) {
|
||||
var dv *dockertypes.Version
|
||||
var err error
|
||||
if ds.versionCache != nil {
|
||||
dv, err = ds.getDockerVersionFromCache()
|
||||
} else {
|
||||
dv, err = ds.getDockerVersion()
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
apiVersion, err := semver.Parse(dv.APIVersion)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &apiVersion, nil
|
||||
}
|
||||
|
||||
func (ds *dockerService) getDockerVersionFromCache() (*dockertypes.Version, error) {
|
||||
// We only store on key in the cache.
|
||||
const dummyKey = "version"
|
||||
value, err := ds.versionCache.Get(dummyKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dv, ok := value.(*dockertypes.Version)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("Converted to *dockertype.Version error")
|
||||
}
|
||||
return dv, nil
|
||||
}
|
||||
|
||||
func toAPIProtocol(protocol Protocol) v1.Protocol {
|
||||
switch protocol {
|
||||
case protocolTCP:
|
||||
return v1.ProtocolTCP
|
||||
case protocolUDP:
|
||||
return v1.ProtocolUDP
|
||||
}
|
||||
glog.Warningf("Unknown protocol %q: defaulting to TCP", protocol)
|
||||
return v1.ProtocolTCP
|
||||
}
|
||||
|
||||
// DockerLegacyService interface embeds some legacy methods for backward compatibility.
|
||||
type DockerLegacyService interface {
|
||||
// GetContainerLogs gets logs for a specific container.
|
||||
GetContainerLogs(*v1.Pod, kubecontainer.ContainerID, *v1.PodLogOptions, io.Writer, io.Writer) error
|
||||
}
|
||||
|
||||
// dockerLegacyService implements the DockerLegacyService. We add this for non json-log driver
|
||||
// support. (See #41996)
|
||||
type dockerLegacyService struct {
|
||||
client libdocker.Interface
|
||||
}
|
||||
|
||||
// NewDockerLegacyService created docker legacy service when log driver is not supported.
|
||||
// TODO(resouer): remove this when deprecating unsupported log driver
|
||||
func (d *dockerService) NewDockerLegacyService() DockerLegacyService {
|
||||
return &dockerLegacyService{client: d.client}
|
||||
}
|
||||
|
||||
// GetContainerLogs get container logs directly from docker daemon.
|
||||
func (d *dockerLegacyService) GetContainerLogs(pod *v1.Pod, containerID kubecontainer.ContainerID, logOptions *v1.PodLogOptions, stdout, stderr io.Writer) error {
|
||||
container, err := d.client.InspectContainer(containerID.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var since int64
|
||||
if logOptions.SinceSeconds != nil {
|
||||
t := metav1.Now().Add(-time.Duration(*logOptions.SinceSeconds) * time.Second)
|
||||
since = t.Unix()
|
||||
}
|
||||
if logOptions.SinceTime != nil {
|
||||
since = logOptions.SinceTime.Unix()
|
||||
}
|
||||
opts := dockertypes.ContainerLogsOptions{
|
||||
ShowStdout: true,
|
||||
ShowStderr: true,
|
||||
Since: strconv.FormatInt(since, 10),
|
||||
Timestamps: logOptions.Timestamps,
|
||||
Follow: logOptions.Follow,
|
||||
}
|
||||
if logOptions.TailLines != nil {
|
||||
opts.Tail = strconv.FormatInt(*logOptions.TailLines, 10)
|
||||
}
|
||||
|
||||
sopts := libdocker.StreamOptions{
|
||||
OutputStream: stdout,
|
||||
ErrorStream: stderr,
|
||||
RawTerminal: container.Config.Tty,
|
||||
}
|
||||
return d.client.Logs(containerID.ID, opts, sopts)
|
||||
}
|
||||
|
||||
// LegacyLogProvider implements the kuberuntime.LegacyLogProvider interface
|
||||
type LegacyLogProvider struct {
|
||||
dls DockerLegacyService
|
||||
}
|
||||
|
||||
func NewLegacyLogProvider(dls DockerLegacyService) LegacyLogProvider {
|
||||
return LegacyLogProvider{dls: dls}
|
||||
}
|
||||
|
||||
// GetContainerLogTail attempts to read up to MaxContainerTerminationMessageLogLength
|
||||
// from the end of the log when docker is configured with a log driver other than json-log.
|
||||
// It reads up to MaxContainerTerminationMessageLogLines lines.
|
||||
func (l LegacyLogProvider) GetContainerLogTail(uid kubetypes.UID, name, namespace string, containerId kubecontainer.ContainerID) (string, error) {
|
||||
value := int64(kubecontainer.MaxContainerTerminationMessageLogLines)
|
||||
buf, _ := circbuf.NewBuffer(kubecontainer.MaxContainerTerminationMessageLogLength)
|
||||
// Although this is not a full spec pod, dockerLegacyService.GetContainerLogs() currently completely ignores its pod param
|
||||
pod := &v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
UID: uid,
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
},
|
||||
}
|
||||
err := l.dls.GetContainerLogs(pod, containerId, &v1.PodLogOptions{TailLines: &value}, buf, buf)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return buf.String(), nil
|
||||
}
|
||||
|
||||
// criSupportedLogDrivers are log drivers supported by native CRI integration.
|
||||
var criSupportedLogDrivers = []string{"json-file"}
|
||||
|
||||
// IsCRISupportedLogDriver checks whether the logging driver used by docker is
|
||||
// suppoted by native CRI integration.
|
||||
func (d *dockerService) IsCRISupportedLogDriver() (bool, error) {
|
||||
info, err := d.client.Info()
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to get docker info: %v", err)
|
||||
}
|
||||
for _, driver := range criSupportedLogDrivers {
|
||||
if info.LoggingDriver == driver {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
136
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/docker_service_test.go
generated
vendored
Normal file
136
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/docker_service_test.go
generated
vendored
Normal file
@ -0,0 +1,136 @@
|
||||
/*
|
||||
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 dockershim
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/blang/semver"
|
||||
dockertypes "github.com/docker/docker/api/types"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/clock"
|
||||
runtimeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
|
||||
containertest "k8s.io/kubernetes/pkg/kubelet/container/testing"
|
||||
"k8s.io/kubernetes/pkg/kubelet/dockershim/libdocker"
|
||||
"k8s.io/kubernetes/pkg/kubelet/network"
|
||||
nettest "k8s.io/kubernetes/pkg/kubelet/network/testing"
|
||||
"k8s.io/kubernetes/pkg/kubelet/util/cache"
|
||||
)
|
||||
|
||||
// newTestNetworkPlugin returns a mock plugin that implements network.NetworkPlugin
|
||||
func newTestNetworkPlugin(t *testing.T) *nettest.MockNetworkPlugin {
|
||||
ctrl := gomock.NewController(t)
|
||||
return nettest.NewMockNetworkPlugin(ctrl)
|
||||
}
|
||||
|
||||
func newTestDockerService() (*dockerService, *libdocker.FakeDockerClient, *clock.FakeClock) {
|
||||
fakeClock := clock.NewFakeClock(time.Time{})
|
||||
c := libdocker.NewFakeDockerClient().WithClock(fakeClock).WithVersion("1.11.2", "1.23")
|
||||
pm := network.NewPluginManager(&network.NoopNetworkPlugin{})
|
||||
return &dockerService{
|
||||
client: c,
|
||||
os: &containertest.FakeOS{},
|
||||
network: pm,
|
||||
checkpointHandler: NewTestPersistentCheckpointHandler(),
|
||||
networkReady: make(map[string]bool),
|
||||
}, c, fakeClock
|
||||
}
|
||||
|
||||
func newTestDockerServiceWithVersionCache() (*dockerService, *libdocker.FakeDockerClient, *clock.FakeClock) {
|
||||
ds, c, fakeClock := newTestDockerService()
|
||||
ds.versionCache = cache.NewObjectCache(
|
||||
func() (interface{}, error) {
|
||||
return ds.getDockerVersion()
|
||||
},
|
||||
time.Hour*10,
|
||||
)
|
||||
return ds, c, fakeClock
|
||||
}
|
||||
|
||||
// TestStatus tests the runtime status logic.
|
||||
func TestStatus(t *testing.T) {
|
||||
ds, fDocker, _ := newTestDockerService()
|
||||
|
||||
assertStatus := func(expected map[string]bool, status *runtimeapi.RuntimeStatus) {
|
||||
conditions := status.GetConditions()
|
||||
assert.Equal(t, len(expected), len(conditions))
|
||||
for k, v := range expected {
|
||||
for _, c := range conditions {
|
||||
if k == c.Type {
|
||||
assert.Equal(t, v, c.Status)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Should report ready status if version returns no error.
|
||||
status, err := ds.Status()
|
||||
assert.NoError(t, err)
|
||||
assertStatus(map[string]bool{
|
||||
runtimeapi.RuntimeReady: true,
|
||||
runtimeapi.NetworkReady: true,
|
||||
}, status)
|
||||
|
||||
// Should not report ready status if version returns error.
|
||||
fDocker.InjectError("version", errors.New("test error"))
|
||||
status, err = ds.Status()
|
||||
assert.NoError(t, err)
|
||||
assertStatus(map[string]bool{
|
||||
runtimeapi.RuntimeReady: false,
|
||||
runtimeapi.NetworkReady: true,
|
||||
}, status)
|
||||
|
||||
// Should not report ready status is network plugin returns error.
|
||||
mockPlugin := newTestNetworkPlugin(t)
|
||||
ds.network = network.NewPluginManager(mockPlugin)
|
||||
defer mockPlugin.Finish()
|
||||
mockPlugin.EXPECT().Status().Return(errors.New("network error"))
|
||||
status, err = ds.Status()
|
||||
assert.NoError(t, err)
|
||||
assertStatus(map[string]bool{
|
||||
runtimeapi.RuntimeReady: true,
|
||||
runtimeapi.NetworkReady: false,
|
||||
}, status)
|
||||
}
|
||||
|
||||
func TestVersion(t *testing.T) {
|
||||
ds, _, _ := newTestDockerService()
|
||||
|
||||
expectedVersion := &dockertypes.Version{Version: "1.11.2", APIVersion: "1.23.0"}
|
||||
v, err := ds.getDockerVersion()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, expectedVersion, v)
|
||||
|
||||
expectedAPIVersion := &semver.Version{Major: 1, Minor: 23, Patch: 0}
|
||||
apiVersion, err := ds.getDockerAPIVersion()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, expectedAPIVersion, apiVersion)
|
||||
}
|
||||
|
||||
func TestAPIVersionWithCache(t *testing.T) {
|
||||
ds, _, _ := newTestDockerServiceWithVersionCache()
|
||||
|
||||
expected := &semver.Version{Major: 1, Minor: 23, Patch: 0}
|
||||
version, err := ds.getDockerAPIVersion()
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, expected, version)
|
||||
}
|
34
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/docker_stats_linux.go
generated
vendored
Normal file
34
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/docker_stats_linux.go
generated
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
// +build linux
|
||||
|
||||
/*
|
||||
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 dockershim
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
runtimeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
|
||||
)
|
||||
|
||||
// ContainerStats returns stats for a container stats request based on container id.
|
||||
func (ds *dockerService) ContainerStats(string) (*runtimeapi.ContainerStats, error) {
|
||||
return nil, fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
// ListContainerStats returns stats for a list container stats request based on a filter.
|
||||
func (ds *dockerService) ListContainerStats(*runtimeapi.ContainerStatsFilter) ([]*runtimeapi.ContainerStats, error) {
|
||||
return nil, fmt.Errorf("not implemented")
|
||||
}
|
35
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/docker_stats_unsupported.go
generated
vendored
Normal file
35
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/docker_stats_unsupported.go
generated
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
// +build !linux,!windows
|
||||
|
||||
/*
|
||||
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 dockershim
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
runtimeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
|
||||
)
|
||||
|
||||
// ContainerStats returns stats for a container stats request based on container id.
|
||||
func (ds *dockerService) ContainerStats(string) (*runtimeapi.ContainerStats, error) {
|
||||
return nil, fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
// ListContainerStats returns stats for a list container stats request based on a filter.
|
||||
func (ds *dockerService) ListContainerStats(*runtimeapi.ContainerStatsFilter) ([]*runtimeapi.ContainerStats, error) {
|
||||
return nil, fmt.Errorf("not implemented")
|
||||
}
|
105
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/docker_stats_windows.go
generated
vendored
Normal file
105
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/docker_stats_windows.go
generated
vendored
Normal file
@ -0,0 +1,105 @@
|
||||
// +build windows
|
||||
|
||||
/*
|
||||
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 dockershim
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
runtimeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
|
||||
)
|
||||
|
||||
// ContainerStats returns stats for a container stats request based on container id.
|
||||
func (ds *dockerService) ContainerStats(containerID string) (*runtimeapi.ContainerStats, error) {
|
||||
containerStats, err := ds.getContainerStats(containerID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return containerStats, nil
|
||||
}
|
||||
|
||||
// ListContainerStats returns stats for a list container stats request based on a filter.
|
||||
func (ds *dockerService) ListContainerStats(containerStatsFilter *runtimeapi.ContainerStatsFilter) ([]*runtimeapi.ContainerStats, error) {
|
||||
filter := &runtimeapi.ContainerFilter{}
|
||||
|
||||
if containerStatsFilter != nil {
|
||||
filter.Id = containerStatsFilter.Id
|
||||
filter.PodSandboxId = containerStatsFilter.PodSandboxId
|
||||
filter.LabelSelector = containerStatsFilter.LabelSelector
|
||||
}
|
||||
|
||||
containers, err := ds.ListContainers(filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var stats []*runtimeapi.ContainerStats
|
||||
for _, container := range containers {
|
||||
containerStats, err := ds.getContainerStats(container.Id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
stats = append(stats, containerStats)
|
||||
}
|
||||
|
||||
return stats, nil
|
||||
}
|
||||
|
||||
func (ds *dockerService) getContainerStats(containerID string) (*runtimeapi.ContainerStats, error) {
|
||||
statsJSON, err := ds.client.GetContainerStats(containerID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
containerJSON, err := ds.client.InspectContainerWithSize(containerID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
status, err := ds.ContainerStatus(containerID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dockerStats := statsJSON.Stats
|
||||
timestamp := time.Now().UnixNano()
|
||||
containerStats := &runtimeapi.ContainerStats{
|
||||
Attributes: &runtimeapi.ContainerAttributes{
|
||||
Id: containerID,
|
||||
Metadata: status.Metadata,
|
||||
Labels: status.Labels,
|
||||
Annotations: status.Annotations,
|
||||
},
|
||||
Cpu: &runtimeapi.CpuUsage{
|
||||
Timestamp: timestamp,
|
||||
// have to multiply cpu usage by 100 since docker stats units is in 100's of nano seconds for Windows
|
||||
// see https://github.com/moby/moby/blob/v1.13.1/api/types/stats.go#L22
|
||||
UsageCoreNanoSeconds: &runtimeapi.UInt64Value{Value: dockerStats.CPUStats.CPUUsage.TotalUsage * 100},
|
||||
},
|
||||
Memory: &runtimeapi.MemoryUsage{
|
||||
Timestamp: timestamp,
|
||||
WorkingSetBytes: &runtimeapi.UInt64Value{Value: dockerStats.MemoryStats.PrivateWorkingSet},
|
||||
},
|
||||
WritableLayer: &runtimeapi.FilesystemUsage{
|
||||
Timestamp: timestamp,
|
||||
UsedBytes: &runtimeapi.UInt64Value{Value: uint64(*containerJSON.SizeRw)},
|
||||
},
|
||||
}
|
||||
return containerStats, nil
|
||||
}
|
217
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/docker_streaming.go
generated
vendored
Normal file
217
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/docker_streaming.go
generated
vendored
Normal file
@ -0,0 +1,217 @@
|
||||
/*
|
||||
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 dockershim
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
dockertypes "github.com/docker/docker/api/types"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
"k8s.io/client-go/tools/remotecommand"
|
||||
runtimeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
|
||||
kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
|
||||
"k8s.io/kubernetes/pkg/kubelet/server/streaming"
|
||||
"k8s.io/kubernetes/pkg/kubelet/util/ioutils"
|
||||
|
||||
"k8s.io/kubernetes/pkg/kubelet/dockershim/libdocker"
|
||||
)
|
||||
|
||||
type streamingRuntime struct {
|
||||
client libdocker.Interface
|
||||
execHandler ExecHandler
|
||||
}
|
||||
|
||||
var _ streaming.Runtime = &streamingRuntime{}
|
||||
|
||||
func (r *streamingRuntime) Exec(containerID string, cmd []string, in io.Reader, out, err io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize) error {
|
||||
return r.exec(containerID, cmd, in, out, err, tty, resize, 0)
|
||||
}
|
||||
|
||||
// Internal version of Exec adds a timeout.
|
||||
func (r *streamingRuntime) exec(containerID string, cmd []string, in io.Reader, out, errw io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize, timeout time.Duration) error {
|
||||
container, err := checkContainerStatus(r.client, containerID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return r.execHandler.ExecInContainer(r.client, container, cmd, in, out, errw, tty, resize, timeout)
|
||||
}
|
||||
|
||||
func (r *streamingRuntime) Attach(containerID string, in io.Reader, out, errw io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize) error {
|
||||
_, err := checkContainerStatus(r.client, containerID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return attachContainer(r.client, containerID, in, out, errw, tty, resize)
|
||||
}
|
||||
|
||||
func (r *streamingRuntime) PortForward(podSandboxID string, port int32, stream io.ReadWriteCloser) error {
|
||||
if port < 0 || port > math.MaxUint16 {
|
||||
return fmt.Errorf("invalid port %d", port)
|
||||
}
|
||||
return portForward(r.client, podSandboxID, port, stream)
|
||||
}
|
||||
|
||||
// ExecSync executes a command in the container, and returns the stdout output.
|
||||
// If command exits with a non-zero exit code, an error is returned.
|
||||
func (ds *dockerService) ExecSync(containerID string, cmd []string, timeout time.Duration) (stdout []byte, stderr []byte, err error) {
|
||||
var stdoutBuffer, stderrBuffer bytes.Buffer
|
||||
err = ds.streamingRuntime.exec(containerID, cmd,
|
||||
nil, // in
|
||||
ioutils.WriteCloserWrapper(&stdoutBuffer),
|
||||
ioutils.WriteCloserWrapper(&stderrBuffer),
|
||||
false, // tty
|
||||
nil, // resize
|
||||
timeout)
|
||||
return stdoutBuffer.Bytes(), stderrBuffer.Bytes(), err
|
||||
}
|
||||
|
||||
// Exec prepares a streaming endpoint to execute a command in the container, and returns the address.
|
||||
func (ds *dockerService) Exec(req *runtimeapi.ExecRequest) (*runtimeapi.ExecResponse, error) {
|
||||
if ds.streamingServer == nil {
|
||||
return nil, streaming.ErrorStreamingDisabled("exec")
|
||||
}
|
||||
_, err := checkContainerStatus(ds.client, req.ContainerId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ds.streamingServer.GetExec(req)
|
||||
}
|
||||
|
||||
// Attach prepares a streaming endpoint to attach to a running container, and returns the address.
|
||||
func (ds *dockerService) Attach(req *runtimeapi.AttachRequest) (*runtimeapi.AttachResponse, error) {
|
||||
if ds.streamingServer == nil {
|
||||
return nil, streaming.ErrorStreamingDisabled("attach")
|
||||
}
|
||||
_, err := checkContainerStatus(ds.client, req.ContainerId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ds.streamingServer.GetAttach(req)
|
||||
}
|
||||
|
||||
// PortForward prepares a streaming endpoint to forward ports from a PodSandbox, and returns the address.
|
||||
func (ds *dockerService) PortForward(req *runtimeapi.PortForwardRequest) (*runtimeapi.PortForwardResponse, error) {
|
||||
if ds.streamingServer == nil {
|
||||
return nil, streaming.ErrorStreamingDisabled("port forward")
|
||||
}
|
||||
_, err := checkContainerStatus(ds.client, req.PodSandboxId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// TODO(tallclair): Verify that ports are exposed.
|
||||
return ds.streamingServer.GetPortForward(req)
|
||||
}
|
||||
|
||||
func checkContainerStatus(client libdocker.Interface, containerID string) (*dockertypes.ContainerJSON, error) {
|
||||
container, err := client.InspectContainer(containerID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !container.State.Running {
|
||||
return nil, fmt.Errorf("container not running (%s)", container.ID)
|
||||
}
|
||||
return container, nil
|
||||
}
|
||||
|
||||
func attachContainer(client libdocker.Interface, containerID string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize) error {
|
||||
// Have to start this before the call to client.AttachToContainer because client.AttachToContainer is a blocking
|
||||
// call :-( Otherwise, resize events don't get processed and the terminal never resizes.
|
||||
kubecontainer.HandleResizing(resize, func(size remotecommand.TerminalSize) {
|
||||
client.ResizeContainerTTY(containerID, uint(size.Height), uint(size.Width))
|
||||
})
|
||||
|
||||
// TODO(random-liu): Do we really use the *Logs* field here?
|
||||
opts := dockertypes.ContainerAttachOptions{
|
||||
Stream: true,
|
||||
Stdin: stdin != nil,
|
||||
Stdout: stdout != nil,
|
||||
Stderr: stderr != nil,
|
||||
}
|
||||
sopts := libdocker.StreamOptions{
|
||||
InputStream: stdin,
|
||||
OutputStream: stdout,
|
||||
ErrorStream: stderr,
|
||||
RawTerminal: tty,
|
||||
}
|
||||
return client.AttachToContainer(containerID, opts, sopts)
|
||||
}
|
||||
|
||||
func portForward(client libdocker.Interface, podSandboxID string, port int32, stream io.ReadWriteCloser) error {
|
||||
container, err := client.InspectContainer(podSandboxID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !container.State.Running {
|
||||
return fmt.Errorf("container not running (%s)", container.ID)
|
||||
}
|
||||
|
||||
containerPid := container.State.Pid
|
||||
socatPath, lookupErr := exec.LookPath("socat")
|
||||
if lookupErr != nil {
|
||||
return fmt.Errorf("unable to do port forwarding: socat not found.")
|
||||
}
|
||||
|
||||
args := []string{"-t", fmt.Sprintf("%d", containerPid), "-n", socatPath, "-", fmt.Sprintf("TCP4:localhost:%d", port)}
|
||||
|
||||
nsenterPath, lookupErr := exec.LookPath("nsenter")
|
||||
if lookupErr != nil {
|
||||
return fmt.Errorf("unable to do port forwarding: nsenter not found.")
|
||||
}
|
||||
|
||||
commandString := fmt.Sprintf("%s %s", nsenterPath, strings.Join(args, " "))
|
||||
glog.V(4).Infof("executing port forwarding command: %s", commandString)
|
||||
|
||||
command := exec.Command(nsenterPath, args...)
|
||||
command.Stdout = stream
|
||||
|
||||
stderr := new(bytes.Buffer)
|
||||
command.Stderr = stderr
|
||||
|
||||
// If we use Stdin, command.Run() won't return until the goroutine that's copying
|
||||
// from stream finishes. Unfortunately, if you have a client like telnet connected
|
||||
// via port forwarding, as long as the user's telnet client is connected to the user's
|
||||
// local listener that port forwarding sets up, the telnet session never exits. This
|
||||
// means that even if socat has finished running, command.Run() won't ever return
|
||||
// (because the client still has the connection and stream open).
|
||||
//
|
||||
// The work around is to use StdinPipe(), as Wait() (called by Run()) closes the pipe
|
||||
// when the command (socat) exits.
|
||||
inPipe, err := command.StdinPipe()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to do port forwarding: error creating stdin pipe: %v", err)
|
||||
}
|
||||
go func() {
|
||||
io.Copy(inPipe, stream)
|
||||
inPipe.Close()
|
||||
}()
|
||||
|
||||
if err := command.Run(); err != nil {
|
||||
return fmt.Errorf("%v: %s", err, stderr.String())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
135
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/exec.go
generated
vendored
Normal file
135
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/exec.go
generated
vendored
Normal file
@ -0,0 +1,135 @@
|
||||
/*
|
||||
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 dockershim
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
dockertypes "github.com/docker/docker/api/types"
|
||||
"github.com/golang/glog"
|
||||
|
||||
"k8s.io/client-go/tools/remotecommand"
|
||||
kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
|
||||
|
||||
"k8s.io/kubernetes/pkg/kubelet/dockershim/libdocker"
|
||||
)
|
||||
|
||||
// ExecHandler knows how to execute a command in a running Docker container.
|
||||
type ExecHandler interface {
|
||||
ExecInContainer(client libdocker.Interface, container *dockertypes.ContainerJSON, cmd []string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize, timeout time.Duration) error
|
||||
}
|
||||
|
||||
type dockerExitError struct {
|
||||
Inspect *dockertypes.ContainerExecInspect
|
||||
}
|
||||
|
||||
func (d *dockerExitError) String() string {
|
||||
return d.Error()
|
||||
}
|
||||
|
||||
func (d *dockerExitError) Error() string {
|
||||
return fmt.Sprintf("Error executing in Docker Container: %d", d.Inspect.ExitCode)
|
||||
}
|
||||
|
||||
func (d *dockerExitError) Exited() bool {
|
||||
return !d.Inspect.Running
|
||||
}
|
||||
|
||||
func (d *dockerExitError) ExitStatus() int {
|
||||
return d.Inspect.ExitCode
|
||||
}
|
||||
|
||||
// NativeExecHandler executes commands in Docker containers using Docker's exec API.
|
||||
type NativeExecHandler struct{}
|
||||
|
||||
func (*NativeExecHandler) ExecInContainer(client libdocker.Interface, container *dockertypes.ContainerJSON, cmd []string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize, timeout time.Duration) error {
|
||||
done := make(chan struct{})
|
||||
defer close(done)
|
||||
|
||||
createOpts := dockertypes.ExecConfig{
|
||||
Cmd: cmd,
|
||||
AttachStdin: stdin != nil,
|
||||
AttachStdout: stdout != nil,
|
||||
AttachStderr: stderr != nil,
|
||||
Tty: tty,
|
||||
}
|
||||
execObj, err := client.CreateExec(container.ID, createOpts)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to exec in container - Exec setup failed - %v", err)
|
||||
}
|
||||
|
||||
// Have to start this before the call to client.StartExec because client.StartExec is a blocking
|
||||
// call :-( Otherwise, resize events don't get processed and the terminal never resizes.
|
||||
//
|
||||
// We also have to delay attempting to send a terminal resize request to docker until after the
|
||||
// exec has started; otherwise, the initial resize request will fail.
|
||||
execStarted := make(chan struct{})
|
||||
go func() {
|
||||
select {
|
||||
case <-execStarted:
|
||||
// client.StartExec has started the exec, so we can start resizing
|
||||
case <-done:
|
||||
// ExecInContainer has returned, so short-circuit
|
||||
return
|
||||
}
|
||||
|
||||
kubecontainer.HandleResizing(resize, func(size remotecommand.TerminalSize) {
|
||||
client.ResizeExecTTY(execObj.ID, uint(size.Height), uint(size.Width))
|
||||
})
|
||||
}()
|
||||
|
||||
startOpts := dockertypes.ExecStartCheck{Detach: false, Tty: tty}
|
||||
streamOpts := libdocker.StreamOptions{
|
||||
InputStream: stdin,
|
||||
OutputStream: stdout,
|
||||
ErrorStream: stderr,
|
||||
RawTerminal: tty,
|
||||
ExecStarted: execStarted,
|
||||
}
|
||||
err = client.StartExec(execObj.ID, startOpts, streamOpts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ticker := time.NewTicker(2 * time.Second)
|
||||
defer ticker.Stop()
|
||||
count := 0
|
||||
for {
|
||||
inspect, err2 := client.InspectExec(execObj.ID)
|
||||
if err2 != nil {
|
||||
return err2
|
||||
}
|
||||
if !inspect.Running {
|
||||
if inspect.ExitCode != 0 {
|
||||
err = &dockerExitError{inspect}
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
count++
|
||||
if count == 5 {
|
||||
glog.Errorf("Exec session %s in container %s terminated but process still running!", execObj.ID, container.ID)
|
||||
break
|
||||
}
|
||||
|
||||
<-ticker.C
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
417
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/helpers.go
generated
vendored
Normal file
417
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/helpers.go
generated
vendored
Normal file
@ -0,0 +1,417 @@
|
||||
/*
|
||||
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 dockershim
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/blang/semver"
|
||||
dockertypes "github.com/docker/docker/api/types"
|
||||
dockercontainer "github.com/docker/docker/api/types/container"
|
||||
dockerfilters "github.com/docker/docker/api/types/filters"
|
||||
dockernat "github.com/docker/go-connections/nat"
|
||||
"github.com/golang/glog"
|
||||
|
||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||
"k8s.io/kubernetes/pkg/credentialprovider"
|
||||
runtimeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
|
||||
"k8s.io/kubernetes/pkg/kubelet/dockershim/libdocker"
|
||||
"k8s.io/kubernetes/pkg/kubelet/types"
|
||||
"k8s.io/kubernetes/pkg/security/apparmor"
|
||||
"k8s.io/kubernetes/pkg/util/parsers"
|
||||
)
|
||||
|
||||
const (
|
||||
annotationPrefix = "annotation."
|
||||
|
||||
// Docker changed the API for specifying options in v1.11
|
||||
securityOptSeparatorChangeVersion = "1.23.0" // Corresponds to docker 1.11.x
|
||||
securityOptSeparatorOld = ':'
|
||||
securityOptSeparatorNew = '='
|
||||
)
|
||||
|
||||
var (
|
||||
conflictRE = regexp.MustCompile(`Conflict. (?:.)+ is already in use by container ([0-9a-z]+)`)
|
||||
|
||||
// this is hacky, but extremely common.
|
||||
// if a container starts but the executable file is not found, runc gives a message that matches
|
||||
startRE = regexp.MustCompile(`\\\\\\\"(.*)\\\\\\\": executable file not found`)
|
||||
|
||||
// Docker changes the security option separator from ':' to '=' in the 1.23
|
||||
// API version.
|
||||
optsSeparatorChangeVersion = semver.MustParse(securityOptSeparatorChangeVersion)
|
||||
|
||||
defaultSeccompOpt = []dockerOpt{{"seccomp", "unconfined", ""}}
|
||||
)
|
||||
|
||||
// generateEnvList converts KeyValue list to a list of strings, in the form of
|
||||
// '<key>=<value>', which can be understood by docker.
|
||||
func generateEnvList(envs []*runtimeapi.KeyValue) (result []string) {
|
||||
for _, env := range envs {
|
||||
result = append(result, fmt.Sprintf("%s=%s", env.Key, env.Value))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// makeLabels converts annotations to labels and merge them with the given
|
||||
// labels. This is necessary because docker does not support annotations;
|
||||
// we *fake* annotations using labels. Note that docker labels are not
|
||||
// updatable.
|
||||
func makeLabels(labels, annotations map[string]string) map[string]string {
|
||||
merged := make(map[string]string)
|
||||
for k, v := range labels {
|
||||
merged[k] = v
|
||||
}
|
||||
for k, v := range annotations {
|
||||
// Assume there won't be conflict.
|
||||
merged[fmt.Sprintf("%s%s", annotationPrefix, k)] = v
|
||||
}
|
||||
return merged
|
||||
}
|
||||
|
||||
// extractLabels converts raw docker labels to the CRI labels and annotations.
|
||||
// It also filters out internal labels used by this shim.
|
||||
func extractLabels(input map[string]string) (map[string]string, map[string]string) {
|
||||
labels := make(map[string]string)
|
||||
annotations := make(map[string]string)
|
||||
for k, v := range input {
|
||||
// Check if the key is used internally by the shim.
|
||||
internal := false
|
||||
for _, internalKey := range internalLabelKeys {
|
||||
if k == internalKey {
|
||||
internal = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if internal {
|
||||
continue
|
||||
}
|
||||
|
||||
// Delete the container name label for the sandbox. It is added in the shim,
|
||||
// should not be exposed via CRI.
|
||||
if k == types.KubernetesContainerNameLabel &&
|
||||
input[containerTypeLabelKey] == containerTypeLabelSandbox {
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if the label should be treated as an annotation.
|
||||
if strings.HasPrefix(k, annotationPrefix) {
|
||||
annotations[strings.TrimPrefix(k, annotationPrefix)] = v
|
||||
continue
|
||||
}
|
||||
labels[k] = v
|
||||
}
|
||||
return labels, annotations
|
||||
}
|
||||
|
||||
// generateMountBindings converts the mount list to a list of strings that
|
||||
// can be understood by docker.
|
||||
// '<HostPath>:<ContainerPath>[:options]', where 'options'
|
||||
// is a comma-separated list of the following strings:
|
||||
// 'ro', if the path is read only
|
||||
// 'Z', if the volume requires SELinux relabeling
|
||||
// propagation mode such as 'rslave'
|
||||
func generateMountBindings(mounts []*runtimeapi.Mount) []string {
|
||||
result := make([]string, 0, len(mounts))
|
||||
for _, m := range mounts {
|
||||
bind := fmt.Sprintf("%s:%s", m.HostPath, m.ContainerPath)
|
||||
var attrs []string
|
||||
if m.Readonly {
|
||||
attrs = append(attrs, "ro")
|
||||
}
|
||||
// Only request relabeling if the pod provides an SELinux context. If the pod
|
||||
// does not provide an SELinux context relabeling will label the volume with
|
||||
// the container's randomly allocated MCS label. This would restrict access
|
||||
// to the volume to the container which mounts it first.
|
||||
if m.SelinuxRelabel {
|
||||
attrs = append(attrs, "Z")
|
||||
}
|
||||
switch m.Propagation {
|
||||
case runtimeapi.MountPropagation_PROPAGATION_PRIVATE:
|
||||
// noop, private is default
|
||||
case runtimeapi.MountPropagation_PROPAGATION_BIDIRECTIONAL:
|
||||
attrs = append(attrs, "rshared")
|
||||
case runtimeapi.MountPropagation_PROPAGATION_HOST_TO_CONTAINER:
|
||||
attrs = append(attrs, "rslave")
|
||||
default:
|
||||
glog.Warningf("unknown propagation mode for hostPath %q", m.HostPath)
|
||||
// Falls back to "private"
|
||||
}
|
||||
|
||||
if len(attrs) > 0 {
|
||||
bind = fmt.Sprintf("%s:%s", bind, strings.Join(attrs, ","))
|
||||
}
|
||||
result = append(result, bind)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func makePortsAndBindings(pm []*runtimeapi.PortMapping) (dockernat.PortSet, map[dockernat.Port][]dockernat.PortBinding) {
|
||||
exposedPorts := dockernat.PortSet{}
|
||||
portBindings := map[dockernat.Port][]dockernat.PortBinding{}
|
||||
for _, port := range pm {
|
||||
exteriorPort := port.HostPort
|
||||
if exteriorPort == 0 {
|
||||
// No need to do port binding when HostPort is not specified
|
||||
continue
|
||||
}
|
||||
interiorPort := port.ContainerPort
|
||||
// Some of this port stuff is under-documented voodoo.
|
||||
// See http://stackoverflow.com/questions/20428302/binding-a-port-to-a-host-interface-using-the-rest-api
|
||||
var protocol string
|
||||
switch port.Protocol {
|
||||
case runtimeapi.Protocol_UDP:
|
||||
protocol = "/udp"
|
||||
case runtimeapi.Protocol_TCP:
|
||||
protocol = "/tcp"
|
||||
default:
|
||||
glog.Warningf("Unknown protocol %q: defaulting to TCP", port.Protocol)
|
||||
protocol = "/tcp"
|
||||
}
|
||||
|
||||
dockerPort := dockernat.Port(strconv.Itoa(int(interiorPort)) + protocol)
|
||||
exposedPorts[dockerPort] = struct{}{}
|
||||
|
||||
hostBinding := dockernat.PortBinding{
|
||||
HostPort: strconv.Itoa(int(exteriorPort)),
|
||||
HostIP: port.HostIp,
|
||||
}
|
||||
|
||||
// Allow multiple host ports bind to same docker port
|
||||
if existedBindings, ok := portBindings[dockerPort]; ok {
|
||||
// If a docker port already map to a host port, just append the host ports
|
||||
portBindings[dockerPort] = append(existedBindings, hostBinding)
|
||||
} else {
|
||||
// Otherwise, it's fresh new port binding
|
||||
portBindings[dockerPort] = []dockernat.PortBinding{
|
||||
hostBinding,
|
||||
}
|
||||
}
|
||||
}
|
||||
return exposedPorts, portBindings
|
||||
}
|
||||
|
||||
// getApparmorSecurityOpts gets apparmor options from container config.
|
||||
func getApparmorSecurityOpts(sc *runtimeapi.LinuxContainerSecurityContext, separator rune) ([]string, error) {
|
||||
if sc == nil || sc.ApparmorProfile == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
appArmorOpts, err := getAppArmorOpts(sc.ApparmorProfile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fmtOpts := fmtDockerOpts(appArmorOpts, separator)
|
||||
return fmtOpts, nil
|
||||
}
|
||||
|
||||
// dockerFilter wraps around dockerfilters.Args and provides methods to modify
|
||||
// the filter easily.
|
||||
type dockerFilter struct {
|
||||
args *dockerfilters.Args
|
||||
}
|
||||
|
||||
func newDockerFilter(args *dockerfilters.Args) *dockerFilter {
|
||||
return &dockerFilter{args: args}
|
||||
}
|
||||
|
||||
func (f *dockerFilter) Add(key, value string) {
|
||||
f.args.Add(key, value)
|
||||
}
|
||||
|
||||
func (f *dockerFilter) AddLabel(key, value string) {
|
||||
f.Add("label", fmt.Sprintf("%s=%s", key, value))
|
||||
}
|
||||
|
||||
// parseUserFromImageUser splits the user out of an user:group string.
|
||||
func parseUserFromImageUser(id string) string {
|
||||
if id == "" {
|
||||
return id
|
||||
}
|
||||
// split instances where the id may contain user:group
|
||||
if strings.Contains(id, ":") {
|
||||
return strings.Split(id, ":")[0]
|
||||
}
|
||||
// no group, just return the id
|
||||
return id
|
||||
}
|
||||
|
||||
// getUserFromImageUser gets uid or user name of the image user.
|
||||
// If user is numeric, it will be treated as uid; or else, it is treated as user name.
|
||||
func getUserFromImageUser(imageUser string) (*int64, string) {
|
||||
user := parseUserFromImageUser(imageUser)
|
||||
// return both nil if user is not specified in the image.
|
||||
if user == "" {
|
||||
return nil, ""
|
||||
}
|
||||
// user could be either uid or user name. Try to interpret as numeric uid.
|
||||
uid, err := strconv.ParseInt(user, 10, 64)
|
||||
if err != nil {
|
||||
// If user is non numeric, assume it's user name.
|
||||
return nil, user
|
||||
}
|
||||
// If user is a numeric uid.
|
||||
return &uid, ""
|
||||
}
|
||||
|
||||
// See #33189. If the previous attempt to create a sandbox container name FOO
|
||||
// failed due to "device or resource busy", it is possible that docker did
|
||||
// not clean up properly and has inconsistent internal state. Docker would
|
||||
// not report the existence of FOO, but would complain if user wants to
|
||||
// create a new container named FOO. To work around this, we parse the error
|
||||
// message to identify failure caused by naming conflict, and try to remove
|
||||
// the old container FOO.
|
||||
// See #40443. Sometimes even removal may fail with "no such container" error.
|
||||
// In that case we have to create the container with a randomized name.
|
||||
// TODO(random-liu): Remove this work around after docker 1.11 is deprecated.
|
||||
// TODO(#33189): Monitor the tests to see if the fix is sufficient.
|
||||
func recoverFromCreationConflictIfNeeded(client libdocker.Interface, createConfig dockertypes.ContainerCreateConfig, err error) (*dockercontainer.ContainerCreateCreatedBody, error) {
|
||||
matches := conflictRE.FindStringSubmatch(err.Error())
|
||||
if len(matches) != 2 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
id := matches[1]
|
||||
glog.Warningf("Unable to create pod sandbox due to conflict. Attempting to remove sandbox %q", id)
|
||||
if rmErr := client.RemoveContainer(id, dockertypes.ContainerRemoveOptions{RemoveVolumes: true}); rmErr == nil {
|
||||
glog.V(2).Infof("Successfully removed conflicting container %q", id)
|
||||
return nil, err
|
||||
} else {
|
||||
glog.Errorf("Failed to remove the conflicting container %q: %v", id, rmErr)
|
||||
// Return if the error is not container not found error.
|
||||
if !libdocker.IsContainerNotFoundError(rmErr) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// randomize the name to avoid conflict.
|
||||
createConfig.Name = randomizeName(createConfig.Name)
|
||||
glog.V(2).Infof("Create the container with randomized name %s", createConfig.Name)
|
||||
return client.CreateContainer(createConfig)
|
||||
}
|
||||
|
||||
// transformStartContainerError does regex parsing on returned error
|
||||
// for where container runtimes are giving less than ideal error messages.
|
||||
func transformStartContainerError(err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
matches := startRE.FindStringSubmatch(err.Error())
|
||||
if len(matches) > 0 {
|
||||
return fmt.Errorf("executable not found in $PATH")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// getSecurityOptSeparator returns the security option separator based on the
|
||||
// docker API version.
|
||||
// TODO: Remove this function along with the relevant code when we no longer
|
||||
// need to support docker 1.10.
|
||||
func getSecurityOptSeparator(v *semver.Version) rune {
|
||||
switch v.Compare(optsSeparatorChangeVersion) {
|
||||
case -1:
|
||||
// Current version is less than the API change version; use the old
|
||||
// separator.
|
||||
return securityOptSeparatorOld
|
||||
default:
|
||||
return securityOptSeparatorNew
|
||||
}
|
||||
}
|
||||
|
||||
// ensureSandboxImageExists pulls the sandbox image when it's not present.
|
||||
func ensureSandboxImageExists(client libdocker.Interface, image string) error {
|
||||
_, err := client.InspectImageByRef(image)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if !libdocker.IsImageNotFoundError(err) {
|
||||
return fmt.Errorf("failed to inspect sandbox image %q: %v", image, err)
|
||||
}
|
||||
|
||||
repoToPull, _, _, err := parsers.ParseImageName(image)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
keyring := credentialprovider.NewDockerKeyring()
|
||||
creds, withCredentials := keyring.Lookup(repoToPull)
|
||||
if !withCredentials {
|
||||
glog.V(3).Infof("Pulling image %q without credentials", image)
|
||||
|
||||
err := client.PullImage(image, dockertypes.AuthConfig{}, dockertypes.ImagePullOptions{})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed pulling image %q: %v", image, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var pullErrs []error
|
||||
for _, currentCreds := range creds {
|
||||
authConfig := credentialprovider.LazyProvide(currentCreds)
|
||||
err := client.PullImage(image, authConfig, dockertypes.ImagePullOptions{})
|
||||
// If there was no error, return success
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
pullErrs = append(pullErrs, err)
|
||||
}
|
||||
|
||||
return utilerrors.NewAggregate(pullErrs)
|
||||
}
|
||||
|
||||
func getAppArmorOpts(profile string) ([]dockerOpt, error) {
|
||||
if profile == "" || profile == apparmor.ProfileRuntimeDefault {
|
||||
// The docker applies the default profile by default.
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Return unconfined profile explicitly
|
||||
if profile == apparmor.ProfileNameUnconfined {
|
||||
return []dockerOpt{{"apparmor", apparmor.ProfileNameUnconfined, ""}}, nil
|
||||
}
|
||||
|
||||
// Assume validation has already happened.
|
||||
profileName := strings.TrimPrefix(profile, apparmor.ProfileNamePrefix)
|
||||
return []dockerOpt{{"apparmor", profileName, ""}}, nil
|
||||
}
|
||||
|
||||
// fmtDockerOpts formats the docker security options using the given separator.
|
||||
func fmtDockerOpts(opts []dockerOpt, sep rune) []string {
|
||||
fmtOpts := make([]string, len(opts))
|
||||
for i, opt := range opts {
|
||||
fmtOpts[i] = fmt.Sprintf("%s%c%s", opt.key, sep, opt.value)
|
||||
}
|
||||
return fmtOpts
|
||||
}
|
||||
|
||||
type dockerOpt struct {
|
||||
// The key-value pair passed to docker.
|
||||
key, value string
|
||||
// The alternative value to use in log/event messages.
|
||||
msg string
|
||||
}
|
||||
|
||||
// Expose key/value from dockerOpt.
|
||||
func (d dockerOpt) GetKV() (string, string) {
|
||||
return d.key, d.value
|
||||
}
|
147
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/helpers_linux.go
generated
vendored
Normal file
147
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/helpers_linux.go
generated
vendored
Normal file
@ -0,0 +1,147 @@
|
||||
// +build linux
|
||||
|
||||
/*
|
||||
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 dockershim
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/blang/semver"
|
||||
dockertypes "github.com/docker/docker/api/types"
|
||||
dockercontainer "github.com/docker/docker/api/types/container"
|
||||
runtimeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
|
||||
)
|
||||
|
||||
func DefaultMemorySwap() int64 {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (ds *dockerService) getSecurityOpts(seccompProfile string, separator rune) ([]string, error) {
|
||||
// Apply seccomp options.
|
||||
seccompSecurityOpts, err := getSeccompSecurityOpts(seccompProfile, separator)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to generate seccomp security options for container: %v", err)
|
||||
}
|
||||
|
||||
return seccompSecurityOpts, nil
|
||||
}
|
||||
|
||||
func getSeccompDockerOpts(seccompProfile string) ([]dockerOpt, error) {
|
||||
if seccompProfile == "" || seccompProfile == "unconfined" {
|
||||
// return early the default
|
||||
return defaultSeccompOpt, nil
|
||||
}
|
||||
|
||||
if seccompProfile == "docker/default" {
|
||||
// return nil so docker will load the default seccomp profile
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(seccompProfile, "localhost/") {
|
||||
return nil, fmt.Errorf("unknown seccomp profile option: %s", seccompProfile)
|
||||
}
|
||||
|
||||
// get the full path of seccomp profile when prefixed with 'localhost/'.
|
||||
fname := strings.TrimPrefix(seccompProfile, "localhost/")
|
||||
if !filepath.IsAbs(fname) {
|
||||
return nil, fmt.Errorf("seccomp profile path must be absolute, but got relative path %q", fname)
|
||||
}
|
||||
file, err := ioutil.ReadFile(filepath.FromSlash(fname))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot load seccomp profile %q: %v", fname, err)
|
||||
}
|
||||
|
||||
b := bytes.NewBuffer(nil)
|
||||
if err := json.Compact(b, file); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Rather than the full profile, just put the filename & md5sum in the event log.
|
||||
msg := fmt.Sprintf("%s(md5:%x)", fname, md5.Sum(file))
|
||||
|
||||
return []dockerOpt{{"seccomp", b.String(), msg}}, nil
|
||||
}
|
||||
|
||||
// getSeccompSecurityOpts gets container seccomp options from container seccomp profile.
|
||||
// It is an experimental feature and may be promoted to official runtime api in the future.
|
||||
func getSeccompSecurityOpts(seccompProfile string, separator rune) ([]string, error) {
|
||||
seccompOpts, err := getSeccompDockerOpts(seccompProfile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return fmtDockerOpts(seccompOpts, separator), nil
|
||||
}
|
||||
|
||||
func (ds *dockerService) updateCreateConfig(
|
||||
createConfig *dockertypes.ContainerCreateConfig,
|
||||
config *runtimeapi.ContainerConfig,
|
||||
sandboxConfig *runtimeapi.PodSandboxConfig,
|
||||
podSandboxID string, securityOptSep rune, apiVersion *semver.Version) error {
|
||||
// Apply Linux-specific options if applicable.
|
||||
if lc := config.GetLinux(); lc != nil {
|
||||
// TODO: Check if the units are correct.
|
||||
// TODO: Can we assume the defaults are sane?
|
||||
rOpts := lc.GetResources()
|
||||
if rOpts != nil {
|
||||
createConfig.HostConfig.Resources = dockercontainer.Resources{
|
||||
Memory: rOpts.MemoryLimitInBytes,
|
||||
MemorySwap: DefaultMemorySwap(),
|
||||
CPUShares: rOpts.CpuShares,
|
||||
CPUQuota: rOpts.CpuQuota,
|
||||
CPUPeriod: rOpts.CpuPeriod,
|
||||
}
|
||||
createConfig.HostConfig.OomScoreAdj = int(rOpts.OomScoreAdj)
|
||||
}
|
||||
// Note: ShmSize is handled in kube_docker_client.go
|
||||
|
||||
// Apply security context.
|
||||
if err := applyContainerSecurityContext(lc, podSandboxID, createConfig.Config, createConfig.HostConfig, securityOptSep); err != nil {
|
||||
return fmt.Errorf("failed to apply container security context for container %q: %v", config.Metadata.Name, err)
|
||||
}
|
||||
modifyPIDNamespaceOverrides(ds.disableSharedPID, apiVersion, createConfig.HostConfig)
|
||||
}
|
||||
|
||||
// Apply cgroupsParent derived from the sandbox config.
|
||||
if lc := sandboxConfig.GetLinux(); lc != nil {
|
||||
// Apply Cgroup options.
|
||||
cgroupParent, err := ds.GenerateExpectedCgroupParent(lc.CgroupParent)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate cgroup parent in expected syntax for container %q: %v", config.Metadata.Name, err)
|
||||
}
|
||||
createConfig.HostConfig.CgroupParent = cgroupParent
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ds *dockerService) determinePodIPBySandboxID(uid string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func getNetworkNamespace(c *dockertypes.ContainerJSON) (string, error) {
|
||||
if c.State.Pid == 0 {
|
||||
// Docker reports pid 0 for an exited container.
|
||||
return "", fmt.Errorf("cannot find network namespace for the terminated container %q", c.ID)
|
||||
}
|
||||
return fmt.Sprintf(dockerNetNSFmt, c.State.Pid), nil
|
||||
}
|
103
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/helpers_linux_test.go
generated
vendored
Normal file
103
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/helpers_linux_test.go
generated
vendored
Normal file
@ -0,0 +1,103 @@
|
||||
// +build linux
|
||||
|
||||
/*
|
||||
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 dockershim
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestGetSeccompSecurityOpts(t *testing.T) {
|
||||
tests := []struct {
|
||||
msg string
|
||||
seccompProfile string
|
||||
expectedOpts []string
|
||||
}{{
|
||||
msg: "No security annotations",
|
||||
seccompProfile: "",
|
||||
expectedOpts: []string{"seccomp=unconfined"},
|
||||
}, {
|
||||
msg: "Seccomp unconfined",
|
||||
seccompProfile: "unconfined",
|
||||
expectedOpts: []string{"seccomp=unconfined"},
|
||||
}, {
|
||||
msg: "Seccomp default",
|
||||
seccompProfile: "docker/default",
|
||||
expectedOpts: nil,
|
||||
}}
|
||||
|
||||
for i, test := range tests {
|
||||
opts, err := getSeccompSecurityOpts(test.seccompProfile, '=')
|
||||
assert.NoError(t, err, "TestCase[%d]: %s", i, test.msg)
|
||||
assert.Len(t, opts, len(test.expectedOpts), "TestCase[%d]: %s", i, test.msg)
|
||||
for _, opt := range test.expectedOpts {
|
||||
assert.Contains(t, opts, opt, "TestCase[%d]: %s", i, test.msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadSeccompLocalhostProfiles(t *testing.T) {
|
||||
tmpdir, err := ioutil.TempDir("", "seccomp-local-profile-test")
|
||||
require.NoError(t, err)
|
||||
defer os.RemoveAll(tmpdir)
|
||||
testProfile := `{"foo": "bar"}`
|
||||
err = ioutil.WriteFile(filepath.Join(tmpdir, "test"), []byte(testProfile), 0644)
|
||||
require.NoError(t, err)
|
||||
|
||||
tests := []struct {
|
||||
msg string
|
||||
seccompProfile string
|
||||
expectedOpts []string
|
||||
expectErr bool
|
||||
}{{
|
||||
msg: "Seccomp localhost/test profile should return correct seccomp profiles",
|
||||
seccompProfile: "localhost/" + filepath.Join(tmpdir, "test"),
|
||||
expectedOpts: []string{`seccomp={"foo":"bar"}`},
|
||||
expectErr: false,
|
||||
}, {
|
||||
msg: "Non-existent profile should return error",
|
||||
seccompProfile: "localhost/" + filepath.Join(tmpdir, "fixtures/non-existent"),
|
||||
expectedOpts: nil,
|
||||
expectErr: true,
|
||||
}, {
|
||||
msg: "Relative profile path should return error",
|
||||
seccompProfile: "localhost/fixtures/test",
|
||||
expectedOpts: nil,
|
||||
expectErr: true,
|
||||
}}
|
||||
|
||||
for i, test := range tests {
|
||||
opts, err := getSeccompSecurityOpts(test.seccompProfile, '=')
|
||||
if test.expectErr {
|
||||
assert.Error(t, err, fmt.Sprintf("TestCase[%d]: %s", i, test.msg))
|
||||
continue
|
||||
}
|
||||
assert.NoError(t, err, "TestCase[%d]: %s", i, test.msg)
|
||||
assert.Len(t, opts, len(test.expectedOpts), "TestCase[%d]: %s", i, test.msg)
|
||||
for _, opt := range test.expectedOpts {
|
||||
assert.Contains(t, opts, opt, "TestCase[%d]: %s", i, test.msg)
|
||||
}
|
||||
}
|
||||
}
|
371
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/helpers_test.go
generated
vendored
Normal file
371
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/helpers_test.go
generated
vendored
Normal file
@ -0,0 +1,371 @@
|
||||
/*
|
||||
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 dockershim
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/blang/semver"
|
||||
dockertypes "github.com/docker/docker/api/types"
|
||||
dockernat "github.com/docker/go-connections/nat"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
runtimeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
|
||||
"k8s.io/kubernetes/pkg/kubelet/dockershim/libdocker"
|
||||
"k8s.io/kubernetes/pkg/security/apparmor"
|
||||
)
|
||||
|
||||
func TestLabelsAndAnnotationsRoundTrip(t *testing.T) {
|
||||
expectedLabels := map[string]string{"foo.123.abc": "baz", "bar.456.xyz": "qwe"}
|
||||
expectedAnnotations := map[string]string{"uio.ert": "dfs", "jkl": "asd"}
|
||||
// Merge labels and annotations into docker labels.
|
||||
dockerLabels := makeLabels(expectedLabels, expectedAnnotations)
|
||||
// Extract labels and annotations from docker labels.
|
||||
actualLabels, actualAnnotations := extractLabels(dockerLabels)
|
||||
assert.Equal(t, expectedLabels, actualLabels)
|
||||
assert.Equal(t, expectedAnnotations, actualAnnotations)
|
||||
}
|
||||
|
||||
// TestGetApparmorSecurityOpts tests the logic of generating container apparmor options from sandbox annotations.
|
||||
func TestGetApparmorSecurityOpts(t *testing.T) {
|
||||
makeConfig := func(profile string) *runtimeapi.LinuxContainerSecurityContext {
|
||||
return &runtimeapi.LinuxContainerSecurityContext{
|
||||
ApparmorProfile: profile,
|
||||
}
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
msg string
|
||||
config *runtimeapi.LinuxContainerSecurityContext
|
||||
expectedOpts []string
|
||||
}{{
|
||||
msg: "No AppArmor options",
|
||||
config: makeConfig(""),
|
||||
expectedOpts: nil,
|
||||
}, {
|
||||
msg: "AppArmor runtime/default",
|
||||
config: makeConfig("runtime/default"),
|
||||
expectedOpts: []string{},
|
||||
}, {
|
||||
msg: "AppArmor local profile",
|
||||
config: makeConfig(apparmor.ProfileNamePrefix + "foo"),
|
||||
expectedOpts: []string{"apparmor=foo"},
|
||||
}}
|
||||
|
||||
for i, test := range tests {
|
||||
opts, err := getApparmorSecurityOpts(test.config, '=')
|
||||
assert.NoError(t, err, "TestCase[%d]: %s", i, test.msg)
|
||||
assert.Len(t, opts, len(test.expectedOpts), "TestCase[%d]: %s", i, test.msg)
|
||||
for _, opt := range test.expectedOpts {
|
||||
assert.Contains(t, opts, opt, "TestCase[%d]: %s", i, test.msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetUserFromImageUser tests the logic of getting image uid or user name of image user.
|
||||
func TestGetUserFromImageUser(t *testing.T) {
|
||||
newI64 := func(i int64) *int64 { return &i }
|
||||
for c, test := range map[string]struct {
|
||||
user string
|
||||
uid *int64
|
||||
name string
|
||||
}{
|
||||
"no gid": {
|
||||
user: "0",
|
||||
uid: newI64(0),
|
||||
},
|
||||
"uid/gid": {
|
||||
user: "0:1",
|
||||
uid: newI64(0),
|
||||
},
|
||||
"empty user": {
|
||||
user: "",
|
||||
},
|
||||
"multiple spearators": {
|
||||
user: "1:2:3",
|
||||
uid: newI64(1),
|
||||
},
|
||||
"root username": {
|
||||
user: "root:root",
|
||||
name: "root",
|
||||
},
|
||||
"username": {
|
||||
user: "test:test",
|
||||
name: "test",
|
||||
},
|
||||
} {
|
||||
t.Logf("TestCase - %q", c)
|
||||
actualUID, actualName := getUserFromImageUser(test.user)
|
||||
assert.Equal(t, test.uid, actualUID)
|
||||
assert.Equal(t, test.name, actualName)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParsingCreationConflictError(t *testing.T) {
|
||||
// Expected error message from docker.
|
||||
msg := "Conflict. The name \"/k8s_POD_pfpod_e2e-tests-port-forwarding-dlxt2_81a3469e-99e1-11e6-89f2-42010af00002_0\" is already in use by container 24666ab8c814d16f986449e504ea0159468ddf8da01897144a770f66dce0e14e. You have to remove (or rename) that container to be able to reuse that name."
|
||||
|
||||
matches := conflictRE.FindStringSubmatch(msg)
|
||||
require.Len(t, matches, 2)
|
||||
require.Equal(t, matches[1], "24666ab8c814d16f986449e504ea0159468ddf8da01897144a770f66dce0e14e")
|
||||
}
|
||||
|
||||
func TestGetSecurityOptSeparator(t *testing.T) {
|
||||
for c, test := range map[string]struct {
|
||||
desc string
|
||||
version *semver.Version
|
||||
expected rune
|
||||
}{
|
||||
"older docker version": {
|
||||
version: &semver.Version{Major: 1, Minor: 22, Patch: 0},
|
||||
expected: ':',
|
||||
},
|
||||
"changed docker version": {
|
||||
version: &semver.Version{Major: 1, Minor: 23, Patch: 0},
|
||||
expected: '=',
|
||||
},
|
||||
"newer docker version": {
|
||||
version: &semver.Version{Major: 1, Minor: 24, Patch: 0},
|
||||
expected: '=',
|
||||
},
|
||||
} {
|
||||
actual := getSecurityOptSeparator(test.version)
|
||||
assert.Equal(t, test.expected, actual, c)
|
||||
}
|
||||
}
|
||||
|
||||
// writeDockerConfig will write a config file into a temporary dir, and return that dir.
|
||||
// Caller is responsible for deleting the dir and its contents.
|
||||
func writeDockerConfig(cfg string) (string, error) {
|
||||
tmpdir, err := ioutil.TempDir("", "dockershim=helpers_test.go=")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
dir := filepath.Join(tmpdir, ".docker")
|
||||
if err := os.Mkdir(dir, 0755); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return tmpdir, ioutil.WriteFile(filepath.Join(dir, "config.json"), []byte(cfg), 0644)
|
||||
}
|
||||
|
||||
func TestEnsureSandboxImageExists(t *testing.T) {
|
||||
sandboxImage := "gcr.io/test/image"
|
||||
authConfig := dockertypes.AuthConfig{Username: "user", Password: "pass"}
|
||||
for desc, test := range map[string]struct {
|
||||
injectImage bool
|
||||
imgNeedsAuth bool
|
||||
injectErr error
|
||||
calls []string
|
||||
err bool
|
||||
configJSON string
|
||||
}{
|
||||
"should not pull image when it already exists": {
|
||||
injectImage: true,
|
||||
injectErr: nil,
|
||||
calls: []string{"inspect_image"},
|
||||
},
|
||||
"should pull image when it doesn't exist": {
|
||||
injectImage: false,
|
||||
injectErr: libdocker.ImageNotFoundError{ID: "image_id"},
|
||||
calls: []string{"inspect_image", "pull"},
|
||||
},
|
||||
"should return error when inspect image fails": {
|
||||
injectImage: false,
|
||||
injectErr: fmt.Errorf("arbitrary error"),
|
||||
calls: []string{"inspect_image"},
|
||||
err: true,
|
||||
},
|
||||
"should return error when image pull needs private auth, but none provided": {
|
||||
injectImage: true,
|
||||
imgNeedsAuth: true,
|
||||
injectErr: libdocker.ImageNotFoundError{ID: "image_id"},
|
||||
calls: []string{"inspect_image", "pull"},
|
||||
err: true,
|
||||
},
|
||||
} {
|
||||
t.Logf("TestCase: %q", desc)
|
||||
_, fakeDocker, _ := newTestDockerService()
|
||||
if test.injectImage {
|
||||
images := []dockertypes.ImageSummary{{ID: sandboxImage}}
|
||||
fakeDocker.InjectImages(images)
|
||||
if test.imgNeedsAuth {
|
||||
fakeDocker.MakeImagesPrivate(images, authConfig)
|
||||
}
|
||||
}
|
||||
fakeDocker.InjectError("inspect_image", test.injectErr)
|
||||
|
||||
err := ensureSandboxImageExists(fakeDocker, sandboxImage)
|
||||
assert.NoError(t, fakeDocker.AssertCalls(test.calls))
|
||||
assert.Equal(t, test.err, err != nil)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMakePortsAndBindings(t *testing.T) {
|
||||
for desc, test := range map[string]struct {
|
||||
pm []*runtimeapi.PortMapping
|
||||
exposedPorts dockernat.PortSet
|
||||
portmappings map[dockernat.Port][]dockernat.PortBinding
|
||||
}{
|
||||
"no port mapping": {
|
||||
pm: nil,
|
||||
exposedPorts: map[dockernat.Port]struct{}{},
|
||||
portmappings: map[dockernat.Port][]dockernat.PortBinding{},
|
||||
},
|
||||
"tcp port mapping": {
|
||||
pm: []*runtimeapi.PortMapping{
|
||||
{
|
||||
Protocol: runtimeapi.Protocol_TCP,
|
||||
ContainerPort: 80,
|
||||
HostPort: 80,
|
||||
},
|
||||
},
|
||||
exposedPorts: map[dockernat.Port]struct{}{
|
||||
"80/tcp": {},
|
||||
},
|
||||
portmappings: map[dockernat.Port][]dockernat.PortBinding{
|
||||
"80/tcp": {
|
||||
{
|
||||
HostPort: "80",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"udp port mapping": {
|
||||
pm: []*runtimeapi.PortMapping{
|
||||
{
|
||||
Protocol: runtimeapi.Protocol_UDP,
|
||||
ContainerPort: 80,
|
||||
HostPort: 80,
|
||||
},
|
||||
},
|
||||
exposedPorts: map[dockernat.Port]struct{}{
|
||||
"80/udp": {},
|
||||
},
|
||||
portmappings: map[dockernat.Port][]dockernat.PortBinding{
|
||||
"80/udp": {
|
||||
{
|
||||
HostPort: "80",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"multipe port mappings": {
|
||||
pm: []*runtimeapi.PortMapping{
|
||||
{
|
||||
Protocol: runtimeapi.Protocol_TCP,
|
||||
ContainerPort: 80,
|
||||
HostPort: 80,
|
||||
},
|
||||
{
|
||||
Protocol: runtimeapi.Protocol_TCP,
|
||||
ContainerPort: 80,
|
||||
HostPort: 81,
|
||||
},
|
||||
},
|
||||
exposedPorts: map[dockernat.Port]struct{}{
|
||||
"80/tcp": {},
|
||||
},
|
||||
portmappings: map[dockernat.Port][]dockernat.PortBinding{
|
||||
"80/tcp": {
|
||||
{
|
||||
HostPort: "80",
|
||||
},
|
||||
{
|
||||
HostPort: "81",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} {
|
||||
t.Logf("TestCase: %s", desc)
|
||||
actualExposedPorts, actualPortMappings := makePortsAndBindings(test.pm)
|
||||
assert.Equal(t, test.exposedPorts, actualExposedPorts)
|
||||
assert.Equal(t, test.portmappings, actualPortMappings)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateMountBindings(t *testing.T) {
|
||||
mounts := []*runtimeapi.Mount{
|
||||
// everything default
|
||||
{
|
||||
HostPath: "/mnt/1",
|
||||
ContainerPath: "/var/lib/mysql/1",
|
||||
},
|
||||
// readOnly
|
||||
{
|
||||
HostPath: "/mnt/2",
|
||||
ContainerPath: "/var/lib/mysql/2",
|
||||
Readonly: true,
|
||||
},
|
||||
// SELinux
|
||||
{
|
||||
HostPath: "/mnt/3",
|
||||
ContainerPath: "/var/lib/mysql/3",
|
||||
SelinuxRelabel: true,
|
||||
},
|
||||
// Propagation private
|
||||
{
|
||||
HostPath: "/mnt/4",
|
||||
ContainerPath: "/var/lib/mysql/4",
|
||||
Propagation: runtimeapi.MountPropagation_PROPAGATION_PRIVATE,
|
||||
},
|
||||
// Propagation rslave
|
||||
{
|
||||
HostPath: "/mnt/5",
|
||||
ContainerPath: "/var/lib/mysql/5",
|
||||
Propagation: runtimeapi.MountPropagation_PROPAGATION_HOST_TO_CONTAINER,
|
||||
},
|
||||
// Propagation rshared
|
||||
{
|
||||
HostPath: "/mnt/6",
|
||||
ContainerPath: "/var/lib/mysql/6",
|
||||
Propagation: runtimeapi.MountPropagation_PROPAGATION_BIDIRECTIONAL,
|
||||
},
|
||||
// Propagation unknown (falls back to private)
|
||||
{
|
||||
HostPath: "/mnt/7",
|
||||
ContainerPath: "/var/lib/mysql/7",
|
||||
Propagation: runtimeapi.MountPropagation(42),
|
||||
},
|
||||
// Everything
|
||||
{
|
||||
HostPath: "/mnt/8",
|
||||
ContainerPath: "/var/lib/mysql/8",
|
||||
Readonly: true,
|
||||
SelinuxRelabel: true,
|
||||
Propagation: runtimeapi.MountPropagation_PROPAGATION_BIDIRECTIONAL,
|
||||
},
|
||||
}
|
||||
expectedResult := []string{
|
||||
"/mnt/1:/var/lib/mysql/1",
|
||||
"/mnt/2:/var/lib/mysql/2:ro",
|
||||
"/mnt/3:/var/lib/mysql/3:Z",
|
||||
"/mnt/4:/var/lib/mysql/4",
|
||||
"/mnt/5:/var/lib/mysql/5:rslave",
|
||||
"/mnt/6:/var/lib/mysql/6:rshared",
|
||||
"/mnt/7:/var/lib/mysql/7",
|
||||
"/mnt/8:/var/lib/mysql/8:ro,Z,rshared",
|
||||
}
|
||||
result := generateMountBindings(mounts)
|
||||
|
||||
assert.Equal(t, result, expectedResult)
|
||||
}
|
55
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/helpers_unsupported.go
generated
vendored
Normal file
55
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/helpers_unsupported.go
generated
vendored
Normal file
@ -0,0 +1,55 @@
|
||||
// +build !linux,!windows
|
||||
|
||||
/*
|
||||
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 dockershim
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/blang/semver"
|
||||
dockertypes "github.com/docker/docker/api/types"
|
||||
"github.com/golang/glog"
|
||||
runtimeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
|
||||
)
|
||||
|
||||
func DefaultMemorySwap() int64 {
|
||||
return -1
|
||||
}
|
||||
|
||||
func (ds *dockerService) getSecurityOpts(seccompProfile string, separator rune) ([]string, error) {
|
||||
glog.Warningf("getSecurityOpts is unsupported in this build")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (ds *dockerService) updateCreateConfig(
|
||||
createConfig *dockertypes.ContainerCreateConfig,
|
||||
config *runtimeapi.ContainerConfig,
|
||||
sandboxConfig *runtimeapi.PodSandboxConfig,
|
||||
podSandboxID string, securityOptSep rune, apiVersion *semver.Version) error {
|
||||
glog.Warningf("updateCreateConfig is unsupported in this build")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ds *dockerService) determinePodIPBySandboxID(uid string) string {
|
||||
glog.Warningf("determinePodIPBySandboxID is unsupported in this build")
|
||||
return ""
|
||||
}
|
||||
|
||||
func getNetworkNamespace(c *dockertypes.ContainerJSON) (string, error) {
|
||||
return "", fmt.Errorf("unsupported platform")
|
||||
}
|
129
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/helpers_windows.go
generated
vendored
Normal file
129
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/helpers_windows.go
generated
vendored
Normal file
@ -0,0 +1,129 @@
|
||||
// +build windows
|
||||
|
||||
/*
|
||||
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 dockershim
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/blang/semver"
|
||||
dockertypes "github.com/docker/docker/api/types"
|
||||
dockercontainer "github.com/docker/docker/api/types/container"
|
||||
dockerfilters "github.com/docker/docker/api/types/filters"
|
||||
"github.com/golang/glog"
|
||||
runtimeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
|
||||
)
|
||||
|
||||
func DefaultMemorySwap() int64 {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (ds *dockerService) getSecurityOpts(seccompProfile string, separator rune) ([]string, error) {
|
||||
if seccompProfile != "" {
|
||||
glog.Warningf("seccomp annotations are not supported on windows")
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (ds *dockerService) updateCreateConfig(
|
||||
createConfig *dockertypes.ContainerCreateConfig,
|
||||
config *runtimeapi.ContainerConfig,
|
||||
sandboxConfig *runtimeapi.PodSandboxConfig,
|
||||
podSandboxID string, securityOptSep rune, apiVersion *semver.Version) error {
|
||||
if networkMode := os.Getenv("CONTAINER_NETWORK"); networkMode != "" {
|
||||
createConfig.HostConfig.NetworkMode = dockercontainer.NetworkMode(networkMode)
|
||||
} else {
|
||||
// Todo: Refactor this call in future for calling methods directly in security_context.go
|
||||
modifyHostNetworkOptionForContainer(false, podSandboxID, createConfig.HostConfig)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ds *dockerService) determinePodIPBySandboxID(sandboxID string) string {
|
||||
opts := dockertypes.ContainerListOptions{
|
||||
All: true,
|
||||
Filters: dockerfilters.NewArgs(),
|
||||
}
|
||||
|
||||
f := newDockerFilter(&opts.Filters)
|
||||
f.AddLabel(containerTypeLabelKey, containerTypeLabelContainer)
|
||||
f.AddLabel(sandboxIDLabelKey, sandboxID)
|
||||
containers, err := ds.client.ListContainers(opts)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
for _, c := range containers {
|
||||
r, err := ds.client.InspectContainer(c.ID)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Versions and feature support
|
||||
// ============================
|
||||
// Windows version == Windows Server, Version 1709,, Supports both sandbox and non-sandbox case
|
||||
// Windows version == Windows Server 2016 Support only non-sandbox case
|
||||
// Windows version < Windows Server 2016 is Not Supported
|
||||
|
||||
// Sandbox support in Windows mandates CNI Plugin.
|
||||
// Presense of CONTAINER_NETWORK flag is considered as non-Sandbox cases here
|
||||
|
||||
// Todo: Add a kernel version check for more validation
|
||||
|
||||
if networkMode := os.Getenv("CONTAINER_NETWORK"); networkMode == "" {
|
||||
// Do not return any IP, so that we would continue and get the IP of the Sandbox
|
||||
ds.getIP(sandboxID, r)
|
||||
} else {
|
||||
// On Windows, every container that is created in a Sandbox, needs to invoke CNI plugin again for adding the Network,
|
||||
// with the shared container name as NetNS info,
|
||||
// This is passed down to the platform to replicate some necessary information to the new container
|
||||
|
||||
//
|
||||
// This place is chosen as a hack for now, since getContainerIP would end up calling CNI's addToNetwork
|
||||
// That is why addToNetwork is required to be idempotent
|
||||
|
||||
// Instead of relying on this call, an explicit call to addToNetwork should be
|
||||
// done immediately after ContainerCreation, in case of Windows only. TBD Issue # to handle this
|
||||
|
||||
if containerIP := getContainerIP(r); containerIP != "" {
|
||||
return containerIP
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
func getNetworkNamespace(c *dockertypes.ContainerJSON) (string, error) {
|
||||
// Currently in windows there is no identifier exposed for network namespace
|
||||
// Like docker, the referenced container id is used to figure out the network namespace id internally by the platform
|
||||
// so returning the docker networkMode (which holds container:<ref containerid> for network namespace here
|
||||
return string(c.HostConfig.NetworkMode), nil
|
||||
}
|
||||
|
||||
func getContainerIP(container *dockertypes.ContainerJSON) string {
|
||||
if container.NetworkSettings != nil {
|
||||
for _, network := range container.NetworkSettings.Networks {
|
||||
if network.IPAddress != "" {
|
||||
return network.IPAddress
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
61
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/libdocker/BUILD
generated
vendored
Normal file
61
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/libdocker/BUILD
generated
vendored
Normal file
@ -0,0 +1,61 @@
|
||||
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",
|
||||
],
|
||||
importpath = "k8s.io/kubernetes/pkg/kubelet/dockershim/libdocker",
|
||||
library = ":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/golang.org/x/net/context: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"],
|
||||
)
|
137
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/libdocker/client.go
generated
vendored
Normal file
137
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/libdocker/client.go
generated
vendored
Normal file
@ -0,0 +1,137 @@
|
||||
/*
|
||||
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"
|
||||
|
||||
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.10.x
|
||||
MinimumDockerAPIVersion = "1.22.0"
|
||||
|
||||
// Status of a container returned by ListContainers.
|
||||
StatusRunningPrefix = "Up"
|
||||
StatusCreatedPrefix = "Created"
|
||||
StatusExitedPrefix = "Exited"
|
||||
|
||||
// This is only used by GetKubeletDockerContainers(), and should be removed
|
||||
// along with the function.
|
||||
containerNamePrefix = "k8s"
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// GetKubeletDockerContainers lists all container or just the running ones.
|
||||
// Returns a list of docker containers that we manage
|
||||
// TODO: This function should be deleted after migrating
|
||||
// test/e2e_node/garbage_collector_test.go off of it.
|
||||
func GetKubeletDockerContainers(client Interface, allContainers bool) ([]*dockertypes.Container, error) {
|
||||
result := []*dockertypes.Container{}
|
||||
containers, err := client.ListContainers(dockertypes.ContainerListOptions{All: allContainers})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i := range containers {
|
||||
container := &containers[i]
|
||||
if len(container.Names) == 0 {
|
||||
continue
|
||||
}
|
||||
// Skip containers that we didn't create to allow users to manually
|
||||
// spin up their own containers if they want.
|
||||
if !strings.HasPrefix(container.Names[0], "/"+containerNamePrefix+"_") {
|
||||
glog.V(5).Infof("Docker Container: %s is not managed by kubelet.", container.Names[0])
|
||||
continue
|
||||
}
|
||||
result = append(result, container)
|
||||
}
|
||||
return result, nil
|
||||
}
|
911
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/libdocker/fake_client.go
generated
vendored
Normal file
911
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/libdocker/fake_client.go
generated
vendored
Normal file
@ -0,0 +1,911 @@
|
||||
/*
|
||||
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
|
||||
}
|
||||
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)
|
||||
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
|
||||
}
|
||||
|
||||
}
|
||||
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"
|
||||
"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"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// 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.Now().Sub(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))
|
||||
}
|
23
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/metrics/BUILD
generated
vendored
Normal file
23
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/metrics/BUILD
generated
vendored
Normal file
@ -0,0 +1,23 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["metrics.go"],
|
||||
importpath = "k8s.io/kubernetes/pkg/kubelet/dockershim/metrics",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = ["//vendor/github.com/prometheus/client_golang/prometheus:go_default_library"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
96
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/metrics/metrics.go
generated
vendored
Normal file
96
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/metrics/metrics.go
generated
vendored
Normal file
@ -0,0 +1,96 @@
|
||||
/*
|
||||
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 metrics
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
const (
|
||||
// DockerOperationsKey is the key for docker operation metrics.
|
||||
DockerOperationsKey = "docker_operations"
|
||||
// DockerOperationsLatencyKey is the key for the operation latency metrics.
|
||||
DockerOperationsLatencyKey = "docker_operations_latency_microseconds"
|
||||
// DockerOperationsErrorsKey is the key for the operation error metrics.
|
||||
DockerOperationsErrorsKey = "docker_operations_errors"
|
||||
// DockerOperationsTimeoutKey is the key for the operation timoeut metrics.
|
||||
DockerOperationsTimeoutKey = "docker_operations_timeout"
|
||||
|
||||
// Keep the "kubelet" subsystem for backward compatibility.
|
||||
kubeletSubsystem = "kubelet"
|
||||
)
|
||||
|
||||
var (
|
||||
// DockerOperationsLatency collects operation latency numbers by operation
|
||||
// type.
|
||||
DockerOperationsLatency = prometheus.NewSummaryVec(
|
||||
prometheus.SummaryOpts{
|
||||
Subsystem: kubeletSubsystem,
|
||||
Name: DockerOperationsLatencyKey,
|
||||
Help: "Latency in microseconds of Docker operations. Broken down by operation type.",
|
||||
},
|
||||
[]string{"operation_type"},
|
||||
)
|
||||
// DockerOperations collects operation counts by operation type.
|
||||
DockerOperations = prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Subsystem: kubeletSubsystem,
|
||||
Name: DockerOperationsKey,
|
||||
Help: "Cumulative number of Docker operations by operation type.",
|
||||
},
|
||||
[]string{"operation_type"},
|
||||
)
|
||||
// DockerOperationsErrors collects operation errors by operation
|
||||
// type.
|
||||
DockerOperationsErrors = prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Subsystem: kubeletSubsystem,
|
||||
Name: DockerOperationsErrorsKey,
|
||||
Help: "Cumulative number of Docker operation errors by operation type.",
|
||||
},
|
||||
[]string{"operation_type"},
|
||||
)
|
||||
// DockerOperationsTimeout collects operation timeouts by operation type.
|
||||
DockerOperationsTimeout = prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Subsystem: kubeletSubsystem,
|
||||
Name: DockerOperationsTimeoutKey,
|
||||
Help: "Cumulative number of Docker operation timeout by operation type.",
|
||||
},
|
||||
[]string{"operation_type"},
|
||||
)
|
||||
)
|
||||
|
||||
var registerMetrics sync.Once
|
||||
|
||||
// Register all metrics.
|
||||
func Register() {
|
||||
registerMetrics.Do(func() {
|
||||
prometheus.MustRegister(DockerOperationsLatency)
|
||||
prometheus.MustRegister(DockerOperations)
|
||||
prometheus.MustRegister(DockerOperationsErrors)
|
||||
prometheus.MustRegister(DockerOperationsTimeout)
|
||||
})
|
||||
}
|
||||
|
||||
// SinceInMicroseconds gets the time since the specified start in microseconds.
|
||||
func SinceInMicroseconds(start time.Time) float64 {
|
||||
return float64(time.Since(start).Nanoseconds() / time.Microsecond.Nanoseconds())
|
||||
}
|
149
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/naming.go
generated
vendored
Normal file
149
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/naming.go
generated
vendored
Normal file
@ -0,0 +1,149 @@
|
||||
/*
|
||||
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 dockershim
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
runtimeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
|
||||
"k8s.io/kubernetes/pkg/kubelet/leaky"
|
||||
)
|
||||
|
||||
// Container "names" are implementation details that do not concern
|
||||
// kubelet/CRI. This CRI shim uses names to fulfill the CRI requirement to
|
||||
// make sandbox/container creation idempotent. CRI states that there can
|
||||
// only exist one sandbox/container with the given metadata. To enforce this,
|
||||
// this shim constructs a name using the fields in the metadata so that
|
||||
// docker will reject the creation request if the name already exists.
|
||||
//
|
||||
// Note that changes to naming will likely break the backward compatibility.
|
||||
// Code must be added to ensure the shim knows how to recognize and extract
|
||||
// information the older containers.
|
||||
//
|
||||
// TODO: Add code to handle backward compatibility, i.e., making sure we can
|
||||
// recognize older containers and extract information from their names if
|
||||
// necessary.
|
||||
|
||||
const (
|
||||
// kubePrefix is used to identify the containers/sandboxes on the node managed by kubelet
|
||||
kubePrefix = "k8s"
|
||||
// sandboxContainerName is a string to include in the docker container so
|
||||
// that users can easily identify the sandboxes.
|
||||
sandboxContainerName = leaky.PodInfraContainerName
|
||||
// Delimiter used to construct docker container names.
|
||||
nameDelimiter = "_"
|
||||
// DockerImageIDPrefix is the prefix of image id in container status.
|
||||
DockerImageIDPrefix = "docker://"
|
||||
// DockerPullableImageIDPrefix is the prefix of pullable image id in container status.
|
||||
DockerPullableImageIDPrefix = "docker-pullable://"
|
||||
)
|
||||
|
||||
func makeSandboxName(s *runtimeapi.PodSandboxConfig) string {
|
||||
return strings.Join([]string{
|
||||
kubePrefix, // 0
|
||||
sandboxContainerName, // 1
|
||||
s.Metadata.Name, // 2
|
||||
s.Metadata.Namespace, // 3
|
||||
s.Metadata.Uid, // 4
|
||||
fmt.Sprintf("%d", s.Metadata.Attempt), // 5
|
||||
}, nameDelimiter)
|
||||
}
|
||||
|
||||
func makeContainerName(s *runtimeapi.PodSandboxConfig, c *runtimeapi.ContainerConfig) string {
|
||||
return strings.Join([]string{
|
||||
kubePrefix, // 0
|
||||
c.Metadata.Name, // 1:
|
||||
s.Metadata.Name, // 2: sandbox name
|
||||
s.Metadata.Namespace, // 3: sandbox namesapce
|
||||
s.Metadata.Uid, // 4 sandbox uid
|
||||
fmt.Sprintf("%d", c.Metadata.Attempt), // 5
|
||||
}, nameDelimiter)
|
||||
}
|
||||
|
||||
// randomizeName randomizes the container name. This should only be used when we hit the
|
||||
// docker container name conflict bug.
|
||||
func randomizeName(name string) string {
|
||||
return strings.Join([]string{
|
||||
name,
|
||||
fmt.Sprintf("%08x", rand.Uint32()),
|
||||
}, nameDelimiter)
|
||||
}
|
||||
|
||||
func parseUint32(s string) (uint32, error) {
|
||||
n, err := strconv.ParseUint(s, 10, 32)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return uint32(n), nil
|
||||
}
|
||||
|
||||
// TODO: Evaluate whether we should rely on labels completely.
|
||||
func parseSandboxName(name string) (*runtimeapi.PodSandboxMetadata, error) {
|
||||
// Docker adds a "/" prefix to names. so trim it.
|
||||
name = strings.TrimPrefix(name, "/")
|
||||
|
||||
parts := strings.Split(name, nameDelimiter)
|
||||
// Tolerate the random suffix.
|
||||
// TODO(random-liu): Remove 7 field case when docker 1.11 is deprecated.
|
||||
if len(parts) != 6 && len(parts) != 7 {
|
||||
return nil, fmt.Errorf("failed to parse the sandbox name: %q", name)
|
||||
}
|
||||
if parts[0] != kubePrefix {
|
||||
return nil, fmt.Errorf("container is not managed by kubernetes: %q", name)
|
||||
}
|
||||
|
||||
attempt, err := parseUint32(parts[5])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse the sandbox name %q: %v", name, err)
|
||||
}
|
||||
|
||||
return &runtimeapi.PodSandboxMetadata{
|
||||
Name: parts[2],
|
||||
Namespace: parts[3],
|
||||
Uid: parts[4],
|
||||
Attempt: attempt,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// TODO: Evaluate whether we should rely on labels completely.
|
||||
func parseContainerName(name string) (*runtimeapi.ContainerMetadata, error) {
|
||||
// Docker adds a "/" prefix to names. so trim it.
|
||||
name = strings.TrimPrefix(name, "/")
|
||||
|
||||
parts := strings.Split(name, nameDelimiter)
|
||||
// Tolerate the random suffix.
|
||||
// TODO(random-liu): Remove 7 field case when docker 1.11 is deprecated.
|
||||
if len(parts) != 6 && len(parts) != 7 {
|
||||
return nil, fmt.Errorf("failed to parse the container name: %q", name)
|
||||
}
|
||||
if parts[0] != kubePrefix {
|
||||
return nil, fmt.Errorf("container is not managed by kubernetes: %q", name)
|
||||
}
|
||||
|
||||
attempt, err := parseUint32(parts[5])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse the container name %q: %v", name, err)
|
||||
}
|
||||
|
||||
return &runtimeapi.ContainerMetadata{
|
||||
Name: parts[1],
|
||||
Attempt: attempt,
|
||||
}, nil
|
||||
}
|
106
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/naming_test.go
generated
vendored
Normal file
106
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/naming_test.go
generated
vendored
Normal file
@ -0,0 +1,106 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package dockershim
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
runtimeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
|
||||
)
|
||||
|
||||
func TestSandboxNameRoundTrip(t *testing.T) {
|
||||
config := makeSandboxConfig("foo", "bar", "iamuid", 3)
|
||||
actualName := makeSandboxName(config)
|
||||
assert.Equal(t, "k8s_POD_foo_bar_iamuid_3", actualName)
|
||||
|
||||
actualMetadata, err := parseSandboxName(actualName)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, config.Metadata, actualMetadata)
|
||||
}
|
||||
|
||||
func TestNonParsableSandboxNames(t *testing.T) {
|
||||
// All names must start with the kubernetes prefix "k8s".
|
||||
_, err := parseSandboxName("owner_POD_foo_bar_iamuid_4")
|
||||
assert.Error(t, err)
|
||||
|
||||
// All names must contain exactly 6 parts.
|
||||
_, err = parseSandboxName("k8s_POD_dummy_foo_bar_iamuid_4")
|
||||
assert.Error(t, err)
|
||||
_, err = parseSandboxName("k8s_foo_bar_iamuid_4")
|
||||
assert.Error(t, err)
|
||||
|
||||
// Should be able to parse attempt number.
|
||||
_, err = parseSandboxName("k8s_POD_foo_bar_iamuid_notanumber")
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestContainerNameRoundTrip(t *testing.T) {
|
||||
sConfig := makeSandboxConfig("foo", "bar", "iamuid", 3)
|
||||
name, attempt := "pause", uint32(5)
|
||||
config := &runtimeapi.ContainerConfig{
|
||||
Metadata: &runtimeapi.ContainerMetadata{
|
||||
Name: name,
|
||||
Attempt: attempt,
|
||||
},
|
||||
}
|
||||
actualName := makeContainerName(sConfig, config)
|
||||
assert.Equal(t, "k8s_pause_foo_bar_iamuid_5", actualName)
|
||||
|
||||
actualMetadata, err := parseContainerName(actualName)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, config.Metadata, actualMetadata)
|
||||
}
|
||||
|
||||
func TestNonParsableContainerNames(t *testing.T) {
|
||||
// All names must start with the kubernetes prefix "k8s".
|
||||
_, err := parseContainerName("owner_frontend_foo_bar_iamuid_4")
|
||||
assert.Error(t, err)
|
||||
|
||||
// All names must contain exactly 6 parts.
|
||||
_, err = parseContainerName("k8s_frontend_dummy_foo_bar_iamuid_4")
|
||||
assert.Error(t, err)
|
||||
_, err = parseContainerName("k8s_foo_bar_iamuid_4")
|
||||
assert.Error(t, err)
|
||||
|
||||
// Should be able to parse attempt number.
|
||||
_, err = parseContainerName("k8s_frontend_foo_bar_iamuid_notanumber")
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestParseRandomizedNames(t *testing.T) {
|
||||
// Test randomized sandbox name.
|
||||
sConfig := makeSandboxConfig("foo", "bar", "iamuid", 3)
|
||||
sActualName := randomizeName(makeSandboxName(sConfig))
|
||||
sActualMetadata, err := parseSandboxName(sActualName)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, sConfig.Metadata, sActualMetadata)
|
||||
|
||||
// Test randomized container name.
|
||||
name, attempt := "pause", uint32(5)
|
||||
config := &runtimeapi.ContainerConfig{
|
||||
Metadata: &runtimeapi.ContainerMetadata{
|
||||
Name: name,
|
||||
Attempt: attempt,
|
||||
},
|
||||
}
|
||||
actualName := randomizeName(makeContainerName(sConfig, config))
|
||||
actualMetadata, err := parseContainerName(actualName)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, config.Metadata, actualMetadata)
|
||||
}
|
39
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/remote/BUILD
generated
vendored
Normal file
39
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/remote/BUILD
generated
vendored
Normal file
@ -0,0 +1,39 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"docker_server.go",
|
||||
"docker_service.go",
|
||||
],
|
||||
importpath = "k8s.io/kubernetes/pkg/kubelet/dockershim/remote",
|
||||
deps = [
|
||||
"//pkg/kubelet/apis/cri:go_default_library",
|
||||
"//pkg/kubelet/apis/cri/v1alpha1/runtime:go_default_library",
|
||||
"//pkg/kubelet/dockershim:go_default_library",
|
||||
"//pkg/kubelet/util:go_default_library",
|
||||
"//pkg/util/interrupt:go_default_library",
|
||||
"//vendor/github.com/golang/glog:go_default_library",
|
||||
"//vendor/golang.org/x/net/context:go_default_library",
|
||||
"//vendor/google.golang.org/grpc:go_default_library",
|
||||
"//vendor/k8s.io/utils/exec:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
75
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/remote/docker_server.go
generated
vendored
Normal file
75
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/remote/docker_server.go
generated
vendored
Normal file
@ -0,0 +1,75 @@
|
||||
/*
|
||||
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 remote
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"google.golang.org/grpc"
|
||||
|
||||
runtimeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
|
||||
"k8s.io/kubernetes/pkg/kubelet/dockershim"
|
||||
"k8s.io/kubernetes/pkg/kubelet/util"
|
||||
"k8s.io/kubernetes/pkg/util/interrupt"
|
||||
)
|
||||
|
||||
// DockerServer is the grpc server of dockershim.
|
||||
type DockerServer struct {
|
||||
// endpoint is the endpoint to serve on.
|
||||
endpoint string
|
||||
// service is the docker service which implements runtime and image services.
|
||||
service DockerService
|
||||
// server is the grpc server.
|
||||
server *grpc.Server
|
||||
}
|
||||
|
||||
// NewDockerServer creates the dockershim grpc server.
|
||||
func NewDockerServer(endpoint string, s dockershim.DockerService) *DockerServer {
|
||||
return &DockerServer{
|
||||
endpoint: endpoint,
|
||||
service: NewDockerService(s),
|
||||
}
|
||||
}
|
||||
|
||||
// Start starts the dockershim grpc server.
|
||||
func (s *DockerServer) Start() error {
|
||||
glog.V(2).Infof("Start dockershim grpc server")
|
||||
l, err := util.CreateListener(s.endpoint)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to listen on %q: %v", s.endpoint, err)
|
||||
}
|
||||
// Create the grpc server and register runtime and image services.
|
||||
s.server = grpc.NewServer()
|
||||
runtimeapi.RegisterRuntimeServiceServer(s.server, s.service)
|
||||
runtimeapi.RegisterImageServiceServer(s.server, s.service)
|
||||
go func() {
|
||||
// Use interrupt handler to make sure the server to be stopped properly.
|
||||
h := interrupt.New(nil, s.Stop)
|
||||
err := h.Run(func() error { return s.server.Serve(l) })
|
||||
if err != nil {
|
||||
glog.Errorf("Failed to serve connections: %v", err)
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop stops the dockershim grpc server.
|
||||
func (s *DockerServer) Stop() {
|
||||
glog.V(2).Infof("Stop docker server")
|
||||
s.server.Stop()
|
||||
}
|
249
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/remote/docker_service.go
generated
vendored
Normal file
249
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/remote/docker_service.go
generated
vendored
Normal file
@ -0,0 +1,249 @@
|
||||
/*
|
||||
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 remote
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
internalapi "k8s.io/kubernetes/pkg/kubelet/apis/cri"
|
||||
runtimeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
|
||||
"k8s.io/kubernetes/pkg/kubelet/dockershim"
|
||||
utilexec "k8s.io/utils/exec"
|
||||
)
|
||||
|
||||
// DockerService is the interface implement CRI remote service server.
|
||||
type DockerService interface {
|
||||
runtimeapi.RuntimeServiceServer
|
||||
runtimeapi.ImageServiceServer
|
||||
}
|
||||
|
||||
// dockerService uses dockershim service to implement DockerService.
|
||||
// Notice that the contexts in the functions are not used now.
|
||||
// TODO(random-liu): Change the dockershim service to support context, and implement
|
||||
// internal services and remote services with the dockershim service.
|
||||
type dockerService struct {
|
||||
runtimeService internalapi.RuntimeService
|
||||
imageService internalapi.ImageManagerService
|
||||
}
|
||||
|
||||
func NewDockerService(s dockershim.DockerService) DockerService {
|
||||
return &dockerService{runtimeService: s, imageService: s}
|
||||
}
|
||||
|
||||
func (d *dockerService) Version(ctx context.Context, r *runtimeapi.VersionRequest) (*runtimeapi.VersionResponse, error) {
|
||||
return d.runtimeService.Version(r.Version)
|
||||
}
|
||||
|
||||
func (d *dockerService) Status(ctx context.Context, r *runtimeapi.StatusRequest) (*runtimeapi.StatusResponse, error) {
|
||||
status, err := d.runtimeService.Status()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &runtimeapi.StatusResponse{Status: status}, nil
|
||||
}
|
||||
|
||||
func (d *dockerService) RunPodSandbox(ctx context.Context, r *runtimeapi.RunPodSandboxRequest) (*runtimeapi.RunPodSandboxResponse, error) {
|
||||
podSandboxId, err := d.runtimeService.RunPodSandbox(r.GetConfig())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &runtimeapi.RunPodSandboxResponse{PodSandboxId: podSandboxId}, nil
|
||||
}
|
||||
|
||||
func (d *dockerService) StopPodSandbox(ctx context.Context, r *runtimeapi.StopPodSandboxRequest) (*runtimeapi.StopPodSandboxResponse, error) {
|
||||
err := d.runtimeService.StopPodSandbox(r.PodSandboxId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &runtimeapi.StopPodSandboxResponse{}, nil
|
||||
}
|
||||
|
||||
func (d *dockerService) RemovePodSandbox(ctx context.Context, r *runtimeapi.RemovePodSandboxRequest) (*runtimeapi.RemovePodSandboxResponse, error) {
|
||||
err := d.runtimeService.RemovePodSandbox(r.PodSandboxId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &runtimeapi.RemovePodSandboxResponse{}, nil
|
||||
}
|
||||
|
||||
func (d *dockerService) PodSandboxStatus(ctx context.Context, r *runtimeapi.PodSandboxStatusRequest) (*runtimeapi.PodSandboxStatusResponse, error) {
|
||||
podSandboxStatus, err := d.runtimeService.PodSandboxStatus(r.PodSandboxId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &runtimeapi.PodSandboxStatusResponse{Status: podSandboxStatus}, nil
|
||||
}
|
||||
|
||||
func (d *dockerService) ListPodSandbox(ctx context.Context, r *runtimeapi.ListPodSandboxRequest) (*runtimeapi.ListPodSandboxResponse, error) {
|
||||
items, err := d.runtimeService.ListPodSandbox(r.GetFilter())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &runtimeapi.ListPodSandboxResponse{Items: items}, nil
|
||||
}
|
||||
|
||||
func (d *dockerService) CreateContainer(ctx context.Context, r *runtimeapi.CreateContainerRequest) (*runtimeapi.CreateContainerResponse, error) {
|
||||
containerId, err := d.runtimeService.CreateContainer(r.PodSandboxId, r.GetConfig(), r.GetSandboxConfig())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &runtimeapi.CreateContainerResponse{ContainerId: containerId}, nil
|
||||
}
|
||||
|
||||
func (d *dockerService) StartContainer(ctx context.Context, r *runtimeapi.StartContainerRequest) (*runtimeapi.StartContainerResponse, error) {
|
||||
err := d.runtimeService.StartContainer(r.ContainerId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &runtimeapi.StartContainerResponse{}, nil
|
||||
}
|
||||
|
||||
func (d *dockerService) StopContainer(ctx context.Context, r *runtimeapi.StopContainerRequest) (*runtimeapi.StopContainerResponse, error) {
|
||||
err := d.runtimeService.StopContainer(r.ContainerId, r.Timeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &runtimeapi.StopContainerResponse{}, nil
|
||||
}
|
||||
|
||||
func (d *dockerService) RemoveContainer(ctx context.Context, r *runtimeapi.RemoveContainerRequest) (*runtimeapi.RemoveContainerResponse, error) {
|
||||
err := d.runtimeService.RemoveContainer(r.ContainerId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &runtimeapi.RemoveContainerResponse{}, nil
|
||||
}
|
||||
|
||||
func (d *dockerService) ListContainers(ctx context.Context, r *runtimeapi.ListContainersRequest) (*runtimeapi.ListContainersResponse, error) {
|
||||
containers, err := d.runtimeService.ListContainers(r.GetFilter())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &runtimeapi.ListContainersResponse{Containers: containers}, nil
|
||||
}
|
||||
|
||||
func (d *dockerService) ContainerStatus(ctx context.Context, r *runtimeapi.ContainerStatusRequest) (*runtimeapi.ContainerStatusResponse, error) {
|
||||
status, err := d.runtimeService.ContainerStatus(r.ContainerId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &runtimeapi.ContainerStatusResponse{Status: status}, nil
|
||||
}
|
||||
|
||||
func (d *dockerService) UpdateContainerResources(ctx context.Context, r *runtimeapi.UpdateContainerResourcesRequest) (*runtimeapi.UpdateContainerResourcesResponse, error) {
|
||||
err := d.runtimeService.UpdateContainerResources(r.ContainerId, r.Linux)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &runtimeapi.UpdateContainerResourcesResponse{}, nil
|
||||
}
|
||||
|
||||
func (d *dockerService) ExecSync(ctx context.Context, r *runtimeapi.ExecSyncRequest) (*runtimeapi.ExecSyncResponse, error) {
|
||||
stdout, stderr, err := d.runtimeService.ExecSync(r.ContainerId, r.Cmd, time.Duration(r.Timeout)*time.Second)
|
||||
var exitCode int32
|
||||
if err != nil {
|
||||
exitError, ok := err.(utilexec.ExitError)
|
||||
if !ok {
|
||||
return nil, err
|
||||
}
|
||||
exitCode = int32(exitError.ExitStatus())
|
||||
}
|
||||
return &runtimeapi.ExecSyncResponse{
|
||||
Stdout: stdout,
|
||||
Stderr: stderr,
|
||||
ExitCode: exitCode,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *dockerService) Exec(ctx context.Context, r *runtimeapi.ExecRequest) (*runtimeapi.ExecResponse, error) {
|
||||
return d.runtimeService.Exec(r)
|
||||
}
|
||||
|
||||
func (d *dockerService) Attach(ctx context.Context, r *runtimeapi.AttachRequest) (*runtimeapi.AttachResponse, error) {
|
||||
return d.runtimeService.Attach(r)
|
||||
}
|
||||
|
||||
func (d *dockerService) PortForward(ctx context.Context, r *runtimeapi.PortForwardRequest) (*runtimeapi.PortForwardResponse, error) {
|
||||
return d.runtimeService.PortForward(r)
|
||||
}
|
||||
|
||||
func (d *dockerService) UpdateRuntimeConfig(ctx context.Context, r *runtimeapi.UpdateRuntimeConfigRequest) (*runtimeapi.UpdateRuntimeConfigResponse, error) {
|
||||
err := d.runtimeService.UpdateRuntimeConfig(r.GetRuntimeConfig())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &runtimeapi.UpdateRuntimeConfigResponse{}, nil
|
||||
}
|
||||
|
||||
func (d *dockerService) ListImages(ctx context.Context, r *runtimeapi.ListImagesRequest) (*runtimeapi.ListImagesResponse, error) {
|
||||
images, err := d.imageService.ListImages(r.GetFilter())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &runtimeapi.ListImagesResponse{Images: images}, nil
|
||||
}
|
||||
|
||||
func (d *dockerService) ImageStatus(ctx context.Context, r *runtimeapi.ImageStatusRequest) (*runtimeapi.ImageStatusResponse, error) {
|
||||
image, err := d.imageService.ImageStatus(r.GetImage())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &runtimeapi.ImageStatusResponse{Image: image}, nil
|
||||
}
|
||||
|
||||
func (d *dockerService) PullImage(ctx context.Context, r *runtimeapi.PullImageRequest) (*runtimeapi.PullImageResponse, error) {
|
||||
image, err := d.imageService.PullImage(r.GetImage(), r.GetAuth())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &runtimeapi.PullImageResponse{ImageRef: image}, nil
|
||||
}
|
||||
|
||||
func (d *dockerService) RemoveImage(ctx context.Context, r *runtimeapi.RemoveImageRequest) (*runtimeapi.RemoveImageResponse, error) {
|
||||
err := d.imageService.RemoveImage(r.GetImage())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &runtimeapi.RemoveImageResponse{}, nil
|
||||
}
|
||||
|
||||
// ImageFsInfo returns information of the filesystem that is used to store images.
|
||||
func (d *dockerService) ImageFsInfo(ctx context.Context, r *runtimeapi.ImageFsInfoRequest) (*runtimeapi.ImageFsInfoResponse, error) {
|
||||
filesystems, err := d.imageService.ImageFsInfo()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &runtimeapi.ImageFsInfoResponse{ImageFilesystems: filesystems}, nil
|
||||
}
|
||||
|
||||
func (d *dockerService) ContainerStats(ctx context.Context, r *runtimeapi.ContainerStatsRequest) (*runtimeapi.ContainerStatsResponse, error) {
|
||||
stats, err := d.runtimeService.ContainerStats(r.ContainerId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &runtimeapi.ContainerStatsResponse{Stats: stats}, nil
|
||||
}
|
||||
|
||||
func (d *dockerService) ListContainerStats(ctx context.Context, r *runtimeapi.ListContainerStatsRequest) (*runtimeapi.ListContainerStatsResponse, error) {
|
||||
stats, err := d.runtimeService.ListContainerStats(r.GetFilter())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &runtimeapi.ListContainerStatsResponse{Stats: stats}, nil
|
||||
}
|
204
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/security_context.go
generated
vendored
Normal file
204
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/security_context.go
generated
vendored
Normal file
@ -0,0 +1,204 @@
|
||||
/*
|
||||
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 dockershim
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/blang/semver"
|
||||
dockercontainer "github.com/docker/docker/api/types/container"
|
||||
|
||||
runtimeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
|
||||
knetwork "k8s.io/kubernetes/pkg/kubelet/network"
|
||||
)
|
||||
|
||||
// applySandboxSecurityContext updates docker sandbox options according to security context.
|
||||
func applySandboxSecurityContext(lc *runtimeapi.LinuxPodSandboxConfig, config *dockercontainer.Config, hc *dockercontainer.HostConfig, network *knetwork.PluginManager, separator rune) error {
|
||||
if lc == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var sc *runtimeapi.LinuxContainerSecurityContext
|
||||
if lc.SecurityContext != nil {
|
||||
sc = &runtimeapi.LinuxContainerSecurityContext{
|
||||
SupplementalGroups: lc.SecurityContext.SupplementalGroups,
|
||||
RunAsUser: lc.SecurityContext.RunAsUser,
|
||||
ReadonlyRootfs: lc.SecurityContext.ReadonlyRootfs,
|
||||
SelinuxOptions: lc.SecurityContext.SelinuxOptions,
|
||||
NamespaceOptions: lc.SecurityContext.NamespaceOptions,
|
||||
}
|
||||
}
|
||||
|
||||
modifyContainerConfig(sc, config)
|
||||
if err := modifyHostConfig(sc, hc, separator); err != nil {
|
||||
return err
|
||||
}
|
||||
modifySandboxNamespaceOptions(sc.GetNamespaceOptions(), hc, network)
|
||||
return nil
|
||||
}
|
||||
|
||||
// applyContainerSecurityContext updates docker container options according to security context.
|
||||
func applyContainerSecurityContext(lc *runtimeapi.LinuxContainerConfig, podSandboxID string, config *dockercontainer.Config, hc *dockercontainer.HostConfig, separator rune) error {
|
||||
if lc == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
modifyContainerConfig(lc.SecurityContext, config)
|
||||
if err := modifyHostConfig(lc.SecurityContext, hc, separator); err != nil {
|
||||
return err
|
||||
}
|
||||
modifyContainerNamespaceOptions(lc.SecurityContext.GetNamespaceOptions(), podSandboxID, hc)
|
||||
return nil
|
||||
}
|
||||
|
||||
// modifyContainerConfig applies container security context config to dockercontainer.Config.
|
||||
func modifyContainerConfig(sc *runtimeapi.LinuxContainerSecurityContext, config *dockercontainer.Config) {
|
||||
if sc == nil {
|
||||
return
|
||||
}
|
||||
if sc.RunAsUser != nil {
|
||||
config.User = strconv.FormatInt(sc.GetRunAsUser().Value, 10)
|
||||
}
|
||||
if sc.RunAsUsername != "" {
|
||||
config.User = sc.RunAsUsername
|
||||
}
|
||||
}
|
||||
|
||||
// modifyHostConfig applies security context config to dockercontainer.HostConfig.
|
||||
func modifyHostConfig(sc *runtimeapi.LinuxContainerSecurityContext, hostConfig *dockercontainer.HostConfig, separator rune) error {
|
||||
if sc == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Apply supplemental groups.
|
||||
for _, group := range sc.SupplementalGroups {
|
||||
hostConfig.GroupAdd = append(hostConfig.GroupAdd, strconv.FormatInt(group, 10))
|
||||
}
|
||||
|
||||
// Apply security context for the container.
|
||||
hostConfig.Privileged = sc.Privileged
|
||||
hostConfig.ReadonlyRootfs = sc.ReadonlyRootfs
|
||||
if sc.Capabilities != nil {
|
||||
hostConfig.CapAdd = sc.GetCapabilities().AddCapabilities
|
||||
hostConfig.CapDrop = sc.GetCapabilities().DropCapabilities
|
||||
}
|
||||
if sc.SelinuxOptions != nil {
|
||||
hostConfig.SecurityOpt = addSELinuxOptions(
|
||||
hostConfig.SecurityOpt,
|
||||
sc.SelinuxOptions,
|
||||
separator,
|
||||
)
|
||||
}
|
||||
|
||||
// Apply apparmor options.
|
||||
apparmorSecurityOpts, err := getApparmorSecurityOpts(sc, separator)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate apparmor security options: %v", err)
|
||||
}
|
||||
hostConfig.SecurityOpt = append(hostConfig.SecurityOpt, apparmorSecurityOpts...)
|
||||
|
||||
if sc.NoNewPrivs {
|
||||
hostConfig.SecurityOpt = append(hostConfig.SecurityOpt, "no-new-privileges")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// modifySandboxNamespaceOptions apply namespace options for sandbox
|
||||
func modifySandboxNamespaceOptions(nsOpts *runtimeapi.NamespaceOption, hostConfig *dockercontainer.HostConfig, network *knetwork.PluginManager) {
|
||||
hostNetwork := false
|
||||
if nsOpts != nil {
|
||||
hostNetwork = nsOpts.HostNetwork
|
||||
}
|
||||
modifyCommonNamespaceOptions(nsOpts, hostConfig)
|
||||
modifyHostNetworkOptionForSandbox(hostNetwork, network, hostConfig)
|
||||
}
|
||||
|
||||
// modifyContainerNamespaceOptions apply namespace options for container
|
||||
func modifyContainerNamespaceOptions(nsOpts *runtimeapi.NamespaceOption, podSandboxID string, hostConfig *dockercontainer.HostConfig) {
|
||||
hostNetwork := false
|
||||
if nsOpts != nil {
|
||||
hostNetwork = nsOpts.HostNetwork
|
||||
}
|
||||
hostConfig.PidMode = dockercontainer.PidMode(fmt.Sprintf("container:%v", podSandboxID))
|
||||
modifyCommonNamespaceOptions(nsOpts, hostConfig)
|
||||
modifyHostNetworkOptionForContainer(hostNetwork, podSandboxID, hostConfig)
|
||||
}
|
||||
|
||||
// modifyCommonNamespaceOptions apply common namespace options for sandbox and container
|
||||
func modifyCommonNamespaceOptions(nsOpts *runtimeapi.NamespaceOption, hostConfig *dockercontainer.HostConfig) {
|
||||
if nsOpts != nil {
|
||||
if nsOpts.HostPid {
|
||||
hostConfig.PidMode = namespaceModeHost
|
||||
}
|
||||
if nsOpts.HostIpc {
|
||||
hostConfig.IpcMode = namespaceModeHost
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// modifyHostNetworkOptionForSandbox applies NetworkMode/UTSMode to sandbox's dockercontainer.HostConfig.
|
||||
func modifyHostNetworkOptionForSandbox(hostNetwork bool, network *knetwork.PluginManager, hc *dockercontainer.HostConfig) {
|
||||
if hostNetwork {
|
||||
hc.NetworkMode = namespaceModeHost
|
||||
return
|
||||
}
|
||||
|
||||
if network == nil {
|
||||
hc.NetworkMode = "default"
|
||||
return
|
||||
}
|
||||
|
||||
switch network.PluginName() {
|
||||
case "cni":
|
||||
fallthrough
|
||||
case "kubenet":
|
||||
hc.NetworkMode = "none"
|
||||
default:
|
||||
hc.NetworkMode = "default"
|
||||
}
|
||||
}
|
||||
|
||||
// modifyHostNetworkOptionForContainer applies NetworkMode/UTSMode to container's dockercontainer.HostConfig.
|
||||
func modifyHostNetworkOptionForContainer(hostNetwork bool, podSandboxID string, hc *dockercontainer.HostConfig) {
|
||||
sandboxNSMode := fmt.Sprintf("container:%v", podSandboxID)
|
||||
hc.NetworkMode = dockercontainer.NetworkMode(sandboxNSMode)
|
||||
hc.IpcMode = dockercontainer.IpcMode(sandboxNSMode)
|
||||
hc.UTSMode = ""
|
||||
|
||||
if hostNetwork {
|
||||
hc.UTSMode = namespaceModeHost
|
||||
}
|
||||
}
|
||||
|
||||
// modifyPIDNamespaceOverrides implements two temporary overrides for the default PID namespace sharing for Docker:
|
||||
// 1. Docker engine prior to API Version 1.24 doesn't support attaching to another container's
|
||||
// PID namespace, and it didn't stabilize until 1.26. This check can be removed when Kubernetes'
|
||||
// minimum Docker version is at least 1.13.1 (API version 1.26).
|
||||
// 2. The administrator has overridden the default behavior by means of a kubelet flag. This is an
|
||||
// "escape hatch" to return to previous behavior of isolated namespaces and should be removed once
|
||||
// no longer needed.
|
||||
func modifyPIDNamespaceOverrides(disableSharedPID bool, version *semver.Version, hc *dockercontainer.HostConfig) {
|
||||
if !strings.HasPrefix(string(hc.PidMode), "container:") {
|
||||
return
|
||||
}
|
||||
if disableSharedPID || version.LT(semver.Version{Major: 1, Minor: 26}) {
|
||||
hc.PidMode = ""
|
||||
}
|
||||
}
|
423
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/security_context_test.go
generated
vendored
Normal file
423
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/security_context_test.go
generated
vendored
Normal file
@ -0,0 +1,423 @@
|
||||
/*
|
||||
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 dockershim
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/blang/semver"
|
||||
dockercontainer "github.com/docker/docker/api/types/container"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
runtimeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
|
||||
)
|
||||
|
||||
func TestModifyContainerConfig(t *testing.T) {
|
||||
var uid int64 = 123
|
||||
var username = "testuser"
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
sc *runtimeapi.LinuxContainerSecurityContext
|
||||
expected *dockercontainer.Config
|
||||
}{
|
||||
{
|
||||
name: "container.SecurityContext.RunAsUser set",
|
||||
sc: &runtimeapi.LinuxContainerSecurityContext{
|
||||
RunAsUser: &runtimeapi.Int64Value{Value: uid},
|
||||
},
|
||||
expected: &dockercontainer.Config{
|
||||
User: strconv.FormatInt(uid, 10),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "container.SecurityContext.RunAsUsername set",
|
||||
sc: &runtimeapi.LinuxContainerSecurityContext{
|
||||
RunAsUsername: username,
|
||||
},
|
||||
expected: &dockercontainer.Config{
|
||||
User: username,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no RunAsUser value set",
|
||||
sc: &runtimeapi.LinuxContainerSecurityContext{},
|
||||
expected: &dockercontainer.Config{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
dockerCfg := &dockercontainer.Config{}
|
||||
modifyContainerConfig(tc.sc, dockerCfg)
|
||||
assert.Equal(t, tc.expected, dockerCfg, "[Test case %q]", tc.name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestModifyHostConfig(t *testing.T) {
|
||||
setNetworkHC := &dockercontainer.HostConfig{}
|
||||
setPrivSC := &runtimeapi.LinuxContainerSecurityContext{}
|
||||
setPrivSC.Privileged = true
|
||||
setPrivHC := &dockercontainer.HostConfig{
|
||||
Privileged: true,
|
||||
}
|
||||
setCapsHC := &dockercontainer.HostConfig{
|
||||
CapAdd: []string{"addCapA", "addCapB"},
|
||||
CapDrop: []string{"dropCapA", "dropCapB"},
|
||||
}
|
||||
setSELinuxHC := &dockercontainer.HostConfig{
|
||||
SecurityOpt: []string{
|
||||
fmt.Sprintf("%s:%s", selinuxLabelUser('='), "user"),
|
||||
fmt.Sprintf("%s:%s", selinuxLabelRole('='), "role"),
|
||||
fmt.Sprintf("%s:%s", selinuxLabelType('='), "type"),
|
||||
fmt.Sprintf("%s:%s", selinuxLabelLevel('='), "level"),
|
||||
},
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
sc *runtimeapi.LinuxContainerSecurityContext
|
||||
expected *dockercontainer.HostConfig
|
||||
}{
|
||||
{
|
||||
name: "fully set container.SecurityContext",
|
||||
sc: fullValidSecurityContext(),
|
||||
expected: fullValidHostConfig(),
|
||||
},
|
||||
{
|
||||
name: "empty container.SecurityContext",
|
||||
sc: &runtimeapi.LinuxContainerSecurityContext{},
|
||||
expected: setNetworkHC,
|
||||
},
|
||||
{
|
||||
name: "container.SecurityContext.Privileged",
|
||||
sc: setPrivSC,
|
||||
expected: setPrivHC,
|
||||
},
|
||||
{
|
||||
name: "container.SecurityContext.Capabilities",
|
||||
sc: &runtimeapi.LinuxContainerSecurityContext{
|
||||
Capabilities: inputCapabilities(),
|
||||
},
|
||||
expected: setCapsHC,
|
||||
},
|
||||
{
|
||||
name: "container.SecurityContext.SELinuxOptions",
|
||||
sc: &runtimeapi.LinuxContainerSecurityContext{
|
||||
SelinuxOptions: inputSELinuxOptions(),
|
||||
},
|
||||
expected: setSELinuxHC,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
dockerCfg := &dockercontainer.HostConfig{}
|
||||
modifyHostConfig(tc.sc, dockerCfg, '=')
|
||||
assert.Equal(t, tc.expected, dockerCfg, "[Test case %q]", tc.name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestModifyHostConfigWithGroups(t *testing.T) {
|
||||
supplementalGroupsSC := &runtimeapi.LinuxContainerSecurityContext{}
|
||||
supplementalGroupsSC.SupplementalGroups = []int64{2222}
|
||||
supplementalGroupHC := &dockercontainer.HostConfig{}
|
||||
supplementalGroupHC.GroupAdd = []string{"2222"}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
securityContext *runtimeapi.LinuxContainerSecurityContext
|
||||
expected *dockercontainer.HostConfig
|
||||
}{
|
||||
{
|
||||
name: "nil",
|
||||
securityContext: nil,
|
||||
expected: &dockercontainer.HostConfig{},
|
||||
},
|
||||
{
|
||||
name: "SupplementalGroup",
|
||||
securityContext: supplementalGroupsSC,
|
||||
expected: supplementalGroupHC,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
dockerCfg := &dockercontainer.HostConfig{}
|
||||
modifyHostConfig(tc.securityContext, dockerCfg, '=')
|
||||
assert.Equal(t, tc.expected, dockerCfg, "[Test case %q]", tc.name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestModifyHostConfigAndNamespaceOptionsForContainer(t *testing.T) {
|
||||
priv := true
|
||||
sandboxID := "sandbox"
|
||||
sandboxNSMode := fmt.Sprintf("container:%v", sandboxID)
|
||||
setPrivSC := &runtimeapi.LinuxContainerSecurityContext{}
|
||||
setPrivSC.Privileged = priv
|
||||
setPrivHC := &dockercontainer.HostConfig{
|
||||
Privileged: true,
|
||||
IpcMode: dockercontainer.IpcMode(sandboxNSMode),
|
||||
NetworkMode: dockercontainer.NetworkMode(sandboxNSMode),
|
||||
PidMode: dockercontainer.PidMode(sandboxNSMode),
|
||||
}
|
||||
setCapsHC := &dockercontainer.HostConfig{
|
||||
CapAdd: []string{"addCapA", "addCapB"},
|
||||
CapDrop: []string{"dropCapA", "dropCapB"},
|
||||
IpcMode: dockercontainer.IpcMode(sandboxNSMode),
|
||||
NetworkMode: dockercontainer.NetworkMode(sandboxNSMode),
|
||||
PidMode: dockercontainer.PidMode(sandboxNSMode),
|
||||
}
|
||||
setSELinuxHC := &dockercontainer.HostConfig{
|
||||
SecurityOpt: []string{
|
||||
fmt.Sprintf("%s:%s", selinuxLabelUser('='), "user"),
|
||||
fmt.Sprintf("%s:%s", selinuxLabelRole('='), "role"),
|
||||
fmt.Sprintf("%s:%s", selinuxLabelType('='), "type"),
|
||||
fmt.Sprintf("%s:%s", selinuxLabelLevel('='), "level"),
|
||||
},
|
||||
IpcMode: dockercontainer.IpcMode(sandboxNSMode),
|
||||
NetworkMode: dockercontainer.NetworkMode(sandboxNSMode),
|
||||
PidMode: dockercontainer.PidMode(sandboxNSMode),
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
sc *runtimeapi.LinuxContainerSecurityContext
|
||||
expected *dockercontainer.HostConfig
|
||||
}{
|
||||
{
|
||||
name: "container.SecurityContext.Privileged",
|
||||
sc: setPrivSC,
|
||||
expected: setPrivHC,
|
||||
},
|
||||
{
|
||||
name: "container.SecurityContext.Capabilities",
|
||||
sc: &runtimeapi.LinuxContainerSecurityContext{
|
||||
Capabilities: inputCapabilities(),
|
||||
},
|
||||
expected: setCapsHC,
|
||||
},
|
||||
{
|
||||
name: "container.SecurityContext.SELinuxOptions",
|
||||
sc: &runtimeapi.LinuxContainerSecurityContext{
|
||||
SelinuxOptions: inputSELinuxOptions(),
|
||||
},
|
||||
expected: setSELinuxHC,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
dockerCfg := &dockercontainer.HostConfig{}
|
||||
modifyHostConfig(tc.sc, dockerCfg, '=')
|
||||
modifyContainerNamespaceOptions(tc.sc.GetNamespaceOptions(), sandboxID, dockerCfg)
|
||||
assert.Equal(t, tc.expected, dockerCfg, "[Test case %q]", tc.name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestModifySandboxNamespaceOptions(t *testing.T) {
|
||||
set := true
|
||||
cases := []struct {
|
||||
name string
|
||||
nsOpt *runtimeapi.NamespaceOption
|
||||
expected *dockercontainer.HostConfig
|
||||
}{
|
||||
{
|
||||
name: "NamespaceOption.HostNetwork",
|
||||
nsOpt: &runtimeapi.NamespaceOption{
|
||||
HostNetwork: set,
|
||||
},
|
||||
expected: &dockercontainer.HostConfig{
|
||||
NetworkMode: namespaceModeHost,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "NamespaceOption.HostIpc",
|
||||
nsOpt: &runtimeapi.NamespaceOption{
|
||||
HostIpc: set,
|
||||
},
|
||||
expected: &dockercontainer.HostConfig{
|
||||
IpcMode: namespaceModeHost,
|
||||
NetworkMode: "default",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "NamespaceOption.HostPid",
|
||||
nsOpt: &runtimeapi.NamespaceOption{
|
||||
HostPid: set,
|
||||
},
|
||||
expected: &dockercontainer.HostConfig{
|
||||
PidMode: namespaceModeHost,
|
||||
NetworkMode: "default",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
dockerCfg := &dockercontainer.HostConfig{}
|
||||
modifySandboxNamespaceOptions(tc.nsOpt, dockerCfg, nil)
|
||||
assert.Equal(t, tc.expected, dockerCfg, "[Test case %q]", tc.name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestModifyContainerNamespaceOptions(t *testing.T) {
|
||||
set := true
|
||||
sandboxID := "sandbox"
|
||||
sandboxNSMode := fmt.Sprintf("container:%v", sandboxID)
|
||||
cases := []struct {
|
||||
name string
|
||||
nsOpt *runtimeapi.NamespaceOption
|
||||
expected *dockercontainer.HostConfig
|
||||
}{
|
||||
{
|
||||
name: "NamespaceOption.HostNetwork",
|
||||
nsOpt: &runtimeapi.NamespaceOption{
|
||||
HostNetwork: set,
|
||||
},
|
||||
expected: &dockercontainer.HostConfig{
|
||||
NetworkMode: dockercontainer.NetworkMode(sandboxNSMode),
|
||||
IpcMode: dockercontainer.IpcMode(sandboxNSMode),
|
||||
UTSMode: namespaceModeHost,
|
||||
PidMode: dockercontainer.PidMode(sandboxNSMode),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "NamespaceOption.HostIpc",
|
||||
nsOpt: &runtimeapi.NamespaceOption{
|
||||
HostIpc: set,
|
||||
},
|
||||
expected: &dockercontainer.HostConfig{
|
||||
NetworkMode: dockercontainer.NetworkMode(sandboxNSMode),
|
||||
IpcMode: dockercontainer.IpcMode(sandboxNSMode),
|
||||
PidMode: dockercontainer.PidMode(sandboxNSMode),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "NamespaceOption.HostPid",
|
||||
nsOpt: &runtimeapi.NamespaceOption{
|
||||
HostPid: set,
|
||||
},
|
||||
expected: &dockercontainer.HostConfig{
|
||||
NetworkMode: dockercontainer.NetworkMode(sandboxNSMode),
|
||||
IpcMode: dockercontainer.IpcMode(sandboxNSMode),
|
||||
PidMode: namespaceModeHost,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
dockerCfg := &dockercontainer.HostConfig{}
|
||||
modifyContainerNamespaceOptions(tc.nsOpt, sandboxID, dockerCfg)
|
||||
assert.Equal(t, tc.expected, dockerCfg, "[Test case %q]", tc.name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestModifyContainerNamespacePIDOverride(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
disable bool
|
||||
version *semver.Version
|
||||
input, expected dockercontainer.PidMode
|
||||
}{
|
||||
{
|
||||
name: "SharedPID.Enable",
|
||||
disable: false,
|
||||
version: &semver.Version{Major: 1, Minor: 26},
|
||||
input: "container:sandbox",
|
||||
expected: "container:sandbox",
|
||||
},
|
||||
{
|
||||
name: "SharedPID.Disable",
|
||||
disable: true,
|
||||
version: &semver.Version{Major: 1, Minor: 26},
|
||||
input: "container:sandbox",
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
name: "SharedPID.OldDocker",
|
||||
disable: false,
|
||||
version: &semver.Version{Major: 1, Minor: 25},
|
||||
input: "container:sandbox",
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
name: "SharedPID.HostPid",
|
||||
disable: true,
|
||||
version: &semver.Version{Major: 1, Minor: 27},
|
||||
input: "host",
|
||||
expected: "host",
|
||||
},
|
||||
{
|
||||
name: "SharedPID.DistantFuture",
|
||||
disable: false,
|
||||
version: &semver.Version{Major: 2, Minor: 10},
|
||||
input: "container:sandbox",
|
||||
expected: "container:sandbox",
|
||||
},
|
||||
{
|
||||
name: "SharedPID.EmptyPidMode",
|
||||
disable: true,
|
||||
version: &semver.Version{Major: 1, Minor: 25},
|
||||
input: "",
|
||||
expected: "",
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
dockerCfg := &dockercontainer.HostConfig{PidMode: tc.input}
|
||||
modifyPIDNamespaceOverrides(tc.disable, tc.version, dockerCfg)
|
||||
assert.Equal(t, tc.expected, dockerCfg.PidMode, "[Test case %q]", tc.name)
|
||||
}
|
||||
}
|
||||
|
||||
func fullValidSecurityContext() *runtimeapi.LinuxContainerSecurityContext {
|
||||
return &runtimeapi.LinuxContainerSecurityContext{
|
||||
Privileged: true,
|
||||
Capabilities: inputCapabilities(),
|
||||
SelinuxOptions: inputSELinuxOptions(),
|
||||
}
|
||||
}
|
||||
|
||||
func inputCapabilities() *runtimeapi.Capability {
|
||||
return &runtimeapi.Capability{
|
||||
AddCapabilities: []string{"addCapA", "addCapB"},
|
||||
DropCapabilities: []string{"dropCapA", "dropCapB"},
|
||||
}
|
||||
}
|
||||
|
||||
func inputSELinuxOptions() *runtimeapi.SELinuxOption {
|
||||
user := "user"
|
||||
role := "role"
|
||||
stype := "type"
|
||||
level := "level"
|
||||
|
||||
return &runtimeapi.SELinuxOption{
|
||||
User: user,
|
||||
Role: role,
|
||||
Type: stype,
|
||||
Level: level,
|
||||
}
|
||||
}
|
||||
|
||||
func fullValidHostConfig() *dockercontainer.HostConfig {
|
||||
return &dockercontainer.HostConfig{
|
||||
Privileged: true,
|
||||
CapAdd: []string{"addCapA", "addCapB"},
|
||||
CapDrop: []string{"dropCapA", "dropCapB"},
|
||||
SecurityOpt: []string{
|
||||
fmt.Sprintf("%s:%s", selinuxLabelUser('='), "user"),
|
||||
fmt.Sprintf("%s:%s", selinuxLabelRole('='), "role"),
|
||||
fmt.Sprintf("%s:%s", selinuxLabelType('='), "type"),
|
||||
fmt.Sprintf("%s:%s", selinuxLabelLevel('='), "level"),
|
||||
},
|
||||
}
|
||||
}
|
92
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/selinux_util.go
generated
vendored
Normal file
92
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/selinux_util.go
generated
vendored
Normal file
@ -0,0 +1,92 @@
|
||||
/*
|
||||
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 dockershim
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
runtimeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
|
||||
)
|
||||
|
||||
// selinuxLabelUser returns the fragment of a Docker security opt that
|
||||
// describes the SELinux user. Note that strictly speaking this is not
|
||||
// actually the name of the security opt, but a fragment of the whole key-
|
||||
// value pair necessary to set the opt.
|
||||
func selinuxLabelUser(separator rune) string {
|
||||
return fmt.Sprintf("label%cuser", separator)
|
||||
}
|
||||
|
||||
// selinuxLabelRole returns the fragment of a Docker security opt that
|
||||
// describes the SELinux role. Note that strictly speaking this is not
|
||||
// actually the name of the security opt, but a fragment of the whole key-
|
||||
// value pair necessary to set the opt.
|
||||
func selinuxLabelRole(separator rune) string {
|
||||
return fmt.Sprintf("label%crole", separator)
|
||||
}
|
||||
|
||||
// selinuxLabelType returns the fragment of a Docker security opt that
|
||||
// describes the SELinux type. Note that strictly speaking this is not
|
||||
// actually the name of the security opt, but a fragment of the whole key-
|
||||
// value pair necessary to set the opt.
|
||||
func selinuxLabelType(separator rune) string {
|
||||
return fmt.Sprintf("label%ctype", separator)
|
||||
}
|
||||
|
||||
// selinuxLabelLevel returns the fragment of a Docker security opt that
|
||||
// describes the SELinux level. Note that strictly speaking this is not
|
||||
// actually the name of the security opt, but a fragment of the whole key-
|
||||
// value pair necessary to set the opt.
|
||||
func selinuxLabelLevel(separator rune) string {
|
||||
return fmt.Sprintf("label%clevel", separator)
|
||||
}
|
||||
|
||||
// dockerLaelDisable returns the Docker security opt that disables SELinux for
|
||||
// the container.
|
||||
func selinuxLabelDisable(separator rune) string {
|
||||
return fmt.Sprintf("label%cdisable", separator)
|
||||
}
|
||||
|
||||
// addSELinuxOptions adds SELinux options to config using the given
|
||||
// separator.
|
||||
func addSELinuxOptions(config []string, selinuxOpts *runtimeapi.SELinuxOption, separator rune) []string {
|
||||
// Note, strictly speaking, we are actually mutating the values of these
|
||||
// keys, rather than formatting name and value into a string. Docker re-
|
||||
// uses the same option name multiple times (it's just 'label') with
|
||||
// different values which are themselves key-value pairs. For example,
|
||||
// the SELinux type is represented by the security opt:
|
||||
//
|
||||
// label<separator>type:<selinux_type>
|
||||
//
|
||||
// In Docker API versions before 1.23, the separator was the `:` rune; in
|
||||
// API version 1.23 it changed to the `=` rune.
|
||||
config = modifySecurityOption(config, selinuxLabelUser(separator), selinuxOpts.User)
|
||||
config = modifySecurityOption(config, selinuxLabelRole(separator), selinuxOpts.Role)
|
||||
config = modifySecurityOption(config, selinuxLabelType(separator), selinuxOpts.Type)
|
||||
config = modifySecurityOption(config, selinuxLabelLevel(separator), selinuxOpts.Level)
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
// modifySecurityOption adds the security option of name to the config array
|
||||
// with value in the form of name:value.
|
||||
func modifySecurityOption(config []string, name, value string) []string {
|
||||
if len(value) > 0 {
|
||||
config = append(config, fmt.Sprintf("%s:%s", name, value))
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
54
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/selinux_util_test.go
generated
vendored
Normal file
54
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/selinux_util_test.go
generated
vendored
Normal file
@ -0,0 +1,54 @@
|
||||
/*
|
||||
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 dockershim
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestModifySecurityOptions(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
config []string
|
||||
optName string
|
||||
optVal string
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
name: "Empty val",
|
||||
config: []string{"a:b", "c:d"},
|
||||
optName: "optA",
|
||||
optVal: "",
|
||||
expected: []string{"a:b", "c:d"},
|
||||
},
|
||||
{
|
||||
name: "Valid",
|
||||
config: []string{"a:b", "c:d"},
|
||||
optName: "e",
|
||||
optVal: "f",
|
||||
expected: []string{"a:b", "c:d", "e:f"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
actual := modifySecurityOption(tc.config, tc.optName, tc.optVal)
|
||||
if !reflect.DeepEqual(tc.expected, actual) {
|
||||
t.Errorf("Failed to apply options correctly for tc: %s. Expected: %v but got %v", tc.name, tc.expected, actual)
|
||||
}
|
||||
}
|
||||
}
|
25
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/testing/BUILD
generated
vendored
Normal file
25
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/testing/BUILD
generated
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["util.go"],
|
||||
importpath = "k8s.io/kubernetes/pkg/kubelet/dockershim/testing",
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
66
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/testing/util.go
generated
vendored
Normal file
66
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/testing/util.go
generated
vendored
Normal file
@ -0,0 +1,66 @@
|
||||
/*
|
||||
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 testing
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// MemStore is an implementation of CheckpointStore interface which stores checkpoint in memory.
|
||||
type MemStore struct {
|
||||
mem map[string][]byte
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
func NewMemStore() *MemStore {
|
||||
return &MemStore{mem: make(map[string][]byte)}
|
||||
}
|
||||
|
||||
func (mstore *MemStore) Write(key string, data []byte) error {
|
||||
mstore.Lock()
|
||||
defer mstore.Unlock()
|
||||
mstore.mem[key] = data
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mstore *MemStore) Read(key string) ([]byte, error) {
|
||||
mstore.Lock()
|
||||
defer mstore.Unlock()
|
||||
data, ok := mstore.mem[key]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("checkpoint is not found")
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (mstore *MemStore) Delete(key string) error {
|
||||
mstore.Lock()
|
||||
defer mstore.Unlock()
|
||||
delete(mstore.mem, key)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mstore *MemStore) List() ([]string, error) {
|
||||
mstore.Lock()
|
||||
defer mstore.Unlock()
|
||||
keys := make([]string, 0)
|
||||
for key := range mstore.mem {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
return keys, nil
|
||||
}
|
Reference in New Issue
Block a user