Switch to github.com/golang/dep for vendoring

Signed-off-by: Mrunal Patel <mrunalp@gmail.com>
This commit is contained in:
Mrunal Patel 2017-01-31 16:45:59 -08:00
parent d6ab91be27
commit 8e5b17cf13
15431 changed files with 3971413 additions and 8881 deletions

256
vendor/k8s.io/kubernetes/pkg/kubelet/BUILD generated vendored Normal file
View file

@ -0,0 +1,256 @@
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = [
"active_deadline.go",
"disk_manager.go",
"doc.go",
"kubelet.go",
"kubelet_cadvisor.go",
"kubelet_getters.go",
"kubelet_network.go",
"kubelet_node_status.go",
"kubelet_pods.go",
"kubelet_resources.go",
"kubelet_volumes.go",
"networks.go",
"oom_watcher.go",
"pod_container_deletor.go",
"pod_workers.go",
"reason_cache.go",
"runonce.go",
"runtime.go",
"util.go",
"volume_host.go",
],
tags = ["automanaged"],
deps = [
"//cmd/kubelet/app/options:go_default_library",
"//pkg/api:go_default_library",
"//pkg/api/resource:go_default_library",
"//pkg/api/v1:go_default_library",
"//pkg/api/v1/pod:go_default_library",
"//pkg/api/v1/validation:go_default_library",
"//pkg/apis/componentconfig:go_default_library",
"//pkg/apis/componentconfig/v1alpha1:go_default_library",
"//pkg/capabilities:go_default_library",
"//pkg/client/cache:go_default_library",
"//pkg/client/clientset_generated/clientset:go_default_library",
"//pkg/client/record:go_default_library",
"//pkg/cloudprovider:go_default_library",
"//pkg/fieldpath:go_default_library",
"//pkg/fields:go_default_library",
"//pkg/kubelet/api:go_default_library",
"//pkg/kubelet/cadvisor:go_default_library",
"//pkg/kubelet/cm:go_default_library",
"//pkg/kubelet/config:go_default_library",
"//pkg/kubelet/container:go_default_library",
"//pkg/kubelet/dockershim:go_default_library",
"//pkg/kubelet/dockershim/remote:go_default_library",
"//pkg/kubelet/dockertools:go_default_library",
"//pkg/kubelet/envvars:go_default_library",
"//pkg/kubelet/events:go_default_library",
"//pkg/kubelet/eviction:go_default_library",
"//pkg/kubelet/images:go_default_library",
"//pkg/kubelet/kuberuntime:go_default_library",
"//pkg/kubelet/lifecycle:go_default_library",
"//pkg/kubelet/metrics:go_default_library",
"//pkg/kubelet/network:go_default_library",
"//pkg/kubelet/pleg:go_default_library",
"//pkg/kubelet/pod:go_default_library",
"//pkg/kubelet/prober:go_default_library",
"//pkg/kubelet/prober/results:go_default_library",
"//pkg/kubelet/qos:go_default_library",
"//pkg/kubelet/remote:go_default_library",
"//pkg/kubelet/rkt:go_default_library",
"//pkg/kubelet/server:go_default_library",
"//pkg/kubelet/server/remotecommand:go_default_library",
"//pkg/kubelet/server/stats:go_default_library",
"//pkg/kubelet/server/streaming:go_default_library",
"//pkg/kubelet/status:go_default_library",
"//pkg/kubelet/sysctl:go_default_library",
"//pkg/kubelet/types:go_default_library",
"//pkg/kubelet/util/format:go_default_library",
"//pkg/kubelet/util/queue:go_default_library",
"//pkg/kubelet/util/sliceutils:go_default_library",
"//pkg/kubelet/volumemanager:go_default_library",
"//pkg/security/apparmor:go_default_library",
"//pkg/securitycontext:go_default_library",
"//pkg/util:go_default_library",
"//pkg/util/bandwidth:go_default_library",
"//pkg/util/clock:go_default_library",
"//pkg/util/config:go_default_library",
"//pkg/util/dbus:go_default_library",
"//pkg/util/exec:go_default_library",
"//pkg/util/flowcontrol:go_default_library",
"//pkg/util/integer:go_default_library",
"//pkg/util/io:go_default_library",
"//pkg/util/iptables:go_default_library",
"//pkg/util/mount:go_default_library",
"//pkg/util/node:go_default_library",
"//pkg/util/oom:go_default_library",
"//pkg/util/procfs:go_default_library",
"//pkg/util/term:go_default_library",
"//pkg/version:go_default_library",
"//pkg/volume:go_default_library",
"//pkg/volume/util:go_default_library",
"//pkg/volume/util/types:go_default_library",
"//pkg/volume/util/volumehelper:go_default_library",
"//plugin/pkg/scheduler/algorithm/predicates:go_default_library",
"//third_party/forked/golang/expansion:go_default_library",
"//vendor:github.com/golang/glog",
"//vendor:github.com/golang/groupcache/lru",
"//vendor:github.com/google/cadvisor/events",
"//vendor:github.com/google/cadvisor/info/v1",
"//vendor:github.com/google/cadvisor/info/v2",
"//vendor:k8s.io/apimachinery/pkg/api/errors",
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
"//vendor:k8s.io/apimachinery/pkg/conversion",
"//vendor:k8s.io/apimachinery/pkg/labels",
"//vendor:k8s.io/apimachinery/pkg/types",
"//vendor:k8s.io/apimachinery/pkg/util/errors",
"//vendor:k8s.io/apimachinery/pkg/util/net",
"//vendor:k8s.io/apimachinery/pkg/util/runtime",
"//vendor:k8s.io/apimachinery/pkg/util/sets",
"//vendor:k8s.io/apimachinery/pkg/util/validation",
"//vendor:k8s.io/apimachinery/pkg/util/validation/field",
"//vendor:k8s.io/apimachinery/pkg/util/wait",
"//vendor:k8s.io/client-go/kubernetes",
],
)
go_test(
name = "go_default_test",
srcs = [
"active_deadline_test.go",
"disk_manager_test.go",
"kubelet_cadvisor_test.go",
"kubelet_getters_test.go",
"kubelet_network_test.go",
"kubelet_node_status_test.go",
"kubelet_pods_test.go",
"kubelet_resources_test.go",
"kubelet_test.go",
"kubelet_volumes_test.go",
"oom_watcher_test.go",
"pod_container_deletor_test.go",
"pod_workers_test.go",
"reason_cache_test.go",
"runonce_test.go",
],
library = ":go_default_library",
tags = ["automanaged"],
deps = [
"//pkg/api:go_default_library",
"//pkg/api/resource:go_default_library",
"//pkg/api/v1:go_default_library",
"//pkg/apis/componentconfig:go_default_library",
"//pkg/capabilities:go_default_library",
"//pkg/client/clientset_generated/clientset/fake:go_default_library",
"//pkg/client/record:go_default_library",
"//pkg/client/testing/core:go_default_library",
"//pkg/kubelet/cadvisor/testing:go_default_library",
"//pkg/kubelet/cm:go_default_library",
"//pkg/kubelet/config:go_default_library",
"//pkg/kubelet/container:go_default_library",
"//pkg/kubelet/container/testing:go_default_library",
"//pkg/kubelet/eviction:go_default_library",
"//pkg/kubelet/images:go_default_library",
"//pkg/kubelet/lifecycle:go_default_library",
"//pkg/kubelet/network:go_default_library",
"//pkg/kubelet/network/testing:go_default_library",
"//pkg/kubelet/pleg:go_default_library",
"//pkg/kubelet/pod:go_default_library",
"//pkg/kubelet/pod/testing:go_default_library",
"//pkg/kubelet/prober/results:go_default_library",
"//pkg/kubelet/prober/testing:go_default_library",
"//pkg/kubelet/server/remotecommand:go_default_library",
"//pkg/kubelet/server/stats:go_default_library",
"//pkg/kubelet/status:go_default_library",
"//pkg/kubelet/types:go_default_library",
"//pkg/kubelet/util/queue:go_default_library",
"//pkg/kubelet/util/sliceutils:go_default_library",
"//pkg/kubelet/volumemanager:go_default_library",
"//pkg/util/bandwidth:go_default_library",
"//pkg/util/clock:go_default_library",
"//pkg/util/flowcontrol:go_default_library",
"//pkg/util/mount:go_default_library",
"//pkg/util/strategicpatch:go_default_library",
"//pkg/util/testing:go_default_library",
"//pkg/util/uuid:go_default_library",
"//pkg/version:go_default_library",
"//pkg/volume:go_default_library",
"//pkg/volume/host_path:go_default_library",
"//pkg/volume/testing:go_default_library",
"//pkg/volume/util/volumehelper:go_default_library",
"//vendor:github.com/google/cadvisor/info/v1",
"//vendor:github.com/google/cadvisor/info/v2",
"//vendor:github.com/stretchr/testify/assert",
"//vendor:github.com/stretchr/testify/require",
"//vendor:k8s.io/apimachinery/pkg/api/errors",
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
"//vendor:k8s.io/apimachinery/pkg/labels",
"//vendor:k8s.io/apimachinery/pkg/runtime",
"//vendor:k8s.io/apimachinery/pkg/types",
"//vendor:k8s.io/apimachinery/pkg/util/diff",
"//vendor:k8s.io/apimachinery/pkg/util/rand",
"//vendor:k8s.io/apimachinery/pkg/util/runtime",
"//vendor:k8s.io/apimachinery/pkg/util/sets",
"//vendor:k8s.io/apimachinery/pkg/util/wait",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//pkg/kubelet/api:all-srcs",
"//pkg/kubelet/cadvisor:all-srcs",
"//pkg/kubelet/client:all-srcs",
"//pkg/kubelet/cm:all-srcs",
"//pkg/kubelet/config:all-srcs",
"//pkg/kubelet/container:all-srcs",
"//pkg/kubelet/custommetrics:all-srcs",
"//pkg/kubelet/dockershim:all-srcs",
"//pkg/kubelet/dockertools:all-srcs",
"//pkg/kubelet/envvars:all-srcs",
"//pkg/kubelet/events:all-srcs",
"//pkg/kubelet/eviction:all-srcs",
"//pkg/kubelet/images:all-srcs",
"//pkg/kubelet/kuberuntime:all-srcs",
"//pkg/kubelet/leaky:all-srcs",
"//pkg/kubelet/lifecycle:all-srcs",
"//pkg/kubelet/metrics:all-srcs",
"//pkg/kubelet/network:all-srcs",
"//pkg/kubelet/pleg:all-srcs",
"//pkg/kubelet/pod:all-srcs",
"//pkg/kubelet/prober:all-srcs",
"//pkg/kubelet/qos:all-srcs",
"//pkg/kubelet/remote:all-srcs",
"//pkg/kubelet/rkt:all-srcs",
"//pkg/kubelet/rktshim:all-srcs",
"//pkg/kubelet/server:all-srcs",
"//pkg/kubelet/status:all-srcs",
"//pkg/kubelet/sysctl:all-srcs",
"//pkg/kubelet/types:all-srcs",
"//pkg/kubelet/util:all-srcs",
"//pkg/kubelet/volumemanager:all-srcs",
],
tags = ["automanaged"],
)

4
vendor/k8s.io/kubernetes/pkg/kubelet/OWNERS generated vendored Normal file
View file

@ -0,0 +1,4 @@
assignees:
- dchen1107
- vishh
- yujuhong

View file

@ -0,0 +1,98 @@
/*
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 kubelet
import (
"fmt"
"time"
"k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/client/record"
"k8s.io/kubernetes/pkg/kubelet/lifecycle"
"k8s.io/kubernetes/pkg/kubelet/status"
"k8s.io/kubernetes/pkg/util/clock"
)
const (
reason = "DeadlineExceeded"
message = "Pod was active on the node longer than the specified deadline"
)
// activeDeadlineHandler knows how to enforce active deadlines on pods.
type activeDeadlineHandler struct {
// the clock to use for deadline enforcement
clock clock.Clock
// the provider of pod status
podStatusProvider status.PodStatusProvider
// the recorder to dispatch events when we identify a pod has exceeded active deadline
recorder record.EventRecorder
}
// newActiveDeadlineHandler returns an active deadline handler that can enforce pod active deadline
func newActiveDeadlineHandler(
podStatusProvider status.PodStatusProvider,
recorder record.EventRecorder,
clock clock.Clock,
) (*activeDeadlineHandler, error) {
// check for all required fields
if clock == nil || podStatusProvider == nil || recorder == nil {
return nil, fmt.Errorf("Required arguments must not be nil: %v, %v, %v", clock, podStatusProvider, recorder)
}
return &activeDeadlineHandler{
clock: clock,
podStatusProvider: podStatusProvider,
recorder: recorder,
}, nil
}
// ShouldSync returns true if the pod is past its active deadline.
func (m *activeDeadlineHandler) ShouldSync(pod *v1.Pod) bool {
return m.pastActiveDeadline(pod)
}
// ShouldEvict returns true if the pod is past its active deadline.
// It dispatches an event that the pod should be evicted if it is past its deadline.
func (m *activeDeadlineHandler) ShouldEvict(pod *v1.Pod) lifecycle.ShouldEvictResponse {
if !m.pastActiveDeadline(pod) {
return lifecycle.ShouldEvictResponse{Evict: false}
}
m.recorder.Eventf(pod, v1.EventTypeNormal, reason, message)
return lifecycle.ShouldEvictResponse{Evict: true, Reason: reason, Message: message}
}
// pastActiveDeadline returns true if the pod has been active for more than its ActiveDeadlineSeconds
func (m *activeDeadlineHandler) pastActiveDeadline(pod *v1.Pod) bool {
// no active deadline was specified
if pod.Spec.ActiveDeadlineSeconds == nil {
return false
}
// get the latest status to determine if it was started
podStatus, ok := m.podStatusProvider.GetPodStatus(pod.UID)
if !ok {
podStatus = pod.Status
}
// we have no start time so just return
if podStatus.StartTime.IsZero() {
return false
}
// determine if the deadline was exceeded
start := podStatus.StartTime.Time
duration := m.clock.Since(start)
allowedDuration := time.Duration(*pod.Spec.ActiveDeadlineSeconds) * time.Second
return duration >= allowedDuration
}

View file

@ -0,0 +1,95 @@
/*
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 kubelet
import (
"testing"
"time"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/client/record"
"k8s.io/kubernetes/pkg/util/clock"
)
// mockPodStatusProvider returns the status on the specified pod
type mockPodStatusProvider struct {
pods []*v1.Pod
}
// GetPodStatus returns the status on the associated pod with matching uid (if found)
func (m *mockPodStatusProvider) GetPodStatus(uid types.UID) (v1.PodStatus, bool) {
for _, pod := range m.pods {
if pod.UID == uid {
return pod.Status, true
}
}
return v1.PodStatus{}, false
}
// TestActiveDeadlineHandler verifies the active deadline handler functions as expected.
func TestActiveDeadlineHandler(t *testing.T) {
pods := newTestPods(4)
fakeClock := clock.NewFakeClock(time.Now())
podStatusProvider := &mockPodStatusProvider{pods: pods}
fakeRecorder := &record.FakeRecorder{}
handler, err := newActiveDeadlineHandler(podStatusProvider, fakeRecorder, fakeClock)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
now := metav1.Now()
startTime := metav1.NewTime(now.Time.Add(-1 * time.Minute))
// this pod has exceeded its active deadline
exceededActiveDeadlineSeconds := int64(30)
pods[0].Status.StartTime = &startTime
pods[0].Spec.ActiveDeadlineSeconds = &exceededActiveDeadlineSeconds
// this pod has not exceeded its active deadline
notYetActiveDeadlineSeconds := int64(120)
pods[1].Status.StartTime = &startTime
pods[1].Spec.ActiveDeadlineSeconds = &notYetActiveDeadlineSeconds
// this pod has no deadline
pods[2].Status.StartTime = &startTime
pods[2].Spec.ActiveDeadlineSeconds = nil
testCases := []struct {
pod *v1.Pod
expected bool
}{{pods[0], true}, {pods[1], false}, {pods[2], false}, {pods[3], false}}
for i, testCase := range testCases {
if actual := handler.ShouldSync(testCase.pod); actual != testCase.expected {
t.Errorf("[%d] ShouldSync expected %#v, got %#v", i, testCase.expected, actual)
}
actual := handler.ShouldEvict(testCase.pod)
if actual.Evict != testCase.expected {
t.Errorf("[%d] ShouldEvict.Evict expected %#v, got %#v", i, testCase.expected, actual.Evict)
}
if testCase.expected {
if actual.Reason != reason {
t.Errorf("[%d] ShouldEvict.Reason expected %#v, got %#v", i, message, actual.Reason)
}
if actual.Message != message {
t.Errorf("[%d] ShouldEvict.Message expected %#v, got %#v", i, message, actual.Message)
}
}
}
}

33
vendor/k8s.io/kubernetes/pkg/kubelet/api/BUILD generated vendored Normal file
View file

@ -0,0 +1,33 @@
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["services.go"],
tags = ["automanaged"],
deps = ["//pkg/kubelet/api/v1alpha1/runtime:go_default_library"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//pkg/kubelet/api/testing:all-srcs",
"//pkg/kubelet/api/v1alpha1/runtime:all-srcs",
"//pkg/kubelet/api/v1alpha1/stats:all-srcs",
],
tags = ["automanaged"],
)

100
vendor/k8s.io/kubernetes/pkg/kubelet/api/services.go generated vendored Normal file
View file

@ -0,0 +1,100 @@
/*
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 api
import (
"time"
runtimeapi "k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime"
)
// RuntimeVersioner contains methods for runtime name, version and API version.
type RuntimeVersioner interface {
// Version returns the runtime name, runtime version and runtime API version
Version(apiVersion string) (*runtimeapi.VersionResponse, error)
}
// ContainerManager contains methods to manipulate containers managed by a
// container runtime. The methods are thread-safe.
type ContainerManager interface {
// CreateContainer creates a new container in specified PodSandbox.
CreateContainer(podSandboxID string, config *runtimeapi.ContainerConfig, sandboxConfig *runtimeapi.PodSandboxConfig) (string, error)
// StartContainer starts the container.
StartContainer(containerID string) error
// StopContainer stops a running container with a grace period (i.e., timeout).
StopContainer(containerID string, timeout int64) error
// RemoveContainer removes the container.
RemoveContainer(containerID string) error
// ListContainers lists all containers by filters.
ListContainers(filter *runtimeapi.ContainerFilter) ([]*runtimeapi.Container, error)
// ContainerStatus returns the status of the container.
ContainerStatus(containerID string) (*runtimeapi.ContainerStatus, error)
// 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.
ExecSync(containerID string, cmd []string, timeout time.Duration) (stdout []byte, stderr []byte, err error)
// Exec prepares a streaming endpoint to execute a command in the container, and returns the address.
Exec(*runtimeapi.ExecRequest) (*runtimeapi.ExecResponse, error)
// Attach prepares a streaming endpoint to attach to a running container, and returns the address.
Attach(req *runtimeapi.AttachRequest) (*runtimeapi.AttachResponse, error)
}
// PodSandboxManager contains methods for operating on PodSandboxes. The methods
// are thread-safe.
type PodSandboxManager interface {
// RunPodSandbox creates and starts a pod-level sandbox. Runtimes should ensure
// the sandbox is in ready state.
RunPodSandbox(config *runtimeapi.PodSandboxConfig) (string, error)
// StopPodSandbox stops the sandbox. If there are any running containers in the
// sandbox, they should be force terminated.
StopPodSandbox(podSandboxID string) error
// RemovePodSandbox removes the sandbox. If there are running containers in the
// sandbox, they should be forcibly removed.
RemovePodSandbox(podSandboxID string) error
// PodSandboxStatus returns the Status of the PodSandbox.
PodSandboxStatus(podSandboxID string) (*runtimeapi.PodSandboxStatus, error)
// ListPodSandbox returns a list of Sandbox.
ListPodSandbox(filter *runtimeapi.PodSandboxFilter) ([]*runtimeapi.PodSandbox, error)
// PortForward prepares a streaming endpoint to forward ports from a PodSandbox, and returns the address.
PortForward(*runtimeapi.PortForwardRequest) (*runtimeapi.PortForwardResponse, error)
}
// RuntimeService interface should be implemented by a container runtime.
// The methods should be thread-safe.
type RuntimeService interface {
RuntimeVersioner
ContainerManager
PodSandboxManager
// UpdateRuntimeConfig updates runtime configuration if specified
UpdateRuntimeConfig(runtimeConfig *runtimeapi.RuntimeConfig) error
// Status returns the status of the runtime.
Status() (*runtimeapi.RuntimeStatus, error)
}
// ImageManagerService interface should be implemented by a container image
// manager.
// The methods should be thread-safe.
type ImageManagerService interface {
// ListImages lists the existing images.
ListImages(filter *runtimeapi.ImageFilter) ([]*runtimeapi.Image, error)
// ImageStatus returns the status of the image.
ImageStatus(image *runtimeapi.ImageSpec) (*runtimeapi.Image, error)
// PullImage pulls an image with the authentication config.
PullImage(image *runtimeapi.ImageSpec, auth *runtimeapi.AuthConfig) (string, error)
// RemoveImage removes the image.
RemoveImage(image *runtimeapi.ImageSpec) error
}

35
vendor/k8s.io/kubernetes/pkg/kubelet/api/testing/BUILD generated vendored Normal file
View file

@ -0,0 +1,35 @@
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"fake_image_service.go",
"fake_runtime_service.go",
"utils.go",
],
tags = ["automanaged"],
deps = [
"//pkg/kubelet/api/v1alpha1/runtime:go_default_library",
"//pkg/kubelet/util/sliceutils:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View file

@ -0,0 +1,120 @@
/*
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 testing
import (
"sync"
runtimeapi "k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime"
"k8s.io/kubernetes/pkg/kubelet/util/sliceutils"
)
type FakeImageService struct {
sync.Mutex
FakeImageSize uint64
Called []string
Images map[string]*runtimeapi.Image
}
func (r *FakeImageService) SetFakeImages(images []string) {
r.Lock()
defer r.Unlock()
r.Images = make(map[string]*runtimeapi.Image)
for _, image := range images {
r.Images[image] = r.makeFakeImage(image)
}
}
func (r *FakeImageService) SetFakeImageSize(size uint64) {
r.Lock()
defer r.Unlock()
r.FakeImageSize = size
}
func NewFakeImageService() *FakeImageService {
return &FakeImageService{
Called: make([]string, 0),
Images: make(map[string]*runtimeapi.Image),
}
}
func (r *FakeImageService) makeFakeImage(image string) *runtimeapi.Image {
return &runtimeapi.Image{
Id: &image,
Size_: &r.FakeImageSize,
RepoTags: []string{image},
}
}
func (r *FakeImageService) ListImages(filter *runtimeapi.ImageFilter) ([]*runtimeapi.Image, error) {
r.Lock()
defer r.Unlock()
r.Called = append(r.Called, "ListImages")
images := make([]*runtimeapi.Image, 0)
for _, img := range r.Images {
if filter != nil && filter.Image != nil {
if !sliceutils.StringInSlice(filter.Image.GetImage(), img.RepoTags) {
continue
}
}
images = append(images, img)
}
return images, nil
}
func (r *FakeImageService) ImageStatus(image *runtimeapi.ImageSpec) (*runtimeapi.Image, error) {
r.Lock()
defer r.Unlock()
r.Called = append(r.Called, "ImageStatus")
return r.Images[image.GetImage()], nil
}
func (r *FakeImageService) PullImage(image *runtimeapi.ImageSpec, auth *runtimeapi.AuthConfig) (string, error) {
r.Lock()
defer r.Unlock()
r.Called = append(r.Called, "PullImage")
// ImageID should be randomized for real container runtime, but here just use
// image's name for easily making fake images.
imageID := image.GetImage()
if _, ok := r.Images[imageID]; !ok {
r.Images[imageID] = r.makeFakeImage(image.GetImage())
}
return imageID, nil
}
func (r *FakeImageService) RemoveImage(image *runtimeapi.ImageSpec) error {
r.Lock()
defer r.Unlock()
r.Called = append(r.Called, "RemoveImage")
// Remove the image
delete(r.Images, image.GetImage())
return nil
}

View file

@ -0,0 +1,395 @@
/*
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 testing
import (
"fmt"
"reflect"
"sync"
"time"
runtimeapi "k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime"
)
var (
version = "0.1.0"
FakeRuntimeName = "fakeRuntime"
FakePodSandboxIP = "192.168.192.168"
)
type FakePodSandbox struct {
// PodSandboxStatus contains the runtime information for a sandbox.
runtimeapi.PodSandboxStatus
}
type FakeContainer struct {
// ContainerStatus contains the runtime information for a container.
runtimeapi.ContainerStatus
// the sandbox id of this container
SandboxID string
}
type FakeRuntimeService struct {
sync.Mutex
Called []string
FakeStatus *runtimeapi.RuntimeStatus
Containers map[string]*FakeContainer
Sandboxes map[string]*FakePodSandbox
}
func (r *FakeRuntimeService) SetFakeSandboxes(sandboxes []*FakePodSandbox) {
r.Lock()
defer r.Unlock()
r.Sandboxes = make(map[string]*FakePodSandbox)
for _, sandbox := range sandboxes {
sandboxID := sandbox.GetId()
r.Sandboxes[sandboxID] = sandbox
}
}
func (r *FakeRuntimeService) SetFakeContainers(containers []*FakeContainer) {
r.Lock()
defer r.Unlock()
r.Containers = make(map[string]*FakeContainer)
for _, c := range containers {
containerID := c.GetId()
r.Containers[containerID] = c
}
}
func (r *FakeRuntimeService) AssertCalls(calls []string) error {
r.Lock()
defer r.Unlock()
if !reflect.DeepEqual(calls, r.Called) {
return fmt.Errorf("expected %#v, got %#v", calls, r.Called)
}
return nil
}
func NewFakeRuntimeService() *FakeRuntimeService {
return &FakeRuntimeService{
Called: make([]string, 0),
Containers: make(map[string]*FakeContainer),
Sandboxes: make(map[string]*FakePodSandbox),
}
}
func (r *FakeRuntimeService) Version(apiVersion string) (*runtimeapi.VersionResponse, error) {
r.Lock()
defer r.Unlock()
r.Called = append(r.Called, "Version")
return &runtimeapi.VersionResponse{
Version: &version,
RuntimeName: &FakeRuntimeName,
RuntimeVersion: &version,
RuntimeApiVersion: &version,
}, nil
}
func (r *FakeRuntimeService) Status() (*runtimeapi.RuntimeStatus, error) {
r.Lock()
defer r.Unlock()
r.Called = append(r.Called, "Status")
return r.FakeStatus, nil
}
func (r *FakeRuntimeService) RunPodSandbox(config *runtimeapi.PodSandboxConfig) (string, error) {
r.Lock()
defer r.Unlock()
r.Called = append(r.Called, "RunPodSandbox")
// PodSandboxID should be randomized for real container runtime, but here just use
// fixed name from BuildSandboxName() for easily making fake sandboxes.
podSandboxID := BuildSandboxName(config.Metadata)
createdAt := time.Now().Unix()
readyState := runtimeapi.PodSandboxState_SANDBOX_READY
r.Sandboxes[podSandboxID] = &FakePodSandbox{
PodSandboxStatus: runtimeapi.PodSandboxStatus{
Id: &podSandboxID,
Metadata: config.Metadata,
State: &readyState,
CreatedAt: &createdAt,
Network: &runtimeapi.PodSandboxNetworkStatus{
Ip: &FakePodSandboxIP,
},
Labels: config.Labels,
Annotations: config.Annotations,
},
}
return podSandboxID, nil
}
func (r *FakeRuntimeService) StopPodSandbox(podSandboxID string) error {
r.Lock()
defer r.Unlock()
r.Called = append(r.Called, "StopPodSandbox")
notReadyState := runtimeapi.PodSandboxState_SANDBOX_NOTREADY
if s, ok := r.Sandboxes[podSandboxID]; ok {
s.State = &notReadyState
} else {
return fmt.Errorf("pod sandbox %s not found", podSandboxID)
}
return nil
}
func (r *FakeRuntimeService) RemovePodSandbox(podSandboxID string) error {
r.Lock()
defer r.Unlock()
r.Called = append(r.Called, "RemovePodSandbox")
// Remove the pod sandbox
delete(r.Sandboxes, podSandboxID)
return nil
}
func (r *FakeRuntimeService) PodSandboxStatus(podSandboxID string) (*runtimeapi.PodSandboxStatus, error) {
r.Lock()
defer r.Unlock()
r.Called = append(r.Called, "PodSandboxStatus")
s, ok := r.Sandboxes[podSandboxID]
if !ok {
return nil, fmt.Errorf("pod sandbox %q not found", podSandboxID)
}
status := s.PodSandboxStatus
return &status, nil
}
func (r *FakeRuntimeService) ListPodSandbox(filter *runtimeapi.PodSandboxFilter) ([]*runtimeapi.PodSandbox, error) {
r.Lock()
defer r.Unlock()
r.Called = append(r.Called, "ListPodSandbox")
result := make([]*runtimeapi.PodSandbox, 0)
for id, s := range r.Sandboxes {
if filter != nil {
if filter.Id != nil && filter.GetId() != id {
continue
}
if filter.State != nil && filter.GetState() != s.GetState() {
continue
}
if filter.LabelSelector != nil && !filterInLabels(filter.LabelSelector, s.GetLabels()) {
continue
}
}
result = append(result, &runtimeapi.PodSandbox{
Id: s.Id,
Metadata: s.Metadata,
State: s.State,
CreatedAt: s.CreatedAt,
Labels: s.Labels,
Annotations: s.Annotations,
})
}
return result, nil
}
func (r *FakeRuntimeService) PortForward(*runtimeapi.PortForwardRequest) (*runtimeapi.PortForwardResponse, error) {
r.Lock()
defer r.Unlock()
r.Called = append(r.Called, "PortForward")
return &runtimeapi.PortForwardResponse{}, nil
}
func (r *FakeRuntimeService) CreateContainer(podSandboxID string, config *runtimeapi.ContainerConfig, sandboxConfig *runtimeapi.PodSandboxConfig) (string, error) {
r.Lock()
defer r.Unlock()
r.Called = append(r.Called, "CreateContainer")
// ContainerID should be randomized for real container runtime, but here just use
// fixed BuildContainerName() for easily making fake containers.
containerID := BuildContainerName(config.Metadata, podSandboxID)
createdAt := time.Now().Unix()
createdState := runtimeapi.ContainerState_CONTAINER_CREATED
imageRef := config.Image.GetImage()
r.Containers[containerID] = &FakeContainer{
ContainerStatus: runtimeapi.ContainerStatus{
Id: &containerID,
Metadata: config.Metadata,
Image: config.Image,
ImageRef: &imageRef,
CreatedAt: &createdAt,
State: &createdState,
Labels: config.Labels,
Annotations: config.Annotations,
},
SandboxID: podSandboxID,
}
return containerID, nil
}
func (r *FakeRuntimeService) StartContainer(containerID string) error {
r.Lock()
defer r.Unlock()
r.Called = append(r.Called, "StartContainer")
c, ok := r.Containers[containerID]
if !ok {
return fmt.Errorf("container %s not found", containerID)
}
// Set container to running.
startedAt := time.Now().Unix()
runningState := runtimeapi.ContainerState_CONTAINER_RUNNING
c.State = &runningState
c.StartedAt = &startedAt
return nil
}
func (r *FakeRuntimeService) StopContainer(containerID string, timeout int64) error {
r.Lock()
defer r.Unlock()
r.Called = append(r.Called, "StopContainer")
c, ok := r.Containers[containerID]
if !ok {
return fmt.Errorf("container %q not found", containerID)
}
// Set container to exited state.
finishedAt := time.Now().Unix()
exitedState := runtimeapi.ContainerState_CONTAINER_EXITED
c.State = &exitedState
c.FinishedAt = &finishedAt
return nil
}
func (r *FakeRuntimeService) RemoveContainer(containerID string) error {
r.Lock()
defer r.Unlock()
r.Called = append(r.Called, "RemoveContainer")
// Remove the container
delete(r.Containers, containerID)
return nil
}
func (r *FakeRuntimeService) ListContainers(filter *runtimeapi.ContainerFilter) ([]*runtimeapi.Container, error) {
r.Lock()
defer r.Unlock()
r.Called = append(r.Called, "ListContainers")
result := make([]*runtimeapi.Container, 0)
for _, s := range r.Containers {
if filter != nil {
if filter.Id != nil && filter.GetId() != s.GetId() {
continue
}
if filter.PodSandboxId != nil && filter.GetPodSandboxId() != s.SandboxID {
continue
}
if filter.State != nil && filter.GetState() != s.GetState() {
continue
}
if filter.LabelSelector != nil && !filterInLabels(filter.LabelSelector, s.GetLabels()) {
continue
}
}
result = append(result, &runtimeapi.Container{
Id: s.Id,
CreatedAt: s.CreatedAt,
PodSandboxId: &s.SandboxID,
Metadata: s.Metadata,
State: s.State,
Image: s.Image,
ImageRef: s.ImageRef,
Labels: s.Labels,
Annotations: s.Annotations,
})
}
return result, nil
}
func (r *FakeRuntimeService) ContainerStatus(containerID string) (*runtimeapi.ContainerStatus, error) {
r.Lock()
defer r.Unlock()
r.Called = append(r.Called, "ContainerStatus")
c, ok := r.Containers[containerID]
if !ok {
return nil, fmt.Errorf("container %q not found", containerID)
}
status := c.ContainerStatus
return &status, nil
}
func (r *FakeRuntimeService) ExecSync(containerID string, cmd []string, timeout time.Duration) (stdout []byte, stderr []byte, err error) {
r.Lock()
defer r.Unlock()
r.Called = append(r.Called, "ExecSync")
return nil, nil, nil
}
func (r *FakeRuntimeService) Exec(*runtimeapi.ExecRequest) (*runtimeapi.ExecResponse, error) {
r.Lock()
defer r.Unlock()
r.Called = append(r.Called, "Exec")
return &runtimeapi.ExecResponse{}, nil
}
func (r *FakeRuntimeService) Attach(req *runtimeapi.AttachRequest) (*runtimeapi.AttachResponse, error) {
r.Lock()
defer r.Unlock()
r.Called = append(r.Called, "Attach")
return &runtimeapi.AttachResponse{}, nil
}
func (r *FakeRuntimeService) UpdateRuntimeConfig(runtimeCOnfig *runtimeapi.RuntimeConfig) error {
return nil
}

View file

@ -0,0 +1,46 @@
/*
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 testing
import (
"fmt"
runtimeapi "k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime"
)
func BuildContainerName(metadata *runtimeapi.ContainerMetadata, sandboxID string) string {
// include the sandbox ID to make the container ID unique.
return fmt.Sprintf("%s_%s_%d", sandboxID, metadata.GetName(), metadata.GetAttempt())
}
func BuildSandboxName(metadata *runtimeapi.PodSandboxMetadata) string {
return fmt.Sprintf("%s_%s_%s_%d", metadata.GetName(), metadata.GetNamespace(), metadata.GetUid(), metadata.GetAttempt())
}
func filterInLabels(filter, labels map[string]string) bool {
for k, v := range filter {
if value, ok := labels[k]; ok {
if value != v {
return false
}
} else {
return false
}
}
return true
}

View file

@ -0,0 +1,28 @@
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["types.go"],
tags = ["automanaged"],
deps = ["//vendor:k8s.io/apimachinery/pkg/apis/meta/v1"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View file

@ -0,0 +1,255 @@
/*
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 stats
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// Summary is a top-level container for holding NodeStats and PodStats.
type Summary struct {
// Overall node stats.
Node NodeStats `json:"node"`
// Per-pod stats.
Pods []PodStats `json:"pods"`
}
// NodeStats holds node-level unprocessed sample stats.
type NodeStats struct {
// Reference to the measured Node.
NodeName string `json:"nodeName"`
// Stats of system daemons tracked as raw containers.
// The system containers are named according to the SystemContainer* constants.
// +optional
SystemContainers []ContainerStats `json:"systemContainers,omitempty" patchStrategy:"merge" patchMergeKey:"name"`
// The time at which data collection for the node-scoped (i.e. aggregate) stats was (re)started.
StartTime metav1.Time `json:"startTime"`
// Stats pertaining to CPU resources.
// +optional
CPU *CPUStats `json:"cpu,omitempty"`
// Stats pertaining to memory (RAM) resources.
// +optional
Memory *MemoryStats `json:"memory,omitempty"`
// Stats pertaining to network resources.
// +optional
Network *NetworkStats `json:"network,omitempty"`
// Stats pertaining to total usage of filesystem resources on the rootfs used by node k8s components.
// NodeFs.Used is the total bytes used on the filesystem.
// +optional
Fs *FsStats `json:"fs,omitempty"`
// Stats about the underlying container runtime.
// +optional
Runtime *RuntimeStats `json:"runtime,omitempty"`
}
// Stats pertaining to the underlying container runtime.
type RuntimeStats struct {
// Stats about the underlying filesystem where container images are stored.
// This filesystem could be the same as the primary (root) filesystem.
// Usage here refers to the total number of bytes occupied by images on the filesystem.
// +optional
ImageFs *FsStats `json:"imageFs,omitempty"`
}
const (
// Container name for the system container tracking Kubelet usage.
SystemContainerKubelet = "kubelet"
// Container name for the system container tracking the runtime (e.g. docker or rkt) usage.
SystemContainerRuntime = "runtime"
// Container name for the system container tracking non-kubernetes processes.
SystemContainerMisc = "misc"
)
// PodStats holds pod-level unprocessed sample stats.
type PodStats struct {
// Reference to the measured Pod.
PodRef PodReference `json:"podRef"`
// The time at which data collection for the pod-scoped (e.g. network) stats was (re)started.
StartTime metav1.Time `json:"startTime"`
// Stats of containers in the measured pod.
Containers []ContainerStats `json:"containers" patchStrategy:"merge" patchMergeKey:"name"`
// Stats pertaining to network resources.
// +optional
Network *NetworkStats `json:"network,omitempty"`
// Stats pertaining to volume usage of filesystem resources.
// VolumeStats.UsedBytes is the number of bytes used by the Volume
// +optional
VolumeStats []VolumeStats `json:"volume,omitempty" patchStrategy:"merge" patchMergeKey:"name"`
}
// ContainerStats holds container-level unprocessed sample stats.
type ContainerStats struct {
// Reference to the measured container.
Name string `json:"name"`
// The time at which data collection for this container was (re)started.
StartTime metav1.Time `json:"startTime"`
// Stats pertaining to CPU resources.
// +optional
CPU *CPUStats `json:"cpu,omitempty"`
// Stats pertaining to memory (RAM) resources.
// +optional
Memory *MemoryStats `json:"memory,omitempty"`
// Stats pertaining to container rootfs usage of filesystem resources.
// Rootfs.UsedBytes is the number of bytes used for the container write layer.
// +optional
Rootfs *FsStats `json:"rootfs,omitempty"`
// Stats pertaining to container logs usage of filesystem resources.
// Logs.UsedBytes is the number of bytes used for the container logs.
// +optional
Logs *FsStats `json:"logs,omitempty"`
// User defined metrics that are exposed by containers in the pod. Typically, we expect only one container in the pod to be exposing user defined metrics. In the event of multiple containers exposing metrics, they will be combined here.
UserDefinedMetrics []UserDefinedMetric `json:"userDefinedMetrics,omitmepty" patchStrategy:"merge" patchMergeKey:"name"`
}
// PodReference contains enough information to locate the referenced pod.
type PodReference struct {
Name string `json:"name"`
Namespace string `json:"namespace"`
UID string `json:"uid"`
}
// NetworkStats contains data about network resources.
type NetworkStats struct {
// The time at which these stats were updated.
Time metav1.Time `json:"time"`
// Cumulative count of bytes received.
// +optional
RxBytes *uint64 `json:"rxBytes,omitempty"`
// Cumulative count of receive errors encountered.
// +optional
RxErrors *uint64 `json:"rxErrors,omitempty"`
// Cumulative count of bytes transmitted.
// +optional
TxBytes *uint64 `json:"txBytes,omitempty"`
// Cumulative count of transmit errors encountered.
// +optional
TxErrors *uint64 `json:"txErrors,omitempty"`
}
// CPUStats contains data about CPU usage.
type CPUStats struct {
// The time at which these stats were updated.
Time metav1.Time `json:"time"`
// Total CPU usage (sum of all cores) averaged over the sample window.
// The "core" unit can be interpreted as CPU core-nanoseconds per second.
// +optional
UsageNanoCores *uint64 `json:"usageNanoCores,omitempty"`
// Cumulative CPU usage (sum of all cores) since object creation.
// +optional
UsageCoreNanoSeconds *uint64 `json:"usageCoreNanoSeconds,omitempty"`
}
// MemoryStats contains data about memory usage.
type MemoryStats struct {
// The time at which these stats were updated.
Time metav1.Time `json:"time"`
// Available memory for use. This is defined as the memory limit - workingSetBytes.
// If memory limit is undefined, the available bytes is omitted.
// +optional
AvailableBytes *uint64 `json:"availableBytes,omitempty"`
// Total memory in use. This includes all memory regardless of when it was accessed.
// +optional
UsageBytes *uint64 `json:"usageBytes,omitempty"`
// The amount of working set memory. This includes recently accessed memory,
// dirty memory, and kernel memory. WorkingSetBytes is <= UsageBytes
// +optional
WorkingSetBytes *uint64 `json:"workingSetBytes,omitempty"`
// The amount of anonymous and swap cache memory (includes transparent
// hugepages).
// +optional
RSSBytes *uint64 `json:"rssBytes,omitempty"`
// Cumulative number of minor page faults.
// +optional
PageFaults *uint64 `json:"pageFaults,omitempty"`
// Cumulative number of major page faults.
// +optional
MajorPageFaults *uint64 `json:"majorPageFaults,omitempty"`
}
// VolumeStats contains data about Volume filesystem usage.
type VolumeStats struct {
// Embedded FsStats
FsStats
// Name is the name given to the Volume
// +optional
Name string `json:"name,omitempty"`
}
// FsStats contains data about filesystem usage.
type FsStats struct {
// AvailableBytes represents the storage space available (bytes) for the filesystem.
// +optional
AvailableBytes *uint64 `json:"availableBytes,omitempty"`
// CapacityBytes represents the total capacity (bytes) of the filesystems underlying storage.
// +optional
CapacityBytes *uint64 `json:"capacityBytes,omitempty"`
// UsedBytes represents the bytes used for a specific task on the filesystem.
// This may differ from the total bytes used on the filesystem and may not equal CapacityBytes - AvailableBytes.
// e.g. For ContainerStats.Rootfs this is the bytes used by the container rootfs on the filesystem.
// +optional
UsedBytes *uint64 `json:"usedBytes,omitempty"`
// InodesFree represents the free inodes in the filesystem.
// +optional
InodesFree *uint64 `json:"inodesFree,omitempty"`
// Inodes represents the total inodes in the filesystem.
// +optional
Inodes *uint64 `json:"inodes,omitempty"`
// InodesUsed represents the inodes used by the filesystem
// This may not equal Inodes - InodesFree because this filesystem may share inodes with other "filesystems"
// e.g. For ContainerStats.Rootfs, this is the inodes used only by that container, and does not count inodes used by other containers.
InodesUsed *uint64 `json:"inodesUsed,omitempty"`
}
// UserDefinedMetricType defines how the metric should be interpreted by the user.
type UserDefinedMetricType string
const (
// Instantaneous value. May increase or decrease.
MetricGauge UserDefinedMetricType = "gauge"
// A counter-like value that is only expected to increase.
MetricCumulative UserDefinedMetricType = "cumulative"
// Rate over a time period.
MetricDelta UserDefinedMetricType = "delta"
)
// UserDefinedMetricDescriptor contains metadata that describes a user defined metric.
type UserDefinedMetricDescriptor struct {
// The name of the metric.
Name string `json:"name"`
// Type of the metric.
Type UserDefinedMetricType `json:"type"`
// Display Units for the stats.
Units string `json:"units"`
// Metadata labels associated with this metric.
// +optional
Labels map[string]string `json:"labels,omitempty"`
}
// UserDefinedMetric represents a metric defined and generate by users.
type UserDefinedMetric struct {
UserDefinedMetricDescriptor `json:",inline"`
// The time at which these stats were updated.
Time metav1.Time `json:"time"`
// Value of the metric. Float64s have 53 bit precision.
// We do not foresee any metrics exceeding that value.
Value float64 `json:"value"`
}

65
vendor/k8s.io/kubernetes/pkg/kubelet/cadvisor/BUILD generated vendored Normal file
View file

@ -0,0 +1,65 @@
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = [
"cadvisor_linux.go",
"doc.go",
"types.go",
"util.go",
],
tags = ["automanaged"],
deps = [
"//pkg/api/resource:go_default_library",
"//pkg/api/v1:go_default_library",
"//pkg/kubelet/types:go_default_library",
"//vendor:github.com/golang/glog",
"//vendor:github.com/google/cadvisor/cache/memory",
"//vendor:github.com/google/cadvisor/container",
"//vendor:github.com/google/cadvisor/events",
"//vendor:github.com/google/cadvisor/fs",
"//vendor:github.com/google/cadvisor/http",
"//vendor:github.com/google/cadvisor/info/v1",
"//vendor:github.com/google/cadvisor/info/v2",
"//vendor:github.com/google/cadvisor/manager",
"//vendor:github.com/google/cadvisor/metrics",
"//vendor:github.com/google/cadvisor/utils/sysfs",
"//vendor:k8s.io/apimachinery/pkg/util/runtime",
],
)
go_test(
name = "go_default_test",
srcs = ["cadvisor_linux_test.go"],
library = ":go_default_library",
tags = ["automanaged"],
deps = [
"//pkg/kubelet/types:go_default_library",
"//vendor:github.com/google/cadvisor/info/v1",
"//vendor:github.com/google/cadvisor/metrics",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//pkg/kubelet/cadvisor/testing:all-srcs",
],
tags = ["automanaged"],
)

View file

@ -0,0 +1,228 @@
// +build cgo,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 cadvisor
import (
"flag"
"fmt"
"net/http"
"time"
"github.com/golang/glog"
"github.com/google/cadvisor/cache/memory"
cadvisormetrics "github.com/google/cadvisor/container"
"github.com/google/cadvisor/events"
cadvisorfs "github.com/google/cadvisor/fs"
cadvisorhttp "github.com/google/cadvisor/http"
cadvisorapi "github.com/google/cadvisor/info/v1"
cadvisorapiv2 "github.com/google/cadvisor/info/v2"
"github.com/google/cadvisor/manager"
"github.com/google/cadvisor/metrics"
"github.com/google/cadvisor/utils/sysfs"
"k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/kubernetes/pkg/kubelet/types"
)
type cadvisorClient struct {
runtime string
rootPath string
manager.Manager
}
var _ Interface = new(cadvisorClient)
// TODO(vmarmol): Make configurable.
// The amount of time for which to keep stats in memory.
const statsCacheDuration = 2 * time.Minute
const maxHousekeepingInterval = 15 * time.Second
const defaultHousekeepingInterval = 10 * time.Second
const allowDynamicHousekeeping = true
func init() {
// Override cAdvisor flag defaults.
flagOverrides := map[string]string{
// Override the default cAdvisor housekeeping interval.
"housekeeping_interval": defaultHousekeepingInterval.String(),
// Disable event storage by default.
"event_storage_event_limit": "default=0",
"event_storage_age_limit": "default=0",
}
for name, defaultValue := range flagOverrides {
if f := flag.Lookup(name); f != nil {
f.DefValue = defaultValue
f.Value.Set(defaultValue)
} else {
glog.Errorf("Expected cAdvisor flag %q not found", name)
}
}
}
func containerLabels(c *cadvisorapi.ContainerInfo) map[string]string {
set := map[string]string{metrics.LabelID: c.Name}
if len(c.Aliases) > 0 {
set[metrics.LabelName] = c.Aliases[0]
}
if image := c.Spec.Image; len(image) > 0 {
set[metrics.LabelImage] = image
}
if v, ok := c.Spec.Labels[types.KubernetesPodNameLabel]; ok {
set["pod_name"] = v
}
if v, ok := c.Spec.Labels[types.KubernetesPodNamespaceLabel]; ok {
set["namespace"] = v
}
if v, ok := c.Spec.Labels[types.KubernetesContainerNameLabel]; ok {
set["container_name"] = v
}
return set
}
// New creates a cAdvisor and exports its API on the specified port if port > 0.
func New(port uint, runtime string, rootPath string) (Interface, error) {
sysFs, err := sysfs.NewRealSysFs()
if err != nil {
return nil, err
}
// Create and start the cAdvisor container manager.
m, err := manager.New(memory.New(statsCacheDuration, nil), sysFs, maxHousekeepingInterval, allowDynamicHousekeeping, cadvisormetrics.MetricSet{cadvisormetrics.NetworkTcpUsageMetrics: struct{}{}}, http.DefaultClient)
if err != nil {
return nil, err
}
cadvisorClient := &cadvisorClient{
runtime: runtime,
rootPath: rootPath,
Manager: m,
}
err = cadvisorClient.exportHTTP(port)
if err != nil {
return nil, err
}
return cadvisorClient, nil
}
func (cc *cadvisorClient) Start() error {
return cc.Manager.Start()
}
func (cc *cadvisorClient) exportHTTP(port uint) error {
// Register the handlers regardless as this registers the prometheus
// collector properly.
mux := http.NewServeMux()
err := cadvisorhttp.RegisterHandlers(mux, cc, "", "", "", "")
if err != nil {
return err
}
cadvisorhttp.RegisterPrometheusHandler(mux, cc, "/metrics", containerLabels)
// Only start the http server if port > 0
if port > 0 {
serv := &http.Server{
Addr: fmt.Sprintf(":%d", port),
Handler: mux,
}
// TODO(vmarmol): Remove this when the cAdvisor port is once again free.
// If export failed, retry in the background until we are able to bind.
// This allows an existing cAdvisor to be killed before this one registers.
go func() {
defer runtime.HandleCrash()
err := serv.ListenAndServe()
for err != nil {
glog.Infof("Failed to register cAdvisor on port %d, retrying. Error: %v", port, err)
time.Sleep(time.Minute)
err = serv.ListenAndServe()
}
}()
}
return nil
}
func (cc *cadvisorClient) ContainerInfo(name string, req *cadvisorapi.ContainerInfoRequest) (*cadvisorapi.ContainerInfo, error) {
return cc.GetContainerInfo(name, req)
}
func (cc *cadvisorClient) ContainerInfoV2(name string, options cadvisorapiv2.RequestOptions) (map[string]cadvisorapiv2.ContainerInfo, error) {
return cc.GetContainerInfoV2(name, options)
}
func (cc *cadvisorClient) VersionInfo() (*cadvisorapi.VersionInfo, error) {
return cc.GetVersionInfo()
}
func (cc *cadvisorClient) SubcontainerInfo(name string, req *cadvisorapi.ContainerInfoRequest) (map[string]*cadvisorapi.ContainerInfo, error) {
infos, err := cc.SubcontainersInfo(name, req)
if err != nil && len(infos) == 0 {
return nil, err
}
result := make(map[string]*cadvisorapi.ContainerInfo, len(infos))
for _, info := range infos {
result[info.Name] = info
}
return result, err
}
func (cc *cadvisorClient) MachineInfo() (*cadvisorapi.MachineInfo, error) {
return cc.GetMachineInfo()
}
func (cc *cadvisorClient) ImagesFsInfo() (cadvisorapiv2.FsInfo, error) {
var label string
switch cc.runtime {
case "docker":
label = cadvisorfs.LabelDockerImages
case "rkt":
label = cadvisorfs.LabelRktImages
default:
return cadvisorapiv2.FsInfo{}, fmt.Errorf("ImagesFsInfo: unknown runtime: %v", cc.runtime)
}
return cc.getFsInfo(label)
}
func (cc *cadvisorClient) RootFsInfo() (cadvisorapiv2.FsInfo, error) {
return cc.GetDirFsInfo(cc.rootPath)
}
func (cc *cadvisorClient) getFsInfo(label string) (cadvisorapiv2.FsInfo, error) {
res, err := cc.GetFsInfo(label)
if err != nil {
return cadvisorapiv2.FsInfo{}, err
}
if len(res) == 0 {
return cadvisorapiv2.FsInfo{}, fmt.Errorf("failed to find information for the filesystem labeled %q", label)
}
// TODO(vmarmol): Handle this better when a label has more than one image filesystem.
if len(res) > 1 {
glog.Warningf("More than one filesystem labeled %q: %#v. Only using the first one", label, res)
}
return res[0], nil
}
func (cc *cadvisorClient) WatchEvents(request *events.Request) (*events.EventChannel, error) {
return cc.WatchForEvents(request)
}

View file

@ -0,0 +1,65 @@
// +build cgo,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 cadvisor
import (
"reflect"
"testing"
info "github.com/google/cadvisor/info/v1"
"github.com/google/cadvisor/metrics"
"k8s.io/kubernetes/pkg/kubelet/types"
)
func TestContainerLabels(t *testing.T) {
container := &info.ContainerInfo{
ContainerReference: info.ContainerReference{
Name: "/docker/f81ad5335d390944e454ea19ab0924037d57337c19731524ad96eb26e74b6c6d",
Aliases: []string{"k8s_POD.639b2af2_foo-web-315473031-e40e2_foobar_a369ace2-5fa9-11e6-b10f-c81f66e5e84d_851a97fd"},
},
Spec: info.ContainerSpec{
Image: "qux/foo:latest",
Labels: map[string]string{
"io.kubernetes.container.hash": "639b2af2",
types.KubernetesContainerNameLabel: "POD",
"io.kubernetes.container.restartCount": "0",
"io.kubernetes.container.terminationMessagePath": "",
types.KubernetesPodNameLabel: "foo-web-315473031-e40e2",
types.KubernetesPodNamespaceLabel: "foobar",
"io.kubernetes.pod.terminationGracePeriod": "30",
types.KubernetesPodUIDLabel: "a369ace2-5fa9-11e6-b10f-c81f66e5e84d",
},
Envs: map[string]string{
"foo+env": "prod",
},
},
}
want := map[string]string{
metrics.LabelID: "/docker/f81ad5335d390944e454ea19ab0924037d57337c19731524ad96eb26e74b6c6d",
metrics.LabelName: "k8s_POD.639b2af2_foo-web-315473031-e40e2_foobar_a369ace2-5fa9-11e6-b10f-c81f66e5e84d_851a97fd",
metrics.LabelImage: "qux/foo:latest",
"namespace": "foobar",
"container_name": "POD",
"pod_name": "foo-web-315473031-e40e2",
}
if have := containerLabels(container); !reflect.DeepEqual(want, have) {
t.Errorf("want %v, have %v", want, have)
}
}

View file

@ -0,0 +1,78 @@
// +build !linux,!windows linux,!cgo
/*
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 cadvisor
import (
"errors"
"github.com/google/cadvisor/events"
cadvisorapi "github.com/google/cadvisor/info/v1"
cadvisorapiv2 "github.com/google/cadvisor/info/v2"
)
type cadvisorUnsupported struct {
}
var _ Interface = new(cadvisorUnsupported)
func New(port uint, runtime string, rootPath string) (Interface, error) {
return &cadvisorUnsupported{}, nil
}
var unsupportedErr = errors.New("cAdvisor is unsupported in this build")
func (cu *cadvisorUnsupported) Start() error {
return unsupportedErr
}
func (cu *cadvisorUnsupported) DockerContainer(name string, req *cadvisorapi.ContainerInfoRequest) (cadvisorapi.ContainerInfo, error) {
return cadvisorapi.ContainerInfo{}, unsupportedErr
}
func (cu *cadvisorUnsupported) ContainerInfo(name string, req *cadvisorapi.ContainerInfoRequest) (*cadvisorapi.ContainerInfo, error) {
return nil, unsupportedErr
}
func (cu *cadvisorUnsupported) ContainerInfoV2(name string, options cadvisorapiv2.RequestOptions) (map[string]cadvisorapiv2.ContainerInfo, error) {
return nil, unsupportedErr
}
func (cu *cadvisorUnsupported) SubcontainerInfo(name string, req *cadvisorapi.ContainerInfoRequest) (map[string]*cadvisorapi.ContainerInfo, error) {
return nil, unsupportedErr
}
func (cu *cadvisorUnsupported) MachineInfo() (*cadvisorapi.MachineInfo, error) {
return nil, unsupportedErr
}
func (cu *cadvisorUnsupported) VersionInfo() (*cadvisorapi.VersionInfo, error) {
return nil, unsupportedErr
}
func (cu *cadvisorUnsupported) ImagesFsInfo() (cadvisorapiv2.FsInfo, error) {
return cadvisorapiv2.FsInfo{}, unsupportedErr
}
func (cu *cadvisorUnsupported) RootFsInfo() (cadvisorapiv2.FsInfo, error) {
return cadvisorapiv2.FsInfo{}, unsupportedErr
}
func (cu *cadvisorUnsupported) WatchEvents(request *events.Request) (*events.EventChannel, error) {
return nil, unsupportedErr
}

View file

@ -0,0 +1,75 @@
// +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 cadvisor
import (
"github.com/google/cadvisor/events"
cadvisorapi "github.com/google/cadvisor/info/v1"
cadvisorapiv2 "github.com/google/cadvisor/info/v2"
)
type cadvisorClient struct {
}
var _ Interface = new(cadvisorClient)
// New creates a cAdvisor and exports its API on the specified port if port > 0.
func New(port uint, runtime string, rootPath string) (Interface, error) {
return &cadvisorClient{}, nil
}
func (cu *cadvisorClient) Start() error {
return nil
}
func (cu *cadvisorClient) DockerContainer(name string, req *cadvisorapi.ContainerInfoRequest) (cadvisorapi.ContainerInfo, error) {
return cadvisorapi.ContainerInfo{}, nil
}
func (cu *cadvisorClient) ContainerInfo(name string, req *cadvisorapi.ContainerInfoRequest) (*cadvisorapi.ContainerInfo, error) {
return &cadvisorapi.ContainerInfo{}, nil
}
func (cu *cadvisorClient) ContainerInfoV2(name string, options cadvisorapiv2.RequestOptions) (map[string]cadvisorapiv2.ContainerInfo, error) {
return make(map[string]cadvisorapiv2.ContainerInfo), nil
}
func (cu *cadvisorClient) SubcontainerInfo(name string, req *cadvisorapi.ContainerInfoRequest) (map[string]*cadvisorapi.ContainerInfo, error) {
return nil, nil
}
func (cu *cadvisorClient) MachineInfo() (*cadvisorapi.MachineInfo, error) {
return &cadvisorapi.MachineInfo{}, nil
}
func (cu *cadvisorClient) VersionInfo() (*cadvisorapi.VersionInfo, error) {
return &cadvisorapi.VersionInfo{}, nil
}
func (cu *cadvisorClient) ImagesFsInfo() (cadvisorapiv2.FsInfo, error) {
return cadvisorapiv2.FsInfo{}, nil
}
func (cu *cadvisorClient) RootFsInfo() (cadvisorapiv2.FsInfo, error) {
return cadvisorapiv2.FsInfo{}, nil
}
func (cu *cadvisorClient) WatchEvents(request *events.Request) (*events.EventChannel, error) {
return &events.EventChannel{}, nil
}

18
vendor/k8s.io/kubernetes/pkg/kubelet/cadvisor/doc.go generated vendored Normal file
View file

@ -0,0 +1,18 @@
/*
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.
*/
// Kubelet interactions with cAdvisor.
package cadvisor // import "k8s.io/kubernetes/pkg/kubelet/cadvisor"

View file

@ -0,0 +1,37 @@
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"cadvisor_fake.go",
"cadvisor_mock.go",
],
tags = ["automanaged"],
deps = [
"//pkg/kubelet/cadvisor:go_default_library",
"//vendor:github.com/google/cadvisor/events",
"//vendor:github.com/google/cadvisor/info/v1",
"//vendor:github.com/google/cadvisor/info/v2",
"//vendor:github.com/stretchr/testify/mock",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View file

@ -0,0 +1,75 @@
/*
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 testing
import (
"github.com/google/cadvisor/events"
cadvisorapi "github.com/google/cadvisor/info/v1"
cadvisorapiv2 "github.com/google/cadvisor/info/v2"
"k8s.io/kubernetes/pkg/kubelet/cadvisor"
)
// Fake cAdvisor implementation.
type Fake struct {
}
var _ cadvisor.Interface = new(Fake)
func (c *Fake) Start() error {
return nil
}
func (c *Fake) ContainerInfo(name string, req *cadvisorapi.ContainerInfoRequest) (*cadvisorapi.ContainerInfo, error) {
return new(cadvisorapi.ContainerInfo), nil
}
func (c *Fake) ContainerInfoV2(name string, options cadvisorapiv2.RequestOptions) (map[string]cadvisorapiv2.ContainerInfo, error) {
return map[string]cadvisorapiv2.ContainerInfo{}, nil
}
func (c *Fake) SubcontainerInfo(name string, req *cadvisorapi.ContainerInfoRequest) (map[string]*cadvisorapi.ContainerInfo, error) {
return map[string]*cadvisorapi.ContainerInfo{}, nil
}
func (c *Fake) DockerContainer(name string, req *cadvisorapi.ContainerInfoRequest) (cadvisorapi.ContainerInfo, error) {
return cadvisorapi.ContainerInfo{}, nil
}
func (c *Fake) MachineInfo() (*cadvisorapi.MachineInfo, error) {
// Simulate a matchin with 1 core and 3.75GB of memory.
// We set it to non-zero values to make non-zero-capacity machines in Kubemark.
return &cadvisorapi.MachineInfo{
NumCores: 1,
MemoryCapacity: 4026531840,
}, nil
}
func (c *Fake) VersionInfo() (*cadvisorapi.VersionInfo, error) {
return new(cadvisorapi.VersionInfo), nil
}
func (c *Fake) ImagesFsInfo() (cadvisorapiv2.FsInfo, error) {
return cadvisorapiv2.FsInfo{}, nil
}
func (c *Fake) RootFsInfo() (cadvisorapiv2.FsInfo, error) {
return cadvisorapiv2.FsInfo{}, nil
}
func (c *Fake) WatchEvents(request *events.Request) (*events.EventChannel, error) {
return new(events.EventChannel), nil
}

View file

@ -0,0 +1,85 @@
/*
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 testing
import (
"github.com/google/cadvisor/events"
cadvisorapi "github.com/google/cadvisor/info/v1"
cadvisorapiv2 "github.com/google/cadvisor/info/v2"
"github.com/stretchr/testify/mock"
"k8s.io/kubernetes/pkg/kubelet/cadvisor"
)
type Mock struct {
mock.Mock
}
var _ cadvisor.Interface = new(Mock)
func (c *Mock) Start() error {
args := c.Called()
return args.Error(0)
}
// ContainerInfo is a mock implementation of Interface.ContainerInfo.
func (c *Mock) ContainerInfo(name string, req *cadvisorapi.ContainerInfoRequest) (*cadvisorapi.ContainerInfo, error) {
args := c.Called(name, req)
return args.Get(0).(*cadvisorapi.ContainerInfo), args.Error(1)
}
// ContainerInfoV2 is a mock implementation of Interface.ContainerInfoV2.
func (c *Mock) ContainerInfoV2(name string, options cadvisorapiv2.RequestOptions) (map[string]cadvisorapiv2.ContainerInfo, error) {
args := c.Called(name, options)
return args.Get(0).(map[string]cadvisorapiv2.ContainerInfo), args.Error(1)
}
func (c *Mock) SubcontainerInfo(name string, req *cadvisorapi.ContainerInfoRequest) (map[string]*cadvisorapi.ContainerInfo, error) {
args := c.Called(name, req)
return args.Get(0).(map[string]*cadvisorapi.ContainerInfo), args.Error(1)
}
// DockerContainer is a mock implementation of Interface.DockerContainer.
func (c *Mock) DockerContainer(name string, req *cadvisorapi.ContainerInfoRequest) (cadvisorapi.ContainerInfo, error) {
args := c.Called(name, req)
return args.Get(0).(cadvisorapi.ContainerInfo), args.Error(1)
}
// MachineInfo is a mock implementation of Interface.MachineInfo.
func (c *Mock) MachineInfo() (*cadvisorapi.MachineInfo, error) {
args := c.Called()
return args.Get(0).(*cadvisorapi.MachineInfo), args.Error(1)
}
func (c *Mock) VersionInfo() (*cadvisorapi.VersionInfo, error) {
args := c.Called()
return args.Get(0).(*cadvisorapi.VersionInfo), args.Error(1)
}
func (c *Mock) ImagesFsInfo() (cadvisorapiv2.FsInfo, error) {
args := c.Called()
return args.Get(0).(cadvisorapiv2.FsInfo), args.Error(1)
}
func (c *Mock) RootFsInfo() (cadvisorapiv2.FsInfo, error) {
args := c.Called()
return args.Get(0).(cadvisorapiv2.FsInfo), args.Error(1)
}
func (c *Mock) WatchEvents(request *events.Request) (*events.EventChannel, error) {
args := c.Called()
return args.Get(0).(*events.EventChannel), args.Error(1)
}

44
vendor/k8s.io/kubernetes/pkg/kubelet/cadvisor/types.go generated vendored Normal file
View file

@ -0,0 +1,44 @@
/*
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 cadvisor
import (
"github.com/google/cadvisor/events"
cadvisorapi "github.com/google/cadvisor/info/v1"
cadvisorapiv2 "github.com/google/cadvisor/info/v2"
)
// Interface is an abstract interface for testability. It abstracts the interface to cAdvisor.
type Interface interface {
Start() error
DockerContainer(name string, req *cadvisorapi.ContainerInfoRequest) (cadvisorapi.ContainerInfo, error)
ContainerInfo(name string, req *cadvisorapi.ContainerInfoRequest) (*cadvisorapi.ContainerInfo, error)
ContainerInfoV2(name string, options cadvisorapiv2.RequestOptions) (map[string]cadvisorapiv2.ContainerInfo, error)
SubcontainerInfo(name string, req *cadvisorapi.ContainerInfoRequest) (map[string]*cadvisorapi.ContainerInfo, error)
MachineInfo() (*cadvisorapi.MachineInfo, error)
VersionInfo() (*cadvisorapi.VersionInfo, error)
// Returns usage information about the filesystem holding Docker images.
ImagesFsInfo() (cadvisorapiv2.FsInfo, error)
// Returns usage information about the root filesystem.
RootFsInfo() (cadvisorapiv2.FsInfo, error)
// Get events streamed through passedChannel that fit the request.
WatchEvents(request *events.Request) (*events.EventChannel, error)
}

35
vendor/k8s.io/kubernetes/pkg/kubelet/cadvisor/util.go generated vendored Normal file
View file

@ -0,0 +1,35 @@
/*
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 cadvisor
import (
cadvisorapi "github.com/google/cadvisor/info/v1"
"k8s.io/kubernetes/pkg/api/resource"
"k8s.io/kubernetes/pkg/api/v1"
)
func CapacityFromMachineInfo(info *cadvisorapi.MachineInfo) v1.ResourceList {
c := v1.ResourceList{
v1.ResourceCPU: *resource.NewMilliQuantity(
int64(info.NumCores*1000),
resource.DecimalSI),
v1.ResourceMemory: *resource.NewQuantity(
int64(info.MemoryCapacity),
resource.BinarySI),
}
return c
}

51
vendor/k8s.io/kubernetes/pkg/kubelet/client/BUILD generated vendored Normal file
View file

@ -0,0 +1,51 @@
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = ["kubelet_client.go"],
tags = ["automanaged"],
deps = [
"//pkg/api/v1:go_default_library",
"//pkg/client/restclient:go_default_library",
"//pkg/client/transport:go_default_library",
"//pkg/util/node:go_default_library",
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
"//vendor:k8s.io/apimachinery/pkg/types",
"//vendor:k8s.io/apimachinery/pkg/util/net",
],
)
go_test(
name = "go_default_test",
srcs = ["kubelet_client_test.go"],
data = ["//pkg/client/testdata"],
library = ":go_default_library",
tags = [
"automanaged",
],
deps = [
"//pkg/client/clientset_generated/clientset/typed/core/v1:go_default_library",
"//pkg/client/restclient:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View file

@ -0,0 +1,180 @@
/*
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 client
import (
"net/http"
"strconv"
"time"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
utilnet "k8s.io/apimachinery/pkg/util/net"
"k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/client/restclient"
"k8s.io/kubernetes/pkg/client/transport"
nodeutil "k8s.io/kubernetes/pkg/util/node"
)
type KubeletClientConfig struct {
// Default port - used if no information about Kubelet port can be found in Node.NodeStatus.DaemonEndpoints.
Port uint
ReadOnlyPort uint
EnableHttps bool
// PreferredAddressTypes - used to select an address from Node.NodeStatus.Addresses
PreferredAddressTypes []string
// TLSClientConfig contains settings to enable transport layer security
restclient.TLSClientConfig
// Server requires Bearer authentication
BearerToken string
// HTTPTimeout is used by the client to timeout http requests to Kubelet.
HTTPTimeout time.Duration
// Dial is a custom dialer used for the client
Dial utilnet.DialFunc
}
// ConnectionInfo provides the information needed to connect to a kubelet
type ConnectionInfo struct {
Scheme string
Hostname string
Port string
Transport http.RoundTripper
}
// ConnectionInfoGetter provides ConnectionInfo for the kubelet running on a named node
type ConnectionInfoGetter interface {
GetConnectionInfo(nodeName types.NodeName) (*ConnectionInfo, error)
}
func MakeTransport(config *KubeletClientConfig) (http.RoundTripper, error) {
tlsConfig, err := transport.TLSConfigFor(config.transportConfig())
if err != nil {
return nil, err
}
rt := http.DefaultTransport
if config.Dial != nil || tlsConfig != nil {
rt = utilnet.SetOldTransportDefaults(&http.Transport{
Dial: config.Dial,
TLSClientConfig: tlsConfig,
})
}
return transport.HTTPWrappersForConfig(config.transportConfig(), rt)
}
// transportConfig converts a client config to an appropriate transport config.
func (c *KubeletClientConfig) transportConfig() *transport.Config {
cfg := &transport.Config{
TLS: transport.TLSConfig{
CAFile: c.CAFile,
CAData: c.CAData,
CertFile: c.CertFile,
CertData: c.CertData,
KeyFile: c.KeyFile,
KeyData: c.KeyData,
},
BearerToken: c.BearerToken,
}
if c.EnableHttps && !cfg.HasCA() {
cfg.TLS.Insecure = true
}
return cfg
}
// NodeGetter defines an interface for looking up a node by name
type NodeGetter interface {
Get(name string, options metav1.GetOptions) (*v1.Node, error)
}
// NodeGetterFunc allows implementing NodeGetter with a function
type NodeGetterFunc func(name string, options metav1.GetOptions) (*v1.Node, error)
func (f NodeGetterFunc) Get(name string, options metav1.GetOptions) (*v1.Node, error) {
return f(name, options)
}
// NodeConnectionInfoGetter obtains connection info from the status of a Node API object
type NodeConnectionInfoGetter struct {
// nodes is used to look up Node objects
nodes NodeGetter
// scheme is the scheme to use to connect to all kubelets
scheme string
// defaultPort is the port to use if no Kubelet endpoint port is recorded in the node status
defaultPort int
// transport is the transport to use to send a request to all kubelets
transport http.RoundTripper
// preferredAddressTypes specifies the preferred order to use to find a node address
preferredAddressTypes []v1.NodeAddressType
}
func NewNodeConnectionInfoGetter(nodes NodeGetter, config KubeletClientConfig) (ConnectionInfoGetter, error) {
scheme := "http"
if config.EnableHttps {
scheme = "https"
}
transport, err := MakeTransport(&config)
if err != nil {
return nil, err
}
types := []v1.NodeAddressType{}
for _, t := range config.PreferredAddressTypes {
types = append(types, v1.NodeAddressType(t))
}
return &NodeConnectionInfoGetter{
nodes: nodes,
scheme: scheme,
defaultPort: int(config.Port),
transport: transport,
preferredAddressTypes: types,
}, nil
}
func (k *NodeConnectionInfoGetter) GetConnectionInfo(nodeName types.NodeName) (*ConnectionInfo, error) {
node, err := k.nodes.Get(string(nodeName), metav1.GetOptions{})
if err != nil {
return nil, err
}
// Find a kubelet-reported address, using preferred address type
host, err := nodeutil.GetPreferredNodeAddress(node, k.preferredAddressTypes)
if err != nil {
return nil, err
}
// Use the kubelet-reported port, if present
port := int(node.Status.DaemonEndpoints.KubeletEndpoint.Port)
if port <= 0 {
port = k.defaultPort
}
return &ConnectionInfo{
Scheme: k.scheme,
Hostname: host,
Port: strconv.Itoa(port),
Transport: k.transport,
}, nil
}

View file

@ -0,0 +1,70 @@
/*
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 client
import (
"testing"
v1core "k8s.io/kubernetes/pkg/client/clientset_generated/clientset/typed/core/v1"
"k8s.io/kubernetes/pkg/client/restclient"
)
// Ensure a node client can be used as a NodeGetter.
// This allows anyone with a node client to easily construct a NewNodeConnectionInfoGetter.
var _ = NodeGetter(v1core.NodeInterface(nil))
func TestMakeTransportInvalid(t *testing.T) {
config := &KubeletClientConfig{
EnableHttps: true,
//Invalid certificate and key path
TLSClientConfig: restclient.TLSClientConfig{
CertFile: "../../client/testdata/mycertinvalid.cer",
KeyFile: "../../client/testdata/mycertinvalid.key",
CAFile: "../../client/testdata/myCA.cer",
},
}
rt, err := MakeTransport(config)
if err == nil {
t.Errorf("Expected an error")
}
if rt != nil {
t.Error("rt should be nil as we provided invalid cert file")
}
}
func TestMakeTransportValid(t *testing.T) {
config := &KubeletClientConfig{
Port: 1234,
EnableHttps: true,
TLSClientConfig: restclient.TLSClientConfig{
CertFile: "../../client/testdata/mycertvalid.cer",
// TLS Configuration, only applies if EnableHttps is true.
KeyFile: "../../client/testdata/mycertvalid.key",
// TLS Configuration, only applies if EnableHttps is true.
CAFile: "../../client/testdata/myCA.cer",
},
}
rt, err := MakeTransport(config)
if err != nil {
t.Errorf("Not expecting an error #%v", err)
}
if rt == nil {
t.Error("rt should not be nil")
}
}

81
vendor/k8s.io/kubernetes/pkg/kubelet/cm/BUILD generated vendored Normal file
View file

@ -0,0 +1,81 @@
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = [
"cgroup_manager_linux.go",
"container_manager.go",
"container_manager_linux.go",
"container_manager_stub.go",
"helpers_linux.go",
"pod_container_manager_linux.go",
"pod_container_manager_stub.go",
"types.go",
],
tags = ["automanaged"],
deps = [
"//pkg/api/resource:go_default_library",
"//pkg/api/v1:go_default_library",
"//pkg/kubelet/cadvisor:go_default_library",
"//pkg/kubelet/cm/util:go_default_library",
"//pkg/kubelet/qos:go_default_library",
"//pkg/util:go_default_library",
"//pkg/util/mount:go_default_library",
"//pkg/util/oom:go_default_library",
"//pkg/util/procfs:go_default_library",
"//pkg/util/sysctl:go_default_library",
"//pkg/util/version:go_default_library",
"//vendor:github.com/golang/glog",
"//vendor:github.com/opencontainers/runc/libcontainer/cgroups",
"//vendor:github.com/opencontainers/runc/libcontainer/cgroups/fs",
"//vendor:github.com/opencontainers/runc/libcontainer/cgroups/systemd",
"//vendor:github.com/opencontainers/runc/libcontainer/configs",
"//vendor:k8s.io/apimachinery/pkg/types",
"//vendor:k8s.io/apimachinery/pkg/util/errors",
"//vendor:k8s.io/apimachinery/pkg/util/runtime",
"//vendor:k8s.io/apimachinery/pkg/util/sets",
"//vendor:k8s.io/apimachinery/pkg/util/wait",
],
)
go_test(
name = "go_default_test",
srcs = [
"cgroup_manager_linux_test.go",
"container_manager_linux_test.go",
"helpers_linux_test.go",
],
library = ":go_default_library",
tags = ["automanaged"],
deps = [
"//pkg/api/resource:go_default_library",
"//pkg/api/v1:go_default_library",
"//pkg/util/mount:go_default_library",
"//vendor:github.com/stretchr/testify/assert",
"//vendor:github.com/stretchr/testify/require",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//pkg/kubelet/cm/util:all-srcs",
],
tags = ["automanaged"],
)

View file

@ -0,0 +1,454 @@
/*
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"
"os"
"path"
"path/filepath"
"strings"
"github.com/golang/glog"
libcontainercgroups "github.com/opencontainers/runc/libcontainer/cgroups"
cgroupfs "github.com/opencontainers/runc/libcontainer/cgroups/fs"
cgroupsystemd "github.com/opencontainers/runc/libcontainer/cgroups/systemd"
libcontainerconfigs "github.com/opencontainers/runc/libcontainer/configs"
"k8s.io/apimachinery/pkg/util/sets"
)
// libcontainerCgroupManagerType defines how to interface with libcontainer
type libcontainerCgroupManagerType string
const (
// libcontainerCgroupfs means use libcontainer with cgroupfs
libcontainerCgroupfs libcontainerCgroupManagerType = "cgroupfs"
// libcontainerSystemd means use libcontainer with systemd
libcontainerSystemd libcontainerCgroupManagerType = "systemd"
)
// ConvertCgroupNameToSystemd converts the internal cgroup name to a systemd name.
// For example, the name /Burstable/pod_123-456 becomes Burstable-pod_123_456.slice
// If outputToCgroupFs is true, it expands the systemd name into the cgroupfs form.
// For example, it will return /Burstable.slice/Burstable-pod_123_456.slice in above scenario.
func ConvertCgroupNameToSystemd(cgroupName CgroupName, outputToCgroupFs bool) string {
name := string(cgroupName)
result := ""
if name != "" && name != "/" {
// systemd treats - as a step in the hierarchy, we convert all - to _
name = strings.Replace(name, "-", "_", -1)
parts := strings.Split(name, "/")
for _, part := range parts {
// ignore leading stuff for now
if part == "" {
continue
}
if len(result) > 0 {
result = result + "-"
}
result = result + part
}
} else {
// root converts to -
result = "-"
}
// always have a .slice suffix
result = result + ".slice"
// if the caller desired the result in cgroupfs format...
if outputToCgroupFs {
var err error
result, err = cgroupsystemd.ExpandSlice(result)
if err != nil {
panic(fmt.Errorf("error adapting cgroup name, input: %v, err: %v", name, err))
}
}
return result
}
// ConvertCgroupFsNameToSystemd converts an expanded cgroupfs name to its systemd name.
// For example, it will convert test.slice/test-a.slice/test-a-b.slice to become test-a-b.slice
// NOTE: this is public right now to allow its usage in dockermanager and dockershim, ideally both those
// code areas could use something from libcontainer if we get this style function upstream.
func ConvertCgroupFsNameToSystemd(cgroupfsName string) (string, error) {
// TODO: see if libcontainer systemd implementation could use something similar, and if so, move
// this function up to that library. At that time, it would most likely do validation specific to systemd
// above and beyond the simple assumption here that the base of the path encodes the hierarchy
// per systemd convention.
return path.Base(cgroupfsName), nil
}
// libcontainerAdapter provides a simplified interface to libcontainer based on libcontainer type.
type libcontainerAdapter struct {
// cgroupManagerType defines how to interface with libcontainer
cgroupManagerType libcontainerCgroupManagerType
}
// newLibcontainerAdapter returns a configured libcontainerAdapter for specified manager.
// it does any initialization required by that manager to function.
func newLibcontainerAdapter(cgroupManagerType libcontainerCgroupManagerType) *libcontainerAdapter {
return &libcontainerAdapter{cgroupManagerType: cgroupManagerType}
}
// newManager returns an implementation of cgroups.Manager
func (l *libcontainerAdapter) newManager(cgroups *libcontainerconfigs.Cgroup, paths map[string]string) (libcontainercgroups.Manager, error) {
switch l.cgroupManagerType {
case libcontainerCgroupfs:
return &cgroupfs.Manager{
Cgroups: cgroups,
Paths: paths,
}, nil
case libcontainerSystemd:
// this means you asked systemd to manage cgroups, but systemd was not on the host, so all you can do is panic...
if !cgroupsystemd.UseSystemd() {
panic("systemd cgroup manager not available")
}
return &cgroupsystemd.Manager{
Cgroups: cgroups,
Paths: paths,
}, nil
}
return nil, fmt.Errorf("invalid cgroup manager configuration")
}
func (l *libcontainerAdapter) revertName(name string) CgroupName {
if l.cgroupManagerType != libcontainerSystemd {
return CgroupName(name)
}
driverName, err := ConvertCgroupFsNameToSystemd(name)
if err != nil {
panic(err)
}
driverName = strings.TrimSuffix(driverName, ".slice")
driverName = strings.Replace(driverName, "_", "-", -1)
return CgroupName(driverName)
}
// adaptName converts a CgroupName identifer to a driver specific conversion value.
// if outputToCgroupFs is true, the result is returned in the cgroupfs format rather than the driver specific form.
func (l *libcontainerAdapter) adaptName(cgroupName CgroupName, outputToCgroupFs bool) string {
if l.cgroupManagerType != libcontainerSystemd {
name := string(cgroupName)
return name
}
return ConvertCgroupNameToSystemd(cgroupName, outputToCgroupFs)
}
// CgroupSubsystems holds information about the mounted cgroup subsytems
type CgroupSubsystems struct {
// Cgroup subsystem mounts.
// e.g.: "/sys/fs/cgroup/cpu" -> ["cpu", "cpuacct"]
Mounts []libcontainercgroups.Mount
// Cgroup subsystem to their mount location.
// e.g.: "cpu" -> "/sys/fs/cgroup/cpu"
MountPoints map[string]string
}
// cgroupManagerImpl implements the CgroupManager interface.
// Its a stateless object which can be used to
// update,create or delete any number of cgroups
// It uses the Libcontainer raw fs cgroup manager for cgroup management.
type cgroupManagerImpl struct {
// subsystems holds information about all the
// mounted cgroup subsytems on the node
subsystems *CgroupSubsystems
// simplifies interaction with libcontainer and its cgroup managers
adapter *libcontainerAdapter
}
// Make sure that cgroupManagerImpl implements the CgroupManager interface
var _ CgroupManager = &cgroupManagerImpl{}
// NewCgroupManager is a factory method that returns a CgroupManager
func NewCgroupManager(cs *CgroupSubsystems, cgroupDriver string) CgroupManager {
managerType := libcontainerCgroupfs
if cgroupDriver == string(libcontainerSystemd) {
managerType = libcontainerSystemd
}
return &cgroupManagerImpl{
subsystems: cs,
adapter: newLibcontainerAdapter(managerType),
}
}
// Name converts the cgroup to the driver specific value in cgroupfs form.
func (m *cgroupManagerImpl) Name(name CgroupName) string {
return m.adapter.adaptName(name, true)
}
// CgroupName converts the literal cgroupfs name on the host to an internal identifier.
func (m *cgroupManagerImpl) CgroupName(name string) CgroupName {
return m.adapter.revertName(name)
}
// buildCgroupPaths builds a path to each cgroup subsystem for the specified name.
func (m *cgroupManagerImpl) buildCgroupPaths(name CgroupName) map[string]string {
cgroupFsAdaptedName := m.Name(name)
cgroupPaths := make(map[string]string, len(m.subsystems.MountPoints))
for key, val := range m.subsystems.MountPoints {
cgroupPaths[key] = path.Join(val, cgroupFsAdaptedName)
}
return cgroupPaths
}
// Exists checks if all subsystem cgroups already exist
func (m *cgroupManagerImpl) Exists(name CgroupName) bool {
// Get map of all cgroup paths on the system for the particular cgroup
cgroupPaths := m.buildCgroupPaths(name)
// If even one cgroup path doesn't exist, then the cgroup doesn't exist.
for _, path := range cgroupPaths {
if !libcontainercgroups.PathExists(path) {
return false
}
}
return true
}
// Destroy destroys the specified cgroup
func (m *cgroupManagerImpl) Destroy(cgroupConfig *CgroupConfig) error {
cgroupPaths := m.buildCgroupPaths(cgroupConfig.Name)
// we take the location in traditional cgroupfs format.
abstractCgroupFsName := string(cgroupConfig.Name)
abstractParent := CgroupName(path.Dir(abstractCgroupFsName))
abstractName := CgroupName(path.Base(abstractCgroupFsName))
driverParent := m.adapter.adaptName(abstractParent, false)
driverName := m.adapter.adaptName(abstractName, false)
// this is an ugly abstraction bleed, but systemd cgroup driver requires full paths...
if m.adapter.cgroupManagerType == libcontainerSystemd {
driverName = m.adapter.adaptName(cgroupConfig.Name, false)
}
// Initialize libcontainer's cgroup config with driver specific naming.
libcontainerCgroupConfig := &libcontainerconfigs.Cgroup{
Name: driverName,
Parent: driverParent,
}
manager, err := m.adapter.newManager(libcontainerCgroupConfig, cgroupPaths)
if err != nil {
return err
}
// Delete cgroups using libcontainers Managers Destroy() method
if err = manager.Destroy(); err != nil {
return fmt.Errorf("Unable to destroy cgroup paths for cgroup %v : %v", cgroupConfig.Name, err)
}
return nil
}
type subsystem interface {
// Name returns the name of the subsystem.
Name() string
// Set the cgroup represented by cgroup.
Set(path string, cgroup *libcontainerconfigs.Cgroup) error
}
// Cgroup subsystems we currently support
var supportedSubsystems = []subsystem{
&cgroupfs.MemoryGroup{},
&cgroupfs.CpuGroup{},
}
// setSupportedSubsytems sets cgroup resource limits only on the supported
// subsytems. ie. cpu and memory. We don't use libcontainer's cgroup/fs/Set()
// method as it doesn't allow us to skip updates on the devices cgroup
// Allowing or denying all devices by writing 'a' to devices.allow or devices.deny is
// not possible once the device cgroups has children. Once the pod level cgroup are
// created under the QOS level cgroup we cannot update the QOS level device cgroup.
// We would like to skip setting any values on the device cgroup in this case
// but this is not possible with libcontainers Set() method
// See https://github.com/opencontainers/runc/issues/932
func setSupportedSubsytems(cgroupConfig *libcontainerconfigs.Cgroup) error {
for _, sys := range supportedSubsystems {
if _, ok := cgroupConfig.Paths[sys.Name()]; !ok {
return fmt.Errorf("Failed to find subsytem mount for subsytem: %v", sys.Name())
}
if err := sys.Set(cgroupConfig.Paths[sys.Name()], cgroupConfig); err != nil {
return fmt.Errorf("Failed to set config for supported subsystems : %v", err)
}
}
return nil
}
func (m *cgroupManagerImpl) toResources(resourceConfig *ResourceConfig) *libcontainerconfigs.Resources {
resources := &libcontainerconfigs.Resources{}
if resourceConfig == nil {
return resources
}
if resourceConfig.Memory != nil {
resources.Memory = *resourceConfig.Memory
}
if resourceConfig.CpuShares != nil {
resources.CpuShares = *resourceConfig.CpuShares
}
if resourceConfig.CpuQuota != nil {
resources.CpuQuota = *resourceConfig.CpuQuota
}
if resourceConfig.CpuPeriod != nil {
resources.CpuPeriod = *resourceConfig.CpuPeriod
}
return resources
}
// Update updates the cgroup with the specified Cgroup Configuration
func (m *cgroupManagerImpl) Update(cgroupConfig *CgroupConfig) error {
// Extract the cgroup resource parameters
resourceConfig := cgroupConfig.ResourceParameters
resources := m.toResources(resourceConfig)
cgroupPaths := m.buildCgroupPaths(cgroupConfig.Name)
// we take the location in traditional cgroupfs format.
abstractCgroupFsName := string(cgroupConfig.Name)
abstractParent := CgroupName(path.Dir(abstractCgroupFsName))
abstractName := CgroupName(path.Base(abstractCgroupFsName))
driverParent := m.adapter.adaptName(abstractParent, false)
driverName := m.adapter.adaptName(abstractName, false)
// this is an ugly abstraction bleed, but systemd cgroup driver requires full paths...
if m.adapter.cgroupManagerType == libcontainerSystemd {
driverName = m.adapter.adaptName(cgroupConfig.Name, false)
}
// Initialize libcontainer's cgroup config
libcontainerCgroupConfig := &libcontainerconfigs.Cgroup{
Name: driverName,
Parent: driverParent,
Resources: resources,
Paths: cgroupPaths,
}
if err := setSupportedSubsytems(libcontainerCgroupConfig); err != nil {
return fmt.Errorf("failed to set supported cgroup subsystems for cgroup %v: %v", cgroupConfig.Name, err)
}
return nil
}
// Create creates the specified cgroup
func (m *cgroupManagerImpl) Create(cgroupConfig *CgroupConfig) error {
// we take the location in traditional cgroupfs format.
abstractCgroupFsName := string(cgroupConfig.Name)
abstractParent := CgroupName(path.Dir(abstractCgroupFsName))
abstractName := CgroupName(path.Base(abstractCgroupFsName))
driverParent := m.adapter.adaptName(abstractParent, false)
driverName := m.adapter.adaptName(abstractName, false)
// this is an ugly abstraction bleed, but systemd cgroup driver requires full paths...
if m.adapter.cgroupManagerType == libcontainerSystemd {
driverName = m.adapter.adaptName(cgroupConfig.Name, false)
}
resources := m.toResources(cgroupConfig.ResourceParameters)
// Initialize libcontainer's cgroup config with driver specific naming.
libcontainerCgroupConfig := &libcontainerconfigs.Cgroup{
Name: driverName,
Parent: driverParent,
Resources: resources,
}
// get the manager with the specified cgroup configuration
manager, err := m.adapter.newManager(libcontainerCgroupConfig, nil)
if err != nil {
return err
}
// Apply(-1) is a hack to create the cgroup directories for each resource
// subsystem. The function [cgroups.Manager.apply()] applies cgroup
// configuration to the process with the specified pid.
// It creates cgroup files for each subsytems and writes the pid
// in the tasks file. We use the function to create all the required
// cgroup files but not attach any "real" pid to the cgroup.
if err := manager.Apply(-1); err != nil {
return err
}
// it may confuse why we call set after we do apply, but the issue is that runc
// follows a similar pattern. it's needed to ensure cpu quota is set properly.
m.Update(cgroupConfig)
return nil
}
// Scans through all subsytems to find pids associated with specified cgroup.
func (m *cgroupManagerImpl) Pids(name CgroupName) []int {
// we need the driver specific name
cgroupFsName := m.Name(name)
// Get a list of processes that we need to kill
pidsToKill := sets.NewInt()
var pids []int
for _, val := range m.subsystems.MountPoints {
dir := path.Join(val, cgroupFsName)
_, err := os.Stat(dir)
if os.IsNotExist(err) {
// The subsystem pod cgroup is already deleted
// do nothing, continue
continue
}
// Get a list of pids that are still charged to the pod's cgroup
pids, err = getCgroupProcs(dir)
if err != nil {
continue
}
pidsToKill.Insert(pids...)
// WalkFunc which is called for each file and directory in the pod cgroup dir
visitor := func(path string, info os.FileInfo, err error) error {
if !info.IsDir() {
return nil
}
pids, err = getCgroupProcs(path)
if err != nil {
glog.V(5).Infof("cgroup manager encountered error getting procs for cgroup path %v", path)
return filepath.SkipDir
}
pidsToKill.Insert(pids...)
return nil
}
// Walk through the pod cgroup directory to check if
// container cgroups haven't been GCed yet. Get attached processes to
// all such unwanted containers under the pod cgroup
if err = filepath.Walk(dir, visitor); err != nil {
glog.V(5).Infof("cgroup manager encountered error scanning pids for directory: %v", dir)
}
}
return pidsToKill.List()
}
// ReduceCPULimits reduces the cgroup's cpu shares to the lowest possible value
func (m *cgroupManagerImpl) ReduceCPULimits(cgroupName CgroupName) error {
// Set lowest possible CpuShares value for the cgroup
minimumCPUShares := int64(MinShares)
resources := &ResourceConfig{
CpuShares: &minimumCPUShares,
}
containerConfig := &CgroupConfig{
Name: cgroupName,
ResourceParameters: resources,
}
return m.Update(containerConfig)
}

View file

@ -0,0 +1,81 @@
// +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 "testing"
func TestLibcontainerAdapterAdaptToSystemd(t *testing.T) {
testCases := []struct {
input string
expected string
}{
{
input: "/",
expected: "-.slice",
},
{
input: "/Burstable",
expected: "Burstable.slice",
},
{
input: "/Burstable/pod_123",
expected: "Burstable-pod_123.slice",
},
{
input: "/BestEffort/pod_6c1a4e95-6bb6-11e6-bc26-28d2444e470d",
expected: "BestEffort-pod_6c1a4e95_6bb6_11e6_bc26_28d2444e470d.slice",
},
}
for _, testCase := range testCases {
f := newLibcontainerAdapter(libcontainerSystemd)
if actual := f.adaptName(CgroupName(testCase.input), false); actual != testCase.expected {
t.Errorf("Unexpected result, input: %v, expected: %v, actual: %v", testCase.input, testCase.expected, actual)
}
}
}
func TestLibcontainerAdapterAdaptToSystemdAsCgroupFs(t *testing.T) {
testCases := []struct {
input string
expected string
}{
{
input: "/",
expected: "/",
},
{
input: "/Burstable",
expected: "Burstable.slice/",
},
{
input: "/Burstable/pod_123",
expected: "Burstable.slice/Burstable-pod_123.slice/",
},
{
input: "/BestEffort/pod_6c1a4e95-6bb6-11e6-bc26-28d2444e470d",
expected: "BestEffort.slice/BestEffort-pod_6c1a4e95_6bb6_11e6_bc26_28d2444e470d.slice/",
},
}
for _, testCase := range testCases {
f := newLibcontainerAdapter(libcontainerSystemd)
if actual := f.adaptName(CgroupName(testCase.input), true); actual != testCase.expected {
t.Errorf("Unexpected result, input: %v, expected: %v, actual: %v", testCase.input, testCase.expected, actual)
}
}
}

View file

@ -0,0 +1,75 @@
// +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"
type unsupportedCgroupManager struct{}
// Make sure that unsupportedCgroupManager implements the CgroupManager interface
var _ CgroupManager = &unsupportedCgroupManager{}
type CgroupSubsystems struct {
Mounts []interface{}
MountPoints map[string]string
}
func NewCgroupManager(_ interface{}) CgroupManager {
return &unsupportedCgroupManager{}
}
func (m *unsupportedCgroupManager) Name(_ CgroupName) string {
return ""
}
func (m *unsupportedCgroupManager) Exists(_ CgroupName) bool {
return false
}
func (m *unsupportedCgroupManager) Destroy(_ *CgroupConfig) error {
return nil
}
func (m *unsupportedCgroupManager) Update(_ *CgroupConfig) error {
return nil
}
func (m *unsupportedCgroupManager) Create(_ *CgroupConfig) error {
return fmt.Errorf("Cgroup Manager is not supported in this build")
}
func (m *unsupportedCgroupManager) Pids(_ CgroupName) []int {
return nil
}
func (m *unsupportedCgroupManager) CgroupName(name string) CgroupName {
return ""
}
func (m *unsupportedCgroupManager) ReduceCPULimits(cgroupName CgroupName) error {
return nil
}
func ConvertCgroupFsNameToSystemd(cgroupfsName string) (string, error) {
return "", nil
}
func ConvertCgroupNameToSystemd(cgroupName CgroupName, outputToCgroupFs bool) string {
return ""
}

View file

@ -0,0 +1,64 @@
/*
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 cm
import "k8s.io/kubernetes/pkg/api/v1"
// Manages the containers running on a machine.
type ContainerManager interface {
// Runs the container manager's housekeeping.
// - Ensures that the Docker daemon is in a container.
// - Creates the system container where all non-containerized processes run.
Start(*v1.Node) error
// Returns resources allocated to system cgroups in the machine.
// These cgroups include the system and Kubernetes services.
SystemCgroupsLimit() v1.ResourceList
// Returns a NodeConfig that is being used by the container manager.
GetNodeConfig() NodeConfig
// Returns internal Status.
Status() Status
// NewPodContainerManager is a factory method which returns a podContainerManager object
// Returns a noop implementation if qos cgroup hierarchy is not enabled
NewPodContainerManager() PodContainerManager
// GetMountedSubsystems returns the mounted cgroup subsytems on the node
GetMountedSubsystems() *CgroupSubsystems
// GetQOSContainersInfo returns the names of top level QoS containers
GetQOSContainersInfo() QOSContainersInfo
}
type NodeConfig struct {
RuntimeCgroupsName string
SystemCgroupsName string
KubeletCgroupsName string
ContainerRuntime string
CgroupsPerQOS bool
CgroupRoot string
CgroupDriver string
ProtectKernelDefaults bool
EnableCRI bool
}
type Status struct {
// Any soft requirements that were unsatisfied.
SoftRequirements error
}

View file

@ -0,0 +1,812 @@
// +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 cm
import (
"bufio"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path"
"strconv"
"sync"
"time"
"github.com/golang/glog"
"github.com/opencontainers/runc/libcontainer/cgroups"
"github.com/opencontainers/runc/libcontainer/cgroups/fs"
"github.com/opencontainers/runc/libcontainer/configs"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/kubernetes/pkg/api/resource"
"k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/kubelet/cadvisor"
cmutil "k8s.io/kubernetes/pkg/kubelet/cm/util"
"k8s.io/kubernetes/pkg/kubelet/qos"
"k8s.io/kubernetes/pkg/util"
"k8s.io/kubernetes/pkg/util/mount"
"k8s.io/kubernetes/pkg/util/oom"
"k8s.io/kubernetes/pkg/util/procfs"
utilsysctl "k8s.io/kubernetes/pkg/util/sysctl"
utilversion "k8s.io/kubernetes/pkg/util/version"
)
const (
// The percent of the machine memory capacity. The value is used to calculate
// docker memory resource container's hardlimit to workaround docker memory
// leakage issue. Please see kubernetes/issues/9881 for more detail.
DockerMemoryLimitThresholdPercent = 70
// The minimum memory limit allocated to docker container: 150Mi
MinDockerMemoryLimit = 150 * 1024 * 1024
dockerProcessName = "docker"
dockerPidFile = "/var/run/docker.pid"
containerdProcessName = "docker-containerd"
containerdPidFile = "/run/docker/libcontainerd/docker-containerd.pid"
)
var (
// The docker version in which containerd was introduced.
containerdVersion = utilversion.MustParseSemantic("1.11.0")
)
// A non-user container tracked by the Kubelet.
type systemContainer struct {
// Absolute name of the container.
name string
// CPU limit in millicores.
cpuMillicores int64
// Function that ensures the state of the container.
// m is the cgroup manager for the specified container.
ensureStateFunc func(m *fs.Manager) error
// Manager for the cgroups of the external container.
manager *fs.Manager
}
func newSystemCgroups(containerName string) *systemContainer {
return &systemContainer{
name: containerName,
manager: createManager(containerName),
}
}
type containerManagerImpl struct {
sync.RWMutex
cadvisorInterface cadvisor.Interface
mountUtil mount.Interface
NodeConfig
status Status
// External containers being managed.
systemContainers []*systemContainer
qosContainers QOSContainersInfo
periodicTasks []func()
// holds all the mounted cgroup subsystems
subsystems *CgroupSubsystems
nodeInfo *v1.Node
}
type features struct {
cpuHardcapping bool
}
var _ ContainerManager = &containerManagerImpl{}
// checks if the required cgroups subsystems are mounted.
// As of now, only 'cpu' and 'memory' are required.
// cpu quota is a soft requirement.
func validateSystemRequirements(mountUtil mount.Interface) (features, error) {
const (
cgroupMountType = "cgroup"
localErr = "system validation failed"
)
var (
cpuMountPoint string
f features
)
mountPoints, err := mountUtil.List()
if err != nil {
return f, fmt.Errorf("%s - %v", localErr, err)
}
expectedCgroups := sets.NewString("cpu", "cpuacct", "cpuset", "memory")
for _, mountPoint := range mountPoints {
if mountPoint.Type == cgroupMountType {
for _, opt := range mountPoint.Opts {
if expectedCgroups.Has(opt) {
expectedCgroups.Delete(opt)
}
if opt == "cpu" {
cpuMountPoint = mountPoint.Path
}
}
}
}
if expectedCgroups.Len() > 0 {
return f, fmt.Errorf("%s - Following Cgroup subsystem not mounted: %v", localErr, expectedCgroups.List())
}
// Check if cpu quota is available.
// CPU cgroup is required and so it expected to be mounted at this point.
periodExists, err := util.FileExists(path.Join(cpuMountPoint, "cpu.cfs_period_us"))
if err != nil {
glog.Errorf("failed to detect if CPU cgroup cpu.cfs_period_us is available - %v", err)
}
quotaExists, err := util.FileExists(path.Join(cpuMountPoint, "cpu.cfs_quota_us"))
if err != nil {
glog.Errorf("failed to detect if CPU cgroup cpu.cfs_quota_us is available - %v", err)
}
if quotaExists && periodExists {
f.cpuHardcapping = true
}
return f, nil
}
// TODO(vmarmol): Add limits to the system containers.
// Takes the absolute name of the specified containers.
// Empty container name disables use of the specified container.
func NewContainerManager(mountUtil mount.Interface, cadvisorInterface cadvisor.Interface, nodeConfig NodeConfig, failSwapOn bool) (ContainerManager, error) {
subsystems, err := GetCgroupSubsystems()
if err != nil {
return nil, fmt.Errorf("failed to get mounted cgroup subsystems: %v", err)
}
// Check whether swap is enabled. The Kubelet does not support running with swap enabled.
cmd := exec.Command("cat", "/proc/swaps")
stdout, err := cmd.StdoutPipe()
if err != nil {
return nil, err
}
if err := cmd.Start(); err != nil {
return nil, err
}
var buf []string
scanner := bufio.NewScanner(stdout)
for scanner.Scan() { // Splits on newlines by default
buf = append(buf, scanner.Text())
}
if err := cmd.Wait(); err != nil { // Clean up
return nil, err
}
// TODO(#34726:1.8.0): Remove the opt-in for failing when swap is enabled.
// Running with swap enabled should be considered an error, but in order to maintain legacy
// behavior we have to require an opt-in to this error for a period of time.
// If there is more than one line (table headers) in /proc/swaps, swap is enabled and we should error out.
if len(buf) > 1 {
if failSwapOn {
return nil, fmt.Errorf("Running with swap on is not supported, please disable swap! /proc/swaps contained: %v", buf)
}
glog.Warningf("Running with swap on is not supported, please disable swap! " +
"This will be a fatal error by default starting in K8s v1.6! " +
"In the meantime, you can opt-in to making this a fatal error by enabling --experimental-fail-swap-on.")
}
// Check if Cgroup-root actually exists on the node
if nodeConfig.CgroupsPerQOS {
// this does default to / when enabled, but this tests against regressions.
if nodeConfig.CgroupRoot == "" {
return nil, fmt.Errorf("invalid configuration: experimental-cgroups-per-qos was specified and cgroup-root was not specified. To enable the QoS cgroup hierarchy you need to specify a valid cgroup-root")
}
// we need to check that the cgroup root actually exists for each subsystem
// of note, we always use the cgroupfs driver when performing this check since
// the input is provided in that format.
// this is important because we do not want any name conversion to occur.
cgroupManager := NewCgroupManager(subsystems, "cgroupfs")
if !cgroupManager.Exists(CgroupName(nodeConfig.CgroupRoot)) {
return nil, fmt.Errorf("invalid configuration: cgroup-root doesn't exist: %v", err)
}
}
return &containerManagerImpl{
cadvisorInterface: cadvisorInterface,
mountUtil: mountUtil,
NodeConfig: nodeConfig,
subsystems: subsystems,
}, nil
}
// NewPodContainerManager is a factory method returns a PodContainerManager object
// If qosCgroups are enabled then it returns the general pod container manager
// otherwise it returns a no-op manager which essentially does nothing
func (cm *containerManagerImpl) NewPodContainerManager() PodContainerManager {
if cm.NodeConfig.CgroupsPerQOS {
return &podContainerManagerImpl{
qosContainersInfo: cm.qosContainers,
nodeInfo: cm.nodeInfo,
subsystems: cm.subsystems,
cgroupManager: NewCgroupManager(cm.subsystems, cm.NodeConfig.CgroupDriver),
}
}
return &podContainerManagerNoop{
cgroupRoot: CgroupName(cm.NodeConfig.CgroupRoot),
}
}
// Create a cgroup container manager.
func createManager(containerName string) *fs.Manager {
allowAllDevices := true
return &fs.Manager{
Cgroups: &configs.Cgroup{
Parent: "/",
Name: containerName,
Resources: &configs.Resources{
AllowAllDevices: &allowAllDevices,
},
},
}
}
type KernelTunableBehavior string
const (
KernelTunableWarn KernelTunableBehavior = "warn"
KernelTunableError KernelTunableBehavior = "error"
KernelTunableModify KernelTunableBehavior = "modify"
)
// InitQOS creates the top level qos cgroup containers
// We create top level QoS containers for only Burstable and Best Effort
// and not Guaranteed QoS class. All guaranteed pods are nested under the
// RootContainer by default. InitQOS is called only once during kubelet bootstrapping.
func InitQOS(cgroupDriver, rootContainer string, subsystems *CgroupSubsystems) (QOSContainersInfo, error) {
cm := NewCgroupManager(subsystems, cgroupDriver)
// Top level for Qos containers are created only for Burstable
// and Best Effort classes
qosClasses := [2]v1.PodQOSClass{v1.PodQOSBurstable, v1.PodQOSBestEffort}
// Create containers for both qos classes
for _, qosClass := range qosClasses {
// get the container's absolute name
absoluteContainerName := CgroupName(path.Join(rootContainer, string(qosClass)))
// containerConfig object stores the cgroup specifications
containerConfig := &CgroupConfig{
Name: absoluteContainerName,
ResourceParameters: &ResourceConfig{},
}
// check if it exists
if !cm.Exists(absoluteContainerName) {
if err := cm.Create(containerConfig); err != nil {
return QOSContainersInfo{}, fmt.Errorf("failed to create top level %v QOS cgroup : %v", qosClass, err)
}
}
}
// Store the top level qos container names
qosContainersInfo := QOSContainersInfo{
Guaranteed: rootContainer,
Burstable: path.Join(rootContainer, string(v1.PodQOSBurstable)),
BestEffort: path.Join(rootContainer, string(v1.PodQOSBestEffort)),
}
return qosContainersInfo, nil
}
// setupKernelTunables validates kernel tunable flags are set as expected
// depending upon the specified option, it will either warn, error, or modify the kernel tunable flags
func setupKernelTunables(option KernelTunableBehavior) error {
desiredState := map[string]int{
utilsysctl.VmOvercommitMemory: utilsysctl.VmOvercommitMemoryAlways,
utilsysctl.VmPanicOnOOM: utilsysctl.VmPanicOnOOMInvokeOOMKiller,
utilsysctl.KernelPanic: utilsysctl.KernelPanicRebootTimeout,
utilsysctl.KernelPanicOnOops: utilsysctl.KernelPanicOnOopsAlways,
}
sysctl := utilsysctl.New()
errList := []error{}
for flag, expectedValue := range desiredState {
val, err := sysctl.GetSysctl(flag)
if err != nil {
errList = append(errList, err)
continue
}
if val == expectedValue {
continue
}
switch option {
case KernelTunableError:
errList = append(errList, fmt.Errorf("Invalid kernel flag: %v, expected value: %v, actual value: %v", flag, expectedValue, val))
case KernelTunableWarn:
glog.V(2).Infof("Invalid kernel flag: %v, expected value: %v, actual value: %v", flag, expectedValue, val)
case KernelTunableModify:
glog.V(2).Infof("Updating kernel flag: %v, expected value: %v, actual value: %v", flag, expectedValue, val)
err = sysctl.SetSysctl(flag, expectedValue)
if err != nil {
errList = append(errList, err)
}
}
}
return utilerrors.NewAggregate(errList)
}
func (cm *containerManagerImpl) setupNode() error {
f, err := validateSystemRequirements(cm.mountUtil)
if err != nil {
return err
}
if !f.cpuHardcapping {
cm.status.SoftRequirements = fmt.Errorf("CPU hardcapping unsupported")
}
b := KernelTunableModify
if cm.GetNodeConfig().ProtectKernelDefaults {
b = KernelTunableError
}
if err := setupKernelTunables(b); err != nil {
return err
}
// Setup top level qos containers only if CgroupsPerQOS flag is specified as true
if cm.NodeConfig.CgroupsPerQOS {
qosContainersInfo, err := InitQOS(cm.NodeConfig.CgroupDriver, cm.NodeConfig.CgroupRoot, cm.subsystems)
if err != nil {
return fmt.Errorf("failed to initialise top level QOS containers: %v", err)
}
cm.qosContainers = qosContainersInfo
}
systemContainers := []*systemContainer{}
if cm.ContainerRuntime == "docker" {
dockerVersion := getDockerVersion(cm.cadvisorInterface)
if cm.EnableCRI {
// If kubelet uses CRI, dockershim will manage the cgroups and oom
// score for the docker processes.
// In the future, NodeSpec should mandate the cgroup that the
// runtime processes need to be in. For now, we still check the
// cgroup for docker periodically, so that kubelet can recognize
// the cgroup for docker and serve stats for the runtime.
// TODO(#27097): Fix this after NodeSpec is clearly defined.
cm.periodicTasks = append(cm.periodicTasks, func() {
glog.V(4).Infof("[ContainerManager]: Adding periodic tasks for docker CRI integration")
cont, err := getContainerNameForProcess(dockerProcessName, dockerPidFile)
if err != nil {
glog.Error(err)
return
}
glog.V(2).Infof("[ContainerManager]: Discovered runtime cgroups name: %s", cont)
cm.Lock()
defer cm.Unlock()
cm.RuntimeCgroupsName = cont
})
} else if cm.RuntimeCgroupsName != "" {
cont := newSystemCgroups(cm.RuntimeCgroupsName)
var capacity = v1.ResourceList{}
if info, err := cm.cadvisorInterface.MachineInfo(); err == nil {
capacity = cadvisor.CapacityFromMachineInfo(info)
}
memoryLimit := (int64(capacity.Memory().Value() * DockerMemoryLimitThresholdPercent / 100))
if memoryLimit < MinDockerMemoryLimit {
glog.Warningf("Memory limit %d for container %s is too small, reset it to %d", memoryLimit, cm.RuntimeCgroupsName, MinDockerMemoryLimit)
memoryLimit = MinDockerMemoryLimit
}
glog.V(2).Infof("Configure resource-only container %s with memory limit: %d", cm.RuntimeCgroupsName, memoryLimit)
allowAllDevices := true
dockerContainer := &fs.Manager{
Cgroups: &configs.Cgroup{
Parent: "/",
Name: cm.RuntimeCgroupsName,
Resources: &configs.Resources{
Memory: memoryLimit,
MemorySwap: -1,
AllowAllDevices: &allowAllDevices,
},
},
}
cont.ensureStateFunc = func(manager *fs.Manager) error {
return EnsureDockerInContainer(dockerVersion, qos.DockerOOMScoreAdj, dockerContainer)
}
systemContainers = append(systemContainers, cont)
} else {
cm.periodicTasks = append(cm.periodicTasks, func() {
glog.V(10).Infof("Adding docker daemon periodic tasks")
if err := EnsureDockerInContainer(dockerVersion, qos.DockerOOMScoreAdj, nil); err != nil {
glog.Error(err)
return
}
cont, err := getContainerNameForProcess(dockerProcessName, dockerPidFile)
if err != nil {
glog.Error(err)
return
}
glog.V(2).Infof("Discovered runtime cgroups name: %s", cont)
cm.Lock()
defer cm.Unlock()
cm.RuntimeCgroupsName = cont
})
}
}
if cm.SystemCgroupsName != "" {
if cm.SystemCgroupsName == "/" {
return fmt.Errorf("system container cannot be root (\"/\")")
}
cont := newSystemCgroups(cm.SystemCgroupsName)
cont.ensureStateFunc = func(manager *fs.Manager) error {
return ensureSystemCgroups("/", manager)
}
systemContainers = append(systemContainers, cont)
}
if cm.KubeletCgroupsName != "" {
cont := newSystemCgroups(cm.KubeletCgroupsName)
allowAllDevices := true
manager := fs.Manager{
Cgroups: &configs.Cgroup{
Parent: "/",
Name: cm.KubeletCgroupsName,
Resources: &configs.Resources{
AllowAllDevices: &allowAllDevices,
},
},
}
cont.ensureStateFunc = func(_ *fs.Manager) error {
return ensureProcessInContainerWithOOMScore(os.Getpid(), qos.KubeletOOMScoreAdj, &manager)
}
systemContainers = append(systemContainers, cont)
} else {
cm.periodicTasks = append(cm.periodicTasks, func() {
if err := ensureProcessInContainerWithOOMScore(os.Getpid(), qos.KubeletOOMScoreAdj, nil); err != nil {
glog.Error(err)
return
}
cont, err := getContainer(os.Getpid())
if err != nil {
glog.Errorf("failed to find cgroups of kubelet - %v", err)
return
}
cm.Lock()
defer cm.Unlock()
cm.KubeletCgroupsName = cont
})
}
cm.systemContainers = systemContainers
return nil
}
func getContainerNameForProcess(name, pidFile string) (string, error) {
pids, err := getPidsForProcess(name, pidFile)
if err != nil {
return "", fmt.Errorf("failed to detect process id for %q - %v", name, err)
}
if len(pids) == 0 {
return "", nil
}
cont, err := getContainer(pids[0])
if err != nil {
return "", err
}
return cont, nil
}
func (cm *containerManagerImpl) GetNodeConfig() NodeConfig {
cm.RLock()
defer cm.RUnlock()
return cm.NodeConfig
}
func (cm *containerManagerImpl) GetMountedSubsystems() *CgroupSubsystems {
return cm.subsystems
}
func (cm *containerManagerImpl) GetQOSContainersInfo() QOSContainersInfo {
return cm.qosContainers
}
func (cm *containerManagerImpl) Status() Status {
cm.RLock()
defer cm.RUnlock()
return cm.status
}
func (cm *containerManagerImpl) Start(node *v1.Node) error {
// cache the node Info including resource capacity and
// allocatable of the node
cm.nodeInfo = node
// Setup the node
if err := cm.setupNode(); err != nil {
return err
}
// Don't run a background thread if there are no ensureStateFuncs.
hasEnsureStateFuncs := false
for _, cont := range cm.systemContainers {
if cont.ensureStateFunc != nil {
hasEnsureStateFuncs = true
break
}
}
if hasEnsureStateFuncs {
// Run ensure state functions every minute.
go wait.Until(func() {
for _, cont := range cm.systemContainers {
if cont.ensureStateFunc != nil {
if err := cont.ensureStateFunc(cont.manager); err != nil {
glog.Warningf("[ContainerManager] Failed to ensure state of %q: %v", cont.name, err)
}
}
}
}, time.Minute, wait.NeverStop)
}
if len(cm.periodicTasks) > 0 {
go wait.Until(func() {
for _, task := range cm.periodicTasks {
if task != nil {
task()
}
}
}, 5*time.Minute, wait.NeverStop)
}
return nil
}
func (cm *containerManagerImpl) SystemCgroupsLimit() v1.ResourceList {
cpuLimit := int64(0)
// Sum up resources of all external containers.
for _, cont := range cm.systemContainers {
cpuLimit += cont.cpuMillicores
}
return v1.ResourceList{
v1.ResourceCPU: *resource.NewMilliQuantity(
cpuLimit,
resource.DecimalSI),
}
}
func isProcessRunningInHost(pid int) (bool, error) {
// Get init pid namespace.
initPidNs, err := os.Readlink("/proc/1/ns/pid")
if err != nil {
return false, fmt.Errorf("failed to find pid namespace of init process")
}
glog.V(10).Infof("init pid ns is %q", initPidNs)
processPidNs, err := os.Readlink(fmt.Sprintf("/proc/%d/ns/pid", pid))
if err != nil {
return false, fmt.Errorf("failed to find pid namespace of process %q", pid)
}
glog.V(10).Infof("Pid %d pid ns is %q", pid, processPidNs)
return initPidNs == processPidNs, nil
}
func getPidFromPidFile(pidFile string) (int, error) {
file, err := os.Open(pidFile)
if err != nil {
return 0, fmt.Errorf("error opening pid file %s: %v", pidFile, err)
}
defer file.Close()
data, err := ioutil.ReadAll(file)
if err != nil {
return 0, fmt.Errorf("error reading pid file %s: %v", pidFile, err)
}
pid, err := strconv.Atoi(string(data))
if err != nil {
return 0, fmt.Errorf("error parsing %s as a number: %v", string(data), err)
}
return pid, nil
}
func getPidsForProcess(name, pidFile string) ([]int, error) {
if len(pidFile) > 0 {
if pid, err := getPidFromPidFile(pidFile); err == nil {
return []int{pid}, nil
} else {
// log the error and fall back to pidof
runtime.HandleError(err)
}
}
return procfs.PidOf(name)
}
// Ensures that the Docker daemon is in the desired container.
// Temporarily export the function to be used by dockershim.
// TODO(yujuhong): Move this function to dockershim once kubelet migrates to
// dockershim as the default.
func EnsureDockerInContainer(dockerVersion *utilversion.Version, oomScoreAdj int, manager *fs.Manager) error {
type process struct{ name, file string }
dockerProcs := []process{{dockerProcessName, dockerPidFile}}
if dockerVersion.AtLeast(containerdVersion) {
dockerProcs = append(dockerProcs, process{containerdProcessName, containerdPidFile})
}
var errs []error
for _, proc := range dockerProcs {
pids, err := getPidsForProcess(proc.name, proc.file)
if err != nil {
errs = append(errs, fmt.Errorf("failed to get pids for %q: %v", proc.name, err))
continue
}
// Move if the pid is not already in the desired container.
for _, pid := range pids {
if err := ensureProcessInContainerWithOOMScore(pid, oomScoreAdj, manager); err != nil {
errs = append(errs, fmt.Errorf("errors moving %q pid: %v", proc.name, err))
}
}
}
return utilerrors.NewAggregate(errs)
}
func ensureProcessInContainerWithOOMScore(pid int, oomScoreAdj int, manager *fs.Manager) error {
if runningInHost, err := isProcessRunningInHost(pid); err != nil {
// Err on the side of caution. Avoid moving the docker daemon unless we are able to identify its context.
return err
} else if !runningInHost {
// Process is running inside a container. Don't touch that.
glog.V(2).Infof("pid %d is not running in the host namespaces", pid)
return nil
}
var errs []error
if manager != nil {
cont, err := getContainer(pid)
if err != nil {
errs = append(errs, fmt.Errorf("failed to find container of PID %d: %v", pid, err))
}
if cont != manager.Cgroups.Name {
err = manager.Apply(pid)
if err != nil {
errs = append(errs, fmt.Errorf("failed to move PID %d (in %q) to %q: %v", pid, cont, manager.Cgroups.Name, err))
}
}
}
// Also apply oom-score-adj to processes
oomAdjuster := oom.NewOOMAdjuster()
glog.V(5).Infof("attempting to apply oom_score_adj of %d to pid %d", oomScoreAdj, pid)
if err := oomAdjuster.ApplyOOMScoreAdj(pid, oomScoreAdj); err != nil {
glog.V(3).Infof("Failed to apply oom_score_adj %d for pid %d: %v", oomScoreAdj, pid, err)
errs = append(errs, fmt.Errorf("failed to apply oom score %d to PID %d: %v", oomScoreAdj, pid, err))
}
return utilerrors.NewAggregate(errs)
}
// getContainer returns the cgroup associated with the specified pid.
// It enforces a unified hierarchy for memory and cpu cgroups.
// On systemd environments, it uses the name=systemd cgroup for the specified pid.
func getContainer(pid int) (string, error) {
cgs, err := cgroups.ParseCgroupFile(fmt.Sprintf("/proc/%d/cgroup", pid))
if err != nil {
return "", err
}
cpu, found := cgs["cpu"]
if !found {
return "", cgroups.NewNotFoundError("cpu")
}
memory, found := cgs["memory"]
if !found {
return "", cgroups.NewNotFoundError("memory")
}
// since we use this container for accounting, we need to ensure its a unified hierarchy.
if cpu != memory {
return "", fmt.Errorf("cpu and memory cgroup hierarchy not unified. cpu: %s, memory: %s", cpu, memory)
}
// on systemd, every pid is in a unified cgroup hierarchy (name=systemd as seen in systemd-cgls)
// cpu and memory accounting is off by default, users may choose to enable it per unit or globally.
// users could enable CPU and memory accounting globally via /etc/systemd/system.conf (DefaultCPUAccounting=true DefaultMemoryAccounting=true).
// users could also enable CPU and memory accounting per unit via CPUAccounting=true and MemoryAccounting=true
// we only warn if accounting is not enabled for CPU or memory so as to not break local development flows where kubelet is launched in a terminal.
// for example, the cgroup for the user session will be something like /user.slice/user-X.slice/session-X.scope, but the cpu and memory
// cgroup will be the closest ancestor where accounting is performed (most likely /) on systems that launch docker containers.
// as a result, on those systems, you will not get cpu or memory accounting statistics for kubelet.
// in addition, you would not get memory or cpu accounting for the runtime unless accounting was enabled on its unit (or globally).
if systemd, found := cgs["name=systemd"]; found {
if systemd != cpu {
glog.Warningf("CPUAccounting not enabled for pid: %d", pid)
}
if systemd != memory {
glog.Warningf("MemoryAccounting not enabled for pid: %d", pid)
}
return systemd, nil
}
return cpu, nil
}
// Ensures the system container is created and all non-kernel threads and process 1
// without a container are moved to it.
//
// The reason of leaving kernel threads at root cgroup is that we don't want to tie the
// execution of these threads with to-be defined /system quota and create priority inversions.
//
func ensureSystemCgroups(rootCgroupPath string, manager *fs.Manager) error {
// Move non-kernel PIDs to the system container.
attemptsRemaining := 10
var errs []error
for attemptsRemaining >= 0 {
// Only keep errors on latest attempt.
errs = []error{}
attemptsRemaining--
allPids, err := cmutil.GetPids(rootCgroupPath)
if err != nil {
errs = append(errs, fmt.Errorf("failed to list PIDs for root: %v", err))
continue
}
// Remove kernel pids and other protected PIDs (pid 1, PIDs already in system & kubelet containers)
pids := make([]int, 0, len(allPids))
for _, pid := range allPids {
if pid == 1 || isKernelPid(pid) {
continue
}
pids = append(pids, pid)
}
glog.Infof("Found %d PIDs in root, %d of them are not to be moved", len(allPids), len(allPids)-len(pids))
// Check if we have moved all the non-kernel PIDs.
if len(pids) == 0 {
break
}
glog.Infof("Moving non-kernel processes: %v", pids)
for _, pid := range pids {
err := manager.Apply(pid)
if err != nil {
errs = append(errs, fmt.Errorf("failed to move PID %d into the system container %q: %v", pid, manager.Cgroups.Name, err))
}
}
}
if attemptsRemaining < 0 {
errs = append(errs, fmt.Errorf("ran out of attempts to create system containers %q", manager.Cgroups.Name))
}
return utilerrors.NewAggregate(errs)
}
// Determines whether the specified PID is a kernel PID.
func isKernelPid(pid int) bool {
// Kernel threads have no associated executable.
_, err := os.Readlink(fmt.Sprintf("/proc/%d/exe", pid))
return err != nil
}
// Helper for getting the docker version.
func getDockerVersion(cadvisor cadvisor.Interface) *utilversion.Version {
versions, err := cadvisor.VersionInfo()
if err != nil {
glog.Errorf("Error requesting cAdvisor VersionInfo: %v", err)
return utilversion.MustParseSemantic("0.0.0")
}
dockerVersion, err := utilversion.ParseSemantic(versions.DockerVersion)
if err != nil {
glog.Errorf("Error parsing docker version %q: %v", versions.DockerVersion, err)
return utilversion.MustParseSemantic("0.0.0")
}
return dockerVersion
}

View file

@ -0,0 +1,180 @@
// +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 cm
import (
"fmt"
"io/ioutil"
"os"
"path"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"k8s.io/kubernetes/pkg/util/mount"
)
type fakeMountInterface struct {
mountPoints []mount.MountPoint
}
func (mi *fakeMountInterface) Mount(source string, target string, fstype string, options []string) error {
return fmt.Errorf("unsupported")
}
func (mi *fakeMountInterface) Unmount(target string) error {
return fmt.Errorf("unsupported")
}
func (mi *fakeMountInterface) List() ([]mount.MountPoint, error) {
return mi.mountPoints, nil
}
func (mi *fakeMountInterface) IsLikelyNotMountPoint(file string) (bool, error) {
return false, fmt.Errorf("unsupported")
}
func (mi *fakeMountInterface) GetDeviceNameFromMount(mountPath, pluginDir string) (string, error) {
return "", nil
}
func (mi *fakeMountInterface) DeviceOpened(pathname string) (bool, error) {
for _, mp := range mi.mountPoints {
if mp.Device == pathname {
return true, nil
}
}
return false, nil
}
func (mi *fakeMountInterface) PathIsDevice(pathname string) (bool, error) {
return true, nil
}
func fakeContainerMgrMountInt() mount.Interface {
return &fakeMountInterface{
[]mount.MountPoint{
{
Device: "cgroup",
Type: "cgroup",
Opts: []string{"rw", "relatime", "cpuset"},
},
{
Device: "cgroup",
Type: "cgroup",
Opts: []string{"rw", "relatime", "cpu"},
},
{
Device: "cgroup",
Type: "cgroup",
Opts: []string{"rw", "relatime", "cpuacct"},
},
{
Device: "cgroup",
Type: "cgroup",
Opts: []string{"rw", "relatime", "memory"},
},
},
}
}
func TestCgroupMountValidationSuccess(t *testing.T) {
f, err := validateSystemRequirements(fakeContainerMgrMountInt())
assert.Nil(t, err)
assert.False(t, f.cpuHardcapping, "cpu hardcapping is expected to be disabled")
}
func TestCgroupMountValidationMemoryMissing(t *testing.T) {
mountInt := &fakeMountInterface{
[]mount.MountPoint{
{
Device: "cgroup",
Type: "cgroup",
Opts: []string{"rw", "relatime", "cpuset"},
},
{
Device: "cgroup",
Type: "cgroup",
Opts: []string{"rw", "relatime", "cpu"},
},
{
Device: "cgroup",
Type: "cgroup",
Opts: []string{"rw", "relatime", "cpuacct"},
},
},
}
_, err := validateSystemRequirements(mountInt)
assert.Error(t, err)
}
func TestCgroupMountValidationMultipleSubsytem(t *testing.T) {
mountInt := &fakeMountInterface{
[]mount.MountPoint{
{
Device: "cgroup",
Type: "cgroup",
Opts: []string{"rw", "relatime", "cpuset", "memory"},
},
{
Device: "cgroup",
Type: "cgroup",
Opts: []string{"rw", "relatime", "cpu"},
},
{
Device: "cgroup",
Type: "cgroup",
Opts: []string{"rw", "relatime", "cpuacct"},
},
},
}
_, err := validateSystemRequirements(mountInt)
assert.Nil(t, err)
}
func TestSoftRequirementsValidationSuccess(t *testing.T) {
req := require.New(t)
tempDir, err := ioutil.TempDir("", "")
req.NoError(err)
req.NoError(ioutil.WriteFile(path.Join(tempDir, "cpu.cfs_period_us"), []byte("0"), os.ModePerm))
req.NoError(ioutil.WriteFile(path.Join(tempDir, "cpu.cfs_quota_us"), []byte("0"), os.ModePerm))
mountInt := &fakeMountInterface{
[]mount.MountPoint{
{
Device: "cgroup",
Type: "cgroup",
Opts: []string{"rw", "relatime", "cpuset"},
},
{
Device: "cgroup",
Type: "cgroup",
Opts: []string{"rw", "relatime", "cpu"},
Path: tempDir,
},
{
Device: "cgroup",
Type: "cgroup",
Opts: []string{"rw", "relatime", "cpuacct", "memory"},
},
},
}
f, err := validateSystemRequirements(mountInt)
assert.NoError(t, err)
assert.True(t, f.cpuHardcapping, "cpu hardcapping is expected to be enabled")
}

View file

@ -0,0 +1,59 @@
/*
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 cm
import (
"github.com/golang/glog"
"k8s.io/kubernetes/pkg/api/v1"
)
type containerManagerStub struct{}
var _ ContainerManager = &containerManagerStub{}
func (cm *containerManagerStub) Start(_ *v1.Node) error {
glog.V(2).Infof("Starting stub container manager")
return nil
}
func (cm *containerManagerStub) SystemCgroupsLimit() v1.ResourceList {
return v1.ResourceList{}
}
func (cm *containerManagerStub) GetNodeConfig() NodeConfig {
return NodeConfig{}
}
func (cm *containerManagerStub) GetMountedSubsystems() *CgroupSubsystems {
return &CgroupSubsystems{}
}
func (cm *containerManagerStub) GetQOSContainersInfo() QOSContainersInfo {
return QOSContainersInfo{}
}
func (cm *containerManagerStub) Status() Status {
return Status{}
}
func (cm *containerManagerStub) NewPodContainerManager() PodContainerManager {
return &podContainerManagerStub{}
}
func NewStubContainerManager() ContainerManager {
return &containerManagerStub{}
}

View file

@ -0,0 +1,64 @@
// +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 cm
import (
"fmt"
"k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/kubelet/cadvisor"
"k8s.io/kubernetes/pkg/util/mount"
)
type unsupportedContainerManager struct {
}
var _ ContainerManager = &unsupportedContainerManager{}
func (unsupportedContainerManager) Start(_ *v1.Node) error {
return fmt.Errorf("Container Manager is unsupported in this build")
}
func (unsupportedContainerManager) SystemCgroupsLimit() v1.ResourceList {
return v1.ResourceList{}
}
func (unsupportedContainerManager) GetNodeConfig() NodeConfig {
return NodeConfig{}
}
func (unsupportedContainerManager) GetMountedSubsystems() *CgroupSubsystems {
return &CgroupSubsystems{}
}
func (unsupportedContainerManager) GetQOSContainersInfo() QOSContainersInfo {
return QOSContainersInfo{}
}
func (cm *unsupportedContainerManager) Status() Status {
return Status{}
}
func (cm *unsupportedContainerManager) NewPodContainerManager() PodContainerManager {
return &unsupportedPodContainerManager{}
}
func NewContainerManager(_ mount.Interface, _ cadvisor.Interface, _ NodeConfig, failSwapOn bool) (ContainerManager, error) {
return &unsupportedContainerManager{}, nil
}

View file

@ -0,0 +1,89 @@
// +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 cm
import (
"fmt"
"k8s.io/kubernetes/pkg/util/mount"
)
type fakeMountInterface struct {
mountPoints []mount.MountPoint
}
func (mi *fakeMountInterface) Mount(source string, target string, fstype string, options []string) error {
return fmt.Errorf("unsupported")
}
func (mi *fakeMountInterface) Unmount(target string) error {
return fmt.Errorf("unsupported")
}
func (mi *fakeMountInterface) List() ([]mount.MountPoint, error) {
return mi.mountPoints, nil
}
func (mi *fakeMountInterface) IsLikelyNotMountPoint(file string) (bool, error) {
return false, fmt.Errorf("unsupported")
}
func (mi *fakeMountInterface) DeviceOpened(pathname string) (bool, error) {
for _, mp := range mi.mountPoints {
if mp.Device == pathname {
return true, nil
}
}
return false, nil
}
func (mi *fakeMountInterface) PathIsDevice(pathname string) (bool, error) {
return true, nil
}
func (mi *fakeMountInterface) GetDeviceNameFromMount(mountPath, pluginDir string) (string, error) {
return "", nil
}
func fakeContainerMgrMountInt() mount.Interface {
return &fakeMountInterface{
[]mount.MountPoint{
{
Device: "cgroup",
Type: "cgroup",
Opts: []string{"rw", "relatime", "cpuset"},
},
{
Device: "cgroup",
Type: "cgroup",
Opts: []string{"rw", "relatime", "cpu"},
},
{
Device: "cgroup",
Type: "cgroup",
Opts: []string{"rw", "relatime", "cpuacct"},
},
{
Device: "cgroup",
Type: "cgroup",
Opts: []string{"rw", "relatime", "memory"},
},
},
}
}

View file

@ -0,0 +1,42 @@
// +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 cm
import (
"github.com/golang/glog"
"k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/kubelet/cadvisor"
"k8s.io/kubernetes/pkg/util/mount"
)
type containerManagerImpl struct {
containerManagerStub
}
var _ ContainerManager = &containerManagerImpl{}
func (cm *containerManagerImpl) Start(_ *v1.Node) error {
glog.V(2).Infof("Starting Windows stub container manager")
return nil
}
func NewContainerManager(mountUtil mount.Interface, cadvisorInterface cadvisor.Interface, nodeConfig NodeConfig, failSwapOn bool) (ContainerManager, error) {
return &containerManagerImpl{}, nil
}

View file

@ -0,0 +1,184 @@
/*
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 (
"bufio"
"fmt"
"os"
"path/filepath"
"strconv"
libcontainercgroups "github.com/opencontainers/runc/libcontainer/cgroups"
"k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/kubelet/qos"
)
const (
// Taken from lmctfy https://github.com/google/lmctfy/blob/master/lmctfy/controllers/cpu_controller.cc
MinShares = 2
SharesPerCPU = 1024
MilliCPUToCPU = 1000
// 100000 is equivalent to 100ms
QuotaPeriod = 100000
MinQuotaPeriod = 1000
)
// MilliCPUToQuota converts milliCPU to CFS quota and period values.
func MilliCPUToQuota(milliCPU int64) (quota int64, period int64) {
// CFS quota is measured in two values:
// - cfs_period_us=100ms (the amount of time to measure usage across)
// - cfs_quota=20ms (the amount of cpu time allowed to be used across a period)
// so in the above example, you are limited to 20% of a single CPU
// for multi-cpu environments, you just scale equivalent amounts
if milliCPU == 0 {
return
}
// we set the period to 100ms by default
period = QuotaPeriod
// we then convert your milliCPU to a value normalized over a period
quota = (milliCPU * QuotaPeriod) / MilliCPUToCPU
// quota needs to be a minimum of 1ms.
if quota < MinQuotaPeriod {
quota = MinQuotaPeriod
}
return
}
// MilliCPUToShares converts the milliCPU to CFS shares.
func MilliCPUToShares(milliCPU int64) int64 {
if milliCPU == 0 {
// Docker converts zero milliCPU to unset, which maps to kernel default
// for unset: 1024. Return 2 here to really match kernel default for
// zero milliCPU.
return MinShares
}
// Conceptually (milliCPU / milliCPUToCPU) * sharesPerCPU, but factored to improve rounding.
shares := (milliCPU * SharesPerCPU) / MilliCPUToCPU
if shares < MinShares {
return MinShares
}
return shares
}
// ResourceConfigForPod takes the input pod and outputs the cgroup resource config.
func ResourceConfigForPod(pod *v1.Pod) *ResourceConfig {
// sum requests and limits, track if limits were applied for each resource.
cpuRequests := int64(0)
cpuLimits := int64(0)
memoryLimits := int64(0)
memoryLimitsDeclared := true
cpuLimitsDeclared := true
for _, container := range pod.Spec.Containers {
cpuRequests += container.Resources.Requests.Cpu().MilliValue()
cpuLimits += container.Resources.Limits.Cpu().MilliValue()
if container.Resources.Limits.Cpu().IsZero() {
cpuLimitsDeclared = false
}
memoryLimits += container.Resources.Limits.Memory().Value()
if container.Resources.Limits.Memory().IsZero() {
memoryLimitsDeclared = false
}
}
// convert to CFS values
cpuShares := MilliCPUToShares(cpuRequests)
cpuQuota, cpuPeriod := MilliCPUToQuota(cpuLimits)
// determine the qos class
qosClass := qos.GetPodQOS(pod)
// build the result
result := &ResourceConfig{}
if qosClass == v1.PodQOSGuaranteed {
result.CpuShares = &cpuShares
result.CpuQuota = &cpuQuota
result.CpuPeriod = &cpuPeriod
result.Memory = &memoryLimits
} else if qosClass == v1.PodQOSBurstable {
result.CpuShares = &cpuShares
if cpuLimitsDeclared {
result.CpuQuota = &cpuQuota
result.CpuPeriod = &cpuPeriod
}
if memoryLimitsDeclared {
result.Memory = &memoryLimits
}
} else {
shares := int64(MinShares)
result.CpuShares = &shares
}
return result
}
// GetCgroupSubsystems returns information about the mounted cgroup subsystems
func GetCgroupSubsystems() (*CgroupSubsystems, error) {
// get all cgroup mounts.
allCgroups, err := libcontainercgroups.GetCgroupMounts(true)
if err != nil {
return &CgroupSubsystems{}, err
}
if len(allCgroups) == 0 {
return &CgroupSubsystems{}, fmt.Errorf("failed to find cgroup mounts")
}
mountPoints := make(map[string]string, len(allCgroups))
for _, mount := range allCgroups {
for _, subsystem := range mount.Subsystems {
mountPoints[subsystem] = mount.Mountpoint
}
}
return &CgroupSubsystems{
Mounts: allCgroups,
MountPoints: mountPoints,
}, nil
}
// getCgroupProcs takes a cgroup directory name as an argument
// reads through the cgroup's procs file and returns a list of tgid's.
// It returns an empty list if a procs file doesn't exists
func getCgroupProcs(dir string) ([]int, error) {
procsFile := filepath.Join(dir, "cgroup.procs")
f, err := os.Open(procsFile)
if err != nil {
if os.IsNotExist(err) {
// The procsFile does not exist, So no pids attached to this directory
return []int{}, nil
}
return nil, err
}
defer f.Close()
s := bufio.NewScanner(f)
out := []int{}
for s.Scan() {
if t := s.Text(); t != "" {
pid, err := strconv.Atoi(t)
if err != nil {
return nil, fmt.Errorf("unexpected line in %v; could not convert to pid: %v", procsFile, err)
}
out = append(out, pid)
}
}
return out, nil
}

View file

@ -0,0 +1,199 @@
// +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 cm
import (
"reflect"
"testing"
"k8s.io/kubernetes/pkg/api/resource"
"k8s.io/kubernetes/pkg/api/v1"
)
// getResourceList returns a ResourceList with the
// specified cpu and memory resource values
func getResourceList(cpu, memory string) v1.ResourceList {
res := v1.ResourceList{}
if cpu != "" {
res[v1.ResourceCPU] = resource.MustParse(cpu)
}
if memory != "" {
res[v1.ResourceMemory] = resource.MustParse(memory)
}
return res
}
// getResourceRequirements returns a ResourceRequirements object
func getResourceRequirements(requests, limits v1.ResourceList) v1.ResourceRequirements {
res := v1.ResourceRequirements{}
res.Requests = requests
res.Limits = limits
return res
}
func TestResourceConfigForPod(t *testing.T) {
minShares := int64(MinShares)
burstableShares := MilliCPUToShares(100)
memoryQuantity := resource.MustParse("200Mi")
burstableMemory := memoryQuantity.Value()
burstablePartialShares := MilliCPUToShares(200)
burstableQuota, burstablePeriod := MilliCPUToQuota(200)
guaranteedShares := MilliCPUToShares(100)
guaranteedQuota, guaranteedPeriod := MilliCPUToQuota(100)
memoryQuantity = resource.MustParse("100Mi")
guaranteedMemory := memoryQuantity.Value()
testCases := map[string]struct {
pod *v1.Pod
expected *ResourceConfig
}{
"besteffort": {
pod: &v1.Pod{
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Resources: getResourceRequirements(getResourceList("", ""), getResourceList("", "")),
},
},
},
},
expected: &ResourceConfig{CpuShares: &minShares},
},
"burstable-no-limits": {
pod: &v1.Pod{
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Resources: getResourceRequirements(getResourceList("100m", "100Mi"), getResourceList("", "")),
},
},
},
},
expected: &ResourceConfig{CpuShares: &burstableShares},
},
"burstable-with-limits": {
pod: &v1.Pod{
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Resources: getResourceRequirements(getResourceList("100m", "100Mi"), getResourceList("200m", "200Mi")),
},
},
},
},
expected: &ResourceConfig{CpuShares: &burstableShares, CpuQuota: &burstableQuota, CpuPeriod: &burstablePeriod, Memory: &burstableMemory},
},
"burstable-partial-limits": {
pod: &v1.Pod{
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Resources: getResourceRequirements(getResourceList("100m", "100Mi"), getResourceList("200m", "200Mi")),
},
{
Resources: getResourceRequirements(getResourceList("100m", "100Mi"), getResourceList("", "")),
},
},
},
},
expected: &ResourceConfig{CpuShares: &burstablePartialShares},
},
"guaranteed": {
pod: &v1.Pod{
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Resources: getResourceRequirements(getResourceList("100m", "100Mi"), getResourceList("100m", "100Mi")),
},
},
},
},
expected: &ResourceConfig{CpuShares: &guaranteedShares, CpuQuota: &guaranteedQuota, CpuPeriod: &guaranteedPeriod, Memory: &guaranteedMemory},
},
}
for testName, testCase := range testCases {
actual := ResourceConfigForPod(testCase.pod)
if !reflect.DeepEqual(actual.CpuPeriod, testCase.expected.CpuPeriod) {
t.Errorf("unexpected result, test: %v, cpu period not as expected", testName)
}
if !reflect.DeepEqual(actual.CpuQuota, testCase.expected.CpuQuota) {
t.Errorf("unexpected result, test: %v, cpu quota not as expected", testName)
}
if !reflect.DeepEqual(actual.CpuShares, testCase.expected.CpuShares) {
t.Errorf("unexpected result, test: %v, cpu shares not as expected", testName)
}
if !reflect.DeepEqual(actual.Memory, testCase.expected.Memory) {
t.Errorf("unexpected result, test: %v, memory not as expected", testName)
}
}
}
func TestMilliCPUToQuota(t *testing.T) {
testCases := []struct {
input int64
quota int64
period int64
}{
{
input: int64(0),
quota: int64(0),
period: int64(0),
},
{
input: int64(5),
quota: int64(1000),
period: int64(100000),
},
{
input: int64(9),
quota: int64(1000),
period: int64(100000),
},
{
input: int64(10),
quota: int64(1000),
period: int64(100000),
},
{
input: int64(200),
quota: int64(20000),
period: int64(100000),
},
{
input: int64(500),
quota: int64(50000),
period: int64(100000),
},
{
input: int64(1000),
quota: int64(100000),
period: int64(100000),
},
{
input: int64(1500),
quota: int64(150000),
period: int64(100000),
},
}
for _, testCase := range testCases {
quota, period := MilliCPUToQuota(testCase.input)
if quota != testCase.quota || period != testCase.period {
t.Errorf("Input %v, expected quota %v period %v, but got quota %v period %v", testCase.input, testCase.quota, testCase.period, quota, period)
}
}
}

View file

@ -0,0 +1,54 @@
// +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 "k8s.io/kubernetes/pkg/api/v1"
const (
MinShares = 0
SharesPerCPU = 0
MilliCPUToCPU = 0
QuotaPeriod = 0
MinQuotaPeriod = 0
)
// MilliCPUToQuota converts milliCPU to CFS quota and period values.
func MilliCPUToQuota(milliCPU int64) (int64, int64) {
return 0, 0
}
// MilliCPUToShares converts the milliCPU to CFS shares.
func MilliCPUToShares(milliCPU int64) int64 {
return 0
}
// ResourceConfigForPod takes the input pod and outputs the cgroup resource config.
func ResourceConfigForPod(pod *v1.Pod) *ResourceConfig {
return nil
}
// GetCgroupSubsystems returns information about the mounted cgroup subsystems
func GetCgroupSubsystems() (*CgroupSubsystems, error) {
return nil, nil
}
func getCgroupProcs(dir string) ([]int, error) {
return nil, nil
}

View file

@ -0,0 +1,263 @@
/*
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"
"os"
"path"
"strings"
"github.com/golang/glog"
"k8s.io/apimachinery/pkg/types"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/kubelet/qos"
)
const (
podCgroupNamePrefix = "pod"
)
// podContainerManagerImpl implements podContainerManager interface.
// It is the general implementation which allows pod level container
// management if qos Cgroup is enabled.
type podContainerManagerImpl struct {
// nodeInfo stores information about the node resource capacity
nodeInfo *v1.Node
// qosContainersInfo hold absolute paths of the top level qos containers
qosContainersInfo QOSContainersInfo
// Stores the mounted cgroup subsystems
subsystems *CgroupSubsystems
// cgroupManager is the cgroup Manager Object responsible for managing all
// pod cgroups.
cgroupManager CgroupManager
}
// Make sure that podContainerManagerImpl implements the PodContainerManager interface
var _ PodContainerManager = &podContainerManagerImpl{}
// applyLimits sets pod cgroup resource limits
// It also updates the resource limits on top level qos containers.
func (m *podContainerManagerImpl) applyLimits(pod *v1.Pod) error {
// This function will house the logic for setting the resource parameters
// on the pod container config and updating top level qos container configs
return nil
}
// Exists checks if the pod's cgroup already exists
func (m *podContainerManagerImpl) Exists(pod *v1.Pod) bool {
podContainerName, _ := m.GetPodContainerName(pod)
return m.cgroupManager.Exists(podContainerName)
}
// EnsureExists takes a pod as argument and makes sure that
// pod cgroup exists if qos cgroup hierarchy flag is enabled.
// If the pod level container doesen't already exist it is created.
func (m *podContainerManagerImpl) EnsureExists(pod *v1.Pod) error {
podContainerName, _ := m.GetPodContainerName(pod)
// check if container already exist
alreadyExists := m.Exists(pod)
if !alreadyExists {
// Create the pod container
containerConfig := &CgroupConfig{
Name: podContainerName,
ResourceParameters: ResourceConfigForPod(pod),
}
if err := m.cgroupManager.Create(containerConfig); err != nil {
return fmt.Errorf("failed to create container for %v : %v", podContainerName, err)
}
}
// Apply appropriate resource limits on the pod container
// Top level qos containers limits are not updated
// until we figure how to maintain the desired state in the kubelet.
// Because maintaining the desired state is difficult without checkpointing.
if err := m.applyLimits(pod); err != nil {
return fmt.Errorf("failed to apply resource limits on container for %v : %v", podContainerName, err)
}
return nil
}
// GetPodContainerName returns the CgroupName identifer, and its literal cgroupfs form on the host.
func (m *podContainerManagerImpl) GetPodContainerName(pod *v1.Pod) (CgroupName, string) {
podQOS := qos.GetPodQOS(pod)
// Get the parent QOS container name
var parentContainer string
switch podQOS {
case v1.PodQOSGuaranteed:
parentContainer = m.qosContainersInfo.Guaranteed
case v1.PodQOSBurstable:
parentContainer = m.qosContainersInfo.Burstable
case v1.PodQOSBestEffort:
parentContainer = m.qosContainersInfo.BestEffort
}
podContainer := podCgroupNamePrefix + string(pod.UID)
// Get the absolute path of the cgroup
cgroupName := (CgroupName)(path.Join(parentContainer, podContainer))
// Get the literal cgroupfs name
cgroupfsName := m.cgroupManager.Name(cgroupName)
return cgroupName, cgroupfsName
}
// Scan through the whole cgroup directory and kill all processes either
// attached to the pod cgroup or to a container cgroup under the pod cgroup
func (m *podContainerManagerImpl) tryKillingCgroupProcesses(podCgroup CgroupName) error {
pidsToKill := m.cgroupManager.Pids(podCgroup)
// No pids charged to the terminated pod cgroup return
if len(pidsToKill) == 0 {
return nil
}
var errlist []error
// os.Kill often errors out,
// We try killing all the pids multiple times
for i := 0; i < 5; i++ {
if i != 0 {
glog.V(3).Infof("Attempt %v failed to kill all unwanted process. Retyring", i)
}
errlist = []error{}
for _, pid := range pidsToKill {
p, err := os.FindProcess(pid)
if err != nil {
// Process not running anymore, do nothing
continue
}
glog.V(3).Infof("Attempt to kill process with pid: %v", pid)
if err := p.Kill(); err != nil {
glog.V(3).Infof("failed to kill process with pid: %v", pid)
errlist = append(errlist, err)
}
}
if len(errlist) == 0 {
glog.V(3).Infof("successfully killed all unwanted processes.")
return nil
}
}
return utilerrors.NewAggregate(errlist)
}
// Destroy destroys the pod container cgroup paths
func (m *podContainerManagerImpl) Destroy(podCgroup CgroupName) error {
// Try killing all the processes attached to the pod cgroup
if err := m.tryKillingCgroupProcesses(podCgroup); err != nil {
glog.V(3).Infof("failed to kill all the processes attached to the %v cgroups", podCgroup)
return fmt.Errorf("failed to kill all the processes attached to the %v cgroups : %v", podCgroup, err)
}
// Now its safe to remove the pod's cgroup
containerConfig := &CgroupConfig{
Name: podCgroup,
ResourceParameters: &ResourceConfig{},
}
if err := m.cgroupManager.Destroy(containerConfig); err != nil {
return fmt.Errorf("failed to delete cgroup paths for %v : %v", podCgroup, err)
}
return nil
}
// ReduceCPULimits reduces the CPU CFS values to the minimum amount of shares.
func (m *podContainerManagerImpl) ReduceCPULimits(podCgroup CgroupName) error {
return m.cgroupManager.ReduceCPULimits(podCgroup)
}
// GetAllPodsFromCgroups scans through all the subsytems of pod cgroups
// Get list of pods whose cgroup still exist on the cgroup mounts
func (m *podContainerManagerImpl) GetAllPodsFromCgroups() (map[types.UID]CgroupName, error) {
// Map for storing all the found pods on the disk
foundPods := make(map[types.UID]CgroupName)
qosContainersList := [3]string{m.qosContainersInfo.BestEffort, m.qosContainersInfo.Burstable, m.qosContainersInfo.Guaranteed}
// Scan through all the subsystem mounts
// and through each QoS cgroup directory for each subsystem mount
// If a pod cgroup exists in even a single subsystem mount
// we will attempt to delete it
for _, val := range m.subsystems.MountPoints {
for _, qosContainerName := range qosContainersList {
// get the subsystems QoS cgroup absolute name
qcConversion := m.cgroupManager.Name(CgroupName(qosContainerName))
qc := path.Join(val, qcConversion)
dirInfo, err := ioutil.ReadDir(qc)
if err != nil {
return nil, fmt.Errorf("failed to read the cgroup directory %v : %v", qc, err)
}
for i := range dirInfo {
// note: we do a contains check because on systemd, the literal cgroupfs name will prefix the qos as well.
if dirInfo[i].IsDir() && strings.Contains(dirInfo[i].Name(), podCgroupNamePrefix) {
// we need to convert the name to an internal identifier
internalName := m.cgroupManager.CgroupName(dirInfo[i].Name())
// we then split the name on the pod prefix to determine the uid
parts := strings.Split(string(internalName), podCgroupNamePrefix)
// the uid is missing, so we log the unexpected cgroup not of form pod<uid>
if len(parts) != 2 {
location := path.Join(qc, dirInfo[i].Name())
glog.Errorf("pod cgroup manager ignoring unexpected cgroup %v because it is not a pod", location)
continue
}
podUID := parts[1]
// because the literal cgroupfs name could encode the qos tier (on systemd), we avoid double encoding
// by just rebuilding the fully qualified CgroupName according to our internal convention.
cgroupName := CgroupName(path.Join(qosContainerName, podCgroupNamePrefix+podUID))
foundPods[types.UID(podUID)] = cgroupName
}
}
}
}
return foundPods, nil
}
// podContainerManagerNoop implements podContainerManager interface.
// It is a no-op implementation and basically does nothing
// podContainerManagerNoop is used in case the QoS cgroup Hierarchy is not
// enabled, so Exists() returns true always as the cgroupRoot
// is expected to always exist.
type podContainerManagerNoop struct {
cgroupRoot CgroupName
}
// Make sure that podContainerManagerStub implements the PodContainerManager interface
var _ PodContainerManager = &podContainerManagerNoop{}
func (m *podContainerManagerNoop) Exists(_ *v1.Pod) bool {
return true
}
func (m *podContainerManagerNoop) EnsureExists(_ *v1.Pod) error {
return nil
}
func (m *podContainerManagerNoop) GetPodContainerName(_ *v1.Pod) (CgroupName, string) {
return m.cgroupRoot, string(m.cgroupRoot)
}
func (m *podContainerManagerNoop) GetPodContainerNameForDriver(_ *v1.Pod) string {
return ""
}
// Destroy destroys the pod container cgroup paths
func (m *podContainerManagerNoop) Destroy(_ CgroupName) error {
return nil
}
func (m *podContainerManagerNoop) ReduceCPULimits(_ CgroupName) error {
return nil
}
func (m *podContainerManagerNoop) GetAllPodsFromCgroups() (map[types.UID]CgroupName, error) {
return nil, nil
}

View file

@ -0,0 +1,51 @@
/*
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 (
"k8s.io/apimachinery/pkg/types"
"k8s.io/kubernetes/pkg/api/v1"
)
type podContainerManagerStub struct {
}
var _ PodContainerManager = &podContainerManagerStub{}
func (m *podContainerManagerStub) Exists(_ *v1.Pod) bool {
return true
}
func (m *podContainerManagerStub) EnsureExists(_ *v1.Pod) error {
return nil
}
func (m *podContainerManagerStub) GetPodContainerName(_ *v1.Pod) (CgroupName, string) {
return "", ""
}
func (m *podContainerManagerStub) Destroy(_ CgroupName) error {
return nil
}
func (m *podContainerManagerStub) ReduceCPULimits(_ CgroupName) error {
return nil
}
func (m *podContainerManagerStub) GetAllPodsFromCgroups() (map[types.UID]CgroupName, error) {
return nil, nil
}

View file

@ -0,0 +1,53 @@
// +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 (
"k8s.io/apimachinery/pkg/types"
"k8s.io/kubernetes/pkg/api/v1"
)
type unsupportedPodContainerManager struct {
}
var _ PodContainerManager = &unsupportedPodContainerManager{}
func (m *unsupportedPodContainerManager) Exists(_ *v1.Pod) bool {
return true
}
func (m *unsupportedPodContainerManager) EnsureExists(_ *v1.Pod) error {
return nil
}
func (m *unsupportedPodContainerManager) GetPodContainerName(_ *v1.Pod) (CgroupName, string) {
return "", ""
}
func (m *unsupportedPodContainerManager) ReduceCPULimits(_ CgroupName) error {
return nil
}
func (m *unsupportedPodContainerManager) GetAllPodsFromCgroups() (map[types.UID]CgroupName, error) {
return nil, nil
}
func (m *unsupportedPodContainerManager) Destroy(name CgroupName) error {
return nil
}

107
vendor/k8s.io/kubernetes/pkg/kubelet/cm/types.go generated vendored Normal file
View file

@ -0,0 +1,107 @@
/*
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 (
"k8s.io/apimachinery/pkg/types"
"k8s.io/kubernetes/pkg/api/v1"
)
// ResourceConfig holds information about all the supported cgroup resource parameters.
type ResourceConfig struct {
// Memory limit (in bytes).
Memory *int64
// CPU shares (relative weight vs. other containers).
CpuShares *int64
// CPU hardcap limit (in usecs). Allowed cpu time in a given period.
CpuQuota *int64
// CPU quota period.
CpuPeriod *int64
}
// CgroupName is the abstract name of a cgroup prior to any driver specific conversion.
type CgroupName string
// CgroupConfig holds the cgroup configuration information.
// This is common object which is used to specify
// cgroup information to both systemd and raw cgroup fs
// implementation of the Cgroup Manager interface.
type CgroupConfig struct {
// Fully qualified name prior to any driver specific conversions.
Name CgroupName
// ResourceParameters contains various cgroups settings to apply.
ResourceParameters *ResourceConfig
}
// CgroupManager allows for cgroup management.
// Supports Cgroup Creation ,Deletion and Updates.
type CgroupManager interface {
// Create creates and applies the cgroup configurations on the cgroup.
// It just creates the leaf cgroups.
// It expects the parent cgroup to already exist.
Create(*CgroupConfig) error
// Destroy the cgroup.
Destroy(*CgroupConfig) error
// Update cgroup configuration.
Update(*CgroupConfig) error
// Exists checks if the cgroup already exists
Exists(name CgroupName) bool
// Name returns the literal cgroupfs name on the host after any driver specific conversions.
// We would expect systemd implementation to make appropriate name conversion.
// For example, if we pass /foo/bar
// then systemd should convert the name to something like
// foo.slice/foo-bar.slice
Name(name CgroupName) string
// CgroupName converts the literal cgroupfs name on the host to an internal identifier.
CgroupName(name string) CgroupName
// Pids scans through all subsytems to find pids associated with specified cgroup.
Pids(name CgroupName) []int
// ReduceCPULimits reduces the CPU CFS values to the minimum amount of shares.
ReduceCPULimits(cgroupName CgroupName) error
}
// QOSContainersInfo stores the names of containers per qos
type QOSContainersInfo struct {
Guaranteed string
BestEffort string
Burstable string
}
// PodContainerManager stores and manages pod level containers
// The Pod workers interact with the PodContainerManager to create and destroy
// containers for the pod.
type PodContainerManager interface {
// GetPodContainerName returns the CgroupName identifer, and its literal cgroupfs form on the host.
GetPodContainerName(*v1.Pod) (CgroupName, string)
// EnsureExists takes a pod as argument and makes sure that
// pod cgroup exists if qos cgroup hierarchy flag is enabled.
// If the pod cgroup doesen't already exist this method creates it.
EnsureExists(*v1.Pod) error
// Exists returns true if the pod cgroup exists.
Exists(*v1.Pod) bool
// Destroy takes a pod Cgroup name as argument and destroys the pod's container.
Destroy(name CgroupName) error
// ReduceCPULimits reduces the CPU CFS values to the minimum amount of shares.
ReduceCPULimits(name CgroupName) error
// GetAllPodsFromCgroups enumerates the set of pod uids to their associated cgroup based on state of cgroupfs system.
GetAllPodsFromCgroups() (map[types.UID]CgroupName, error)
}

31
vendor/k8s.io/kubernetes/pkg/kubelet/cm/util/BUILD generated vendored Normal file
View file

@ -0,0 +1,31 @@
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["cgroups_linux.go"],
tags = ["automanaged"],
deps = [
"//vendor:github.com/opencontainers/runc/libcontainer/cgroups",
"//vendor:github.com/opencontainers/runc/libcontainer/utils",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View file

@ -0,0 +1,76 @@
/*
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 util
import (
"path/filepath"
libcontainercgroups "github.com/opencontainers/runc/libcontainer/cgroups"
libcontainerutils "github.com/opencontainers/runc/libcontainer/utils"
)
// Forked from opencontainers/runc/libcontainer/cgroup/fs.Manager.GetPids()
func GetPids(cgroupPath string) ([]int, error) {
dir, err := getCgroupPath(cgroupPath)
if err != nil {
return nil, err
}
return libcontainercgroups.GetPids(dir)
}
// getCgroupPath gets the file path to the "devices" subsystem of the desired cgroup.
// cgroupPath is the path in the cgroup hierarchy.
func getCgroupPath(cgroupPath string) (string, error) {
cgroupPath = libcontainerutils.CleanPath(cgroupPath)
mnt, root, err := libcontainercgroups.FindCgroupMountpointAndRoot("devices")
// If we didn't mount the subsystem, there is no point we make the path.
if err != nil {
return "", err
}
// If the cgroup name/path is absolute do not look relative to the cgroup of the init process.
if filepath.IsAbs(cgroupPath) {
// Sometimes subsystems can be mounted togethger as 'cpu,cpuacct'.
return filepath.Join(root, mnt, cgroupPath), nil
}
parentPath, err := getCgroupParentPath(mnt, root)
if err != nil {
return "", err
}
return filepath.Join(parentPath, cgroupPath), nil
}
// getCgroupParentPath gets the parent filepath to this cgroup, for resolving relative cgroup paths.
func getCgroupParentPath(mountpoint, root string) (string, error) {
// Use GetThisCgroupDir instead of GetInitCgroupDir, because the creating
// process could in container and shared pid namespace with host, and
// /proc/1/cgroup could point to whole other world of cgroups.
initPath, err := libcontainercgroups.GetThisCgroupDir("devices")
if err != nil {
return "", err
}
// This is needed for nested containers, because in /proc/self/cgroup we
// see paths from host, which don't exist in container.
relDir, err := filepath.Rel(root, initPath)
if err != nil {
return "", err
}
return filepath.Join(mountpoint, relDir), nil
}

View file

@ -0,0 +1,23 @@
// +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 util
func GetPids(cgroupPath string) ([]int, error) {
return nil, nil
}

90
vendor/k8s.io/kubernetes/pkg/kubelet/config/BUILD generated vendored Normal file
View file

@ -0,0 +1,90 @@
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = [
"apiserver.go",
"common.go",
"config.go",
"doc.go",
"file.go",
"file_linux.go",
"http.go",
"sources.go",
],
tags = ["automanaged"],
deps = [
"//pkg/api:go_default_library",
"//pkg/api/v1:go_default_library",
"//pkg/api/v1/pod:go_default_library",
"//pkg/api/validation:go_default_library",
"//pkg/client/cache:go_default_library",
"//pkg/client/clientset_generated/clientset:go_default_library",
"//pkg/client/record:go_default_library",
"//pkg/fields:go_default_library",
"//pkg/kubelet/container:go_default_library",
"//pkg/kubelet/events:go_default_library",
"//pkg/kubelet/types:go_default_library",
"//pkg/kubelet/util/format:go_default_library",
"//pkg/util/config:go_default_library",
"//pkg/util/hash:go_default_library",
"//vendor:github.com/golang/glog",
"//vendor:golang.org/x/exp/inotify",
"//vendor:k8s.io/apimachinery/pkg/runtime",
"//vendor:k8s.io/apimachinery/pkg/types",
"//vendor:k8s.io/apimachinery/pkg/util/sets",
"//vendor:k8s.io/apimachinery/pkg/util/validation/field",
"//vendor:k8s.io/apimachinery/pkg/util/wait",
"//vendor:k8s.io/apimachinery/pkg/util/yaml",
],
)
go_test(
name = "go_default_test",
srcs = [
"apiserver_test.go",
"common_test.go",
"config_test.go",
"file_linux_test.go",
"http_test.go",
],
library = ":go_default_library",
tags = ["automanaged"],
deps = [
"//pkg/api:go_default_library",
"//pkg/api/testapi:go_default_library",
"//pkg/api/v1:go_default_library",
"//pkg/api/validation:go_default_library",
"//pkg/client/cache:go_default_library",
"//pkg/client/record:go_default_library",
"//pkg/kubelet/types:go_default_library",
"//pkg/securitycontext:go_default_library",
"//pkg/util/testing:go_default_library",
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
"//vendor:k8s.io/apimachinery/pkg/runtime",
"//vendor:k8s.io/apimachinery/pkg/types",
"//vendor:k8s.io/apimachinery/pkg/util/wait",
"//vendor:k8s.io/apimachinery/pkg/watch",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View file

@ -0,0 +1,46 @@
/*
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.
*/
// Reads the pod configuration from the Kubernetes apiserver.
package config
import (
"k8s.io/apimachinery/pkg/types"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/client/cache"
"k8s.io/kubernetes/pkg/client/clientset_generated/clientset"
"k8s.io/kubernetes/pkg/fields"
kubetypes "k8s.io/kubernetes/pkg/kubelet/types"
)
// NewSourceApiserver creates a config source that watches and pulls from the apiserver.
func NewSourceApiserver(c *clientset.Clientset, nodeName types.NodeName, updates chan<- interface{}) {
lw := cache.NewListWatchFromClient(c.Core().RESTClient(), "pods", v1.NamespaceAll, fields.OneTermEqualSelector(api.PodHostField, string(nodeName)))
newSourceApiserverFromLW(lw, updates)
}
// newSourceApiserverFromLW holds creates a config source that watches and pulls from the apiserver.
func newSourceApiserverFromLW(lw cache.ListerWatcher, updates chan<- interface{}) {
send := func(objs []interface{}) {
var pods []*v1.Pod
for _, o := range objs {
pods = append(pods, o.(*v1.Pod))
}
updates <- kubetypes.PodUpdate{Pods: pods, Op: kubetypes.SET, Source: kubetypes.ApiserverSource}
}
cache.NewReflector(lw, &v1.Pod{}, cache.NewUndeltaStore(send, cache.MetaNamespaceKeyFunc), 0).Run()
}

View file

@ -0,0 +1,193 @@
/*
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 config
import (
"testing"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/client/cache"
kubetypes "k8s.io/kubernetes/pkg/kubelet/types"
)
type fakePodLW struct {
listResp runtime.Object
watchResp watch.Interface
}
func (lw fakePodLW) List(options v1.ListOptions) (runtime.Object, error) {
return lw.listResp, nil
}
func (lw fakePodLW) Watch(options v1.ListOptions) (watch.Interface, error) {
return lw.watchResp, nil
}
var _ cache.ListerWatcher = fakePodLW{}
func TestNewSourceApiserver_UpdatesAndMultiplePods(t *testing.T) {
pod1v1 := &v1.Pod{
ObjectMeta: v1.ObjectMeta{Name: "p"},
Spec: v1.PodSpec{Containers: []v1.Container{{Image: "image/one"}}}}
pod1v2 := &v1.Pod{
ObjectMeta: v1.ObjectMeta{Name: "p"},
Spec: v1.PodSpec{Containers: []v1.Container{{Image: "image/two"}}}}
pod2 := &v1.Pod{
ObjectMeta: v1.ObjectMeta{Name: "q"},
Spec: v1.PodSpec{Containers: []v1.Container{{Image: "image/blah"}}}}
// Setup fake api client.
fakeWatch := watch.NewFake()
lw := fakePodLW{
listResp: &v1.PodList{Items: []v1.Pod{*pod1v1}},
watchResp: fakeWatch,
}
ch := make(chan interface{})
newSourceApiserverFromLW(lw, ch)
got, ok := <-ch
if !ok {
t.Errorf("Unable to read from channel when expected")
}
update := got.(kubetypes.PodUpdate)
expected := CreatePodUpdate(kubetypes.SET, kubetypes.ApiserverSource, pod1v1)
if !api.Semantic.DeepEqual(expected, update) {
t.Errorf("Expected %#v; Got %#v", expected, update)
}
// Add another pod
fakeWatch.Add(pod2)
got, ok = <-ch
if !ok {
t.Errorf("Unable to read from channel when expected")
}
update = got.(kubetypes.PodUpdate)
// Could be sorted either of these two ways:
expectedA := CreatePodUpdate(kubetypes.SET, kubetypes.ApiserverSource, pod1v1, pod2)
expectedB := CreatePodUpdate(kubetypes.SET, kubetypes.ApiserverSource, pod2, pod1v1)
if !api.Semantic.DeepEqual(expectedA, update) && !api.Semantic.DeepEqual(expectedB, update) {
t.Errorf("Expected %#v or %#v, Got %#v", expectedA, expectedB, update)
}
// Modify pod1
fakeWatch.Modify(pod1v2)
got, ok = <-ch
if !ok {
t.Errorf("Unable to read from channel when expected")
}
update = got.(kubetypes.PodUpdate)
expectedA = CreatePodUpdate(kubetypes.SET, kubetypes.ApiserverSource, pod1v2, pod2)
expectedB = CreatePodUpdate(kubetypes.SET, kubetypes.ApiserverSource, pod2, pod1v2)
if !api.Semantic.DeepEqual(expectedA, update) && !api.Semantic.DeepEqual(expectedB, update) {
t.Errorf("Expected %#v or %#v, Got %#v", expectedA, expectedB, update)
}
// Delete pod1
fakeWatch.Delete(pod1v2)
got, ok = <-ch
if !ok {
t.Errorf("Unable to read from channel when expected")
}
update = got.(kubetypes.PodUpdate)
expected = CreatePodUpdate(kubetypes.SET, kubetypes.ApiserverSource, pod2)
if !api.Semantic.DeepEqual(expected, update) {
t.Errorf("Expected %#v, Got %#v", expected, update)
}
// Delete pod2
fakeWatch.Delete(pod2)
got, ok = <-ch
if !ok {
t.Errorf("Unable to read from channel when expected")
}
update = got.(kubetypes.PodUpdate)
expected = CreatePodUpdate(kubetypes.SET, kubetypes.ApiserverSource)
if !api.Semantic.DeepEqual(expected, update) {
t.Errorf("Expected %#v, Got %#v", expected, update)
}
}
func TestNewSourceApiserver_TwoNamespacesSameName(t *testing.T) {
pod1 := v1.Pod{
ObjectMeta: v1.ObjectMeta{Name: "p", Namespace: "one"},
Spec: v1.PodSpec{Containers: []v1.Container{{Image: "image/one"}}}}
pod2 := v1.Pod{
ObjectMeta: v1.ObjectMeta{Name: "p", Namespace: "two"},
Spec: v1.PodSpec{Containers: []v1.Container{{Image: "image/blah"}}}}
// Setup fake api client.
fakeWatch := watch.NewFake()
lw := fakePodLW{
listResp: &v1.PodList{Items: []v1.Pod{pod1, pod2}},
watchResp: fakeWatch,
}
ch := make(chan interface{})
newSourceApiserverFromLW(lw, ch)
got, ok := <-ch
if !ok {
t.Errorf("Unable to read from channel when expected")
}
update := got.(kubetypes.PodUpdate)
// Make sure that we get both pods. Catches bug #2294.
if !(len(update.Pods) == 2) {
t.Errorf("Expected %d, Got %d", 2, len(update.Pods))
}
// Delete pod1
fakeWatch.Delete(&pod1)
got, ok = <-ch
if !ok {
t.Errorf("Unable to read from channel when expected")
}
update = got.(kubetypes.PodUpdate)
if !(len(update.Pods) == 1) {
t.Errorf("Expected %d, Got %d", 1, len(update.Pods))
}
}
func TestNewSourceApiserverInitialEmptySendsEmptyPodUpdate(t *testing.T) {
// Setup fake api client.
fakeWatch := watch.NewFake()
lw := fakePodLW{
listResp: &v1.PodList{Items: []v1.Pod{}},
watchResp: fakeWatch,
}
ch := make(chan interface{})
newSourceApiserverFromLW(lw, ch)
got, ok := <-ch
if !ok {
t.Errorf("Unable to read from channel when expected")
}
update := got.(kubetypes.PodUpdate)
expected := CreatePodUpdate(kubetypes.SET, kubetypes.ApiserverSource)
if !api.Semantic.DeepEqual(expected, update) {
t.Errorf("Expected %#v; Got %#v", expected, update)
}
}

149
vendor/k8s.io/kubernetes/pkg/kubelet/config/common.go generated vendored Normal file
View file

@ -0,0 +1,149 @@
/*
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.
*/
// Common logic used by both http and file channels.
package config
import (
"crypto/md5"
"encoding/hex"
"fmt"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
utilyaml "k8s.io/apimachinery/pkg/util/yaml"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/api/validation"
kubetypes "k8s.io/kubernetes/pkg/kubelet/types"
"k8s.io/kubernetes/pkg/util/hash"
"github.com/golang/glog"
)
// Generate a pod name that is unique among nodes by appending the nodeName.
func generatePodName(name string, nodeName types.NodeName) string {
return fmt.Sprintf("%s-%s", name, nodeName)
}
func applyDefaults(pod *api.Pod, source string, isFile bool, nodeName types.NodeName) error {
if len(pod.UID) == 0 {
hasher := md5.New()
if isFile {
fmt.Fprintf(hasher, "host:%s", nodeName)
fmt.Fprintf(hasher, "file:%s", source)
} else {
fmt.Fprintf(hasher, "url:%s", source)
}
hash.DeepHashObject(hasher, pod)
pod.UID = types.UID(hex.EncodeToString(hasher.Sum(nil)[0:]))
glog.V(5).Infof("Generated UID %q pod %q from %s", pod.UID, pod.Name, source)
}
pod.Name = generatePodName(pod.Name, nodeName)
glog.V(5).Infof("Generated Name %q for UID %q from URL %s", pod.Name, pod.UID, source)
if pod.Namespace == "" {
pod.Namespace = kubetypes.NamespaceDefault
}
glog.V(5).Infof("Using namespace %q for pod %q from %s", pod.Namespace, pod.Name, source)
// Set the Host field to indicate this pod is scheduled on the current node.
pod.Spec.NodeName = string(nodeName)
pod.ObjectMeta.SelfLink = getSelfLink(pod.Name, pod.Namespace)
if pod.Annotations == nil {
pod.Annotations = make(map[string]string)
}
// The generated UID is the hash of the file.
pod.Annotations[kubetypes.ConfigHashAnnotationKey] = string(pod.UID)
// Set the default status to pending.
pod.Status.Phase = api.PodPending
return nil
}
func getSelfLink(name, namespace string) string {
var selfLink string
if len(namespace) == 0 {
namespace = api.NamespaceDefault
}
selfLink = fmt.Sprintf("/api/"+api.Registry.GroupOrDie(api.GroupName).GroupVersion.Version+"/pods/namespaces/%s/%s", name, namespace)
return selfLink
}
type defaultFunc func(pod *api.Pod) error
func tryDecodeSinglePod(data []byte, defaultFn defaultFunc) (parsed bool, pod *v1.Pod, err error) {
// JSON is valid YAML, so this should work for everything.
json, err := utilyaml.ToJSON(data)
if err != nil {
return false, nil, err
}
obj, err := runtime.Decode(api.Codecs.UniversalDecoder(), json)
if err != nil {
return false, pod, err
}
// Check whether the object could be converted to single pod.
if _, ok := obj.(*api.Pod); !ok {
err = fmt.Errorf("invalid pod: %#v", obj)
return false, pod, err
}
newPod := obj.(*api.Pod)
// Apply default values and validate the pod.
if err = defaultFn(newPod); err != nil {
return true, pod, err
}
if errs := validation.ValidatePod(newPod); len(errs) > 0 {
err = fmt.Errorf("invalid pod: %v", errs)
return true, pod, err
}
v1Pod := &v1.Pod{}
if err := v1.Convert_api_Pod_To_v1_Pod(newPod, v1Pod, nil); err != nil {
return true, nil, err
}
return true, v1Pod, nil
}
func tryDecodePodList(data []byte, defaultFn defaultFunc) (parsed bool, pods v1.PodList, err error) {
obj, err := runtime.Decode(api.Codecs.UniversalDecoder(), data)
if err != nil {
return false, pods, err
}
// Check whether the object could be converted to list of pods.
if _, ok := obj.(*api.PodList); !ok {
err = fmt.Errorf("invalid pods list: %#v", obj)
return false, pods, err
}
newPods := obj.(*api.PodList)
// Apply default values and validate pods.
for i := range newPods.Items {
newPod := &newPods.Items[i]
if err = defaultFn(newPod); err != nil {
return true, pods, err
}
if errs := validation.ValidatePod(newPod); len(errs) > 0 {
err = fmt.Errorf("invalid pod: %v", errs)
return true, pods, err
}
}
v1Pods := &v1.PodList{}
if err := v1.Convert_api_PodList_To_v1_PodList(newPods, v1Pods, nil); err != nil {
return true, pods, err
}
return true, *v1Pods, err
}

View file

@ -0,0 +1,157 @@
/*
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 config
import (
"reflect"
"testing"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/testapi"
"k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/securitycontext"
)
func noDefault(*api.Pod) error { return nil }
func TestDecodeSinglePod(t *testing.T) {
grace := int64(30)
pod := &v1.Pod{
TypeMeta: metav1.TypeMeta{
APIVersion: "",
},
ObjectMeta: v1.ObjectMeta{
Name: "test",
UID: "12345",
Namespace: "mynamespace",
},
Spec: v1.PodSpec{
RestartPolicy: v1.RestartPolicyAlways,
DNSPolicy: v1.DNSClusterFirst,
TerminationGracePeriodSeconds: &grace,
Containers: []v1.Container{{
Name: "image",
Image: "test/image",
ImagePullPolicy: "IfNotPresent",
TerminationMessagePath: "/dev/termination-log",
SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults(),
}},
SecurityContext: &v1.PodSecurityContext{},
},
}
json, err := runtime.Encode(testapi.Default.Codec(), pod)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
parsed, podOut, err := tryDecodeSinglePod(json, noDefault)
if !parsed {
t.Errorf("expected to have parsed file: (%s)", string(json))
}
if err != nil {
t.Errorf("unexpected error: %v (%s)", err, string(json))
}
if !reflect.DeepEqual(pod, podOut) {
t.Errorf("expected:\n%#v\ngot:\n%#v\n%s", pod, podOut, string(json))
}
for _, gv := range api.Registry.EnabledVersionsForGroup(v1.GroupName) {
info, _ := runtime.SerializerInfoForMediaType(api.Codecs.SupportedMediaTypes(), "application/yaml")
encoder := api.Codecs.EncoderForVersion(info.Serializer, gv)
yaml, err := runtime.Encode(encoder, pod)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
parsed, podOut, err = tryDecodeSinglePod(yaml, noDefault)
if !parsed {
t.Errorf("expected to have parsed file: (%s)", string(yaml))
}
if err != nil {
t.Errorf("unexpected error: %v (%s)", err, string(yaml))
}
if !reflect.DeepEqual(pod, podOut) {
t.Errorf("expected:\n%#v\ngot:\n%#v\n%s", pod, podOut, string(yaml))
}
}
}
func TestDecodePodList(t *testing.T) {
grace := int64(30)
pod := &v1.Pod{
TypeMeta: metav1.TypeMeta{
APIVersion: "",
},
ObjectMeta: v1.ObjectMeta{
Name: "test",
UID: "12345",
Namespace: "mynamespace",
},
Spec: v1.PodSpec{
RestartPolicy: v1.RestartPolicyAlways,
DNSPolicy: v1.DNSClusterFirst,
TerminationGracePeriodSeconds: &grace,
Containers: []v1.Container{{
Name: "image",
Image: "test/image",
ImagePullPolicy: "IfNotPresent",
TerminationMessagePath: "/dev/termination-log",
SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults(),
}},
SecurityContext: &v1.PodSecurityContext{},
},
}
podList := &v1.PodList{
Items: []v1.Pod{*pod},
}
json, err := runtime.Encode(testapi.Default.Codec(), podList)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
parsed, podListOut, err := tryDecodePodList(json, noDefault)
if !parsed {
t.Errorf("expected to have parsed file: (%s)", string(json))
}
if err != nil {
t.Errorf("unexpected error: %v (%s)", err, string(json))
}
if !reflect.DeepEqual(podList, &podListOut) {
t.Errorf("expected:\n%#v\ngot:\n%#v\n%s", podList, &podListOut, string(json))
}
for _, gv := range api.Registry.EnabledVersionsForGroup(v1.GroupName) {
info, _ := runtime.SerializerInfoForMediaType(api.Codecs.SupportedMediaTypes(), "application/yaml")
encoder := api.Codecs.EncoderForVersion(info.Serializer, gv)
yaml, err := runtime.Encode(encoder, podList)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
parsed, podListOut, err = tryDecodePodList(yaml, noDefault)
if !parsed {
t.Errorf("expected to have parsed file: (%s): %v", string(yaml), err)
continue
}
if err != nil {
t.Errorf("unexpected error: %v (%s)", err, string(yaml))
continue
}
if !reflect.DeepEqual(podList, &podListOut) {
t.Errorf("expected:\n%#v\ngot:\n%#v\n%s", pod, &podListOut, string(yaml))
}
}
}

541
vendor/k8s.io/kubernetes/pkg/kubelet/config/config.go generated vendored Normal file
View file

@ -0,0 +1,541 @@
/*
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 config
import (
"fmt"
"reflect"
"sync"
"github.com/golang/glog"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/v1"
podutil "k8s.io/kubernetes/pkg/api/v1/pod"
"k8s.io/kubernetes/pkg/api/validation"
"k8s.io/kubernetes/pkg/client/record"
kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
"k8s.io/kubernetes/pkg/kubelet/events"
kubetypes "k8s.io/kubernetes/pkg/kubelet/types"
"k8s.io/kubernetes/pkg/kubelet/util/format"
"k8s.io/kubernetes/pkg/util/config"
)
// PodConfigNotificationMode describes how changes are sent to the update channel.
type PodConfigNotificationMode int
const (
// PodConfigNotificationUnknown is the default value for
// PodConfigNotificationMode when uninitialized.
PodConfigNotificationUnknown = iota
// PodConfigNotificationSnapshot delivers the full configuration as a SET whenever
// any change occurs.
PodConfigNotificationSnapshot
// PodConfigNotificationSnapshotAndUpdates delivers an UPDATE and DELETE message whenever pods are
// changed, and a SET message if there are any additions or removals.
PodConfigNotificationSnapshotAndUpdates
// PodConfigNotificationIncremental delivers ADD, UPDATE, DELETE, REMOVE, RECONCILE to the update channel.
PodConfigNotificationIncremental
)
// PodConfig is a configuration mux that merges many sources of pod configuration into a single
// consistent structure, and then delivers incremental change notifications to listeners
// in order.
type PodConfig struct {
pods *podStorage
mux *config.Mux
// the channel of denormalized changes passed to listeners
updates chan kubetypes.PodUpdate
// contains the list of all configured sources
sourcesLock sync.Mutex
sources sets.String
}
// NewPodConfig creates an object that can merge many configuration sources into a stream
// of normalized updates to a pod configuration.
func NewPodConfig(mode PodConfigNotificationMode, recorder record.EventRecorder) *PodConfig {
updates := make(chan kubetypes.PodUpdate, 50)
storage := newPodStorage(updates, mode, recorder)
podConfig := &PodConfig{
pods: storage,
mux: config.NewMux(storage),
updates: updates,
sources: sets.String{},
}
return podConfig
}
// Channel creates or returns a config source channel. The channel
// only accepts PodUpdates
func (c *PodConfig) Channel(source string) chan<- interface{} {
c.sourcesLock.Lock()
defer c.sourcesLock.Unlock()
c.sources.Insert(source)
return c.mux.Channel(source)
}
// SeenAllSources returns true if seenSources contains all sources in the
// config, and also this config has received a SET message from each source.
func (c *PodConfig) SeenAllSources(seenSources sets.String) bool {
if c.pods == nil {
return false
}
glog.V(6).Infof("Looking for %v, have seen %v", c.sources.List(), seenSources)
return seenSources.HasAll(c.sources.List()...) && c.pods.seenSources(c.sources.List()...)
}
// Updates returns a channel of updates to the configuration, properly denormalized.
func (c *PodConfig) Updates() <-chan kubetypes.PodUpdate {
return c.updates
}
// Sync requests the full configuration be delivered to the update channel.
func (c *PodConfig) Sync() {
c.pods.Sync()
}
// podStorage manages the current pod state at any point in time and ensures updates
// to the channel are delivered in order. Note that this object is an in-memory source of
// "truth" and on creation contains zero entries. Once all previously read sources are
// available, then this object should be considered authoritative.
type podStorage struct {
podLock sync.RWMutex
// map of source name to pod name to pod reference
pods map[string]map[string]*v1.Pod
mode PodConfigNotificationMode
// ensures that updates are delivered in strict order
// on the updates channel
updateLock sync.Mutex
updates chan<- kubetypes.PodUpdate
// contains the set of all sources that have sent at least one SET
sourcesSeenLock sync.RWMutex
sourcesSeen sets.String
// the EventRecorder to use
recorder record.EventRecorder
}
// TODO: PodConfigNotificationMode could be handled by a listener to the updates channel
// in the future, especially with multiple listeners.
// TODO: allow initialization of the current state of the store with snapshotted version.
func newPodStorage(updates chan<- kubetypes.PodUpdate, mode PodConfigNotificationMode, recorder record.EventRecorder) *podStorage {
return &podStorage{
pods: make(map[string]map[string]*v1.Pod),
mode: mode,
updates: updates,
sourcesSeen: sets.String{},
recorder: recorder,
}
}
// Merge normalizes a set of incoming changes from different sources into a map of all Pods
// and ensures that redundant changes are filtered out, and then pushes zero or more minimal
// updates onto the update channel. Ensures that updates are delivered in order.
func (s *podStorage) Merge(source string, change interface{}) error {
s.updateLock.Lock()
defer s.updateLock.Unlock()
seenBefore := s.sourcesSeen.Has(source)
adds, updates, deletes, removes, reconciles := s.merge(source, change)
firstSet := !seenBefore && s.sourcesSeen.Has(source)
// deliver update notifications
switch s.mode {
case PodConfigNotificationIncremental:
if len(removes.Pods) > 0 {
s.updates <- *removes
}
if len(adds.Pods) > 0 {
s.updates <- *adds
}
if len(updates.Pods) > 0 {
s.updates <- *updates
}
if len(deletes.Pods) > 0 {
s.updates <- *deletes
}
if firstSet && len(adds.Pods) == 0 && len(updates.Pods) == 0 && len(deletes.Pods) == 0 {
// Send an empty update when first seeing the source and there are
// no ADD or UPDATE or DELETE pods from the source. This signals kubelet that
// the source is ready.
s.updates <- *adds
}
// Only add reconcile support here, because kubelet doesn't support Snapshot update now.
if len(reconciles.Pods) > 0 {
s.updates <- *reconciles
}
case PodConfigNotificationSnapshotAndUpdates:
if len(removes.Pods) > 0 || len(adds.Pods) > 0 || firstSet {
s.updates <- kubetypes.PodUpdate{Pods: s.MergedState().([]*v1.Pod), Op: kubetypes.SET, Source: source}
}
if len(updates.Pods) > 0 {
s.updates <- *updates
}
if len(deletes.Pods) > 0 {
s.updates <- *deletes
}
case PodConfigNotificationSnapshot:
if len(updates.Pods) > 0 || len(deletes.Pods) > 0 || len(adds.Pods) > 0 || len(removes.Pods) > 0 || firstSet {
s.updates <- kubetypes.PodUpdate{Pods: s.MergedState().([]*v1.Pod), Op: kubetypes.SET, Source: source}
}
case PodConfigNotificationUnknown:
fallthrough
default:
panic(fmt.Sprintf("unsupported PodConfigNotificationMode: %#v", s.mode))
}
return nil
}
func (s *podStorage) merge(source string, change interface{}) (adds, updates, deletes, removes, reconciles *kubetypes.PodUpdate) {
s.podLock.Lock()
defer s.podLock.Unlock()
addPods := []*v1.Pod{}
updatePods := []*v1.Pod{}
deletePods := []*v1.Pod{}
removePods := []*v1.Pod{}
reconcilePods := []*v1.Pod{}
pods := s.pods[source]
if pods == nil {
pods = make(map[string]*v1.Pod)
}
// updatePodFunc is the local function which updates the pod cache *oldPods* with new pods *newPods*.
// After updated, new pod will be stored in the pod cache *pods*.
// Notice that *pods* and *oldPods* could be the same cache.
updatePodsFunc := func(newPods []*v1.Pod, oldPods, pods map[string]*v1.Pod) {
filtered := filterInvalidPods(newPods, source, s.recorder)
for _, ref := range filtered {
name := kubecontainer.GetPodFullName(ref)
// Annotate the pod with the source before any comparison.
if ref.Annotations == nil {
ref.Annotations = make(map[string]string)
}
ref.Annotations[kubetypes.ConfigSourceAnnotationKey] = source
if existing, found := oldPods[name]; found {
pods[name] = existing
needUpdate, needReconcile, needGracefulDelete := checkAndUpdatePod(existing, ref)
if needUpdate {
updatePods = append(updatePods, existing)
} else if needReconcile {
reconcilePods = append(reconcilePods, existing)
} else if needGracefulDelete {
deletePods = append(deletePods, existing)
}
continue
}
recordFirstSeenTime(ref)
pods[name] = ref
addPods = append(addPods, ref)
}
}
update := change.(kubetypes.PodUpdate)
// The InitContainers and InitContainerStatuses fields are lost during
// serialization and deserialization. They are conveyed via Annotations.
// Setting these fields here so that kubelet doesn't have to check for
// annotations.
if source == kubetypes.ApiserverSource {
for _, pod := range update.Pods {
if err := podutil.SetInitContainersAndStatuses(pod); err != nil {
glog.Error(err)
}
}
}
switch update.Op {
case kubetypes.ADD, kubetypes.UPDATE, kubetypes.DELETE:
if update.Op == kubetypes.ADD {
glog.V(4).Infof("Adding new pods from source %s : %v", source, update.Pods)
} else if update.Op == kubetypes.DELETE {
glog.V(4).Infof("Graceful deleting pods from source %s : %v", source, update.Pods)
} else {
glog.V(4).Infof("Updating pods from source %s : %v", source, update.Pods)
}
updatePodsFunc(update.Pods, pods, pods)
case kubetypes.REMOVE:
glog.V(4).Infof("Removing pods from source %s : %v", source, update.Pods)
for _, value := range update.Pods {
name := kubecontainer.GetPodFullName(value)
if existing, found := pods[name]; found {
// this is a delete
delete(pods, name)
removePods = append(removePods, existing)
continue
}
// this is a no-op
}
case kubetypes.SET:
glog.V(4).Infof("Setting pods for source %s", source)
s.markSourceSet(source)
// Clear the old map entries by just creating a new map
oldPods := pods
pods = make(map[string]*v1.Pod)
updatePodsFunc(update.Pods, oldPods, pods)
for name, existing := range oldPods {
if _, found := pods[name]; !found {
// this is a delete
removePods = append(removePods, existing)
}
}
default:
glog.Warningf("Received invalid update type: %v", update)
}
s.pods[source] = pods
adds = &kubetypes.PodUpdate{Op: kubetypes.ADD, Pods: copyPods(addPods), Source: source}
updates = &kubetypes.PodUpdate{Op: kubetypes.UPDATE, Pods: copyPods(updatePods), Source: source}
deletes = &kubetypes.PodUpdate{Op: kubetypes.DELETE, Pods: copyPods(deletePods), Source: source}
removes = &kubetypes.PodUpdate{Op: kubetypes.REMOVE, Pods: copyPods(removePods), Source: source}
reconciles = &kubetypes.PodUpdate{Op: kubetypes.RECONCILE, Pods: copyPods(reconcilePods), Source: source}
return adds, updates, deletes, removes, reconciles
}
func (s *podStorage) markSourceSet(source string) {
s.sourcesSeenLock.Lock()
defer s.sourcesSeenLock.Unlock()
s.sourcesSeen.Insert(source)
}
func (s *podStorage) seenSources(sources ...string) bool {
s.sourcesSeenLock.RLock()
defer s.sourcesSeenLock.RUnlock()
return s.sourcesSeen.HasAll(sources...)
}
func filterInvalidPods(pods []*v1.Pod, source string, recorder record.EventRecorder) (filtered []*v1.Pod) {
names := sets.String{}
for i, pod := range pods {
var errlist field.ErrorList
// TODO: remove the conversion when validation is performed on versioned objects.
internalPod := &api.Pod{}
if err := v1.Convert_v1_Pod_To_api_Pod(pod, internalPod, nil); err != nil {
name := kubecontainer.GetPodFullName(pod)
glog.Warningf("Pod[%d] (%s) from %s failed to convert to v1, ignoring: %v", i+1, name, source, err)
recorder.Eventf(pod, v1.EventTypeWarning, "FailedConversion", "Error converting pod %s from %s, ignoring: %v", name, source, err)
continue
}
if errs := validation.ValidatePod(internalPod); len(errs) != 0 {
errlist = append(errlist, errs...)
// If validation fails, don't trust it any further -
// even Name could be bad.
} else {
name := kubecontainer.GetPodFullName(pod)
if names.Has(name) {
// TODO: when validation becomes versioned, this gets a bit
// more complicated.
errlist = append(errlist, field.Duplicate(field.NewPath("metadata", "name"), pod.Name))
} else {
names.Insert(name)
}
}
if len(errlist) > 0 {
name := bestPodIdentString(pod)
err := errlist.ToAggregate()
glog.Warningf("Pod[%d] (%s) from %s failed validation, ignoring: %v", i+1, name, source, err)
recorder.Eventf(pod, v1.EventTypeWarning, events.FailedValidation, "Error validating pod %s from %s, ignoring: %v", name, source, err)
continue
}
filtered = append(filtered, pod)
}
return
}
// Annotations that the kubelet adds to the pod.
var localAnnotations = []string{
kubetypes.ConfigSourceAnnotationKey,
kubetypes.ConfigMirrorAnnotationKey,
kubetypes.ConfigFirstSeenAnnotationKey,
}
func isLocalAnnotationKey(key string) bool {
for _, localKey := range localAnnotations {
if key == localKey {
return true
}
}
return false
}
// isAnnotationMapEqual returns true if the existing annotation Map is equal to candidate except
// for local annotations.
func isAnnotationMapEqual(existingMap, candidateMap map[string]string) bool {
if candidateMap == nil {
candidateMap = make(map[string]string)
}
for k, v := range candidateMap {
if isLocalAnnotationKey(k) {
continue
}
if existingValue, ok := existingMap[k]; ok && existingValue == v {
continue
}
return false
}
for k := range existingMap {
if isLocalAnnotationKey(k) {
continue
}
// stale entry in existing map.
if _, exists := candidateMap[k]; !exists {
return false
}
}
return true
}
// recordFirstSeenTime records the first seen time of this pod.
func recordFirstSeenTime(pod *v1.Pod) {
glog.V(4).Infof("Receiving a new pod %q", format.Pod(pod))
pod.Annotations[kubetypes.ConfigFirstSeenAnnotationKey] = kubetypes.NewTimestamp().GetString()
}
// updateAnnotations returns an Annotation map containing the api annotation map plus
// locally managed annotations
func updateAnnotations(existing, ref *v1.Pod) {
annotations := make(map[string]string, len(ref.Annotations)+len(localAnnotations))
for k, v := range ref.Annotations {
annotations[k] = v
}
for _, k := range localAnnotations {
if v, ok := existing.Annotations[k]; ok {
annotations[k] = v
}
}
existing.Annotations = annotations
}
func podsDifferSemantically(existing, ref *v1.Pod) bool {
if reflect.DeepEqual(existing.Spec, ref.Spec) &&
reflect.DeepEqual(existing.Labels, ref.Labels) &&
reflect.DeepEqual(existing.DeletionTimestamp, ref.DeletionTimestamp) &&
reflect.DeepEqual(existing.DeletionGracePeriodSeconds, ref.DeletionGracePeriodSeconds) &&
isAnnotationMapEqual(existing.Annotations, ref.Annotations) {
return false
}
return true
}
// checkAndUpdatePod updates existing, and:
// * if ref makes a meaningful change, returns needUpdate=true
// * if ref makes a meaningful change, and this change is graceful deletion, returns needGracefulDelete=true
// * if ref makes no meaningful change, but changes the pod status, returns needReconcile=true
// * else return all false
// Now, needUpdate, needGracefulDelete and needReconcile should never be both true
func checkAndUpdatePod(existing, ref *v1.Pod) (needUpdate, needReconcile, needGracefulDelete bool) {
// 1. this is a reconcile
// TODO: it would be better to update the whole object and only preserve certain things
// like the source annotation or the UID (to ensure safety)
if !podsDifferSemantically(existing, ref) {
// this is not an update
// Only check reconcile when it is not an update, because if the pod is going to
// be updated, an extra reconcile is unnecessary
if !reflect.DeepEqual(existing.Status, ref.Status) {
// Pod with changed pod status needs reconcile, because kubelet should
// be the source of truth of pod status.
existing.Status = ref.Status
needReconcile = true
}
return
}
// Overwrite the first-seen time with the existing one. This is our own
// internal annotation, there is no need to update.
ref.Annotations[kubetypes.ConfigFirstSeenAnnotationKey] = existing.Annotations[kubetypes.ConfigFirstSeenAnnotationKey]
existing.Spec = ref.Spec
existing.Labels = ref.Labels
existing.DeletionTimestamp = ref.DeletionTimestamp
existing.DeletionGracePeriodSeconds = ref.DeletionGracePeriodSeconds
existing.Status = ref.Status
updateAnnotations(existing, ref)
// 2. this is an graceful delete
if ref.DeletionTimestamp != nil {
needGracefulDelete = true
} else {
// 3. this is an update
needUpdate = true
}
return
}
// Sync sends a copy of the current state through the update channel.
func (s *podStorage) Sync() {
s.updateLock.Lock()
defer s.updateLock.Unlock()
s.updates <- kubetypes.PodUpdate{Pods: s.MergedState().([]*v1.Pod), Op: kubetypes.SET, Source: kubetypes.AllSource}
}
// Object implements config.Accessor
func (s *podStorage) MergedState() interface{} {
s.podLock.RLock()
defer s.podLock.RUnlock()
pods := make([]*v1.Pod, 0)
for _, sourcePods := range s.pods {
for _, podRef := range sourcePods {
pod, err := api.Scheme.Copy(podRef)
if err != nil {
glog.Errorf("unable to copy pod: %v", err)
}
pods = append(pods, pod.(*v1.Pod))
}
}
return pods
}
func bestPodIdentString(pod *v1.Pod) string {
namespace := pod.Namespace
if namespace == "" {
namespace = "<empty-namespace>"
}
name := pod.Name
if name == "" {
name = "<empty-name>"
}
return fmt.Sprintf("%s.%s", name, namespace)
}
func copyPods(sourcePods []*v1.Pod) []*v1.Pod {
pods := []*v1.Pod{}
for _, source := range sourcePods {
// Use a deep copy here just in case
pod, err := api.Scheme.Copy(source)
if err != nil {
glog.Errorf("unable to copy pod: %v", err)
}
pods = append(pods, pod.(*v1.Pod))
}
return pods
}

View file

@ -0,0 +1,417 @@
/*
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 config
import (
"math/rand"
"reflect"
"sort"
"strconv"
"testing"
"time"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/client/record"
kubetypes "k8s.io/kubernetes/pkg/kubelet/types"
"k8s.io/kubernetes/pkg/securitycontext"
)
const (
TestSource = "test"
)
func expectEmptyChannel(t *testing.T, ch <-chan interface{}) {
select {
case update := <-ch:
t.Errorf("Expected no update in channel, Got %v", update)
default:
}
}
type sortedPods []*v1.Pod
func (s sortedPods) Len() int {
return len(s)
}
func (s sortedPods) Swap(i, j int) {
s[i], s[j] = s[j], s[i]
}
func (s sortedPods) Less(i, j int) bool {
return s[i].Namespace < s[j].Namespace
}
func CreateValidPod(name, namespace string) *v1.Pod {
return &v1.Pod{
ObjectMeta: v1.ObjectMeta{
UID: types.UID(name), // for the purpose of testing, this is unique enough
Name: name,
Namespace: namespace,
},
Spec: v1.PodSpec{
RestartPolicy: v1.RestartPolicyAlways,
DNSPolicy: v1.DNSClusterFirst,
Containers: []v1.Container{
{
Name: "ctr",
Image: "image",
ImagePullPolicy: "IfNotPresent",
SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults(),
},
},
},
}
}
func CreatePodUpdate(op kubetypes.PodOperation, source string, pods ...*v1.Pod) kubetypes.PodUpdate {
return kubetypes.PodUpdate{Pods: pods, Op: op, Source: source}
}
func createPodConfigTester(mode PodConfigNotificationMode) (chan<- interface{}, <-chan kubetypes.PodUpdate, *PodConfig) {
eventBroadcaster := record.NewBroadcaster()
config := NewPodConfig(mode, eventBroadcaster.NewRecorder(v1.EventSource{Component: "kubelet"}))
channel := config.Channel(TestSource)
ch := config.Updates()
return channel, ch, config
}
func expectPodUpdate(t *testing.T, ch <-chan kubetypes.PodUpdate, expected ...kubetypes.PodUpdate) {
for i := range expected {
update := <-ch
sort.Sort(sortedPods(update.Pods))
sort.Sort(sortedPods(expected[i].Pods))
// Make copies of the expected/actual update to compare all fields
// except for "Pods", which are compared separately below.
expectedCopy, updateCopy := expected[i], update
expectedCopy.Pods, updateCopy.Pods = nil, nil
if !api.Semantic.DeepEqual(expectedCopy, updateCopy) {
t.Fatalf("Expected %#v, Got %#v", expectedCopy, updateCopy)
}
if len(expected[i].Pods) != len(update.Pods) {
t.Fatalf("Expected %#v, Got %#v", expected[i], update)
}
// Compare pods one by one. This is necessary because we don't want to
// compare local annotations.
for j := range expected[i].Pods {
if podsDifferSemantically(expected[i].Pods[j], update.Pods[j]) || !reflect.DeepEqual(expected[i].Pods[j].Status, update.Pods[j].Status) {
t.Fatalf("Expected %#v, Got %#v", expected[i].Pods[j], update.Pods[j])
}
}
}
expectNoPodUpdate(t, ch)
}
func expectNoPodUpdate(t *testing.T, ch <-chan kubetypes.PodUpdate) {
select {
case update := <-ch:
t.Errorf("Expected no update in channel, Got %#v", update)
default:
}
}
func TestNewPodAdded(t *testing.T) {
channel, ch, config := createPodConfigTester(PodConfigNotificationIncremental)
// see an update
podUpdate := CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "new"))
channel <- podUpdate
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "new")))
config.Sync()
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.SET, kubetypes.AllSource, CreateValidPod("foo", "new")))
}
func TestNewPodAddedInvalidNamespace(t *testing.T) {
channel, ch, config := createPodConfigTester(PodConfigNotificationIncremental)
// see an update
podUpdate := CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", ""))
channel <- podUpdate
config.Sync()
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.SET, kubetypes.AllSource))
}
func TestNewPodAddedDefaultNamespace(t *testing.T) {
channel, ch, config := createPodConfigTester(PodConfigNotificationIncremental)
// see an update
podUpdate := CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "default"))
channel <- podUpdate
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "default")))
config.Sync()
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.SET, kubetypes.AllSource, CreateValidPod("foo", "default")))
}
func TestNewPodAddedDifferentNamespaces(t *testing.T) {
channel, ch, config := createPodConfigTester(PodConfigNotificationIncremental)
// see an update
podUpdate := CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "default"))
channel <- podUpdate
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "default")))
// see an update in another namespace
podUpdate = CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "new"))
channel <- podUpdate
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "new")))
config.Sync()
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.SET, kubetypes.AllSource, CreateValidPod("foo", "default"), CreateValidPod("foo", "new")))
}
func TestInvalidPodFiltered(t *testing.T) {
channel, ch, _ := createPodConfigTester(PodConfigNotificationIncremental)
// see an update
podUpdate := CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "new"))
channel <- podUpdate
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "new")))
// add an invalid update
podUpdate = CreatePodUpdate(kubetypes.UPDATE, TestSource, &v1.Pod{ObjectMeta: v1.ObjectMeta{Name: "foo"}})
channel <- podUpdate
expectNoPodUpdate(t, ch)
}
func TestNewPodAddedSnapshotAndUpdates(t *testing.T) {
channel, ch, config := createPodConfigTester(PodConfigNotificationSnapshotAndUpdates)
// see an set
podUpdate := CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "new"))
channel <- podUpdate
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.SET, TestSource, CreateValidPod("foo", "new")))
config.Sync()
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.SET, kubetypes.AllSource, CreateValidPod("foo", "new")))
// container updates are separated as UPDATE
pod := *podUpdate.Pods[0]
pod.Spec.Containers = []v1.Container{{Name: "bar", Image: "test", ImagePullPolicy: v1.PullIfNotPresent}}
channel <- CreatePodUpdate(kubetypes.ADD, TestSource, &pod)
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.UPDATE, TestSource, &pod))
}
func TestNewPodAddedSnapshot(t *testing.T) {
channel, ch, config := createPodConfigTester(PodConfigNotificationSnapshot)
// see an set
podUpdate := CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "new"))
channel <- podUpdate
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.SET, TestSource, CreateValidPod("foo", "new")))
config.Sync()
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.SET, kubetypes.AllSource, CreateValidPod("foo", "new")))
// container updates are separated as UPDATE
pod := *podUpdate.Pods[0]
pod.Spec.Containers = []v1.Container{{Name: "bar", Image: "test", ImagePullPolicy: v1.PullIfNotPresent}}
channel <- CreatePodUpdate(kubetypes.ADD, TestSource, &pod)
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.SET, TestSource, &pod))
}
func TestNewPodAddedUpdatedRemoved(t *testing.T) {
channel, ch, _ := createPodConfigTester(PodConfigNotificationIncremental)
// should register an add
podUpdate := CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "new"))
channel <- podUpdate
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "new")))
// should ignore ADDs that are identical
expectNoPodUpdate(t, ch)
// an kubetypes.ADD should be converted to kubetypes.UPDATE
pod := CreateValidPod("foo", "new")
pod.Spec.Containers = []v1.Container{{Name: "bar", Image: "test", ImagePullPolicy: v1.PullIfNotPresent}}
podUpdate = CreatePodUpdate(kubetypes.ADD, TestSource, pod)
channel <- podUpdate
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.UPDATE, TestSource, pod))
podUpdate = CreatePodUpdate(kubetypes.REMOVE, TestSource, &v1.Pod{ObjectMeta: v1.ObjectMeta{Name: "foo", Namespace: "new"}})
channel <- podUpdate
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.REMOVE, TestSource, pod))
}
func TestNewPodAddedDelete(t *testing.T) {
channel, ch, _ := createPodConfigTester(PodConfigNotificationIncremental)
// should register an add
addedPod := CreateValidPod("foo", "new")
podUpdate := CreatePodUpdate(kubetypes.ADD, TestSource, addedPod)
channel <- podUpdate
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.ADD, TestSource, addedPod))
// mark this pod as deleted
timestamp := metav1.NewTime(time.Now())
deletedPod := CreateValidPod("foo", "new")
deletedPod.ObjectMeta.DeletionTimestamp = &timestamp
podUpdate = CreatePodUpdate(kubetypes.DELETE, TestSource, deletedPod)
channel <- podUpdate
// the existing pod should be gracefully deleted
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.DELETE, TestSource, addedPod))
}
func TestNewPodAddedUpdatedSet(t *testing.T) {
channel, ch, _ := createPodConfigTester(PodConfigNotificationIncremental)
// should register an add
podUpdate := CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "new"), CreateValidPod("foo2", "new"), CreateValidPod("foo3", "new"))
channel <- podUpdate
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "new"), CreateValidPod("foo2", "new"), CreateValidPod("foo3", "new")))
// should ignore ADDs that are identical
expectNoPodUpdate(t, ch)
// should be converted to an kubetypes.ADD, kubetypes.REMOVE, and kubetypes.UPDATE
pod := CreateValidPod("foo2", "new")
pod.Spec.Containers = []v1.Container{{Name: "bar", Image: "test", ImagePullPolicy: v1.PullIfNotPresent}}
podUpdate = CreatePodUpdate(kubetypes.SET, TestSource, pod, CreateValidPod("foo3", "new"), CreateValidPod("foo4", "new"))
channel <- podUpdate
expectPodUpdate(t, ch,
CreatePodUpdate(kubetypes.REMOVE, TestSource, CreateValidPod("foo", "new")),
CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo4", "new")),
CreatePodUpdate(kubetypes.UPDATE, TestSource, pod))
}
func TestNewPodAddedSetReconciled(t *testing.T) {
// Create and touch new test pods, return the new pods and touched pod. We should create new pod list
// before touching to avoid data race.
newTestPods := func(touchStatus, touchSpec bool) ([]*v1.Pod, *v1.Pod) {
pods := []*v1.Pod{
CreateValidPod("changeable-pod-0", "new"),
CreateValidPod("constant-pod-1", "new"),
CreateValidPod("constant-pod-2", "new"),
}
if touchStatus {
pods[0].Status = v1.PodStatus{Message: strconv.Itoa(rand.Int())}
}
if touchSpec {
pods[0].Spec.Containers[0].Name = strconv.Itoa(rand.Int())
}
return pods, pods[0]
}
for _, op := range []kubetypes.PodOperation{
kubetypes.ADD,
kubetypes.SET,
} {
var podWithStatusChange *v1.Pod
pods, _ := newTestPods(false, false)
channel, ch, _ := createPodConfigTester(PodConfigNotificationIncremental)
// Use SET to initialize the config, especially initialize the source set
channel <- CreatePodUpdate(kubetypes.SET, TestSource, pods...)
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.ADD, TestSource, pods...))
// If status is not changed, no reconcile should be triggered
channel <- CreatePodUpdate(op, TestSource, pods...)
expectNoPodUpdate(t, ch)
// If the pod status is changed and not updated, a reconcile should be triggered
pods, podWithStatusChange = newTestPods(true, false)
channel <- CreatePodUpdate(op, TestSource, pods...)
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.RECONCILE, TestSource, podWithStatusChange))
// If the pod status is changed, but the pod is also updated, no reconcile should be triggered
pods, podWithStatusChange = newTestPods(true, true)
channel <- CreatePodUpdate(op, TestSource, pods...)
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.UPDATE, TestSource, podWithStatusChange))
}
}
func TestInitialEmptySet(t *testing.T) {
for _, test := range []struct {
mode PodConfigNotificationMode
op kubetypes.PodOperation
}{
{PodConfigNotificationIncremental, kubetypes.ADD},
{PodConfigNotificationSnapshot, kubetypes.SET},
{PodConfigNotificationSnapshotAndUpdates, kubetypes.SET},
} {
channel, ch, _ := createPodConfigTester(test.mode)
// should register an empty PodUpdate operation
podUpdate := CreatePodUpdate(kubetypes.SET, TestSource)
channel <- podUpdate
expectPodUpdate(t, ch, CreatePodUpdate(test.op, TestSource))
// should ignore following empty sets
podUpdate = CreatePodUpdate(kubetypes.SET, TestSource)
channel <- podUpdate
podUpdate = CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "new"))
channel <- podUpdate
expectPodUpdate(t, ch, CreatePodUpdate(test.op, TestSource, CreateValidPod("foo", "new")))
}
}
func TestPodUpdateAnnotations(t *testing.T) {
channel, ch, _ := createPodConfigTester(PodConfigNotificationIncremental)
pod := CreateValidPod("foo2", "new")
pod.Annotations = make(map[string]string, 0)
pod.Annotations["kubernetes.io/blah"] = "blah"
clone, err := api.Scheme.DeepCopy(pod)
if err != nil {
t.Fatalf("%v", err)
}
podUpdate := CreatePodUpdate(kubetypes.SET, TestSource, CreateValidPod("foo1", "new"), clone.(*v1.Pod), CreateValidPod("foo3", "new"))
channel <- podUpdate
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo1", "new"), pod, CreateValidPod("foo3", "new")))
pod.Annotations["kubenetes.io/blah"] = "superblah"
podUpdate = CreatePodUpdate(kubetypes.SET, TestSource, CreateValidPod("foo1", "new"), pod, CreateValidPod("foo3", "new"))
channel <- podUpdate
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.UPDATE, TestSource, pod))
pod.Annotations["kubernetes.io/otherblah"] = "doh"
podUpdate = CreatePodUpdate(kubetypes.SET, TestSource, CreateValidPod("foo1", "new"), pod, CreateValidPod("foo3", "new"))
channel <- podUpdate
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.UPDATE, TestSource, pod))
delete(pod.Annotations, "kubernetes.io/blah")
podUpdate = CreatePodUpdate(kubetypes.SET, TestSource, CreateValidPod("foo1", "new"), pod, CreateValidPod("foo3", "new"))
channel <- podUpdate
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.UPDATE, TestSource, pod))
}
func TestPodUpdateLabels(t *testing.T) {
channel, ch, _ := createPodConfigTester(PodConfigNotificationIncremental)
pod := CreateValidPod("foo2", "new")
pod.Labels = make(map[string]string, 0)
pod.Labels["key"] = "value"
clone, err := api.Scheme.DeepCopy(pod)
if err != nil {
t.Fatalf("%v", err)
}
podUpdate := CreatePodUpdate(kubetypes.SET, TestSource, clone.(*v1.Pod))
channel <- podUpdate
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.ADD, TestSource, pod))
pod.Labels["key"] = "newValue"
podUpdate = CreatePodUpdate(kubetypes.SET, TestSource, pod)
channel <- podUpdate
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.UPDATE, TestSource, pod))
}

18
vendor/k8s.io/kubernetes/pkg/kubelet/config/doc.go generated vendored Normal file
View file

@ -0,0 +1,18 @@
/*
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 config implements the pod configuration readers.
package config // import "k8s.io/kubernetes/pkg/kubelet/config"

202
vendor/k8s.io/kubernetes/pkg/kubelet/config/file.go generated vendored Normal file
View file

@ -0,0 +1,202 @@
/*
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.
*/
// Reads the pod configuration from file or a directory of files.
package config
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"sort"
"time"
"github.com/golang/glog"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/client/cache"
kubetypes "k8s.io/kubernetes/pkg/kubelet/types"
)
type sourceFile struct {
path string
nodeName types.NodeName
store cache.Store
fileKeyMapping map[string]string
updates chan<- interface{}
}
func NewSourceFile(path string, nodeName types.NodeName, period time.Duration, updates chan<- interface{}) {
config := new(path, nodeName, period, updates)
glog.V(1).Infof("Watching path %q", path)
go wait.Forever(config.run, period)
}
func new(path string, nodeName types.NodeName, period time.Duration, updates chan<- interface{}) *sourceFile {
send := func(objs []interface{}) {
var pods []*v1.Pod
for _, o := range objs {
pods = append(pods, o.(*v1.Pod))
}
updates <- kubetypes.PodUpdate{Pods: pods, Op: kubetypes.SET, Source: kubetypes.FileSource}
}
store := cache.NewUndeltaStore(send, cache.MetaNamespaceKeyFunc)
return &sourceFile{
path: path,
nodeName: nodeName,
store: store,
fileKeyMapping: map[string]string{},
updates: updates,
}
}
func (s *sourceFile) run() {
if err := s.watch(); err != nil {
glog.Errorf("unable to read config path %q: %v", s.path, err)
}
}
func (s *sourceFile) applyDefaults(pod *api.Pod, source string) error {
return applyDefaults(pod, source, true, s.nodeName)
}
func (s *sourceFile) resetStoreFromPath() error {
path := s.path
statInfo, err := os.Stat(path)
if err != nil {
if !os.IsNotExist(err) {
return err
}
// Emit an update with an empty PodList to allow FileSource to be marked as seen
s.updates <- kubetypes.PodUpdate{Pods: []*v1.Pod{}, Op: kubetypes.SET, Source: kubetypes.FileSource}
return fmt.Errorf("path does not exist, ignoring")
}
switch {
case statInfo.Mode().IsDir():
pods, err := s.extractFromDir(path)
if err != nil {
return err
}
if len(pods) == 0 {
// Emit an update with an empty PodList to allow FileSource to be marked as seen
s.updates <- kubetypes.PodUpdate{Pods: pods, Op: kubetypes.SET, Source: kubetypes.FileSource}
return nil
}
return s.replaceStore(pods...)
case statInfo.Mode().IsRegular():
pod, err := s.extractFromFile(path)
if err != nil {
return err
}
return s.replaceStore(pod)
default:
return fmt.Errorf("path is not a directory or file")
}
}
// Get as many pod configs as we can from a directory. Return an error if and only if something
// prevented us from reading anything at all. Do not return an error if only some files
// were problematic.
func (s *sourceFile) extractFromDir(name string) ([]*v1.Pod, error) {
dirents, err := filepath.Glob(filepath.Join(name, "[^.]*"))
if err != nil {
return nil, fmt.Errorf("glob failed: %v", err)
}
pods := make([]*v1.Pod, 0)
if len(dirents) == 0 {
return pods, nil
}
sort.Strings(dirents)
for _, path := range dirents {
statInfo, err := os.Stat(path)
if err != nil {
glog.V(1).Infof("Can't get metadata for %q: %v", path, err)
continue
}
switch {
case statInfo.Mode().IsDir():
glog.V(1).Infof("Not recursing into config path %q", path)
case statInfo.Mode().IsRegular():
pod, err := s.extractFromFile(path)
if err != nil {
glog.V(1).Infof("Can't process config file %q: %v", path, err)
} else {
pods = append(pods, pod)
}
default:
glog.V(1).Infof("Config path %q is not a directory or file: %v", path, statInfo.Mode())
}
}
return pods, nil
}
func (s *sourceFile) extractFromFile(filename string) (pod *v1.Pod, err error) {
glog.V(3).Infof("Reading config file %q", filename)
defer func() {
if err == nil && pod != nil {
objKey, keyErr := cache.MetaNamespaceKeyFunc(pod)
if keyErr != nil {
err = keyErr
return
}
s.fileKeyMapping[filename] = objKey
}
}()
file, err := os.Open(filename)
if err != nil {
return pod, err
}
defer file.Close()
data, err := ioutil.ReadAll(file)
if err != nil {
return pod, err
}
defaultFn := func(pod *api.Pod) error {
return s.applyDefaults(pod, filename)
}
parsed, pod, podErr := tryDecodeSinglePod(data, defaultFn)
if parsed {
if podErr != nil {
return pod, podErr
}
return pod, nil
}
return pod, fmt.Errorf("%v: read '%v', but couldn't parse as pod(%v).\n",
filename, string(data), podErr)
}
func (s *sourceFile) replaceStore(pods ...*v1.Pod) (err error) {
objs := []interface{}{}
for _, pod := range pods {
objs = append(objs, pod)
}
return s.store.Replace(objs, "")
}

View file

@ -0,0 +1,124 @@
// +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.
*/
// Reads the pod configuration from file or a directory of files.
package config
import (
"fmt"
"os"
"github.com/golang/glog"
"golang.org/x/exp/inotify"
"k8s.io/kubernetes/pkg/api/v1"
kubetypes "k8s.io/kubernetes/pkg/kubelet/types"
)
type podEventType int
const (
podAdd podEventType = iota
podModify
podDelete
)
func (s *sourceFile) watch() error {
_, err := os.Stat(s.path)
if err != nil {
if !os.IsNotExist(err) {
return err
}
// Emit an update with an empty PodList to allow FileSource to be marked as seen
s.updates <- kubetypes.PodUpdate{Pods: []*v1.Pod{}, Op: kubetypes.SET, Source: kubetypes.FileSource}
return fmt.Errorf("path does not exist, ignoring")
}
w, err := inotify.NewWatcher()
if err != nil {
return fmt.Errorf("unable to create inotify: %v", err)
}
defer w.Close()
err = w.AddWatch(s.path, inotify.IN_DELETE_SELF|inotify.IN_CREATE|inotify.IN_MOVED_TO|inotify.IN_MODIFY|inotify.IN_MOVED_FROM|inotify.IN_DELETE)
if err != nil {
return fmt.Errorf("unable to create inotify for path %q: %v", s.path, err)
}
// Reset store with config files already existing when starting
if err := s.resetStoreFromPath(); err != nil {
return fmt.Errorf("unable to read config path %q: %v", s.path, err)
}
for {
select {
case event := <-w.Event:
err = s.processEvent(event)
if err != nil {
return fmt.Errorf("error while processing event (%+v): %v", event, err)
}
case err = <-w.Error:
return fmt.Errorf("error while watching %q: %v", s.path, err)
}
}
}
func (s *sourceFile) processEvent(e *inotify.Event) error {
var eventType podEventType
switch {
case (e.Mask & inotify.IN_ISDIR) > 0:
glog.V(1).Infof("Not recursing into config path %q", s.path)
return nil
case (e.Mask & inotify.IN_CREATE) > 0:
eventType = podAdd
case (e.Mask & inotify.IN_MOVED_TO) > 0:
eventType = podAdd
case (e.Mask & inotify.IN_MODIFY) > 0:
eventType = podModify
case (e.Mask & inotify.IN_DELETE) > 0:
eventType = podDelete
case (e.Mask & inotify.IN_MOVED_FROM) > 0:
eventType = podDelete
case (e.Mask & inotify.IN_DELETE_SELF) > 0:
return fmt.Errorf("the watched path is deleted")
default:
// Ignore rest events
return nil
}
switch eventType {
case podAdd, podModify:
if pod, err := s.extractFromFile(e.Name); err != nil {
glog.Errorf("can't process config file %q: %v", e.Name, err)
} else {
return s.store.Add(pod)
}
case podDelete:
if objKey, keyExist := s.fileKeyMapping[e.Name]; keyExist {
pod, podExist, err := s.store.GetByKey(objKey)
if err != nil {
return err
} else if !podExist {
return fmt.Errorf("the pod with key %s doesn't exist in cache", objKey)
} else {
return s.store.Delete(pod)
}
}
}
return nil
}

View file

@ -0,0 +1,421 @@
// +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 config
import (
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"sync"
"testing"
"time"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/testapi"
"k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/api/validation"
kubetypes "k8s.io/kubernetes/pkg/kubelet/types"
"k8s.io/kubernetes/pkg/securitycontext"
utiltesting "k8s.io/kubernetes/pkg/util/testing"
)
func TestExtractFromNonExistentFile(t *testing.T) {
ch := make(chan interface{}, 1)
c := new("/some/fake/file", "localhost", time.Millisecond, ch)
err := c.watch()
if err == nil {
t.Errorf("Expected error")
}
}
func TestUpdateOnNonExistentFile(t *testing.T) {
ch := make(chan interface{})
NewSourceFile("random_non_existent_path", "localhost", time.Millisecond, ch)
select {
case got := <-ch:
update := got.(kubetypes.PodUpdate)
expected := CreatePodUpdate(kubetypes.SET, kubetypes.FileSource)
if !api.Semantic.DeepDerivative(expected, update) {
t.Fatalf("expected %#v, Got %#v", expected, update)
}
case <-time.After(wait.ForeverTestTimeout):
t.Fatalf("expected update, timeout instead")
}
}
func TestReadPodsFromFileExistAlready(t *testing.T) {
hostname := types.NodeName("random-test-hostname")
var testCases = getTestCases(hostname)
for _, testCase := range testCases {
func() {
dirName, err := utiltesting.MkTmpdir("file-test")
if err != nil {
t.Fatalf("unable to create temp dir: %v", err)
}
defer os.RemoveAll(dirName)
file := testCase.writeToFile(dirName, "test_pod_config", t)
ch := make(chan interface{})
NewSourceFile(file, hostname, time.Millisecond, ch)
select {
case got := <-ch:
update := got.(kubetypes.PodUpdate)
for _, pod := range update.Pods {
// TODO: remove the conversion when validation is performed on versioned objects.
internalPod := &api.Pod{}
if err := v1.Convert_v1_Pod_To_api_Pod(pod, internalPod, nil); err != nil {
t.Fatalf("%s: Cannot convert pod %#v, %#v", testCase.desc, pod, err)
}
if errs := validation.ValidatePod(internalPod); len(errs) > 0 {
t.Fatalf("%s: Invalid pod %#v, %#v", testCase.desc, internalPod, errs)
}
}
if !api.Semantic.DeepEqual(testCase.expected, update) {
t.Fatalf("%s: Expected %#v, Got %#v", testCase.desc, testCase.expected, update)
}
case <-time.After(wait.ForeverTestTimeout):
t.Fatalf("%s: Expected update, timeout instead", testCase.desc)
}
}()
}
}
func TestReadPodsFromFileExistLater(t *testing.T) {
watchFileAdded(false, t)
}
func TestReadPodsFromFileChanged(t *testing.T) {
watchFileChanged(false, t)
}
func TestReadPodsFromFileInDirAdded(t *testing.T) {
watchFileAdded(true, t)
}
func TestReadPodsFromFileInDirChanged(t *testing.T) {
watchFileChanged(true, t)
}
func TestExtractFromBadDataFile(t *testing.T) {
dirName, err := utiltesting.MkTmpdir("file-test")
if err != nil {
t.Fatalf("unable to create temp dir: %v", err)
}
defer os.RemoveAll(dirName)
fileName := filepath.Join(dirName, "test_pod_config")
err = ioutil.WriteFile(fileName, []byte{1, 2, 3}, 0555)
if err != nil {
t.Fatalf("unable to write test file %#v", err)
}
ch := make(chan interface{}, 1)
c := new(fileName, "localhost", time.Millisecond, ch)
err = c.resetStoreFromPath()
if err == nil {
t.Fatalf("expected error, got nil")
}
expectEmptyChannel(t, ch)
}
func TestExtractFromEmptyDir(t *testing.T) {
dirName, err := utiltesting.MkTmpdir("file-test")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
defer os.RemoveAll(dirName)
ch := make(chan interface{}, 1)
c := new(dirName, "localhost", time.Millisecond, ch)
err = c.resetStoreFromPath()
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
update := (<-ch).(kubetypes.PodUpdate)
expected := CreatePodUpdate(kubetypes.SET, kubetypes.FileSource)
if !api.Semantic.DeepEqual(expected, update) {
t.Fatalf("expected %#v, Got %#v", expected, update)
}
}
type testCase struct {
desc string
pod runtime.Object
expected kubetypes.PodUpdate
}
func getTestCases(hostname types.NodeName) []*testCase {
grace := int64(30)
return []*testCase{
{
desc: "Simple pod",
pod: &v1.Pod{
TypeMeta: metav1.TypeMeta{
Kind: "Pod",
APIVersion: "",
},
ObjectMeta: v1.ObjectMeta{
Name: "test",
UID: "12345",
Namespace: "mynamespace",
},
Spec: v1.PodSpec{
Containers: []v1.Container{{Name: "image", Image: "test/image", SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults()}},
SecurityContext: &v1.PodSecurityContext{},
},
Status: v1.PodStatus{
Phase: v1.PodPending,
},
},
expected: CreatePodUpdate(kubetypes.SET, kubetypes.FileSource, &v1.Pod{
ObjectMeta: v1.ObjectMeta{
Name: "test-" + string(hostname),
UID: "12345",
Namespace: "mynamespace",
Annotations: map[string]string{kubetypes.ConfigHashAnnotationKey: "12345"},
SelfLink: getSelfLink("test-"+string(hostname), "mynamespace"),
},
Spec: v1.PodSpec{
NodeName: string(hostname),
RestartPolicy: v1.RestartPolicyAlways,
DNSPolicy: v1.DNSClusterFirst,
TerminationGracePeriodSeconds: &grace,
Containers: []v1.Container{{
Name: "image",
Image: "test/image",
TerminationMessagePath: "/dev/termination-log",
ImagePullPolicy: "Always",
SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults()}},
SecurityContext: &v1.PodSecurityContext{},
},
Status: v1.PodStatus{
Phase: v1.PodPending,
},
}),
},
}
}
func (tc *testCase) writeToFile(dir, name string, t *testing.T) string {
var versionedPod runtime.Object
err := testapi.Default.Converter().Convert(&tc.pod, &versionedPod, nil)
if err != nil {
t.Fatalf("%s: error in versioning the pod: %v", tc.desc, err)
}
fileContents, err := runtime.Encode(testapi.Default.Codec(), versionedPod)
if err != nil {
t.Fatalf("%s: error in encoding the pod: %v", tc.desc, err)
}
fileName := filepath.Join(dir, name)
if err := writeFile(fileName, []byte(fileContents)); err != nil {
t.Fatalf("unable to write test file %#v", err)
}
return fileName
}
func watchFileAdded(watchDir bool, t *testing.T) {
hostname := types.NodeName("random-test-hostname")
var testCases = getTestCases(hostname)
fileNamePre := "test_pod_config"
for index, testCase := range testCases {
func() {
dirName, err := utiltesting.MkTmpdir("dir-test")
if err != nil {
t.Fatalf("unable to create temp dir: %v", err)
}
defer os.RemoveAll(dirName)
fileName := fmt.Sprintf("%s_%d", fileNamePre, index)
ch := make(chan interface{})
if watchDir {
NewSourceFile(dirName, hostname, 100*time.Millisecond, ch)
} else {
NewSourceFile(filepath.Join(dirName, fileName), hostname, 100*time.Millisecond, ch)
}
expectEmptyUpdate(t, ch)
addFile := func() {
// Add a file
testCase.writeToFile(dirName, fileName, t)
}
go addFile()
// For !watchDir: expect an update by SourceFile.resetStoreFromPath().
// For watchDir: expect at least one update from CREATE & MODIFY inotify event.
// Shouldn't expect two updates from CREATE & MODIFY because CREATE doesn't guarantee file written.
// In that case no update will be sent from CREATE event.
expectUpdate(t, ch, testCase)
}()
}
}
func watchFileChanged(watchDir bool, t *testing.T) {
hostname := types.NodeName("random-test-hostname")
var testCases = getTestCases(hostname)
fileNamePre := "test_pod_config"
for index, testCase := range testCases {
func() {
dirName, err := utiltesting.MkTmpdir("dir-test")
fileName := fmt.Sprintf("%s_%d", fileNamePre, index)
if err != nil {
t.Fatalf("unable to create temp dir: %v", err)
}
defer os.RemoveAll(dirName)
var file string
lock := &sync.Mutex{}
ch := make(chan interface{})
func() {
lock.Lock()
defer lock.Unlock()
file = testCase.writeToFile(dirName, fileName, t)
}()
if watchDir {
NewSourceFile(dirName, hostname, 100*time.Millisecond, ch)
defer func() {
// Remove the file
deleteFile(dirName, fileName, ch, t)
}()
} else {
NewSourceFile(file, hostname, 100*time.Millisecond, ch)
}
// expect an update by SourceFile.resetStoreFromPath()
expectUpdate(t, ch, testCase)
changeFile := func() {
// Edit the file content
lock.Lock()
defer lock.Unlock()
pod := testCase.pod.(*v1.Pod)
pod.Spec.Containers[0].Name = "image2"
testCase.expected.Pods[0].Spec.Containers[0].Name = "image2"
testCase.writeToFile(dirName, fileName, t)
}
go changeFile()
// expect an update by MODIFY inotify event
expectUpdate(t, ch, testCase)
if watchDir {
from := fileName
fileName = fileName + "_ch"
go changeFileName(dirName, from, fileName, t)
// expect an update by MOVED_FROM inotify event cause changing file name
expectEmptyUpdate(t, ch)
// expect an update by MOVED_TO inotify event cause changing file name
expectUpdate(t, ch, testCase)
}
}()
}
}
func deleteFile(dir, file string, ch chan interface{}, t *testing.T) {
go func() {
path := filepath.Join(dir, file)
err := os.Remove(path)
if err != nil {
t.Errorf("unable to remove test file %s: %s", path, err)
}
}()
expectEmptyUpdate(t, ch)
}
func expectUpdate(t *testing.T, ch chan interface{}, testCase *testCase) {
timer := time.After(5 * time.Second)
for {
select {
case got := <-ch:
update := got.(kubetypes.PodUpdate)
for _, pod := range update.Pods {
// TODO: remove the conversion when validation is performed on versioned objects.
internalPod := &api.Pod{}
if err := v1.Convert_v1_Pod_To_api_Pod(pod, internalPod, nil); err != nil {
t.Fatalf("%s: Cannot convert pod %#v, %#v", testCase.desc, pod, err)
}
if errs := validation.ValidatePod(internalPod); len(errs) > 0 {
t.Fatalf("%s: Invalid pod %#v, %#v", testCase.desc, internalPod, errs)
}
}
if !api.Semantic.DeepEqual(testCase.expected, update) {
t.Fatalf("%s: Expected: %#v, Got: %#v", testCase.desc, testCase.expected, update)
}
return
case <-timer:
t.Fatalf("%s: Expected update, timeout instead", testCase.desc)
}
}
}
func expectEmptyUpdate(t *testing.T, ch chan interface{}) {
timer := time.After(5 * time.Second)
for {
select {
case got := <-ch:
update := got.(kubetypes.PodUpdate)
if len(update.Pods) != 0 {
t.Fatalf("expected empty update, got %#v", update)
}
return
case <-timer:
t.Fatalf("expected empty update, timeout instead")
}
}
}
func writeFile(filename string, data []byte) error {
f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE, 0666)
if err != nil {
return err
}
n, err := f.Write(data)
if err == nil && n < len(data) {
err = io.ErrShortWrite
}
if err1 := f.Close(); err == nil {
err = err1
}
return err
}
func changeFileName(dir, from, to string, t *testing.T) {
fromPath := filepath.Join(dir, from)
toPath := filepath.Join(dir, to)
if err := exec.Command("mv", fromPath, toPath).Run(); err != nil {
t.Errorf("Fail to change file name: %s", err)
}
}

View file

@ -0,0 +1,26 @@
// +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.
*/
// Reads the pod configuration from file or a directory of files.
package config
import "errors"
func (s *sourceFile) watch() error {
return errors.New("source file is unsupported in this build")
}

143
vendor/k8s.io/kubernetes/pkg/kubelet/config/http.go generated vendored Normal file
View file

@ -0,0 +1,143 @@
/*
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.
*/
// Reads the pod configuration from an HTTP GET response.
package config
import (
"bytes"
"fmt"
"io/ioutil"
"net/http"
"time"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/v1"
kubetypes "k8s.io/kubernetes/pkg/kubelet/types"
"github.com/golang/glog"
"k8s.io/apimachinery/pkg/types"
)
type sourceURL struct {
url string
header http.Header
nodeName types.NodeName
updates chan<- interface{}
data []byte
failureLogs int
client *http.Client
}
func NewSourceURL(url string, header http.Header, nodeName types.NodeName, period time.Duration, updates chan<- interface{}) {
config := &sourceURL{
url: url,
header: header,
nodeName: nodeName,
updates: updates,
data: nil,
// Timing out requests leads to retries. This client is only used to
// read the manifest URL passed to kubelet.
client: &http.Client{Timeout: 10 * time.Second},
}
glog.V(1).Infof("Watching URL %s", url)
go wait.Until(config.run, period, wait.NeverStop)
}
func (s *sourceURL) run() {
if err := s.extractFromURL(); err != nil {
// Don't log this multiple times per minute. The first few entries should be
// enough to get the point across.
if s.failureLogs < 3 {
glog.Warningf("Failed to read pods from URL: %v", err)
} else if s.failureLogs == 3 {
glog.Warningf("Failed to read pods from URL. Dropping verbosity of this message to V(4): %v", err)
} else {
glog.V(4).Infof("Failed to read pods from URL: %v", err)
}
s.failureLogs++
} else {
if s.failureLogs > 0 {
glog.Info("Successfully read pods from URL.")
s.failureLogs = 0
}
}
}
func (s *sourceURL) applyDefaults(pod *api.Pod) error {
return applyDefaults(pod, s.url, false, s.nodeName)
}
func (s *sourceURL) extractFromURL() error {
req, err := http.NewRequest("GET", s.url, nil)
if err != nil {
return err
}
req.Header = s.header
resp, err := s.client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("%v: %v", s.url, resp.Status)
}
if len(data) == 0 {
// Emit an update with an empty PodList to allow HTTPSource to be marked as seen
s.updates <- kubetypes.PodUpdate{Pods: []*v1.Pod{}, Op: kubetypes.SET, Source: kubetypes.HTTPSource}
return fmt.Errorf("zero-length data received from %v", s.url)
}
// Short circuit if the data has not changed since the last time it was read.
if bytes.Compare(data, s.data) == 0 {
return nil
}
s.data = data
// First try as it is a single pod.
parsed, pod, singlePodErr := tryDecodeSinglePod(data, s.applyDefaults)
if parsed {
if singlePodErr != nil {
// It parsed but could not be used.
return singlePodErr
}
s.updates <- kubetypes.PodUpdate{Pods: []*v1.Pod{pod}, Op: kubetypes.SET, Source: kubetypes.HTTPSource}
return nil
}
// That didn't work, so try a list of pods.
parsed, podList, multiPodErr := tryDecodePodList(data, s.applyDefaults)
if parsed {
if multiPodErr != nil {
// It parsed but could not be used.
return multiPodErr
}
pods := make([]*v1.Pod, 0)
for i := range podList.Items {
pods = append(pods, &podList.Items[i])
}
s.updates <- kubetypes.PodUpdate{Pods: pods, Op: kubetypes.SET, Source: kubetypes.HTTPSource}
return nil
}
return fmt.Errorf("%v: received '%v', but couldn't parse as "+
"single (%v) or multiple pods (%v).\n",
s.url, string(data), singlePodErr, multiPodErr)
}

View file

@ -0,0 +1,361 @@
/*
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 config
import (
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"time"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/testapi"
"k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/api/validation"
kubetypes "k8s.io/kubernetes/pkg/kubelet/types"
utiltesting "k8s.io/kubernetes/pkg/util/testing"
)
func TestURLErrorNotExistNoUpdate(t *testing.T) {
ch := make(chan interface{})
NewSourceURL("http://localhost:49575/_not_found_", http.Header{}, "localhost", time.Millisecond, ch)
select {
case got := <-ch:
t.Errorf("Expected no update, Got %#v", got)
case <-time.After(2 * time.Millisecond):
}
}
func TestExtractFromHttpBadness(t *testing.T) {
ch := make(chan interface{}, 1)
c := sourceURL{"http://localhost:49575/_not_found_", http.Header{}, "other", ch, nil, 0, http.DefaultClient}
if err := c.extractFromURL(); err == nil {
t.Errorf("Expected error")
}
expectEmptyChannel(t, ch)
}
func TestExtractInvalidPods(t *testing.T) {
var testCases = []struct {
desc string
pod *v1.Pod
}{
{
desc: "No version",
pod: &v1.Pod{TypeMeta: metav1.TypeMeta{APIVersion: ""}},
},
{
desc: "Invalid version",
pod: &v1.Pod{TypeMeta: metav1.TypeMeta{APIVersion: "v1betta2"}},
},
{
desc: "Invalid volume name",
pod: &v1.Pod{
TypeMeta: metav1.TypeMeta{APIVersion: api.Registry.GroupOrDie(v1.GroupName).GroupVersion.String()},
Spec: v1.PodSpec{
Volumes: []v1.Volume{{Name: "_INVALID_"}},
},
},
},
{
desc: "Duplicate volume names",
pod: &v1.Pod{
TypeMeta: metav1.TypeMeta{APIVersion: api.Registry.GroupOrDie(v1.GroupName).GroupVersion.String()},
Spec: v1.PodSpec{
Volumes: []v1.Volume{{Name: "repeated"}, {Name: "repeated"}},
},
},
},
{
desc: "Unspecified container name",
pod: &v1.Pod{
TypeMeta: metav1.TypeMeta{APIVersion: api.Registry.GroupOrDie(v1.GroupName).GroupVersion.String()},
Spec: v1.PodSpec{
Containers: []v1.Container{{Name: ""}},
},
},
},
{
desc: "Invalid container name",
pod: &v1.Pod{
TypeMeta: metav1.TypeMeta{APIVersion: api.Registry.GroupOrDie(v1.GroupName).GroupVersion.String()},
Spec: v1.PodSpec{
Containers: []v1.Container{{Name: "_INVALID_"}},
},
},
},
}
for _, testCase := range testCases {
data, err := json.Marshal(testCase.pod)
if err != nil {
t.Fatalf("%s: Some weird json problem: %v", testCase.desc, err)
}
fakeHandler := utiltesting.FakeHandler{
StatusCode: 200,
ResponseBody: string(data),
}
testServer := httptest.NewServer(&fakeHandler)
defer testServer.Close()
ch := make(chan interface{}, 1)
c := sourceURL{testServer.URL, http.Header{}, "localhost", ch, nil, 0, http.DefaultClient}
if err := c.extractFromURL(); err == nil {
t.Errorf("%s: Expected error", testCase.desc)
}
}
}
func TestExtractPodsFromHTTP(t *testing.T) {
nodeName := "different-value"
grace := int64(30)
var testCases = []struct {
desc string
pods runtime.Object
expected kubetypes.PodUpdate
}{
{
desc: "Single pod",
pods: &v1.Pod{
TypeMeta: metav1.TypeMeta{
Kind: "Pod",
APIVersion: "",
},
ObjectMeta: v1.ObjectMeta{
Name: "foo",
UID: "111",
Namespace: "mynamespace",
},
Spec: v1.PodSpec{
NodeName: string(nodeName),
Containers: []v1.Container{{Name: "1", Image: "foo", ImagePullPolicy: v1.PullAlways}},
SecurityContext: &v1.PodSecurityContext{},
},
Status: v1.PodStatus{
Phase: v1.PodPending,
},
},
expected: CreatePodUpdate(kubetypes.SET,
kubetypes.HTTPSource,
&v1.Pod{
ObjectMeta: v1.ObjectMeta{
UID: "111",
Name: "foo" + "-" + nodeName,
Namespace: "mynamespace",
Annotations: map[string]string{kubetypes.ConfigHashAnnotationKey: "111"},
SelfLink: getSelfLink("foo-"+nodeName, "mynamespace"),
},
Spec: v1.PodSpec{
NodeName: nodeName,
RestartPolicy: v1.RestartPolicyAlways,
DNSPolicy: v1.DNSClusterFirst,
SecurityContext: &v1.PodSecurityContext{},
TerminationGracePeriodSeconds: &grace,
Containers: []v1.Container{{
Name: "1",
Image: "foo",
TerminationMessagePath: "/dev/termination-log",
ImagePullPolicy: "Always",
}},
},
Status: v1.PodStatus{
Phase: v1.PodPending,
},
}),
},
{
desc: "Multiple pods",
pods: &v1.PodList{
TypeMeta: metav1.TypeMeta{
Kind: "PodList",
APIVersion: "",
},
Items: []v1.Pod{
{
ObjectMeta: v1.ObjectMeta{
Name: "foo",
UID: "111",
},
Spec: v1.PodSpec{
NodeName: nodeName,
Containers: []v1.Container{{Name: "1", Image: "foo", ImagePullPolicy: v1.PullAlways}},
SecurityContext: &v1.PodSecurityContext{},
},
Status: v1.PodStatus{
Phase: v1.PodPending,
},
},
{
ObjectMeta: v1.ObjectMeta{
Name: "bar",
UID: "222",
},
Spec: v1.PodSpec{
NodeName: nodeName,
Containers: []v1.Container{{Name: "2", Image: "bar:bartag", ImagePullPolicy: ""}},
SecurityContext: &v1.PodSecurityContext{},
},
Status: v1.PodStatus{
Phase: v1.PodPending,
},
},
},
},
expected: CreatePodUpdate(kubetypes.SET,
kubetypes.HTTPSource,
&v1.Pod{
ObjectMeta: v1.ObjectMeta{
UID: "111",
Name: "foo" + "-" + nodeName,
Namespace: "default",
Annotations: map[string]string{kubetypes.ConfigHashAnnotationKey: "111"},
SelfLink: getSelfLink("foo-"+nodeName, kubetypes.NamespaceDefault),
},
Spec: v1.PodSpec{
NodeName: nodeName,
RestartPolicy: v1.RestartPolicyAlways,
DNSPolicy: v1.DNSClusterFirst,
TerminationGracePeriodSeconds: &grace,
SecurityContext: &v1.PodSecurityContext{},
Containers: []v1.Container{{
Name: "1",
Image: "foo",
TerminationMessagePath: "/dev/termination-log",
ImagePullPolicy: "Always",
}},
},
Status: v1.PodStatus{
Phase: v1.PodPending,
},
},
&v1.Pod{
ObjectMeta: v1.ObjectMeta{
UID: "222",
Name: "bar" + "-" + nodeName,
Namespace: "default",
Annotations: map[string]string{kubetypes.ConfigHashAnnotationKey: "222"},
SelfLink: getSelfLink("bar-"+nodeName, kubetypes.NamespaceDefault),
},
Spec: v1.PodSpec{
NodeName: nodeName,
RestartPolicy: v1.RestartPolicyAlways,
DNSPolicy: v1.DNSClusterFirst,
TerminationGracePeriodSeconds: &grace,
SecurityContext: &v1.PodSecurityContext{},
Containers: []v1.Container{{
Name: "2",
Image: "bar:bartag",
TerminationMessagePath: "/dev/termination-log",
ImagePullPolicy: "IfNotPresent",
}},
},
Status: v1.PodStatus{
Phase: v1.PodPending,
},
}),
},
}
for _, testCase := range testCases {
var versionedPods runtime.Object
err := testapi.Default.Converter().Convert(&testCase.pods, &versionedPods, nil)
if err != nil {
t.Fatalf("%s: error in versioning the pods: %s", testCase.desc, err)
}
data, err := runtime.Encode(testapi.Default.Codec(), versionedPods)
if err != nil {
t.Fatalf("%s: error in encoding the pod: %v", testCase.desc, err)
}
fakeHandler := utiltesting.FakeHandler{
StatusCode: 200,
ResponseBody: string(data),
}
testServer := httptest.NewServer(&fakeHandler)
defer testServer.Close()
ch := make(chan interface{}, 1)
c := sourceURL{testServer.URL, http.Header{}, types.NodeName(nodeName), ch, nil, 0, http.DefaultClient}
if err := c.extractFromURL(); err != nil {
t.Errorf("%s: Unexpected error: %v", testCase.desc, err)
continue
}
update := (<-ch).(kubetypes.PodUpdate)
if !api.Semantic.DeepEqual(testCase.expected, update) {
t.Errorf("%s: Expected: %#v, Got: %#v", testCase.desc, testCase.expected, update)
}
for _, pod := range update.Pods {
// TODO: remove the conversion when validation is performed on versioned objects.
internalPod := &api.Pod{}
if err := v1.Convert_v1_Pod_To_api_Pod(pod, internalPod, nil); err != nil {
t.Fatalf("%s: Cannot convert pod %#v, %#v", testCase.desc, pod, err)
}
if errs := validation.ValidatePod(internalPod); len(errs) != 0 {
t.Errorf("%s: Expected no validation errors on %#v, Got %v", testCase.desc, pod, errs.ToAggregate())
}
}
}
}
func TestURLWithHeader(t *testing.T) {
pod := &v1.Pod{
TypeMeta: metav1.TypeMeta{
APIVersion: api.Registry.GroupOrDie(v1.GroupName).GroupVersion.String(),
Kind: "Pod",
},
ObjectMeta: v1.ObjectMeta{
Name: "foo",
UID: "111",
Namespace: "mynamespace",
},
Spec: v1.PodSpec{
NodeName: "localhost",
Containers: []v1.Container{{Name: "1", Image: "foo", ImagePullPolicy: v1.PullAlways}},
},
}
data, err := json.Marshal(pod)
if err != nil {
t.Fatalf("Unexpected json marshalling error: %v", err)
}
fakeHandler := utiltesting.FakeHandler{
StatusCode: 200,
ResponseBody: string(data),
}
testServer := httptest.NewServer(&fakeHandler)
defer testServer.Close()
ch := make(chan interface{}, 1)
header := make(http.Header)
header.Set("Metadata-Flavor", "Google")
c := sourceURL{testServer.URL, header, "localhost", ch, nil, 0, http.DefaultClient}
if err := c.extractFromURL(); err != nil {
t.Fatalf("Unexpected error extracting from URL: %v", err)
}
update := (<-ch).(kubetypes.PodUpdate)
headerVal := fakeHandler.RequestReceived.Header["Metadata-Flavor"]
if len(headerVal) != 1 || headerVal[0] != "Google" {
t.Errorf("Header missing expected entry %v. Got %v", header, fakeHandler.RequestReceived.Header)
}
if len(update.Pods) != 1 {
t.Errorf("Received wrong number of pods, expected one: %v", update.Pods)
}
}

67
vendor/k8s.io/kubernetes/pkg/kubelet/config/sources.go generated vendored Normal file
View file

@ -0,0 +1,67 @@
/*
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 config implements the pod configuration readers.
package config
import (
"sync"
"k8s.io/apimachinery/pkg/util/sets"
)
// SourcesReadyFn is function that returns true if the specified sources have been seen.
type SourcesReadyFn func(sourcesSeen sets.String) bool
// SourcesReady tracks the set of configured sources seen by the kubelet.
type SourcesReady interface {
// AddSource adds the specified source to the set of sources managed.
AddSource(source string)
// AllReady returns true if the currently configured sources have all been seen.
AllReady() bool
}
// NewSourcesReady returns a SourcesReady with the specified function.
func NewSourcesReady(sourcesReadyFn SourcesReadyFn) SourcesReady {
return &sourcesImpl{
sourcesSeen: sets.NewString(),
sourcesReadyFn: sourcesReadyFn,
}
}
// sourcesImpl implements SourcesReady. It is thread-safe.
type sourcesImpl struct {
// lock protects access to sources seen.
lock sync.RWMutex
// set of sources seen.
sourcesSeen sets.String
// sourcesReady is a function that evaluates if the sources are ready.
sourcesReadyFn SourcesReadyFn
}
// Add adds the specified source to the set of sources managed.
func (s *sourcesImpl) AddSource(source string) {
s.lock.Lock()
defer s.lock.Unlock()
s.sourcesSeen.Insert(source)
}
// AllReady returns true if each configured source is ready.
func (s *sourcesImpl) AllReady() bool {
s.lock.RLock()
defer s.lock.RUnlock()
return s.sourcesReadyFn(s.sourcesSeen)
}

92
vendor/k8s.io/kubernetes/pkg/kubelet/container/BUILD generated vendored Normal file
View file

@ -0,0 +1,92 @@
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = [
"cache.go",
"container_gc.go",
"container_reference_manager.go",
"helpers.go",
"os.go",
"pty_linux.go",
"ref.go",
"resize.go",
"runtime.go",
"runtime_cache.go",
"runtime_cache_fake.go",
"sync_result.go",
],
tags = ["automanaged"],
deps = [
"//pkg/api/v1:go_default_library",
"//pkg/client/record:go_default_library",
"//pkg/kubelet/api/v1alpha1/runtime:go_default_library",
"//pkg/kubelet/util/format:go_default_library",
"//pkg/kubelet/util/ioutils:go_default_library",
"//pkg/util/flowcontrol:go_default_library",
"//pkg/util/hash:go_default_library",
"//pkg/util/term:go_default_library",
"//pkg/volume:go_default_library",
"//third_party/forked/golang/expansion:go_default_library",
"//vendor:github.com/golang/glog",
"//vendor:github.com/kr/pty",
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
"//vendor:k8s.io/apimachinery/pkg/runtime",
"//vendor:k8s.io/apimachinery/pkg/types",
"//vendor:k8s.io/apimachinery/pkg/util/errors",
"//vendor:k8s.io/apimachinery/pkg/util/runtime",
],
)
go_test(
name = "go_default_test",
srcs = [
"cache_test.go",
"helpers_test.go",
"ref_test.go",
"sync_result_test.go",
],
library = ":go_default_library",
tags = ["automanaged"],
deps = [
"//pkg/api:go_default_library",
"//pkg/api/v1:go_default_library",
"//vendor:github.com/stretchr/testify/assert",
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
"//vendor:k8s.io/apimachinery/pkg/types",
],
)
go_test(
name = "go_default_xtest",
srcs = ["runtime_cache_test.go"],
tags = ["automanaged"],
deps = [
"//pkg/kubelet/container:go_default_library",
"//pkg/kubelet/container/testing:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//pkg/kubelet/container/testing:all-srcs",
],
tags = ["automanaged"],
)

199
vendor/k8s.io/kubernetes/pkg/kubelet/container/cache.go generated vendored Normal file
View file

@ -0,0 +1,199 @@
/*
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 container
import (
"sync"
"time"
"k8s.io/apimachinery/pkg/types"
)
// Cache stores the PodStatus for the pods. It represents *all* the visible
// pods/containers in the container runtime. All cache entries are at least as
// new or newer than the global timestamp (set by UpdateTime()), while
// individual entries may be slightly newer than the global timestamp. If a pod
// has no states known by the runtime, Cache returns an empty PodStatus object
// with ID populated.
//
// Cache provides two methods to retrive the PodStatus: the non-blocking Get()
// and the blocking GetNewerThan() method. The component responsible for
// populating the cache is expected to call Delete() to explicitly free the
// cache entries.
type Cache interface {
Get(types.UID) (*PodStatus, error)
Set(types.UID, *PodStatus, error, time.Time)
// GetNewerThan is a blocking call that only returns the status
// when it is newer than the given time.
GetNewerThan(types.UID, time.Time) (*PodStatus, error)
Delete(types.UID)
UpdateTime(time.Time)
}
type data struct {
// Status of the pod.
status *PodStatus
// Error got when trying to inspect the pod.
err error
// Time when the data was last modified.
modified time.Time
}
type subRecord struct {
time time.Time
ch chan *data
}
// cache implements Cache.
type cache struct {
// Lock which guards all internal data structures.
lock sync.RWMutex
// Map that stores the pod statuses.
pods map[types.UID]*data
// A global timestamp represents how fresh the cached data is. All
// cache content is at the least newer than this timestamp. Note that the
// timestamp is nil after initialization, and will only become non-nil when
// it is ready to serve the cached statuses.
timestamp *time.Time
// Map that stores the subscriber records.
subscribers map[types.UID][]*subRecord
}
// NewCache creates a pod cache.
func NewCache() Cache {
return &cache{pods: map[types.UID]*data{}, subscribers: map[types.UID][]*subRecord{}}
}
// Get returns the PodStatus for the pod; callers are expected not to
// modify the objects returned.
func (c *cache) Get(id types.UID) (*PodStatus, error) {
c.lock.RLock()
defer c.lock.RUnlock()
d := c.get(id)
return d.status, d.err
}
func (c *cache) GetNewerThan(id types.UID, minTime time.Time) (*PodStatus, error) {
ch := c.subscribe(id, minTime)
d := <-ch
return d.status, d.err
}
// Set sets the PodStatus for the pod.
func (c *cache) Set(id types.UID, status *PodStatus, err error, timestamp time.Time) {
c.lock.Lock()
defer c.lock.Unlock()
defer c.notify(id, timestamp)
c.pods[id] = &data{status: status, err: err, modified: timestamp}
}
// Delete removes the entry of the pod.
func (c *cache) Delete(id types.UID) {
c.lock.Lock()
defer c.lock.Unlock()
delete(c.pods, id)
}
// UpdateTime modifies the global timestamp of the cache and notify
// subscribers if needed.
func (c *cache) UpdateTime(timestamp time.Time) {
c.lock.Lock()
defer c.lock.Unlock()
c.timestamp = &timestamp
// Notify all the subscribers if the condition is met.
for id := range c.subscribers {
c.notify(id, *c.timestamp)
}
}
func makeDefaultData(id types.UID) *data {
return &data{status: &PodStatus{ID: id}, err: nil}
}
func (c *cache) get(id types.UID) *data {
d, ok := c.pods[id]
if !ok {
// Cache should store *all* pod/container information known by the
// container runtime. A cache miss indicates that there are no states
// regarding the pod last time we queried the container runtime.
// What this *really* means is that there are no visible pod/containers
// associated with this pod. Simply return an default (mostly empty)
// PodStatus to reflect this.
return makeDefaultData(id)
}
return d
}
// getIfNewerThan returns the data it is newer than the given time.
// Otherwise, it returns nil. The caller should acquire the lock.
func (c *cache) getIfNewerThan(id types.UID, minTime time.Time) *data {
d, ok := c.pods[id]
globalTimestampIsNewer := (c.timestamp != nil && c.timestamp.After(minTime))
if !ok && globalTimestampIsNewer {
// Status is not cached, but the global timestamp is newer than
// minTime, return the default status.
return makeDefaultData(id)
}
if ok && (d.modified.After(minTime) || globalTimestampIsNewer) {
// Status is cached, return status if either of the following is true.
// * status was modified after minTime
// * the global timestamp of the cache is newer than minTime.
return d
}
// The pod status is not ready.
return nil
}
// notify sends notifications for pod with the given id, if the requirements
// are met. Note that the caller should acquire the lock.
func (c *cache) notify(id types.UID, timestamp time.Time) {
list, ok := c.subscribers[id]
if !ok {
// No one to notify.
return
}
newList := []*subRecord{}
for i, r := range list {
if timestamp.Before(r.time) {
// Doesn't meet the time requirement; keep the record.
newList = append(newList, list[i])
continue
}
r.ch <- c.get(id)
close(r.ch)
}
if len(newList) == 0 {
delete(c.subscribers, id)
} else {
c.subscribers[id] = newList
}
}
func (c *cache) subscribe(id types.UID, timestamp time.Time) chan *data {
ch := make(chan *data, 1)
c.lock.Lock()
defer c.lock.Unlock()
d := c.getIfNewerThan(id, timestamp)
if d != nil {
// If the cache entry is ready, send the data and return immediately.
ch <- d
return ch
}
// Add the subscription record.
c.subscribers[id] = append(c.subscribers[id], &subRecord{time: timestamp, ch: ch})
return ch
}

View file

@ -0,0 +1,210 @@
/*
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 container
import (
"fmt"
"strconv"
"testing"
"time"
"github.com/stretchr/testify/assert"
"k8s.io/apimachinery/pkg/types"
)
func newTestCache() *cache {
c := NewCache()
return c.(*cache)
}
func TestCacheNotInitialized(t *testing.T) {
cache := newTestCache()
// If the global timestamp is not set, always return nil.
d := cache.getIfNewerThan(types.UID("1234"), time.Time{})
assert.True(t, d == nil, "should return nil since cache is not initialized")
}
func getTestPodIDAndStatus(numContainers int) (types.UID, *PodStatus) {
id := types.UID(strconv.FormatInt(time.Now().UnixNano(), 10))
name := fmt.Sprintf("cache-foo-%s", string(id))
namespace := "ns"
var status *PodStatus
if numContainers > 0 {
status = &PodStatus{ID: id, Name: name, Namespace: namespace}
} else {
status = &PodStatus{ID: id}
}
for i := 0; i < numContainers; i++ {
status.ContainerStatuses = append(status.ContainerStatuses, &ContainerStatus{Name: string(i)})
}
return id, status
}
func TestGetIfNewerThanWhenPodExists(t *testing.T) {
cache := newTestCache()
timestamp := time.Now()
cases := []struct {
cacheTime time.Time
modified time.Time
expected bool
}{
{
// Both the global cache timestamp and the modified time are newer
// than the timestamp.
cacheTime: timestamp.Add(time.Second),
modified: timestamp,
expected: true,
},
{
// Global cache timestamp is newer, but the pod entry modified
// time is older than the given timestamp. This means that the
// entry is up-to-date even though it hasn't changed for a while.
cacheTime: timestamp.Add(time.Second),
modified: timestamp.Add(-time.Second * 10),
expected: true,
},
{
// Global cache timestamp is older, but the pod entry modified
// time is newer than the given timestamp. This means that the
// entry is up-to-date but the rest of the cache are still being
// updated.
cacheTime: timestamp.Add(-time.Second),
modified: timestamp.Add(time.Second * 3),
expected: true,
},
{
// Both the global cache timestamp and the modified time are older
// than the given timestamp.
cacheTime: timestamp.Add(-time.Second),
modified: timestamp.Add(-time.Second),
expected: false,
},
}
for i, c := range cases {
podID, status := getTestPodIDAndStatus(2)
cache.UpdateTime(c.cacheTime)
cache.Set(podID, status, nil, c.modified)
d := cache.getIfNewerThan(podID, timestamp)
assert.Equal(t, c.expected, d != nil, "test[%d]", i)
}
}
func TestGetPodNewerThanWhenPodDoesNotExist(t *testing.T) {
cache := newTestCache()
cacheTime := time.Now()
cache.UpdateTime(cacheTime)
podID := types.UID("1234")
cases := []struct {
timestamp time.Time
expected bool
}{
{
timestamp: cacheTime.Add(-time.Second),
expected: true,
},
{
timestamp: cacheTime.Add(time.Second),
expected: false,
},
}
for i, c := range cases {
d := cache.getIfNewerThan(podID, c.timestamp)
assert.Equal(t, c.expected, d != nil, "test[%d]", i)
}
}
func TestCacheSetAndGet(t *testing.T) {
cache := NewCache()
cases := []struct {
numContainers int
error error
}{
{numContainers: 3, error: nil},
{numContainers: 2, error: fmt.Errorf("unable to get status")},
{numContainers: 0, error: nil},
}
for i, c := range cases {
podID, status := getTestPodIDAndStatus(c.numContainers)
cache.Set(podID, status, c.error, time.Time{})
// Read back the status and error stored in cache and make sure they
// match the original ones.
actualStatus, actualErr := cache.Get(podID)
assert.Equal(t, status, actualStatus, "test[%d]", i)
assert.Equal(t, c.error, actualErr, "test[%d]", i)
}
}
func TestCacheGetPodDoesNotExist(t *testing.T) {
cache := NewCache()
podID, status := getTestPodIDAndStatus(0)
// If the pod does not exist in cache, cache should return an status
// object with id filled.
actualStatus, actualErr := cache.Get(podID)
assert.Equal(t, status, actualStatus)
assert.Equal(t, nil, actualErr)
}
func TestDelete(t *testing.T) {
cache := &cache{pods: map[types.UID]*data{}}
// Write a new pod status into the cache.
podID, status := getTestPodIDAndStatus(3)
cache.Set(podID, status, nil, time.Time{})
actualStatus, actualErr := cache.Get(podID)
assert.Equal(t, status, actualStatus)
assert.Equal(t, nil, actualErr)
// Delete the pod from cache, and verify that we get an empty status.
cache.Delete(podID)
expectedStatus := &PodStatus{ID: podID}
actualStatus, actualErr = cache.Get(podID)
assert.Equal(t, expectedStatus, actualStatus)
assert.Equal(t, nil, actualErr)
}
func verifyNotification(t *testing.T, ch chan *data, expectNotification bool) {
if expectNotification {
assert.True(t, len(ch) > 0, "Did not receive notification")
} else {
assert.True(t, len(ch) < 1, "Should not have triggered the notification")
}
// Drain the channel.
for i := 0; i < len(ch); i++ {
<-ch
}
}
func TestRegisterNotification(t *testing.T) {
cache := newTestCache()
cacheTime := time.Now()
cache.UpdateTime(cacheTime)
podID, status := getTestPodIDAndStatus(1)
ch := cache.subscribe(podID, cacheTime.Add(time.Second))
verifyNotification(t, ch, false)
cache.Set(podID, status, nil, cacheTime.Add(time.Second))
// The Set operation should've triggered the notification.
verifyNotification(t, ch, true)
podID, _ = getTestPodIDAndStatus(1)
ch = cache.subscribe(podID, cacheTime.Add(time.Second))
verifyNotification(t, ch, false)
cache.UpdateTime(cacheTime.Add(time.Second * 2))
// The advance of cache timestamp should've triggered the notification.
verifyNotification(t, ch, true)
}

View file

@ -0,0 +1,68 @@
/*
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 container
import (
"fmt"
"time"
)
// Specified a policy for garbage collecting containers.
type ContainerGCPolicy struct {
// Minimum age at which a container can be garbage collected, zero for no limit.
MinAge time.Duration
// Max number of dead containers any single pod (UID, container name) pair is
// allowed to have, less than zero for no limit.
MaxPerPodContainer int
// Max number of total dead containers, less than zero for no limit.
MaxContainers int
}
// Manages garbage collection of dead containers.
//
// Implementation is thread-compatible.
type ContainerGC interface {
// Garbage collect containers.
GarbageCollect(allSourcesReady bool) error
}
// TODO(vmarmol): Preferentially remove pod infra containers.
type realContainerGC struct {
// Container runtime
runtime Runtime
// Policy for garbage collection.
policy ContainerGCPolicy
}
// New ContainerGC instance with the specified policy.
func NewContainerGC(runtime Runtime, policy ContainerGCPolicy) (ContainerGC, error) {
if policy.MinAge < 0 {
return nil, fmt.Errorf("invalid minimum garbage collection age: %v", policy.MinAge)
}
return &realContainerGC{
runtime: runtime,
policy: policy,
}, nil
}
func (cgc *realContainerGC) GarbageCollect(allSourcesReady bool) error {
return cgc.runtime.GarbageCollect(cgc.policy, allSourcesReady)
}

View file

@ -0,0 +1,60 @@
/*
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 container
import (
"sync"
"k8s.io/kubernetes/pkg/api/v1"
)
// RefManager manages the references for the containers.
// The references are used for reporting events such as creation,
// failure, etc. This manager is thread-safe, no locks are necessary
// for the caller.
type RefManager struct {
sync.RWMutex
containerIDToRef map[ContainerID]*v1.ObjectReference
}
// NewRefManager creates and returns a container reference manager
// with empty contents.
func NewRefManager() *RefManager {
return &RefManager{containerIDToRef: make(map[ContainerID]*v1.ObjectReference)}
}
// SetRef stores a reference to a pod's container, associating it with the given container ID.
func (c *RefManager) SetRef(id ContainerID, ref *v1.ObjectReference) {
c.Lock()
defer c.Unlock()
c.containerIDToRef[id] = ref
}
// ClearRef forgets the given container id and its associated container reference.
func (c *RefManager) ClearRef(id ContainerID) {
c.Lock()
defer c.Unlock()
delete(c.containerIDToRef, id)
}
// GetRef returns the container reference of the given ID, or (nil, false) if none is stored.
func (c *RefManager) GetRef(id ContainerID) (ref *v1.ObjectReference, ok bool) {
c.RLock()
defer c.RUnlock()
ref, ok = c.containerIDToRef[id]
return ref, ok
}

View file

@ -0,0 +1,275 @@
/*
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 container
import (
"bytes"
"fmt"
"hash/adler32"
"strings"
"time"
"github.com/golang/glog"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/client/record"
runtimeapi "k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime"
"k8s.io/kubernetes/pkg/kubelet/util/format"
"k8s.io/kubernetes/pkg/kubelet/util/ioutils"
hashutil "k8s.io/kubernetes/pkg/util/hash"
"k8s.io/kubernetes/third_party/forked/golang/expansion"
)
// HandlerRunner runs a lifecycle handler for a container.
type HandlerRunner interface {
Run(containerID ContainerID, pod *v1.Pod, container *v1.Container, handler *v1.Handler) (string, error)
}
// RuntimeHelper wraps kubelet to make container runtime
// able to get necessary informations like the RunContainerOptions, DNS settings.
type RuntimeHelper interface {
GenerateRunContainerOptions(pod *v1.Pod, container *v1.Container, podIP string) (*RunContainerOptions, error)
GetClusterDNS(pod *v1.Pod) (dnsServers []string, dnsSearches []string, err error)
GetPodDir(podUID types.UID) string
GeneratePodHostNameAndDomain(pod *v1.Pod) (hostname string, hostDomain string, err error)
// GetExtraSupplementalGroupsForPod returns a list of the extra
// supplemental groups for the Pod. These extra supplemental groups come
// from annotations on persistent volumes that the pod depends on.
GetExtraSupplementalGroupsForPod(pod *v1.Pod) []int64
}
// ShouldContainerBeRestarted checks whether a container needs to be restarted.
// TODO(yifan): Think about how to refactor this.
func ShouldContainerBeRestarted(container *v1.Container, pod *v1.Pod, podStatus *PodStatus) bool {
// Get latest container status.
status := podStatus.FindContainerStatusByName(container.Name)
// If the container was never started before, we should start it.
// NOTE(random-liu): If all historical containers were GC'd, we'll also return true here.
if status == nil {
return true
}
// Check whether container is running
if status.State == ContainerStateRunning {
return false
}
// Always restart container in unknown state now
if status.State == ContainerStateUnknown {
return true
}
// Check RestartPolicy for dead container
if pod.Spec.RestartPolicy == v1.RestartPolicyNever {
glog.V(4).Infof("Already ran container %q of pod %q, do nothing", container.Name, format.Pod(pod))
return false
}
if pod.Spec.RestartPolicy == v1.RestartPolicyOnFailure {
// Check the exit code.
if status.ExitCode == 0 {
glog.V(4).Infof("Already successfully ran container %q of pod %q, do nothing", container.Name, format.Pod(pod))
return false
}
}
return true
}
// HashContainer returns the hash of the container. It is used to compare
// the running container with its desired spec.
func HashContainer(container *v1.Container) uint64 {
hash := adler32.New()
hashutil.DeepHashObject(hash, *container)
return uint64(hash.Sum32())
}
// EnvVarsToMap constructs a map of environment name to value from a slice
// of env vars.
func EnvVarsToMap(envs []EnvVar) map[string]string {
result := map[string]string{}
for _, env := range envs {
result[env.Name] = env.Value
}
return result
}
func ExpandContainerCommandAndArgs(container *v1.Container, envs []EnvVar) (command []string, args []string) {
mapping := expansion.MappingFuncFor(EnvVarsToMap(envs))
if len(container.Command) != 0 {
for _, cmd := range container.Command {
command = append(command, expansion.Expand(cmd, mapping))
}
}
if len(container.Args) != 0 {
for _, arg := range container.Args {
args = append(args, expansion.Expand(arg, mapping))
}
}
return command, args
}
// Create an event recorder to record object's event except implicitly required container's, like infra container.
func FilterEventRecorder(recorder record.EventRecorder) record.EventRecorder {
return &innerEventRecorder{
recorder: recorder,
}
}
type innerEventRecorder struct {
recorder record.EventRecorder
}
func (irecorder *innerEventRecorder) shouldRecordEvent(object runtime.Object) (*v1.ObjectReference, bool) {
if object == nil {
return nil, false
}
if ref, ok := object.(*v1.ObjectReference); ok {
if !strings.HasPrefix(ref.FieldPath, ImplicitContainerPrefix) {
return ref, true
}
}
return nil, false
}
func (irecorder *innerEventRecorder) Event(object runtime.Object, eventtype, reason, message string) {
if ref, ok := irecorder.shouldRecordEvent(object); ok {
irecorder.recorder.Event(ref, eventtype, reason, message)
}
}
func (irecorder *innerEventRecorder) Eventf(object runtime.Object, eventtype, reason, messageFmt string, args ...interface{}) {
if ref, ok := irecorder.shouldRecordEvent(object); ok {
irecorder.recorder.Eventf(ref, eventtype, reason, messageFmt, args...)
}
}
func (irecorder *innerEventRecorder) PastEventf(object runtime.Object, timestamp metav1.Time, eventtype, reason, messageFmt string, args ...interface{}) {
if ref, ok := irecorder.shouldRecordEvent(object); ok {
irecorder.recorder.PastEventf(ref, timestamp, eventtype, reason, messageFmt, args...)
}
}
// Pod must not be nil.
func IsHostNetworkPod(pod *v1.Pod) bool {
return pod.Spec.HostNetwork
}
// TODO(random-liu): Convert PodStatus to running Pod, should be deprecated soon
func ConvertPodStatusToRunningPod(runtimeName string, podStatus *PodStatus) Pod {
runningPod := Pod{
ID: podStatus.ID,
Name: podStatus.Name,
Namespace: podStatus.Namespace,
}
for _, containerStatus := range podStatus.ContainerStatuses {
if containerStatus.State != ContainerStateRunning {
continue
}
container := &Container{
ID: containerStatus.ID,
Name: containerStatus.Name,
Image: containerStatus.Image,
ImageID: containerStatus.ImageID,
Hash: containerStatus.Hash,
State: containerStatus.State,
}
runningPod.Containers = append(runningPod.Containers, container)
}
// Populate sandboxes in kubecontainer.Pod
for _, sandbox := range podStatus.SandboxStatuses {
runningPod.Sandboxes = append(runningPod.Sandboxes, &Container{
ID: ContainerID{Type: runtimeName, ID: *sandbox.Id},
State: SandboxToContainerState(*sandbox.State),
})
}
return runningPod
}
// sandboxToContainerState converts runtimeApi.PodSandboxState to
// kubecontainer.ContainerState.
// This is only needed because we need to return sandboxes as if they were
// kubecontainer.Containers to avoid substantial changes to PLEG.
// TODO: Remove this once it becomes obsolete.
func SandboxToContainerState(state runtimeapi.PodSandboxState) ContainerState {
switch state {
case runtimeapi.PodSandboxState_SANDBOX_READY:
return ContainerStateRunning
case runtimeapi.PodSandboxState_SANDBOX_NOTREADY:
return ContainerStateExited
}
return ContainerStateUnknown
}
// FormatPod returns a string representing a pod in a human readable format,
// with pod UID as part of the string.
func FormatPod(pod *Pod) string {
// Use underscore as the delimiter because it is not allowed in pod name
// (DNS subdomain format), while allowed in the container name format.
return fmt.Sprintf("%s_%s(%s)", pod.Name, pod.Namespace, pod.ID)
}
type containerCommandRunnerWrapper struct {
DirectStreamingRuntime
}
var _ ContainerCommandRunner = &containerCommandRunnerWrapper{}
func DirectStreamingRunner(runtime DirectStreamingRuntime) ContainerCommandRunner {
return &containerCommandRunnerWrapper{runtime}
}
func (r *containerCommandRunnerWrapper) RunInContainer(id ContainerID, cmd []string, timeout time.Duration) ([]byte, error) {
var buffer bytes.Buffer
output := ioutils.WriteCloserWrapper(&buffer)
err := r.ExecInContainer(id, cmd, nil, output, output, false, nil, timeout)
// Even if err is non-nil, there still may be output (e.g. the exec wrote to stdout or stderr but
// the command returned a nonzero exit code). Therefore, always return the output along with the
// error.
return buffer.Bytes(), err
}
// GetContainerSpec gets the container spec by containerName.
func GetContainerSpec(pod *v1.Pod, containerName string) *v1.Container {
for i, c := range pod.Spec.Containers {
if containerName == c.Name {
return &pod.Spec.Containers[i]
}
}
for i, c := range pod.Spec.InitContainers {
if containerName == c.Name {
return &pod.Spec.InitContainers[i]
}
}
return nil
}
// HasPrivilegedContainer returns true if any of the containers in the pod are privileged.
func HasPrivilegedContainer(pod *v1.Pod) bool {
for _, c := range pod.Spec.Containers {
if c.SecurityContext != nil &&
c.SecurityContext.Privileged != nil &&
*c.SecurityContext.Privileged {
return true
}
}
return false
}

View file

@ -0,0 +1,254 @@
/*
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 container
import (
"reflect"
"testing"
"k8s.io/kubernetes/pkg/api/v1"
)
func TestEnvVarsToMap(t *testing.T) {
vars := []EnvVar{
{
Name: "foo",
Value: "bar",
},
{
Name: "zoo",
Value: "baz",
},
}
varMap := EnvVarsToMap(vars)
if e, a := len(vars), len(varMap); e != a {
t.Errorf("Unexpected map length; expected: %d, got %d", e, a)
}
if a := varMap["foo"]; a != "bar" {
t.Errorf("Unexpected value of key 'foo': %v", a)
}
if a := varMap["zoo"]; a != "baz" {
t.Errorf("Unexpected value of key 'zoo': %v", a)
}
}
func TestExpandCommandAndArgs(t *testing.T) {
cases := []struct {
name string
container *v1.Container
envs []EnvVar
expectedCommand []string
expectedArgs []string
}{
{
name: "none",
container: &v1.Container{},
},
{
name: "command expanded",
container: &v1.Container{
Command: []string{"foo", "$(VAR_TEST)", "$(VAR_TEST2)"},
},
envs: []EnvVar{
{
Name: "VAR_TEST",
Value: "zoo",
},
{
Name: "VAR_TEST2",
Value: "boo",
},
},
expectedCommand: []string{"foo", "zoo", "boo"},
},
{
name: "args expanded",
container: &v1.Container{
Args: []string{"zap", "$(VAR_TEST)", "$(VAR_TEST2)"},
},
envs: []EnvVar{
{
Name: "VAR_TEST",
Value: "hap",
},
{
Name: "VAR_TEST2",
Value: "trap",
},
},
expectedArgs: []string{"zap", "hap", "trap"},
},
{
name: "both expanded",
container: &v1.Container{
Command: []string{"$(VAR_TEST2)--$(VAR_TEST)", "foo", "$(VAR_TEST3)"},
Args: []string{"foo", "$(VAR_TEST)", "$(VAR_TEST2)"},
},
envs: []EnvVar{
{
Name: "VAR_TEST",
Value: "zoo",
},
{
Name: "VAR_TEST2",
Value: "boo",
},
{
Name: "VAR_TEST3",
Value: "roo",
},
},
expectedCommand: []string{"boo--zoo", "foo", "roo"},
expectedArgs: []string{"foo", "zoo", "boo"},
},
}
for _, tc := range cases {
actualCommand, actualArgs := ExpandContainerCommandAndArgs(tc.container, tc.envs)
if e, a := tc.expectedCommand, actualCommand; !reflect.DeepEqual(e, a) {
t.Errorf("%v: unexpected command; expected %v, got %v", tc.name, e, a)
}
if e, a := tc.expectedArgs, actualArgs; !reflect.DeepEqual(e, a) {
t.Errorf("%v: unexpected args; expected %v, got %v", tc.name, e, a)
}
}
}
func TestShouldContainerBeRestarted(t *testing.T) {
pod := &v1.Pod{
ObjectMeta: v1.ObjectMeta{
UID: "12345678",
Name: "foo",
Namespace: "new",
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{Name: "no-history"},
{Name: "alive"},
{Name: "succeed"},
{Name: "failed"},
{Name: "unknown"},
},
},
}
podStatus := &PodStatus{
ID: pod.UID,
Name: pod.Name,
Namespace: pod.Namespace,
ContainerStatuses: []*ContainerStatus{
{
Name: "alive",
State: ContainerStateRunning,
},
{
Name: "succeed",
State: ContainerStateExited,
ExitCode: 0,
},
{
Name: "failed",
State: ContainerStateExited,
ExitCode: 1,
},
{
Name: "alive",
State: ContainerStateExited,
ExitCode: 2,
},
{
Name: "unknown",
State: ContainerStateUnknown,
},
{
Name: "failed",
State: ContainerStateExited,
ExitCode: 3,
},
},
}
policies := []v1.RestartPolicy{
v1.RestartPolicyNever,
v1.RestartPolicyOnFailure,
v1.RestartPolicyAlways,
}
expected := map[string][]bool{
"no-history": {true, true, true},
"alive": {false, false, false},
"succeed": {false, false, true},
"failed": {false, true, true},
"unknown": {true, true, true},
}
for _, c := range pod.Spec.Containers {
for i, policy := range policies {
pod.Spec.RestartPolicy = policy
e := expected[c.Name][i]
r := ShouldContainerBeRestarted(&c, pod, podStatus)
if r != e {
t.Errorf("Restart for container %q with restart policy %q expected %t, got %t",
c.Name, policy, e, r)
}
}
}
}
func TestHasPrivilegedContainer(t *testing.T) {
newBoolPtr := func(b bool) *bool {
return &b
}
tests := map[string]struct {
securityContext *v1.SecurityContext
expected bool
}{
"nil security context": {
securityContext: nil,
expected: false,
},
"nil privileged": {
securityContext: &v1.SecurityContext{},
expected: false,
},
"false privileged": {
securityContext: &v1.SecurityContext{Privileged: newBoolPtr(false)},
expected: false,
},
"true privileged": {
securityContext: &v1.SecurityContext{Privileged: newBoolPtr(true)},
expected: true,
},
}
for k, v := range tests {
pod := &v1.Pod{
Spec: v1.PodSpec{
Containers: []v1.Container{
{SecurityContext: v.securityContext},
},
},
}
actual := HasPrivilegedContainer(pod)
if actual != v.expected {
t.Errorf("%s expected %t but got %t", k, v.expected, actual)
}
}
}

100
vendor/k8s.io/kubernetes/pkg/kubelet/container/os.go generated vendored Normal file
View file

@ -0,0 +1,100 @@
/*
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 container
import (
"io/ioutil"
"os"
"path/filepath"
"time"
)
// OSInterface collects system level operations that need to be mocked out
// during tests.
type OSInterface interface {
MkdirAll(path string, perm os.FileMode) error
Symlink(oldname string, newname string) error
Stat(path string) (os.FileInfo, error)
Remove(path string) error
RemoveAll(path string) error
Create(path string) (*os.File, error)
Hostname() (name string, err error)
Chtimes(path string, atime time.Time, mtime time.Time) error
Pipe() (r *os.File, w *os.File, err error)
ReadDir(dirname string) ([]os.FileInfo, error)
Glob(pattern string) ([]string, error)
}
// RealOS is used to dispatch the real system level operations.
type RealOS struct{}
// MkDir will will call os.Mkdir to create a directory.
func (RealOS) MkdirAll(path string, perm os.FileMode) error {
return os.MkdirAll(path, perm)
}
// Symlink will call os.Symlink to create a symbolic link.
func (RealOS) Symlink(oldname string, newname string) error {
return os.Symlink(oldname, newname)
}
// Stat will call os.Stat to get the FileInfo for a given path
func (RealOS) Stat(path string) (os.FileInfo, error) {
return os.Stat(path)
}
// Remove will call os.Remove to remove the path.
func (RealOS) Remove(path string) error {
return os.Remove(path)
}
// RemoveAll will call os.RemoveAll to remove the path and its children.
func (RealOS) RemoveAll(path string) error {
return os.RemoveAll(path)
}
// Create will call os.Create to create and return a file
// at path.
func (RealOS) Create(path string) (*os.File, error) {
return os.Create(path)
}
// Hostname will call os.Hostname to return the hostname.
func (RealOS) Hostname() (name string, err error) {
return os.Hostname()
}
// Chtimes will call os.Chtimes to change the atime and mtime of the path
func (RealOS) Chtimes(path string, atime time.Time, mtime time.Time) error {
return os.Chtimes(path, atime, mtime)
}
// Pipe will call os.Pipe to return a connected pair of pipe.
func (RealOS) Pipe() (r *os.File, w *os.File, err error) {
return os.Pipe()
}
// ReadDir will call ioutil.ReadDir to return the files under the directory.
func (RealOS) ReadDir(dirname string) ([]os.FileInfo, error) {
return ioutil.ReadDir(dirname)
}
// Glob will call filepath.Glob to return the names of all files matching
// pattern.
func (RealOS) Glob(pattern string) ([]string, error) {
return filepath.Glob(pattern)
}

View file

@ -0,0 +1,30 @@
// +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 container
import (
"os"
"os/exec"
"github.com/kr/pty"
)
func StartPty(c *exec.Cmd) (*os.File, error) {
return pty.Start(c)
}

View file

@ -0,0 +1,28 @@
// +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 container
import (
"os"
"os/exec"
)
func StartPty(c *exec.Cmd) (pty *os.File, err error) {
return nil, nil
}

71
vendor/k8s.io/kubernetes/pkg/kubelet/container/ref.go generated vendored Normal file
View file

@ -0,0 +1,71 @@
/*
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 container
import (
"fmt"
"k8s.io/kubernetes/pkg/api/v1"
)
var ImplicitContainerPrefix string = "implicitly required container "
// GenerateContainerRef returns an *v1.ObjectReference which references the given container
// within the given pod. Returns an error if the reference can't be constructed or the
// container doesn't actually belong to the pod.
//
// This function will return an error if the provided Pod does not have a selfLink,
// but we expect selfLink to be populated at all call sites for the function.
func GenerateContainerRef(pod *v1.Pod, container *v1.Container) (*v1.ObjectReference, error) {
fieldPath, err := fieldPath(pod, container)
if err != nil {
// TODO: figure out intelligent way to refer to containers that we implicitly
// start (like the pod infra container). This is not a good way, ugh.
fieldPath = ImplicitContainerPrefix + container.Name
}
ref, err := v1.GetPartialReference(pod, fieldPath)
if err != nil {
return nil, err
}
return ref, nil
}
// fieldPath returns a fieldPath locating container within pod.
// Returns an error if the container isn't part of the pod.
func fieldPath(pod *v1.Pod, container *v1.Container) (string, error) {
for i := range pod.Spec.Containers {
here := &pod.Spec.Containers[i]
if here.Name == container.Name {
if here.Name == "" {
return fmt.Sprintf("spec.containers[%d]", i), nil
} else {
return fmt.Sprintf("spec.containers{%s}", here.Name), nil
}
}
}
for i := range pod.Spec.InitContainers {
here := &pod.Spec.InitContainers[i]
if here.Name == container.Name {
if here.Name == "" {
return fmt.Sprintf("spec.initContainers[%d]", i), nil
} else {
return fmt.Sprintf("spec.initContainers{%s}", here.Name), nil
}
}
}
return "", fmt.Errorf("container %#v not found in pod %#v", container, pod)
}

View file

@ -0,0 +1,212 @@
/*
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 container
import (
"testing"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/v1"
)
func TestFieldPath(t *testing.T) {
pod := &v1.Pod{Spec: v1.PodSpec{Containers: []v1.Container{
{Name: "foo"},
{Name: "bar"},
{Name: ""},
{Name: "baz"},
}}}
table := map[string]struct {
pod *v1.Pod
container *v1.Container
path string
success bool
}{
"basic": {pod, &v1.Container{Name: "foo"}, "spec.containers{foo}", true},
"basic2": {pod, &v1.Container{Name: "baz"}, "spec.containers{baz}", true},
"emptyName": {pod, &v1.Container{Name: ""}, "spec.containers[2]", true},
"basicSamePointer": {pod, &pod.Spec.Containers[0], "spec.containers{foo}", true},
"missing": {pod, &v1.Container{Name: "qux"}, "", false},
}
for name, item := range table {
res, err := fieldPath(item.pod, item.container)
if item.success == false {
if err == nil {
t.Errorf("%v: unexpected non-error", name)
}
continue
}
if err != nil {
t.Errorf("%v: unexpected error: %v", name, err)
continue
}
if e, a := item.path, res; e != a {
t.Errorf("%v: wanted %v, got %v", name, e, a)
}
}
}
func TestGenerateContainerRef(t *testing.T) {
var (
okPod = v1.Pod{
TypeMeta: metav1.TypeMeta{
Kind: "Pod",
APIVersion: api.Registry.GroupOrDie(v1.GroupName).GroupVersion.String(),
},
ObjectMeta: v1.ObjectMeta{
Name: "ok",
Namespace: "test-ns",
UID: "bar",
ResourceVersion: "42",
SelfLink: "/api/" + api.Registry.GroupOrDie(v1.GroupName).GroupVersion.String() + "/pods/foo",
},
Spec: v1.PodSpec{
Containers: []v1.Container{
{
Name: "by-name",
},
{},
},
},
}
noSelfLinkPod = okPod
defaultedSelfLinkPod = okPod
)
noSelfLinkPod.Kind = ""
noSelfLinkPod.APIVersion = ""
noSelfLinkPod.ObjectMeta.SelfLink = ""
defaultedSelfLinkPod.ObjectMeta.SelfLink = "/api/" + api.Registry.GroupOrDie(v1.GroupName).GroupVersion.String() + "/pods/ok"
cases := []struct {
name string
pod *v1.Pod
container *v1.Container
expected *v1.ObjectReference
success bool
}{
{
name: "by-name",
pod: &okPod,
container: &v1.Container{
Name: "by-name",
},
expected: &v1.ObjectReference{
Kind: "Pod",
APIVersion: api.Registry.GroupOrDie(v1.GroupName).GroupVersion.String(),
Name: "ok",
Namespace: "test-ns",
UID: "bar",
ResourceVersion: "42",
FieldPath: ".spec.containers{by-name}",
},
success: true,
},
{
name: "no-name",
pod: &okPod,
container: &v1.Container{},
expected: &v1.ObjectReference{
Kind: "Pod",
APIVersion: api.Registry.GroupOrDie(v1.GroupName).GroupVersion.String(),
Name: "ok",
Namespace: "test-ns",
UID: "bar",
ResourceVersion: "42",
FieldPath: ".spec.containers[1]",
},
success: true,
},
{
name: "no-selflink",
pod: &noSelfLinkPod,
container: &v1.Container{},
expected: nil,
success: false,
},
{
name: "defaulted-selflink",
pod: &defaultedSelfLinkPod,
container: &v1.Container{
Name: "by-name",
},
expected: &v1.ObjectReference{
Kind: "Pod",
APIVersion: api.Registry.GroupOrDie(v1.GroupName).GroupVersion.String(),
Name: "ok",
Namespace: "test-ns",
UID: "bar",
ResourceVersion: "42",
FieldPath: ".spec.containers{by-name}",
},
success: true,
},
{
name: "implicitly-required",
pod: &okPod,
container: &v1.Container{
Name: "net",
},
expected: &v1.ObjectReference{
Kind: "Pod",
APIVersion: api.Registry.GroupOrDie(v1.GroupName).GroupVersion.String(),
Name: "ok",
Namespace: "test-ns",
UID: "bar",
ResourceVersion: "42",
FieldPath: "implicitly required container net",
},
success: true,
},
}
for _, tc := range cases {
actual, err := GenerateContainerRef(tc.pod, tc.container)
if err != nil {
if tc.success {
t.Errorf("%v: unexpected error: %v", tc.name, err)
}
continue
}
if !tc.success {
t.Errorf("%v: unexpected success", tc.name)
continue
}
if e, a := tc.expected.Kind, actual.Kind; e != a {
t.Errorf("%v: kind: expected %v, got %v", tc.name, e, a)
}
if e, a := tc.expected.APIVersion, actual.APIVersion; e != a {
t.Errorf("%v: apiVersion: expected %v, got %v", tc.name, e, a)
}
if e, a := tc.expected.Name, actual.Name; e != a {
t.Errorf("%v: name: expected %v, got %v", tc.name, e, a)
}
if e, a := tc.expected.Namespace, actual.Namespace; e != a {
t.Errorf("%v: namespace: expected %v, got %v", tc.name, e, a)
}
if e, a := tc.expected.UID, actual.UID; e != a {
t.Errorf("%v: uid: expected %v, got %v", tc.name, e, a)
}
if e, a := tc.expected.ResourceVersion, actual.ResourceVersion; e != a {
t.Errorf("%v: kind: expected %v, got %v", tc.name, e, a)
}
}
}

View file

@ -0,0 +1,46 @@
/*
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 container
import (
"k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/kubernetes/pkg/util/term"
)
// handleResizing spawns a goroutine that processes the resize channel, calling resizeFunc for each
// term.Size received from the channel. The resize channel must be closed elsewhere to stop the
// goroutine.
func HandleResizing(resize <-chan term.Size, resizeFunc func(size term.Size)) {
if resize == nil {
return
}
go func() {
defer runtime.HandleCrash()
for {
size, ok := <-resize
if !ok {
return
}
if size.Height < 1 || size.Width < 1 {
continue
}
resizeFunc(size)
}
}()
}

View file

@ -0,0 +1,628 @@
/*
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 container
import (
"fmt"
"io"
"net/url"
"reflect"
"strings"
"time"
"github.com/golang/glog"
"k8s.io/apimachinery/pkg/types"
"k8s.io/kubernetes/pkg/api/v1"
runtimeapi "k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime"
"k8s.io/kubernetes/pkg/util/flowcontrol"
"k8s.io/kubernetes/pkg/util/term"
"k8s.io/kubernetes/pkg/volume"
)
type Version interface {
// Compare compares two versions of the runtime. On success it returns -1
// if the version is less than the other, 1 if it is greater than the other,
// or 0 if they are equal.
Compare(other string) (int, error)
// String returns a string that represents the version.
String() string
}
// ImageSpec is an internal representation of an image. Currently, it wraps the
// value of a Container's Image field, but in the future it will include more detailed
// information about the different image types.
type ImageSpec struct {
Image string
}
// ImageStats contains statistics about all the images currently available.
type ImageStats struct {
// Total amount of storage consumed by existing images.
TotalStorageBytes uint64
}
// Runtime interface defines the interfaces that should be implemented
// by a container runtime.
// Thread safety is required from implementations of this interface.
type Runtime interface {
// Type returns the type of the container runtime.
Type() string
// Version returns the version information of the container runtime.
Version() (Version, error)
// APIVersion returns the cached API version information of the container
// runtime. Implementation is expected to update this cache periodically.
// This may be different from the runtime engine's version.
// TODO(random-liu): We should fold this into Version()
APIVersion() (Version, error)
// Status returns the status of the runtime. An error is returned if the Status
// function itself fails, nil otherwise.
Status() (*RuntimeStatus, error)
// GetPods returns a list of containers grouped by pods. The boolean parameter
// specifies whether the runtime returns all containers including those already
// exited and dead containers (used for garbage collection).
GetPods(all bool) ([]*Pod, error)
// GarbageCollect removes dead containers using the specified container gc policy
// If allSourcesReady is not true, it means that kubelet doesn't have the
// complete list of pods from all avialble sources (e.g., apiserver, http,
// file). In this case, garbage collector should refrain itself from aggressive
// behavior such as removing all containers of unrecognized pods (yet).
// TODO: Revisit this method and make it cleaner.
GarbageCollect(gcPolicy ContainerGCPolicy, allSourcesReady bool) error
// Syncs the running pod into the desired pod.
SyncPod(pod *v1.Pod, apiPodStatus v1.PodStatus, podStatus *PodStatus, pullSecrets []v1.Secret, backOff *flowcontrol.Backoff) PodSyncResult
// KillPod kills all the containers of a pod. Pod may be nil, running pod must not be.
// TODO(random-liu): Return PodSyncResult in KillPod.
// gracePeriodOverride if specified allows the caller to override the pod default grace period.
// only hard kill paths are allowed to specify a gracePeriodOverride in the kubelet in order to not corrupt user data.
// it is useful when doing SIGKILL for hard eviction scenarios, or max grace period during soft eviction scenarios.
KillPod(pod *v1.Pod, runningPod Pod, gracePeriodOverride *int64) error
// GetPodStatus retrieves the status of the pod, including the
// information of all containers in the pod that are visble in Runtime.
GetPodStatus(uid types.UID, name, namespace string) (*PodStatus, error)
// Returns the filesystem path of the pod's network namespace; if the
// runtime does not handle namespace creation itself, or cannot return
// the network namespace path, it should return an error.
// TODO: Change ContainerID to a Pod ID since the namespace is shared
// by all containers in the pod.
GetNetNS(containerID ContainerID) (string, error)
// Returns the container ID that represents the Pod, as passed to network
// plugins. For example, if the runtime uses an infra container, returns
// the infra container's ContainerID.
// TODO: Change ContainerID to a Pod ID, see GetNetNS()
GetPodContainerID(*Pod) (ContainerID, error)
// TODO(vmarmol): Unify pod and containerID args.
// GetContainerLogs returns logs of a specific container. By
// default, it returns a snapshot of the container log. Set 'follow' to true to
// stream the log. Set 'follow' to false and specify the number of lines (e.g.
// "100" or "all") to tail the log.
GetContainerLogs(pod *v1.Pod, containerID ContainerID, logOptions *v1.PodLogOptions, stdout, stderr io.Writer) (err error)
// Delete a container. If the container is still running, an error is returned.
DeleteContainer(containerID ContainerID) error
// ImageService provides methods to image-related methods.
ImageService
// UpdatePodCIDR sends a new podCIDR to the runtime.
// This method just proxies a new runtimeConfig with the updated
// CIDR value down to the runtime shim.
UpdatePodCIDR(podCIDR string) error
}
// DirectStreamingRuntime is the interface implemented by runtimes for which the streaming calls
// (exec/attach/port-forward) should be served directly by the Kubelet.
type DirectStreamingRuntime interface {
// Runs the command in the container of the specified pod using nsenter.
// Attaches the processes stdin, stdout, and stderr. Optionally uses a
// tty.
ExecInContainer(containerID ContainerID, cmd []string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan term.Size, timeout time.Duration) error
// Forward the specified port from the specified pod to the stream.
PortForward(pod *Pod, port uint16, stream io.ReadWriteCloser) error
// ContainerAttach encapsulates the attaching to containers for testability
ContainerAttacher
}
// IndirectStreamingRuntime is the interface implemented by runtimes that handle the serving of the
// streaming calls (exec/attach/port-forward) themselves. In this case, Kubelet should redirect to
// the runtime server.
type IndirectStreamingRuntime interface {
GetExec(id ContainerID, cmd []string, stdin, stdout, stderr, tty bool) (*url.URL, error)
GetAttach(id ContainerID, stdin, stdout, stderr, tty bool) (*url.URL, error)
GetPortForward(podName, podNamespace string, podUID types.UID) (*url.URL, error)
}
type ImageService interface {
// PullImage pulls an image from the network to local storage using the supplied
// secrets if necessary. It returns a reference (digest or ID) to the pulled image.
PullImage(image ImageSpec, pullSecrets []v1.Secret) (string, error)
// GetImageRef gets the reference (digest or ID) of the image which has already been in
// the local storage. It returns ("", nil) if the image isn't in the local storage.
GetImageRef(image ImageSpec) (string, error)
// Gets all images currently on the machine.
ListImages() ([]Image, error)
// Removes the specified image.
RemoveImage(image ImageSpec) error
// Returns Image statistics.
ImageStats() (*ImageStats, error)
}
type ContainerAttacher interface {
AttachContainer(id ContainerID, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan term.Size) (err error)
}
type ContainerCommandRunner interface {
// RunInContainer synchronously executes the command in the container, and returns the output.
// If the command completes with a non-0 exit code, a pkg/util/exec.ExitError will be returned.
RunInContainer(id ContainerID, cmd []string, timeout time.Duration) ([]byte, error)
}
// Pod is a group of containers.
type Pod struct {
// The ID of the pod, which can be used to retrieve a particular pod
// from the pod list returned by GetPods().
ID types.UID
// The name and namespace of the pod, which is readable by human.
Name string
Namespace string
// List of containers that belongs to this pod. It may contain only
// running containers, or mixed with dead ones (when GetPods(true)).
Containers []*Container
// List of sandboxes associated with this pod. The sandboxes are converted
// to Container temporariliy to avoid substantial changes to other
// components. This is only populated by kuberuntime.
// TODO: use the runtimeApi.PodSandbox type directly.
Sandboxes []*Container
}
// PodPair contains both runtime#Pod and api#Pod
type PodPair struct {
// APIPod is the v1.Pod
APIPod *v1.Pod
// RunningPod is the pod defined defined in pkg/kubelet/container/runtime#Pod
RunningPod *Pod
}
// ContainerID is a type that identifies a container.
type ContainerID struct {
// The type of the container runtime. e.g. 'docker', 'rkt'.
Type string
// The identification of the container, this is comsumable by
// the underlying container runtime. (Note that the container
// runtime interface still takes the whole struct as input).
ID string
}
func BuildContainerID(typ, ID string) ContainerID {
return ContainerID{Type: typ, ID: ID}
}
// Convenience method for creating a ContainerID from an ID string.
func ParseContainerID(containerID string) ContainerID {
var id ContainerID
if err := id.ParseString(containerID); err != nil {
glog.Error(err)
}
return id
}
func (c *ContainerID) ParseString(data string) error {
// Trim the quotes and split the type and ID.
parts := strings.Split(strings.Trim(data, "\""), "://")
if len(parts) != 2 {
return fmt.Errorf("invalid container ID: %q", data)
}
c.Type, c.ID = parts[0], parts[1]
return nil
}
func (c *ContainerID) String() string {
return fmt.Sprintf("%s://%s", c.Type, c.ID)
}
func (c *ContainerID) IsEmpty() bool {
return *c == ContainerID{}
}
func (c *ContainerID) MarshalJSON() ([]byte, error) {
return []byte(fmt.Sprintf("%q", c.String())), nil
}
func (c *ContainerID) UnmarshalJSON(data []byte) error {
return c.ParseString(string(data))
}
// DockerID is an ID of docker container. It is a type to make it clear when we're working with docker container Ids
type DockerID string
func (id DockerID) ContainerID() ContainerID {
return ContainerID{
Type: "docker",
ID: string(id),
}
}
type ContainerState string
const (
ContainerStateCreated ContainerState = "created"
ContainerStateRunning ContainerState = "running"
ContainerStateExited ContainerState = "exited"
// This unknown encompasses all the states that we currently don't care.
ContainerStateUnknown ContainerState = "unknown"
)
// Container provides the runtime information for a container, such as ID, hash,
// state of the container.
type Container struct {
// The ID of the container, used by the container runtime to identify
// a container.
ID ContainerID
// The name of the container, which should be the same as specified by
// v1.Container.
Name string
// The image name of the container, this also includes the tag of the image,
// the expected form is "NAME:TAG".
Image string
// The id of the image used by the container.
ImageID string
// Hash of the container, used for comparison. Optional for containers
// not managed by kubelet.
Hash uint64
// State is the state of the container.
State ContainerState
}
// PodStatus represents the status of the pod and its containers.
// v1.PodStatus can be derived from examining PodStatus and v1.Pod.
type PodStatus struct {
// ID of the pod.
ID types.UID
// Name of the pod.
Name string
// Namspace of the pod.
Namespace string
// IP of the pod.
IP string
// Status of containers in the pod.
ContainerStatuses []*ContainerStatus
// Status of the pod sandbox.
// Only for kuberuntime now, other runtime may keep it nil.
SandboxStatuses []*runtimeapi.PodSandboxStatus
}
// ContainerStatus represents the status of a container.
type ContainerStatus struct {
// ID of the container.
ID ContainerID
// Name of the container.
Name string
// Status of the container.
State ContainerState
// Creation time of the container.
CreatedAt time.Time
// Start time of the container.
StartedAt time.Time
// Finish time of the container.
FinishedAt time.Time
// Exit code of the container.
ExitCode int
// Name of the image, this also includes the tag of the image,
// the expected form is "NAME:TAG".
Image string
// ID of the image.
ImageID string
// Hash of the container, used for comparison.
Hash uint64
// Number of times that the container has been restarted.
RestartCount int
// A string explains why container is in such a status.
Reason string
// Message written by the container before exiting (stored in
// TerminationMessagePath).
Message string
}
// FindContainerStatusByName returns container status in the pod status with the given name.
// When there are multiple containers' statuses with the same name, the first match will be returned.
func (podStatus *PodStatus) FindContainerStatusByName(containerName string) *ContainerStatus {
for _, containerStatus := range podStatus.ContainerStatuses {
if containerStatus.Name == containerName {
return containerStatus
}
}
return nil
}
// Get container status of all the running containers in a pod
func (podStatus *PodStatus) GetRunningContainerStatuses() []*ContainerStatus {
runningContainerStatuses := []*ContainerStatus{}
for _, containerStatus := range podStatus.ContainerStatuses {
if containerStatus.State == ContainerStateRunning {
runningContainerStatuses = append(runningContainerStatuses, containerStatus)
}
}
return runningContainerStatuses
}
// Basic information about a container image.
type Image struct {
// ID of the image.
ID string
// Other names by which this image is known.
RepoTags []string
// Digests by which this image is known.
RepoDigests []string
// The size of the image in bytes.
Size int64
}
type EnvVar struct {
Name string
Value string
}
type Mount struct {
// Name of the volume mount.
// TODO(yifan): Remove this field, as this is not representing the unique name of the mount,
// but the volume name only.
Name string
// Path of the mount within the container.
ContainerPath string
// Path of the mount on the host.
HostPath string
// Whether the mount is read-only.
ReadOnly bool
// Whether the mount needs SELinux relabeling
SELinuxRelabel bool
}
type PortMapping struct {
// Name of the port mapping
Name string
// Protocol of the port mapping.
Protocol v1.Protocol
// The port number within the container.
ContainerPort int
// The port number on the host.
HostPort int
// The host IP.
HostIP string
}
type DeviceInfo struct {
// Path on host for mapping
PathOnHost string
// Path in Container to map
PathInContainer string
// Cgroup permissions
Permissions string
}
// RunContainerOptions specify the options which are necessary for running containers
type RunContainerOptions struct {
// The environment variables list.
Envs []EnvVar
// The mounts for the containers.
Mounts []Mount
// The host devices mapped into the containers.
Devices []DeviceInfo
// The port mappings for the containers.
PortMappings []PortMapping
// If the container has specified the TerminationMessagePath, then
// this directory will be used to create and mount the log file to
// container.TerminationMessagePath
PodContainerDir string
// The list of DNS servers for the container to use.
DNS []string
// The list of DNS search domains.
DNSSearch []string
// The parent cgroup to pass to Docker
CgroupParent string
// The type of container rootfs
ReadOnly bool
// hostname for pod containers
Hostname string
// EnableHostUserNamespace sets userns=host when users request host namespaces (pid, ipc, net),
// are using non-namespaced capabilities (mknod, sys_time, sys_module), the pod contains a privileged container,
// or using host path volumes.
// This should only be enabled when the container runtime is performing user remapping AND if the
// experimental behavior is desired.
EnableHostUserNamespace bool
}
// VolumeInfo contains information about the volume.
type VolumeInfo struct {
// Mounter is the volume's mounter
Mounter volume.Mounter
// SELinuxLabeled indicates whether this volume has had the
// pod's SELinux label applied to it or not
SELinuxLabeled bool
}
type VolumeMap map[string]VolumeInfo
// RuntimeConditionType is the types of required runtime conditions.
type RuntimeConditionType string
const (
// RuntimeReady means the runtime is up and ready to accept basic containers.
RuntimeReady RuntimeConditionType = "RuntimeReady"
// NetworkReady means the runtime network is up and ready to accept containers which require network.
NetworkReady RuntimeConditionType = "NetworkReady"
)
// RuntimeStatus contains the status of the runtime.
type RuntimeStatus struct {
// Conditions is an array of current observed runtime conditions.
Conditions []RuntimeCondition
}
// GetRuntimeCondition gets a specified runtime condition from the runtime status.
func (r *RuntimeStatus) GetRuntimeCondition(t RuntimeConditionType) *RuntimeCondition {
for i := range r.Conditions {
c := &r.Conditions[i]
if c.Type == t {
return c
}
}
return nil
}
// String formats the runtime status into human readable string.
func (s *RuntimeStatus) String() string {
var ss []string
for _, c := range s.Conditions {
ss = append(ss, c.String())
}
return fmt.Sprintf("Runtime Conditions: %s", strings.Join(ss, ", "))
}
// RuntimeCondition contains condition information for the runtime.
type RuntimeCondition struct {
// Type of runtime condition.
Type RuntimeConditionType
// Status of the condition, one of true/false.
Status bool
// Reason is brief reason for the condition's last transition.
Reason string
// Message is human readable message indicating details about last transition.
Message string
}
// String formats the runtime condition into human readable string.
func (c *RuntimeCondition) String() string {
return fmt.Sprintf("%s=%t reason:%s message:%s", c.Type, c.Status, c.Reason, c.Message)
}
type Pods []*Pod
// FindPodByID finds and returns a pod in the pod list by UID. It will return an empty pod
// if not found.
func (p Pods) FindPodByID(podUID types.UID) Pod {
for i := range p {
if p[i].ID == podUID {
return *p[i]
}
}
return Pod{}
}
// FindPodByFullName finds and returns a pod in the pod list by the full name.
// It will return an empty pod if not found.
func (p Pods) FindPodByFullName(podFullName string) Pod {
for i := range p {
if BuildPodFullName(p[i].Name, p[i].Namespace) == podFullName {
return *p[i]
}
}
return Pod{}
}
// FindPod combines FindPodByID and FindPodByFullName, it finds and returns a pod in the
// pod list either by the full name or the pod ID. It will return an empty pod
// if not found.
func (p Pods) FindPod(podFullName string, podUID types.UID) Pod {
if len(podFullName) > 0 {
return p.FindPodByFullName(podFullName)
}
return p.FindPodByID(podUID)
}
// FindContainerByName returns a container in the pod with the given name.
// When there are multiple containers with the same name, the first match will
// be returned.
func (p *Pod) FindContainerByName(containerName string) *Container {
for _, c := range p.Containers {
if c.Name == containerName {
return c
}
}
return nil
}
func (p *Pod) FindContainerByID(id ContainerID) *Container {
for _, c := range p.Containers {
if c.ID == id {
return c
}
}
return nil
}
func (p *Pod) FindSandboxByID(id ContainerID) *Container {
for _, c := range p.Sandboxes {
if c.ID == id {
return c
}
}
return nil
}
// ToAPIPod converts Pod to v1.Pod. Note that if a field in v1.Pod has no
// corresponding field in Pod, the field would not be populated.
func (p *Pod) ToAPIPod() *v1.Pod {
var pod v1.Pod
pod.UID = p.ID
pod.Name = p.Name
pod.Namespace = p.Namespace
for _, c := range p.Containers {
var container v1.Container
container.Name = c.Name
container.Image = c.Image
pod.Spec.Containers = append(pod.Spec.Containers, container)
}
return &pod
}
// IsEmpty returns true if the pod is empty.
func (p *Pod) IsEmpty() bool {
return reflect.DeepEqual(p, &Pod{})
}
// GetPodFullName returns a name that uniquely identifies a pod.
func GetPodFullName(pod *v1.Pod) string {
// Use underscore as the delimiter because it is not allowed in pod name
// (DNS subdomain format), while allowed in the container name format.
return pod.Name + "_" + pod.Namespace
}
// Build the pod full name from pod name and namespace.
func BuildPodFullName(name, namespace string) string {
return name + "_" + namespace
}
// Parse the pod full name.
func ParsePodFullName(podFullName string) (string, string, error) {
parts := strings.Split(podFullName, "_")
if len(parts) != 2 {
return "", "", fmt.Errorf("failed to parse the pod full name %q", podFullName)
}
return parts[0], parts[1], nil
}
// Option is a functional option type for Runtime, useful for
// completely optional settings.
type Option func(Runtime)
// Sort the container statuses by creation time.
type SortContainerStatusesByCreationTime []*ContainerStatus
func (s SortContainerStatusesByCreationTime) Len() int { return len(s) }
func (s SortContainerStatusesByCreationTime) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s SortContainerStatusesByCreationTime) Less(i, j int) bool {
return s[i].CreatedAt.Before(s[j].CreatedAt)
}

View 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 container
import (
"sync"
"time"
)
var (
// TODO(yifan): Maybe set the them as parameters for NewCache().
defaultCachePeriod = time.Second * 2
)
type RuntimeCache interface {
GetPods() ([]*Pod, error)
ForceUpdateIfOlder(time.Time) error
}
type podsGetter interface {
GetPods(bool) ([]*Pod, error)
}
// NewRuntimeCache creates a container runtime cache.
func NewRuntimeCache(getter podsGetter) (RuntimeCache, error) {
return &runtimeCache{
getter: getter,
}, nil
}
// runtimeCache caches a list of pods. It records a timestamp (cacheTime) right
// before updating the pods, so the timestamp is at most as new as the pods
// (and can be slightly older). The timestamp always moves forward. Callers are
// expected not to modify the pods returned from GetPods.
type runtimeCache struct {
sync.Mutex
// The underlying container runtime used to update the cache.
getter podsGetter
// Last time when cache was updated.
cacheTime time.Time
// The content of the cache.
pods []*Pod
}
// GetPods returns the cached pods if they are not outdated; otherwise, it
// retrieves the latest pods and return them.
func (r *runtimeCache) GetPods() ([]*Pod, error) {
r.Lock()
defer r.Unlock()
if time.Since(r.cacheTime) > defaultCachePeriod {
if err := r.updateCache(); err != nil {
return nil, err
}
}
return r.pods, nil
}
func (r *runtimeCache) ForceUpdateIfOlder(minExpectedCacheTime time.Time) error {
r.Lock()
defer r.Unlock()
if r.cacheTime.Before(minExpectedCacheTime) {
return r.updateCache()
}
return nil
}
func (r *runtimeCache) updateCache() error {
pods, timestamp, err := r.getPodsWithTimestamp()
if err != nil {
return err
}
r.pods, r.cacheTime = pods, timestamp
return nil
}
// getPodsWithTimestamp records a timestamp and retrieves pods from the getter.
func (r *runtimeCache) getPodsWithTimestamp() ([]*Pod, time.Time, error) {
// Always record the timestamp before getting the pods to avoid stale pods.
timestamp := time.Now()
pods, err := r.getter.GetPods(false)
return pods, timestamp, err
}

View file

@ -0,0 +1,45 @@
/*
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 container
// TestRunTimeCache embeds runtimeCache with some additional methods for testing.
// It must be declared in the container package to have visibility to runtimeCache.
// It cannot be in a "..._test.go" file in order for runtime_cache_test.go to have cross-package visibility to it.
// (cross-package declarations in test files cannot be used from dot imports if this package is vendored)
type TestRuntimeCache struct {
runtimeCache
}
func (r *TestRuntimeCache) UpdateCacheWithLock() error {
r.Lock()
defer r.Unlock()
return r.updateCache()
}
func (r *TestRuntimeCache) GetCachedPods() []*Pod {
r.Lock()
defer r.Unlock()
return r.pods
}
func NewTestRuntimeCache(getter podsGetter) *TestRuntimeCache {
return &TestRuntimeCache{
runtimeCache: runtimeCache{
getter: getter,
},
}
}

View file

@ -0,0 +1,74 @@
/*
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 container_test
import (
"reflect"
"testing"
"time"
. "k8s.io/kubernetes/pkg/kubelet/container"
ctest "k8s.io/kubernetes/pkg/kubelet/container/testing"
)
func comparePods(t *testing.T, expected []*ctest.FakePod, actual []*Pod) {
if len(expected) != len(actual) {
t.Errorf("expected %d pods, got %d instead", len(expected), len(actual))
}
for i := range expected {
if !reflect.DeepEqual(expected[i].Pod, actual[i]) {
t.Errorf("expected %#v, got %#v", expected[i].Pod, actual[i])
}
}
}
func TestGetPods(t *testing.T) {
runtime := &ctest.FakeRuntime{}
expected := []*ctest.FakePod{{Pod: &Pod{ID: "1111"}}, {Pod: &Pod{ID: "2222"}}, {Pod: &Pod{ID: "3333"}}}
runtime.PodList = expected
cache := NewTestRuntimeCache(runtime)
actual, err := cache.GetPods()
if err != nil {
t.Errorf("unexpected error %v", err)
}
comparePods(t, expected, actual)
}
func TestForceUpdateIfOlder(t *testing.T) {
runtime := &ctest.FakeRuntime{}
cache := NewTestRuntimeCache(runtime)
// Cache old pods.
oldpods := []*ctest.FakePod{{Pod: &Pod{ID: "1111"}}}
runtime.PodList = oldpods
cache.UpdateCacheWithLock()
// Update the runtime to new pods.
newpods := []*ctest.FakePod{{Pod: &Pod{ID: "1111"}}, {Pod: &Pod{ID: "2222"}}, {Pod: &Pod{ID: "3333"}}}
runtime.PodList = newpods
// An older timestamp should not force an update.
cache.ForceUpdateIfOlder(time.Now().Add(-20 * time.Minute))
actual := cache.GetCachedPods()
comparePods(t, oldpods, actual)
// A newer timestamp should force an update.
cache.ForceUpdateIfOlder(time.Now().Add(20 * time.Second))
actual = cache.GetCachedPods()
comparePods(t, newpods, actual)
}

View file

@ -0,0 +1,128 @@
/*
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 container
import (
"errors"
"fmt"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
)
// TODO(random-liu): We need to better organize runtime errors for introspection.
// Container Terminated and Kubelet is backing off the restart
var ErrCrashLoopBackOff = errors.New("CrashLoopBackOff")
var (
// ErrContainerNotFound returned when a container in the given pod with the
// given container name was not found, amongst those managed by the kubelet.
ErrContainerNotFound = errors.New("no matching container")
)
var (
ErrRunContainer = errors.New("RunContainerError")
ErrKillContainer = errors.New("KillContainerError")
ErrVerifyNonRoot = errors.New("VerifyNonRootError")
ErrRunInitContainer = errors.New("RunInitContainerError")
ErrCreatePodSandbox = errors.New("CreatePodSandboxError")
ErrConfigPodSandbox = errors.New("ConfigPodSandboxError")
ErrKillPodSandbox = errors.New("KillPodSandboxError")
)
var (
ErrSetupNetwork = errors.New("SetupNetworkError")
ErrTeardownNetwork = errors.New("TeardownNetworkError")
)
// SyncAction indicates different kind of actions in SyncPod() and KillPod(). Now there are only actions
// about start/kill container and setup/teardown network.
type SyncAction string
const (
StartContainer SyncAction = "StartContainer"
KillContainer SyncAction = "KillContainer"
SetupNetwork SyncAction = "SetupNetwork"
TeardownNetwork SyncAction = "TeardownNetwork"
InitContainer SyncAction = "InitContainer"
CreatePodSandbox SyncAction = "CreatePodSandbox"
ConfigPodSandbox SyncAction = "ConfigPodSandbox"
KillPodSandbox SyncAction = "KillPodSandbox"
)
// SyncResult is the result of sync action.
type SyncResult struct {
// The associated action of the result
Action SyncAction
// The target of the action, now the target can only be:
// * Container: Target should be container name
// * Network: Target is useless now, we just set it as pod full name now
Target interface{}
// Brief error reason
Error error
// Human readable error reason
Message string
}
// NewSyncResult generates new SyncResult with specific Action and Target
func NewSyncResult(action SyncAction, target interface{}) *SyncResult {
return &SyncResult{Action: action, Target: target}
}
// Fail fails the SyncResult with specific error and message
func (r *SyncResult) Fail(err error, msg string) {
r.Error, r.Message = err, msg
}
// PodSyncResult is the summary result of SyncPod() and KillPod()
type PodSyncResult struct {
// Result of different sync actions
SyncResults []*SyncResult
// Error encountered in SyncPod() and KillPod() that is not already included in SyncResults
SyncError error
}
// AddSyncResult adds multiple SyncResult to current PodSyncResult
func (p *PodSyncResult) AddSyncResult(result ...*SyncResult) {
p.SyncResults = append(p.SyncResults, result...)
}
// AddPodSyncResult merges a PodSyncResult to current one
func (p *PodSyncResult) AddPodSyncResult(result PodSyncResult) {
p.AddSyncResult(result.SyncResults...)
p.SyncError = result.SyncError
}
// Fail fails the PodSyncResult with an error occurred in SyncPod() and KillPod() itself
func (p *PodSyncResult) Fail(err error) {
p.SyncError = err
}
// Error returns an error summarizing all the errors in PodSyncResult
func (p *PodSyncResult) Error() error {
errlist := []error{}
if p.SyncError != nil {
errlist = append(errlist, fmt.Errorf("failed to SyncPod: %v\n", p.SyncError))
}
for _, result := range p.SyncResults {
if result.Error != nil {
errlist = append(errlist, fmt.Errorf("failed to %q for %q with %v: %q\n", result.Action, result.Target,
result.Error, result.Message))
}
}
return utilerrors.NewAggregate(errlist)
}

View file

@ -0,0 +1,68 @@
/*
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 container
import (
"errors"
"testing"
)
func TestPodSyncResult(t *testing.T) {
okResults := []*SyncResult{
NewSyncResult(StartContainer, "container_0"),
NewSyncResult(SetupNetwork, "pod"),
}
errResults := []*SyncResult{
NewSyncResult(KillContainer, "container_1"),
NewSyncResult(TeardownNetwork, "pod"),
}
errResults[0].Fail(errors.New("error_0"), "message_0")
errResults[1].Fail(errors.New("error_1"), "message_1")
// If the PodSyncResult doesn't contain error result, it should not be error
result := PodSyncResult{}
result.AddSyncResult(okResults...)
if result.Error() != nil {
t.Errorf("PodSyncResult should not be error: %v", result)
}
// If the PodSyncResult contains error result, it should be error
result = PodSyncResult{}
result.AddSyncResult(okResults...)
result.AddSyncResult(errResults...)
if result.Error() == nil {
t.Errorf("PodSyncResult should be error: %q", result)
}
// If the PodSyncResult is failed, it should be error
result = PodSyncResult{}
result.AddSyncResult(okResults...)
result.Fail(errors.New("error"))
if result.Error() == nil {
t.Errorf("PodSyncResult should be error: %q", result)
}
// If the PodSyncResult is added an error PodSyncResult, it should be error
errResult := PodSyncResult{}
errResult.AddSyncResult(errResults...)
result = PodSyncResult{}
result.AddSyncResult(okResults...)
result.AddPodSyncResult(errResult)
if result.Error() == nil {
t.Errorf("PodSyncResult should be error: %q", result)
}
}

View file

@ -0,0 +1,43 @@
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"fake_cache.go",
"fake_runtime.go",
"mockfileinfo.go",
"os.go",
"runtime_mock.go",
],
tags = ["automanaged"],
deps = [
"//pkg/api/v1:go_default_library",
"//pkg/kubelet/container:go_default_library",
"//pkg/util/flowcontrol:go_default_library",
"//pkg/util/term:go_default_library",
"//pkg/volume:go_default_library",
"//vendor:github.com/golang/mock/gomock",
"//vendor:github.com/stretchr/testify/mock",
"//vendor:k8s.io/apimachinery/pkg/types",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View file

@ -0,0 +1,49 @@
/*
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 testing
import (
"time"
"k8s.io/apimachinery/pkg/types"
"k8s.io/kubernetes/pkg/kubelet/container"
)
type fakeCache struct {
runtime container.Runtime
}
func NewFakeCache(runtime container.Runtime) container.Cache {
return &fakeCache{runtime: runtime}
}
func (c *fakeCache) Get(id types.UID) (*container.PodStatus, error) {
return c.runtime.GetPodStatus(id, "", "")
}
func (c *fakeCache) GetNewerThan(id types.UID, minTime time.Time) (*container.PodStatus, error) {
return c.Get(id)
}
func (c *fakeCache) Set(id types.UID, status *container.PodStatus, err error, timestamp time.Time) {
}
func (c *fakeCache) Delete(id types.UID) {
}
func (c *fakeCache) UpdateTime(_ time.Time) {
}

View file

@ -0,0 +1,500 @@
/*
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 testing
import (
"fmt"
"io"
"net/url"
"reflect"
"sync"
"time"
"k8s.io/apimachinery/pkg/types"
"k8s.io/kubernetes/pkg/api/v1"
. "k8s.io/kubernetes/pkg/kubelet/container"
"k8s.io/kubernetes/pkg/util/flowcontrol"
"k8s.io/kubernetes/pkg/util/term"
"k8s.io/kubernetes/pkg/volume"
)
type FakePod struct {
Pod *Pod
NetnsPath string
}
// FakeRuntime is a fake container runtime for testing.
type FakeRuntime struct {
sync.Mutex
CalledFunctions []string
PodList []*FakePod
AllPodList []*FakePod
ImageList []Image
APIPodStatus v1.PodStatus
PodStatus PodStatus
StartedPods []string
KilledPods []string
StartedContainers []string
KilledContainers []string
RuntimeStatus *RuntimeStatus
VersionInfo string
APIVersionInfo string
RuntimeType string
Err error
InspectErr error
StatusErr error
}
type FakeDirectStreamingRuntime struct {
*FakeRuntime
// Arguments to streaming method calls.
Args struct {
// Attach / Exec args
ContainerID ContainerID
Cmd []string
Stdin io.Reader
Stdout io.WriteCloser
Stderr io.WriteCloser
TTY bool
// Port-forward args
Pod *Pod
Port uint16
Stream io.ReadWriteCloser
}
}
var _ DirectStreamingRuntime = &FakeDirectStreamingRuntime{}
const FakeHost = "localhost:12345"
type FakeIndirectStreamingRuntime struct {
*FakeRuntime
}
var _ IndirectStreamingRuntime = &FakeIndirectStreamingRuntime{}
// FakeRuntime should implement Runtime.
var _ Runtime = &FakeRuntime{}
type FakeVersion struct {
Version string
}
func (fv *FakeVersion) String() string {
return fv.Version
}
func (fv *FakeVersion) Compare(other string) (int, error) {
result := 0
if fv.Version > other {
result = 1
} else if fv.Version < other {
result = -1
}
return result, nil
}
type podsGetter interface {
GetPods(bool) ([]*Pod, error)
}
type FakeRuntimeCache struct {
getter podsGetter
}
func NewFakeRuntimeCache(getter podsGetter) RuntimeCache {
return &FakeRuntimeCache{getter}
}
func (f *FakeRuntimeCache) GetPods() ([]*Pod, error) {
return f.getter.GetPods(false)
}
func (f *FakeRuntimeCache) ForceUpdateIfOlder(time.Time) error {
return nil
}
// ClearCalls resets the FakeRuntime to the initial state.
func (f *FakeRuntime) ClearCalls() {
f.Lock()
defer f.Unlock()
f.CalledFunctions = []string{}
f.PodList = []*FakePod{}
f.AllPodList = []*FakePod{}
f.APIPodStatus = v1.PodStatus{}
f.StartedPods = []string{}
f.KilledPods = []string{}
f.StartedContainers = []string{}
f.KilledContainers = []string{}
f.RuntimeStatus = nil
f.VersionInfo = ""
f.RuntimeType = ""
f.Err = nil
f.InspectErr = nil
f.StatusErr = nil
}
// UpdatePodCIDR fulfills the cri interface.
func (f *FakeRuntime) UpdatePodCIDR(c string) error {
return nil
}
func (f *FakeRuntime) assertList(expect []string, test []string) error {
if !reflect.DeepEqual(expect, test) {
return fmt.Errorf("expected %#v, got %#v", expect, test)
}
return nil
}
// AssertCalls test if the invoked functions are as expected.
func (f *FakeRuntime) AssertCalls(calls []string) error {
f.Lock()
defer f.Unlock()
return f.assertList(calls, f.CalledFunctions)
}
func (f *FakeRuntime) AssertStartedPods(pods []string) error {
f.Lock()
defer f.Unlock()
return f.assertList(pods, f.StartedPods)
}
func (f *FakeRuntime) AssertKilledPods(pods []string) error {
f.Lock()
defer f.Unlock()
return f.assertList(pods, f.KilledPods)
}
func (f *FakeRuntime) AssertStartedContainers(containers []string) error {
f.Lock()
defer f.Unlock()
return f.assertList(containers, f.StartedContainers)
}
func (f *FakeRuntime) AssertKilledContainers(containers []string) error {
f.Lock()
defer f.Unlock()
return f.assertList(containers, f.KilledContainers)
}
func (f *FakeRuntime) Type() string {
return f.RuntimeType
}
func (f *FakeRuntime) Version() (Version, error) {
f.Lock()
defer f.Unlock()
f.CalledFunctions = append(f.CalledFunctions, "Version")
return &FakeVersion{Version: f.VersionInfo}, f.Err
}
func (f *FakeRuntime) APIVersion() (Version, error) {
f.Lock()
defer f.Unlock()
f.CalledFunctions = append(f.CalledFunctions, "APIVersion")
return &FakeVersion{Version: f.APIVersionInfo}, f.Err
}
func (f *FakeRuntime) Status() (*RuntimeStatus, error) {
f.Lock()
defer f.Unlock()
f.CalledFunctions = append(f.CalledFunctions, "Status")
return f.RuntimeStatus, f.StatusErr
}
func (f *FakeRuntime) GetPods(all bool) ([]*Pod, error) {
f.Lock()
defer f.Unlock()
var pods []*Pod
f.CalledFunctions = append(f.CalledFunctions, "GetPods")
if all {
for _, fakePod := range f.AllPodList {
pods = append(pods, fakePod.Pod)
}
} else {
for _, fakePod := range f.PodList {
pods = append(pods, fakePod.Pod)
}
}
return pods, f.Err
}
func (f *FakeRuntime) SyncPod(pod *v1.Pod, _ v1.PodStatus, _ *PodStatus, _ []v1.Secret, backOff *flowcontrol.Backoff) (result PodSyncResult) {
f.Lock()
defer f.Unlock()
f.CalledFunctions = append(f.CalledFunctions, "SyncPod")
f.StartedPods = append(f.StartedPods, string(pod.UID))
for _, c := range pod.Spec.Containers {
f.StartedContainers = append(f.StartedContainers, c.Name)
}
// TODO(random-liu): Add SyncResult for starting and killing containers
if f.Err != nil {
result.Fail(f.Err)
}
return
}
func (f *FakeRuntime) KillPod(pod *v1.Pod, runningPod Pod, gracePeriodOverride *int64) error {
f.Lock()
defer f.Unlock()
f.CalledFunctions = append(f.CalledFunctions, "KillPod")
f.KilledPods = append(f.KilledPods, string(runningPod.ID))
for _, c := range runningPod.Containers {
f.KilledContainers = append(f.KilledContainers, c.Name)
}
return f.Err
}
func (f *FakeRuntime) RunContainerInPod(container v1.Container, pod *v1.Pod, volumeMap map[string]volume.VolumePlugin) error {
f.Lock()
defer f.Unlock()
f.CalledFunctions = append(f.CalledFunctions, "RunContainerInPod")
f.StartedContainers = append(f.StartedContainers, container.Name)
pod.Spec.Containers = append(pod.Spec.Containers, container)
for _, c := range pod.Spec.Containers {
if c.Name == container.Name { // Container already in the pod.
return f.Err
}
}
pod.Spec.Containers = append(pod.Spec.Containers, container)
return f.Err
}
func (f *FakeRuntime) KillContainerInPod(container v1.Container, pod *v1.Pod) error {
f.Lock()
defer f.Unlock()
f.CalledFunctions = append(f.CalledFunctions, "KillContainerInPod")
f.KilledContainers = append(f.KilledContainers, container.Name)
var containers []v1.Container
for _, c := range pod.Spec.Containers {
if c.Name == container.Name {
continue
}
containers = append(containers, c)
}
return f.Err
}
func (f *FakeRuntime) GetPodStatus(uid types.UID, name, namespace string) (*PodStatus, error) {
f.Lock()
defer f.Unlock()
f.CalledFunctions = append(f.CalledFunctions, "GetPodStatus")
status := f.PodStatus
return &status, f.Err
}
func (f *FakeDirectStreamingRuntime) ExecInContainer(containerID ContainerID, cmd []string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan term.Size, timeout time.Duration) error {
f.Lock()
defer f.Unlock()
f.CalledFunctions = append(f.CalledFunctions, "ExecInContainer")
f.Args.ContainerID = containerID
f.Args.Cmd = cmd
f.Args.Stdin = stdin
f.Args.Stdout = stdout
f.Args.Stderr = stderr
f.Args.TTY = tty
return f.Err
}
func (f *FakeDirectStreamingRuntime) AttachContainer(containerID ContainerID, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan term.Size) error {
f.Lock()
defer f.Unlock()
f.CalledFunctions = append(f.CalledFunctions, "AttachContainer")
f.Args.ContainerID = containerID
f.Args.Stdin = stdin
f.Args.Stdout = stdout
f.Args.Stderr = stderr
f.Args.TTY = tty
return f.Err
}
func (f *FakeRuntime) GetContainerLogs(pod *v1.Pod, containerID ContainerID, logOptions *v1.PodLogOptions, stdout, stderr io.Writer) (err error) {
f.Lock()
defer f.Unlock()
f.CalledFunctions = append(f.CalledFunctions, "GetContainerLogs")
return f.Err
}
func (f *FakeRuntime) PullImage(image ImageSpec, pullSecrets []v1.Secret) (string, error) {
f.Lock()
defer f.Unlock()
f.CalledFunctions = append(f.CalledFunctions, "PullImage")
return image.Image, f.Err
}
func (f *FakeRuntime) GetImageRef(image ImageSpec) (string, error) {
f.Lock()
defer f.Unlock()
f.CalledFunctions = append(f.CalledFunctions, "GetImageRef")
for _, i := range f.ImageList {
if i.ID == image.Image {
return i.ID, nil
}
}
return "", f.InspectErr
}
func (f *FakeRuntime) ListImages() ([]Image, error) {
f.Lock()
defer f.Unlock()
f.CalledFunctions = append(f.CalledFunctions, "ListImages")
return f.ImageList, f.Err
}
func (f *FakeRuntime) RemoveImage(image ImageSpec) error {
f.Lock()
defer f.Unlock()
f.CalledFunctions = append(f.CalledFunctions, "RemoveImage")
index := 0
for i := range f.ImageList {
if f.ImageList[i].ID == image.Image {
index = i
break
}
}
f.ImageList = append(f.ImageList[:index], f.ImageList[index+1:]...)
return f.Err
}
func (f *FakeDirectStreamingRuntime) PortForward(pod *Pod, port uint16, stream io.ReadWriteCloser) error {
f.Lock()
defer f.Unlock()
f.CalledFunctions = append(f.CalledFunctions, "PortForward")
f.Args.Pod = pod
f.Args.Port = port
f.Args.Stream = stream
return f.Err
}
func (f *FakeRuntime) GetNetNS(containerID ContainerID) (string, error) {
f.Lock()
defer f.Unlock()
f.CalledFunctions = append(f.CalledFunctions, "GetNetNS")
for _, fp := range f.AllPodList {
for _, c := range fp.Pod.Containers {
if c.ID == containerID {
return fp.NetnsPath, nil
}
}
}
return "", f.Err
}
func (f *FakeRuntime) GetPodContainerID(pod *Pod) (ContainerID, error) {
f.Lock()
defer f.Unlock()
f.CalledFunctions = append(f.CalledFunctions, "GetPodContainerID")
return ContainerID{}, f.Err
}
func (f *FakeRuntime) GarbageCollect(gcPolicy ContainerGCPolicy, ready bool) error {
f.Lock()
defer f.Unlock()
f.CalledFunctions = append(f.CalledFunctions, "GarbageCollect")
return f.Err
}
func (f *FakeRuntime) DeleteContainer(containerID ContainerID) error {
f.Lock()
defer f.Unlock()
f.CalledFunctions = append(f.CalledFunctions, "DeleteContainer")
return f.Err
}
func (f *FakeRuntime) ImageStats() (*ImageStats, error) {
f.Lock()
defer f.Unlock()
f.CalledFunctions = append(f.CalledFunctions, "ImageStats")
return nil, f.Err
}
func (f *FakeIndirectStreamingRuntime) GetExec(id ContainerID, cmd []string, stdin, stdout, stderr, tty bool) (*url.URL, error) {
f.Lock()
defer f.Unlock()
f.CalledFunctions = append(f.CalledFunctions, "GetExec")
return &url.URL{Host: FakeHost}, f.Err
}
func (f *FakeIndirectStreamingRuntime) GetAttach(id ContainerID, stdin, stdout, stderr, tty bool) (*url.URL, error) {
f.Lock()
defer f.Unlock()
f.CalledFunctions = append(f.CalledFunctions, "GetAttach")
return &url.URL{Host: FakeHost}, f.Err
}
func (f *FakeIndirectStreamingRuntime) GetPortForward(podName, podNamespace string, podUID types.UID) (*url.URL, error) {
f.Lock()
defer f.Unlock()
f.CalledFunctions = append(f.CalledFunctions, "GetPortForward")
return &url.URL{Host: FakeHost}, f.Err
}
type FakeContainerCommandRunner struct {
// what to return
Stdout string
Err error
// actual values when invoked
ContainerID ContainerID
Cmd []string
}
var _ ContainerCommandRunner = &FakeContainerCommandRunner{}
func (f *FakeContainerCommandRunner) RunInContainer(containerID ContainerID, cmd []string, timeout time.Duration) ([]byte, error) {
// record invoked values
f.ContainerID = containerID
f.Cmd = cmd
return []byte(f.Stdout), f.Err
}

View file

@ -0,0 +1,109 @@
/*
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.
*/
// Generated via: mockgen os FileInfo
// Edited to include required boilerplate
// Source: os (interfaces: FileInfo)
package testing
import (
os "os"
time "time"
gomock "github.com/golang/mock/gomock"
)
// Mock of FileInfo interface
type MockFileInfo struct {
ctrl *gomock.Controller
recorder *_MockFileInfoRecorder
}
// Recorder for MockFileInfo (not exported)
type _MockFileInfoRecorder struct {
mock *MockFileInfo
}
func NewMockFileInfo(ctrl *gomock.Controller) *MockFileInfo {
mock := &MockFileInfo{ctrl: ctrl}
mock.recorder = &_MockFileInfoRecorder{mock}
return mock
}
func (_m *MockFileInfo) EXPECT() *_MockFileInfoRecorder {
return _m.recorder
}
func (_m *MockFileInfo) IsDir() bool {
ret := _m.ctrl.Call(_m, "IsDir")
ret0, _ := ret[0].(bool)
return ret0
}
func (_mr *_MockFileInfoRecorder) IsDir() *gomock.Call {
return _mr.mock.ctrl.RecordCall(_mr.mock, "IsDir")
}
func (_m *MockFileInfo) ModTime() time.Time {
ret := _m.ctrl.Call(_m, "ModTime")
ret0, _ := ret[0].(time.Time)
return ret0
}
func (_mr *_MockFileInfoRecorder) ModTime() *gomock.Call {
return _mr.mock.ctrl.RecordCall(_mr.mock, "ModTime")
}
func (_m *MockFileInfo) Mode() os.FileMode {
ret := _m.ctrl.Call(_m, "Mode")
ret0, _ := ret[0].(os.FileMode)
return ret0
}
func (_mr *_MockFileInfoRecorder) Mode() *gomock.Call {
return _mr.mock.ctrl.RecordCall(_mr.mock, "Mode")
}
func (_m *MockFileInfo) Name() string {
ret := _m.ctrl.Call(_m, "Name")
ret0, _ := ret[0].(string)
return ret0
}
func (_mr *_MockFileInfoRecorder) Name() *gomock.Call {
return _mr.mock.ctrl.RecordCall(_mr.mock, "Name")
}
func (_m *MockFileInfo) Size() int64 {
ret := _m.ctrl.Call(_m, "Size")
ret0, _ := ret[0].(int64)
return ret0
}
func (_mr *_MockFileInfoRecorder) Size() *gomock.Call {
return _mr.mock.ctrl.RecordCall(_mr.mock, "Size")
}
func (_m *MockFileInfo) Sys() interface{} {
ret := _m.ctrl.Call(_m, "Sys")
ret0, _ := ret[0].(interface{})
return ret0
}
func (_mr *_MockFileInfoRecorder) Sys() *gomock.Call {
return _mr.mock.ctrl.RecordCall(_mr.mock, "Sys")
}

View file

@ -0,0 +1,112 @@
/*
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 testing
import (
"errors"
"os"
"time"
)
// FakeOS mocks out certain OS calls to avoid perturbing the filesystem
// If a member of the form `*Fn` is set, that function will be called in place
// of the real call.
type FakeOS struct {
StatFn func(string) (os.FileInfo, error)
ReadDirFn func(string) ([]os.FileInfo, error)
MkdirAllFn func(string, os.FileMode) error
SymlinkFn func(string, string) error
HostName string
Removes []string
Files map[string][]*os.FileInfo
}
func NewFakeOS() *FakeOS {
return &FakeOS{
Removes: []string{},
Files: make(map[string][]*os.FileInfo),
}
}
// Mkdir is a fake call that just returns nil.
func (f *FakeOS) MkdirAll(path string, perm os.FileMode) error {
if f.MkdirAllFn != nil {
return f.MkdirAllFn(path, perm)
}
return nil
}
// Symlink is a fake call that just returns nil.
func (f *FakeOS) Symlink(oldname string, newname string) error {
if f.SymlinkFn != nil {
return f.SymlinkFn(oldname, newname)
}
return nil
}
// Stat is a fake that returns an error
func (f FakeOS) Stat(path string) (os.FileInfo, error) {
if f.StatFn != nil {
return f.StatFn(path)
}
return nil, errors.New("unimplemented testing mock")
}
// Remove is a fake call that returns nil.
func (f *FakeOS) Remove(path string) error {
f.Removes = append(f.Removes, path)
return nil
}
// RemoveAll is a fake call that just returns nil.
func (f *FakeOS) RemoveAll(path string) error {
f.Removes = append(f.Removes, path)
return nil
}
// Create is a fake call that returns nil.
func (FakeOS) Create(path string) (*os.File, error) {
return nil, nil
}
// Hostname is a fake call that returns nil.
func (f *FakeOS) Hostname() (name string, err error) {
return f.HostName, nil
}
// Chtimes is a fake call that returns nil.
func (FakeOS) Chtimes(path string, atime time.Time, mtime time.Time) error {
return nil
}
// Pipe is a fake call that returns nil.
func (FakeOS) Pipe() (r *os.File, w *os.File, err error) {
return nil, nil, nil
}
// ReadDir is a fake call that returns the files under the directory.
func (f *FakeOS) ReadDir(dirname string) ([]os.FileInfo, error) {
if f.ReadDirFn != nil {
return f.ReadDirFn(dirname)
}
return nil, nil
}
// Glob is a fake call that returns nil.
func (f *FakeOS) Glob(pattern string) ([]string, error) {
return nil, nil
}

View file

@ -0,0 +1,161 @@
/*
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 testing
import (
"io"
"time"
"github.com/stretchr/testify/mock"
"k8s.io/apimachinery/pkg/types"
"k8s.io/kubernetes/pkg/api/v1"
. "k8s.io/kubernetes/pkg/kubelet/container"
"k8s.io/kubernetes/pkg/util/flowcontrol"
"k8s.io/kubernetes/pkg/util/term"
"k8s.io/kubernetes/pkg/volume"
)
type Mock struct {
mock.Mock
}
var _ Runtime = new(Mock)
func (r *Mock) Start() error {
args := r.Called()
return args.Error(0)
}
func (r *Mock) Type() string {
args := r.Called()
return args.Get(0).(string)
}
func (r *Mock) Version() (Version, error) {
args := r.Called()
return args.Get(0).(Version), args.Error(1)
}
func (r *Mock) APIVersion() (Version, error) {
args := r.Called()
return args.Get(0).(Version), args.Error(1)
}
func (r *Mock) Status() (*RuntimeStatus, error) {
args := r.Called()
return args.Get(0).(*RuntimeStatus), args.Error(0)
}
func (r *Mock) GetPods(all bool) ([]*Pod, error) {
args := r.Called(all)
return args.Get(0).([]*Pod), args.Error(1)
}
func (r *Mock) SyncPod(pod *v1.Pod, apiStatus v1.PodStatus, status *PodStatus, secrets []v1.Secret, backOff *flowcontrol.Backoff) PodSyncResult {
args := r.Called(pod, apiStatus, status, secrets, backOff)
return args.Get(0).(PodSyncResult)
}
func (r *Mock) KillPod(pod *v1.Pod, runningPod Pod, gracePeriodOverride *int64) error {
args := r.Called(pod, runningPod, gracePeriodOverride)
return args.Error(0)
}
func (r *Mock) RunContainerInPod(container v1.Container, pod *v1.Pod, volumeMap map[string]volume.VolumePlugin) error {
args := r.Called(pod, pod, volumeMap)
return args.Error(0)
}
func (r *Mock) KillContainerInPod(container v1.Container, pod *v1.Pod) error {
args := r.Called(pod, pod)
return args.Error(0)
}
func (r *Mock) GetPodStatus(uid types.UID, name, namespace string) (*PodStatus, error) {
args := r.Called(uid, name, namespace)
return args.Get(0).(*PodStatus), args.Error(1)
}
func (r *Mock) ExecInContainer(containerID ContainerID, cmd []string, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan term.Size, timeout time.Duration) error {
args := r.Called(containerID, cmd, stdin, stdout, stderr, tty)
return args.Error(0)
}
func (r *Mock) AttachContainer(containerID ContainerID, stdin io.Reader, stdout, stderr io.WriteCloser, tty bool, resize <-chan term.Size) error {
args := r.Called(containerID, stdin, stdout, stderr, tty)
return args.Error(0)
}
func (r *Mock) GetContainerLogs(pod *v1.Pod, containerID ContainerID, logOptions *v1.PodLogOptions, stdout, stderr io.Writer) (err error) {
args := r.Called(pod, containerID, logOptions, stdout, stderr)
return args.Error(0)
}
func (r *Mock) PullImage(image ImageSpec, pullSecrets []v1.Secret) (string, error) {
args := r.Called(image, pullSecrets)
return image.Image, args.Error(0)
}
func (r *Mock) GetImageRef(image ImageSpec) (string, error) {
args := r.Called(image)
return args.Get(0).(string), args.Error(1)
}
func (r *Mock) ListImages() ([]Image, error) {
args := r.Called()
return args.Get(0).([]Image), args.Error(1)
}
func (r *Mock) RemoveImage(image ImageSpec) error {
args := r.Called(image)
return args.Error(0)
}
func (r *Mock) PortForward(pod *Pod, port uint16, stream io.ReadWriteCloser) error {
args := r.Called(pod, port, stream)
return args.Error(0)
}
func (r *Mock) GetNetNS(containerID ContainerID) (string, error) {
args := r.Called(containerID)
return "", args.Error(0)
}
func (r *Mock) GetPodContainerID(pod *Pod) (ContainerID, error) {
args := r.Called(pod)
return ContainerID{}, args.Error(0)
}
func (r *Mock) GarbageCollect(gcPolicy ContainerGCPolicy, ready bool) error {
args := r.Called(gcPolicy, ready)
return args.Error(0)
}
func (r *Mock) DeleteContainer(containerID ContainerID) error {
args := r.Called(containerID)
return args.Error(0)
}
func (r *Mock) ImageStats() (*ImageStats, error) {
args := r.Called()
return args.Get(0).(*ImageStats), args.Error(1)
}
// UpdatePodCIDR fulfills the cri interface.
func (r *Mock) UpdatePodCIDR(c string) error {
return nil
}

View file

@ -0,0 +1,40 @@
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = ["custom_metrics.go"],
tags = ["automanaged"],
deps = ["//pkg/api/v1:go_default_library"],
)
go_test(
name = "go_default_test",
srcs = ["custom_metrics_test.go"],
library = ":go_default_library",
tags = ["automanaged"],
deps = [
"//pkg/api/v1:go_default_library",
"//vendor:github.com/stretchr/testify/assert",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View file

@ -0,0 +1,48 @@
/*
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 custommetrics contains support for instrumenting cAdvisor to gather custom metrics from pods.
package custommetrics
import (
"path"
"k8s.io/kubernetes/pkg/api/v1"
)
const (
CustomMetricsDefinitionContainerFile = "definition.json"
CustomMetricsDefinitionDir = "/etc/custom-metrics"
)
// Alpha implementation.
// Returns a path to a cAdvisor-specific custom metrics configuration.
func GetCAdvisorCustomMetricsDefinitionPath(container *v1.Container) (*string, error) {
// Assuemes that the container has Custom Metrics enabled if it has "/etc/custom-metrics" directory
// mounted as a volume. Custom Metrics definition is expected to be in "definition.json".
if container.VolumeMounts != nil {
for _, volumeMount := range container.VolumeMounts {
if path.Clean(volumeMount.MountPath) == path.Clean(CustomMetricsDefinitionDir) {
// TODO: add definition file validation.
definitionPath := path.Clean(path.Join(volumeMount.MountPath, CustomMetricsDefinitionContainerFile))
return &definitionPath, nil
}
}
}
// No Custom Metrics definition available.
return nil, nil
}

View file

@ -0,0 +1,48 @@
/*
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 custommetrics
import (
"testing"
"github.com/stretchr/testify/assert"
"k8s.io/kubernetes/pkg/api/v1"
)
func TestGetCAdvisorCustomMetricsDefinitionPath(t *testing.T) {
regularContainer := &v1.Container{
Name: "test_container",
}
cmContainer := &v1.Container{
Name: "test_container",
VolumeMounts: []v1.VolumeMount{
{
Name: "cm",
MountPath: CustomMetricsDefinitionDir,
},
},
}
path, err := GetCAdvisorCustomMetricsDefinitionPath(regularContainer)
assert.Nil(t, path)
assert.NoError(t, err)
path, err = GetCAdvisorCustomMetricsDefinitionPath(cmContainer)
assert.NotEmpty(t, *path)
assert.NoError(t, err)
}

137
vendor/k8s.io/kubernetes/pkg/kubelet/disk_manager.go generated vendored Normal file
View file

@ -0,0 +1,137 @@
/*
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 kubelet
import (
"fmt"
"sync"
"time"
"github.com/golang/glog"
cadvisorapi "github.com/google/cadvisor/info/v2"
"k8s.io/kubernetes/pkg/kubelet/cadvisor"
)
// Manages policy for diskspace management for disks holding docker images and root fs.
// mb is used to easily convert an int to an mb
const mb = 1024 * 1024
// Implementation is thread-safe.
type diskSpaceManager interface {
// Checks the available disk space
IsRootDiskSpaceAvailable() (bool, error)
IsRuntimeDiskSpaceAvailable() (bool, error)
}
type DiskSpacePolicy struct {
// free disk space threshold for filesystem holding docker images.
DockerFreeDiskMB int
// free disk space threshold for root filesystem. Host volumes are created on root fs.
RootFreeDiskMB int
}
type fsInfo struct {
Usage int64
Capacity int64
Available int64
Timestamp time.Time
}
type realDiskSpaceManager struct {
cadvisor cadvisor.Interface
cachedInfo map[string]fsInfo // cache of filesystem info.
lock sync.Mutex // protecting cachedInfo.
policy DiskSpacePolicy // thresholds. Set at creation time.
}
func (dm *realDiskSpaceManager) getFsInfo(fsType string, f func() (cadvisorapi.FsInfo, error)) (fsInfo, error) {
dm.lock.Lock()
defer dm.lock.Unlock()
fsi := fsInfo{}
if info, ok := dm.cachedInfo[fsType]; ok {
timeLimit := time.Now().Add(-2 * time.Second)
if info.Timestamp.After(timeLimit) {
fsi = info
}
}
if fsi.Timestamp.IsZero() {
fs, err := f()
if err != nil {
return fsInfo{}, err
}
fsi.Timestamp = time.Now()
fsi.Usage = int64(fs.Usage)
fsi.Capacity = int64(fs.Capacity)
fsi.Available = int64(fs.Available)
dm.cachedInfo[fsType] = fsi
}
return fsi, nil
}
func (dm *realDiskSpaceManager) IsRuntimeDiskSpaceAvailable() (bool, error) {
return dm.isSpaceAvailable("runtime", dm.policy.DockerFreeDiskMB, dm.cadvisor.ImagesFsInfo)
}
func (dm *realDiskSpaceManager) IsRootDiskSpaceAvailable() (bool, error) {
return dm.isSpaceAvailable("root", dm.policy.RootFreeDiskMB, dm.cadvisor.RootFsInfo)
}
func (dm *realDiskSpaceManager) isSpaceAvailable(fsType string, threshold int, f func() (cadvisorapi.FsInfo, error)) (bool, error) {
fsInfo, err := dm.getFsInfo(fsType, f)
if err != nil {
return true, fmt.Errorf("failed to get fs info for %q: %v", fsType, err)
}
if fsInfo.Capacity == 0 {
return true, fmt.Errorf("could not determine capacity for %q fs. Info: %+v", fsType, fsInfo)
}
if fsInfo.Available < 0 {
return true, fmt.Errorf("wrong available space for %q: %+v", fsType, fsInfo)
}
if fsInfo.Available < int64(threshold)*mb {
glog.Infof("Running out of space on disk for %q: available %d MB, threshold %d MB", fsType, fsInfo.Available/mb, threshold)
return false, nil
}
return true, nil
}
func validatePolicy(policy DiskSpacePolicy) error {
if policy.DockerFreeDiskMB < 0 {
return fmt.Errorf("free disk space should be non-negative. Invalid value %d for docker disk space threshold.", policy.DockerFreeDiskMB)
}
if policy.RootFreeDiskMB < 0 {
return fmt.Errorf("free disk space should be non-negative. Invalid value %d for root disk space threshold.", policy.RootFreeDiskMB)
}
return nil
}
func newDiskSpaceManager(cadvisorInterface cadvisor.Interface, policy DiskSpacePolicy) (diskSpaceManager, error) {
// validate policy
err := validatePolicy(policy)
if err != nil {
return nil, err
}
dm := &realDiskSpaceManager{
cadvisor: cadvisorInterface,
policy: policy,
cachedInfo: map[string]fsInfo{},
}
return dm, nil
}

View file

@ -0,0 +1,295 @@
/*
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 kubelet
import (
"fmt"
"testing"
cadvisorapi "github.com/google/cadvisor/info/v2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
cadvisortest "k8s.io/kubernetes/pkg/kubelet/cadvisor/testing"
)
func testPolicy() DiskSpacePolicy {
return DiskSpacePolicy{
DockerFreeDiskMB: 250,
RootFreeDiskMB: 250,
}
}
func setUp(t *testing.T) (*assert.Assertions, DiskSpacePolicy, *cadvisortest.Mock) {
assert := assert.New(t)
policy := testPolicy()
c := new(cadvisortest.Mock)
return assert, policy, c
}
func TestValidPolicy(t *testing.T) {
assert, policy, c := setUp(t)
_, err := newDiskSpaceManager(c, policy)
assert.NoError(err)
policy = testPolicy()
policy.DockerFreeDiskMB = -1
_, err = newDiskSpaceManager(c, policy)
assert.Error(err)
policy = testPolicy()
policy.RootFreeDiskMB = -1
_, err = newDiskSpaceManager(c, policy)
assert.Error(err)
}
func TestSpaceAvailable(t *testing.T) {
assert, policy, mockCadvisor := setUp(t)
dm, err := newDiskSpaceManager(mockCadvisor, policy)
assert.NoError(err)
mockCadvisor.On("ImagesFsInfo").Return(cadvisorapi.FsInfo{
Usage: 400 * mb,
Capacity: 1000 * mb,
Available: 600 * mb,
}, nil)
mockCadvisor.On("RootFsInfo").Return(cadvisorapi.FsInfo{
Usage: 9 * mb,
Capacity: 10 * mb,
}, nil)
ok, err := dm.IsRuntimeDiskSpaceAvailable()
assert.NoError(err)
assert.True(ok)
ok, err = dm.IsRootDiskSpaceAvailable()
assert.NoError(err)
assert.False(ok)
}
// TestIsRuntimeDiskSpaceAvailableWithSpace verifies IsRuntimeDiskSpaceAvailable results when
// space is available.
func TestIsRuntimeDiskSpaceAvailableWithSpace(t *testing.T) {
assert, policy, mockCadvisor := setUp(t)
dm, err := newDiskSpaceManager(mockCadvisor, policy)
require.NoError(t, err)
// 500MB available
mockCadvisor.On("ImagesFsInfo").Return(cadvisorapi.FsInfo{
Usage: 9500 * mb,
Capacity: 10000 * mb,
Available: 500 * mb,
}, nil)
ok, err := dm.IsRuntimeDiskSpaceAvailable()
assert.NoError(err)
assert.True(ok)
}
// TestIsRuntimeDiskSpaceAvailableWithoutSpace verifies IsRuntimeDiskSpaceAvailable results when
// space is not available.
func TestIsRuntimeDiskSpaceAvailableWithoutSpace(t *testing.T) {
// 1MB available
assert, policy, mockCadvisor := setUp(t)
mockCadvisor.On("ImagesFsInfo").Return(cadvisorapi.FsInfo{
Usage: 999 * mb,
Capacity: 1000 * mb,
Available: 1 * mb,
}, nil)
dm, err := newDiskSpaceManager(mockCadvisor, policy)
require.NoError(t, err)
ok, err := dm.IsRuntimeDiskSpaceAvailable()
assert.NoError(err)
assert.False(ok)
}
// TestIsRootDiskSpaceAvailableWithSpace verifies IsRootDiskSpaceAvailable results when
// space is available.
func TestIsRootDiskSpaceAvailableWithSpace(t *testing.T) {
assert, policy, mockCadvisor := setUp(t)
policy.RootFreeDiskMB = 10
dm, err := newDiskSpaceManager(mockCadvisor, policy)
assert.NoError(err)
// 999MB available
mockCadvisor.On("RootFsInfo").Return(cadvisorapi.FsInfo{
Usage: 1 * mb,
Capacity: 1000 * mb,
Available: 999 * mb,
}, nil)
ok, err := dm.IsRootDiskSpaceAvailable()
assert.NoError(err)
assert.True(ok)
}
// TestIsRootDiskSpaceAvailableWithoutSpace verifies IsRootDiskSpaceAvailable results when
// space is not available.
func TestIsRootDiskSpaceAvailableWithoutSpace(t *testing.T) {
assert, policy, mockCadvisor := setUp(t)
policy.RootFreeDiskMB = 10
dm, err := newDiskSpaceManager(mockCadvisor, policy)
assert.NoError(err)
// 9MB available
mockCadvisor.On("RootFsInfo").Return(cadvisorapi.FsInfo{
Usage: 990 * mb,
Capacity: 1000 * mb,
Available: 9 * mb,
}, nil)
ok, err := dm.IsRootDiskSpaceAvailable()
assert.NoError(err)
assert.False(ok)
}
// TestCache verifies that caching works properly with DiskSpaceAvailable calls
func TestCache(t *testing.T) {
assert, policy, mockCadvisor := setUp(t)
dm, err := newDiskSpaceManager(mockCadvisor, policy)
assert.NoError(err)
mockCadvisor.On("ImagesFsInfo").Return(cadvisorapi.FsInfo{
Usage: 400 * mb,
Capacity: 1000 * mb,
Available: 300 * mb,
}, nil).Once()
mockCadvisor.On("RootFsInfo").Return(cadvisorapi.FsInfo{
Usage: 500 * mb,
Capacity: 1000 * mb,
Available: 500 * mb,
}, nil).Once()
// Initial calls which should be recorded in mockCadvisor
ok, err := dm.IsRuntimeDiskSpaceAvailable()
assert.NoError(err)
assert.True(ok)
ok, err = dm.IsRootDiskSpaceAvailable()
assert.NoError(err)
assert.True(ok)
// Get the current count of calls to mockCadvisor
cadvisorCallCount := len(mockCadvisor.Calls)
// Checking for space again shouldn't need to mock as cache would serve it.
ok, err = dm.IsRuntimeDiskSpaceAvailable()
assert.NoError(err)
assert.True(ok)
ok, err = dm.IsRootDiskSpaceAvailable()
assert.NoError(err)
assert.True(ok)
// Ensure no more calls to the mockCadvisor occurred
assert.Equal(cadvisorCallCount, len(mockCadvisor.Calls))
}
// TestFsInfoError verifies errors are returned by DiskSpaceAvailable calls
// when FsInfo calls return an error
func TestFsInfoError(t *testing.T) {
assert, policy, mockCadvisor := setUp(t)
policy.RootFreeDiskMB = 10
dm, err := newDiskSpaceManager(mockCadvisor, policy)
assert.NoError(err)
mockCadvisor.On("ImagesFsInfo").Return(cadvisorapi.FsInfo{}, fmt.Errorf("can't find fs"))
mockCadvisor.On("RootFsInfo").Return(cadvisorapi.FsInfo{}, fmt.Errorf("EBUSY"))
ok, err := dm.IsRuntimeDiskSpaceAvailable()
assert.Error(err)
assert.True(ok)
ok, err = dm.IsRootDiskSpaceAvailable()
assert.Error(err)
assert.True(ok)
}
// Test_getFSInfo verifies multiple possible cases for getFsInfo.
func Test_getFsInfo(t *testing.T) {
assert, policy, mockCadvisor := setUp(t)
// Sunny day case
mockCadvisor.On("RootFsInfo").Return(cadvisorapi.FsInfo{
Usage: 10 * mb,
Capacity: 100 * mb,
Available: 90 * mb,
}, nil).Once()
dm := &realDiskSpaceManager{
cadvisor: mockCadvisor,
policy: policy,
cachedInfo: map[string]fsInfo{},
}
available, err := dm.isSpaceAvailable("root", 10, dm.cadvisor.RootFsInfo)
assert.True(available)
assert.NoError(err)
// Threshold case
mockCadvisor = new(cadvisortest.Mock)
mockCadvisor.On("RootFsInfo").Return(cadvisorapi.FsInfo{
Usage: 9 * mb,
Capacity: 100 * mb,
Available: 9 * mb,
}, nil).Once()
dm = &realDiskSpaceManager{
cadvisor: mockCadvisor,
policy: policy,
cachedInfo: map[string]fsInfo{},
}
available, err = dm.isSpaceAvailable("root", 10, dm.cadvisor.RootFsInfo)
assert.False(available)
assert.NoError(err)
// Frozen case
mockCadvisor.On("RootFsInfo").Return(cadvisorapi.FsInfo{
Usage: 9 * mb,
Capacity: 10 * mb,
Available: 500 * mb,
}, nil).Once()
dm = &realDiskSpaceManager{
cadvisor: mockCadvisor,
policy: policy,
cachedInfo: map[string]fsInfo{},
}
available, err = dm.isSpaceAvailable("root", 10, dm.cadvisor.RootFsInfo)
assert.True(available)
assert.NoError(err)
// Capacity error case
mockCadvisor = new(cadvisortest.Mock)
mockCadvisor.On("RootFsInfo").Return(cadvisorapi.FsInfo{
Usage: 9 * mb,
Capacity: 0,
Available: 500 * mb,
}, nil).Once()
dm = &realDiskSpaceManager{
cadvisor: mockCadvisor,
policy: policy,
cachedInfo: map[string]fsInfo{},
}
available, err = dm.isSpaceAvailable("root", 10, dm.cadvisor.RootFsInfo)
assert.True(available)
assert.Error(err)
assert.Contains(fmt.Sprintf("%s", err), "could not determine capacity")
// Available error case skipped as v2.FSInfo uses uint64 and this
// can not be less than 0
}

19
vendor/k8s.io/kubernetes/pkg/kubelet/doc.go generated vendored Normal file
View file

@ -0,0 +1,19 @@
/*
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 kubelet is the package that contains the libraries that drive the Kubelet binary.
// The kubelet is responsible for node level pod management. It runs on each worker in the cluster.
package kubelet // import "k8s.io/kubernetes/pkg/kubelet"

105
vendor/k8s.io/kubernetes/pkg/kubelet/dockershim/BUILD generated vendored Normal file
View file

@ -0,0 +1,105 @@
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = [
"convert.go",
"doc.go",
"docker_container.go",
"docker_image.go",
"docker_sandbox.go",
"docker_service.go",
"docker_streaming.go",
"helpers.go",
"naming.go",
"security_context.go",
],
tags = ["automanaged"],
deps = [
"//pkg/api/v1:go_default_library",
"//pkg/apis/componentconfig:go_default_library",
"//pkg/kubelet/api:go_default_library",
"//pkg/kubelet/api/v1alpha1/runtime:go_default_library",
"//pkg/kubelet/cm:go_default_library",
"//pkg/kubelet/container:go_default_library",
"//pkg/kubelet/dockershim/cm:go_default_library",
"//pkg/kubelet/dockertools: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/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/ioutils:go_default_library",
"//pkg/securitycontext:go_default_library",
"//pkg/util/term:go_default_library",
"//vendor:github.com/docker/engine-api/types",
"//vendor:github.com/docker/engine-api/types/container",
"//vendor:github.com/docker/engine-api/types/filters",
"//vendor:github.com/docker/engine-api/types/strslice",
"//vendor:github.com/docker/engine-api/types/versions",
"//vendor:github.com/docker/go-connections/nat",
"//vendor:github.com/golang/glog",
"//vendor:github.com/golang/protobuf/proto",
],
)
go_test(
name = "go_default_test",
srcs = [
"convert_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",
],
library = ":go_default_library",
tags = ["automanaged"],
deps = [
"//pkg/api/v1:go_default_library",
"//pkg/kubelet/api/v1alpha1/runtime:go_default_library",
"//pkg/kubelet/container:go_default_library",
"//pkg/kubelet/container/testing:go_default_library",
"//pkg/kubelet/dockertools:go_default_library",
"//pkg/kubelet/network:go_default_library",
"//pkg/kubelet/network/mock_network:go_default_library",
"//pkg/kubelet/types:go_default_library",
"//pkg/security/apparmor:go_default_library",
"//pkg/securitycontext:go_default_library",
"//pkg/util/clock:go_default_library",
"//vendor:github.com/docker/engine-api/types",
"//vendor:github.com/docker/engine-api/types/container",
"//vendor:github.com/golang/mock/gomock",
"//vendor:github.com/stretchr/testify/assert",
"//vendor:github.com/stretchr/testify/require",
],
)
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/remote:all-srcs",
],
tags = ["automanaged"],
)

View file

@ -0,0 +1,40 @@
package(default_visibility = ["//visibility:public"])
licenses(["notice"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"container_manager.go",
"container_manager_linux.go",
],
tags = ["automanaged"],
deps = [
"//pkg/kubelet/cm:go_default_library",
"//pkg/kubelet/dockertools:go_default_library",
"//pkg/kubelet/qos:go_default_library",
"//pkg/util/version:go_default_library",
"//vendor:github.com/golang/glog",
"//vendor:github.com/opencontainers/runc/libcontainer/cgroups/fs",
"//vendor:github.com/opencontainers/runc/libcontainer/configs",
"//vendor:k8s.io/apimachinery/pkg/util/wait",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View 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
}

View file

@ -0,0 +1,147 @@
// +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/dockertools"
"k8s.io/kubernetes/pkg/kubelet/qos"
utilversion "k8s.io/kubernetes/pkg/util/version"
)
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 dockertools.DockerInterface) ContainerManager {
return &containerManager{
cgroupsName: cgroupsName,
client: client,
}
}
type containerManager struct {
// Docker client.
client dockertools.DockerInterface
// 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.ParseSemantic(v.Version)
if err != nil {
glog.Errorf("Unable to parse docker version %q: %v", v.Version, err)
return
}
// EnsureDockerInConatiner 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
}

View file

@ -0,0 +1,36 @@
// +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"
"k8s.io/kubernetes/pkg/kubelet/dockertools"
)
type unsupportedContainerManager struct {
}
func NewContainerManager(_ string, _ dockertools.DockerInterface) ContainerManager {
return &unsupportedContainerManager{}
}
func (m *unsupportedContainerManager) Start() error {
return fmt.Errorf("Container Manager is unsupported in this build")
}

View file

@ -0,0 +1,167 @@
/*
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/engine-api/types"
runtimeapi "k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime"
)
// This file contains helper functions to convert docker API types to runtime
// API types, or vice versa.
const (
// Status of a container returned by docker ListContainers
statusRunningPrefix = "Up"
statusCreatedPrefix = "Created"
statusExitedPrefix = "Exited"
)
func imageToRuntimeAPIImage(image *dockertypes.Image) (*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 {
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,
}
runtimeImage.Uid, runtimeImage.Username = getUserFromImageUser(image.Config.User)
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, statusRunningPrefix):
return runtimeapi.ContainerState_CONTAINER_RUNNING
case strings.HasPrefix(state, statusExitedPrefix):
return runtimeapi.ContainerState_CONTAINER_EXITED
case strings.HasPrefix(state, 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, statusRunningPrefix):
return runtimeapi.PodSandboxState_SANDBOX_READY
default:
return runtimeapi.PodSandboxState_SANDBOX_NOTREADY
}
}
func toRuntimeAPISandbox(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
}

View 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/engine-api/types"
"github.com/stretchr/testify/assert"
runtimeapi "k8s.io/kubernetes/pkg/kubelet/api/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)
}
}

Some files were not shown because too many files have changed in this diff Show more