vendor files

This commit is contained in:
Serguei Bezverkhi
2018-01-09 13:57:14 -05:00
parent 558bc6c02a
commit 7b24313bd6
16547 changed files with 4527373 additions and 0 deletions

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

@ -0,0 +1,112 @@
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",
"defaults.go",
"doc.go",
"file.go",
"file_unsupported.go",
"flags.go",
"http.go",
"sources.go",
] + select({
"@io_bazel_rules_go//go/platform:linux_amd64": [
"file_linux.go",
],
"//conditions:default": [],
}),
importpath = "k8s.io/kubernetes/pkg/kubelet/config",
visibility = ["//visibility:public"],
deps = [
"//pkg/api/legacyscheme:go_default_library",
"//pkg/apis/core:go_default_library",
"//pkg/apis/core/helper:go_default_library",
"//pkg/apis/core/install:go_default_library",
"//pkg/apis/core/v1:go_default_library",
"//pkg/apis/core/validation:go_default_library",
"//pkg/kubelet/checkpoint: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:go_default_library",
"//vendor/github.com/spf13/pflag:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/fields:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/yaml:go_default_library",
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
"//vendor/k8s.io/client-go/tools/cache:go_default_library",
"//vendor/k8s.io/client-go/tools/record:go_default_library",
] + select({
"@io_bazel_rules_go//go/platform:linux_amd64": [
"//vendor/golang.org/x/exp/inotify:go_default_library",
],
"//conditions:default": [],
}),
)
go_test(
name = "go_default_test",
srcs = [
"apiserver_test.go",
"common_test.go",
"config_test.go",
"http_test.go",
] + select({
"@io_bazel_rules_go//go/platform:linux_amd64": [
"file_linux_test.go",
],
"//conditions:default": [],
}),
importpath = "k8s.io/kubernetes/pkg/kubelet/config",
library = ":go_default_library",
deps = [
"//pkg/api/legacyscheme:go_default_library",
"//pkg/api/testapi:go_default_library",
"//pkg/apis/core:go_default_library",
"//pkg/apis/core/v1:go_default_library",
"//pkg/apis/core/validation:go_default_library",
"//pkg/kubelet/types:go_default_library",
"//pkg/securitycontext:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/watch:go_default_library",
"//vendor/k8s.io/client-go/kubernetes/scheme:go_default_library",
"//vendor/k8s.io/client-go/tools/cache:go_default_library",
"//vendor/k8s.io/client-go/tools/record:go_default_library",
"//vendor/k8s.io/client-go/util/testing:go_default_library",
] + select({
"@io_bazel_rules_go//go/platform:linux_amd64": [
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
],
"//conditions:default": [],
}),
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@ -0,0 +1,49 @@
/*
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/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
clientset "k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/cache"
api "k8s.io/kubernetes/pkg/apis/core"
kubetypes "k8s.io/kubernetes/pkg/kubelet/types"
)
// NewSourceApiserver creates a config source that watches and pulls from the apiserver.
func NewSourceApiserver(c clientset.Interface, nodeName types.NodeName, updates chan<- interface{}) {
lw := cache.NewListWatchFromClient(c.CoreV1().RESTClient(), "pods", metav1.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}
}
r := cache.NewReflector(lw, &v1.Pod{}, cache.NewUndeltaStore(send, cache.MetaNamespaceKeyFunc), 0)
go r.Run(wait.NeverStop)
}

View File

@ -0,0 +1,194 @@
/*
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/api/core/v1"
apiequality "k8s.io/apimachinery/pkg/api/equality"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/client-go/tools/cache"
kubetypes "k8s.io/kubernetes/pkg/kubelet/types"
)
type fakePodLW struct {
listResp runtime.Object
watchResp watch.Interface
}
func (lw fakePodLW) List(options metav1.ListOptions) (runtime.Object, error) {
return lw.listResp, nil
}
func (lw fakePodLW) Watch(options metav1.ListOptions) (watch.Interface, error) {
return lw.watchResp, nil
}
var _ cache.ListerWatcher = fakePodLW{}
func TestNewSourceApiserver_UpdatesAndMultiplePods(t *testing.T) {
pod1v1 := &v1.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "p"},
Spec: v1.PodSpec{Containers: []v1.Container{{Image: "image/one"}}}}
pod1v2 := &v1.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "p"},
Spec: v1.PodSpec{Containers: []v1.Container{{Image: "image/two"}}}}
pod2 := &v1.Pod{
ObjectMeta: metav1.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 !apiequality.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 !apiequality.Semantic.DeepEqual(expectedA, update) && !apiequality.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 !apiequality.Semantic.DeepEqual(expectedA, update) && !apiequality.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 !apiequality.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 !apiequality.Semantic.DeepEqual(expected, update) {
t.Errorf("Expected %#v, Got %#v", expected, update)
}
}
func TestNewSourceApiserver_TwoNamespacesSameName(t *testing.T) {
pod1 := v1.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "p", Namespace: "one"},
Spec: v1.PodSpec{Containers: []v1.Container{{Image: "image/one"}}}}
pod2 := v1.Pod{
ObjectMeta: metav1.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 !apiequality.Semantic.DeepEqual(expected, update) {
t.Errorf("Expected %#v; Got %#v", expected, update)
}
}

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

@ -0,0 +1,169 @@
/*
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/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
utilyaml "k8s.io/apimachinery/pkg/util/yaml"
api "k8s.io/kubernetes/pkg/apis/core"
"k8s.io/kubernetes/pkg/apis/core/helper"
// TODO: remove this import if
// api.Registry.GroupOrDie(v1.GroupName).GroupVersion.String() is changed
// to "v1"?
"k8s.io/kubernetes/pkg/api/legacyscheme"
_ "k8s.io/kubernetes/pkg/apis/core/install"
k8s_api_v1 "k8s.io/kubernetes/pkg/apis/core/v1"
"k8s.io/kubernetes/pkg/apis/core/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 = metav1.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)
if isFile {
// Applying the default Taint tolerations to static pods,
// so they are not evicted when there are node problems.
helper.AddOrUpdateTolerationInPod(pod, &api.Toleration{
Operator: "Exists",
Effect: api.TaintEffectNoExecute,
})
}
// 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 = metav1.NamespaceDefault
}
selfLink = fmt.Sprintf("/api/"+legacyscheme.Registry.GroupOrDie(api.GroupName).GroupVersion.Version+"/namespaces/%s/pods/%s", namespace, name)
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(legacyscheme.Codecs.UniversalDecoder(), json)
if err != nil {
return false, pod, err
}
newPod, ok := obj.(*api.Pod)
// Check whether the object could be converted to single pod.
if !ok {
return false, pod, fmt.Errorf("invalid pod: %#v", obj)
}
// 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 {
return true, pod, fmt.Errorf("invalid pod: %v", errs)
}
v1Pod := &v1.Pod{}
if err := k8s_api_v1.Convert_core_Pod_To_v1_Pod(newPod, v1Pod, nil); err != nil {
glog.Errorf("Pod %q failed to convert to v1", newPod.Name)
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(legacyscheme.Codecs.UniversalDecoder(), data)
if err != nil {
return false, pods, err
}
newPods, ok := obj.(*api.PodList)
// Check whether the object could be converted to list of pods.
if !ok {
err = fmt.Errorf("invalid pods list: %#v", obj)
return false, pods, err
}
// 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 := k8s_api_v1.Convert_core_PodList_To_v1_PodList(newPods, v1Pods, nil); err != nil {
return true, pods, err
}
return true, *v1Pods, err
}

View File

@ -0,0 +1,191 @@
/*
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"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/api/testapi"
api "k8s.io/kubernetes/pkg/apis/core"
"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: metav1.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",
TerminationMessagePolicy: v1.TerminationMessageReadFile,
SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults(),
}},
SecurityContext: &v1.PodSecurityContext{},
SchedulerName: api.DefaultSchedulerName,
},
}
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 legacyscheme.Registry.EnabledVersionsForGroup(v1.GroupName) {
info, _ := runtime.SerializerInfoForMediaType(legacyscheme.Codecs.SupportedMediaTypes(), "application/yaml")
encoder := legacyscheme.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: metav1.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",
TerminationMessagePolicy: v1.TerminationMessageReadFile,
SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults(),
}},
SecurityContext: &v1.PodSecurityContext{},
SchedulerName: api.DefaultSchedulerName,
},
}
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 legacyscheme.Registry.EnabledVersionsForGroup(v1.GroupName) {
info, _ := runtime.SerializerInfoForMediaType(legacyscheme.Codecs.SupportedMediaTypes(), "application/yaml")
encoder := legacyscheme.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))
}
}
}
func TestGetSelfLink(t *testing.T) {
var testCases = []struct {
desc string
name string
namespace string
expectedSelfLink string
}{
{
desc: "No namespace specified",
name: "foo",
namespace: "",
expectedSelfLink: "/api/v1/namespaces/default/pods/foo",
},
{
desc: "Namespace specified",
name: "foo",
namespace: "bar",
expectedSelfLink: "/api/v1/namespaces/bar/pods/foo",
},
}
for _, testCase := range testCases {
selfLink := getSelfLink(testCase.name, testCase.namespace)
if testCase.expectedSelfLink != selfLink {
t.Errorf("%s: getSelfLink error, expected: %s, got: %s", testCase.desc, testCase.expectedSelfLink, selfLink)
}
}
}

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

@ -0,0 +1,508 @@
/*
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/api/core/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/client-go/tools/record"
"k8s.io/kubernetes/pkg/kubelet/checkpoint"
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
checkpointManager checkpoint.Manager
}
// 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()
}
// Restore restores pods from the checkpoint path, *once*
func (c *PodConfig) Restore(path string, updates chan<- interface{}) error {
var err error
if c.checkpointManager == nil {
c.checkpointManager = checkpoint.NewCheckpointManager(path)
pods, err := c.checkpointManager.LoadPods()
if err == nil {
updates <- kubetypes.PodUpdate{Pods: pods, Op: kubetypes.RESTORE, Source: kubetypes.ApiserverSource}
}
}
return err
}
// 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 uid to pod reference
pods map[string]map[types.UID]*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[types.UID]*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, restores := 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 len(restores.Pods) > 0 {
s.updates <- *restores
}
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, restores *kubetypes.PodUpdate) {
s.podLock.Lock()
defer s.podLock.Unlock()
addPods := []*v1.Pod{}
updatePods := []*v1.Pod{}
deletePods := []*v1.Pod{}
removePods := []*v1.Pod{}
reconcilePods := []*v1.Pod{}
restorePods := []*v1.Pod{}
pods := s.pods[source]
if pods == nil {
pods = make(map[types.UID]*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[types.UID]*v1.Pod) {
filtered := filterInvalidPods(newPods, source, s.recorder)
for _, ref := range filtered {
// 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[ref.UID]; found {
pods[ref.UID] = 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[ref.UID] = ref
addPods = append(addPods, ref)
}
}
update := change.(kubetypes.PodUpdate)
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 {
if existing, found := pods[value.UID]; found {
// this is a delete
delete(pods, value.UID)
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[types.UID]*v1.Pod)
updatePodsFunc(update.Pods, oldPods, pods)
for uid, existing := range oldPods {
if _, found := pods[uid]; !found {
// this is a delete
removePods = append(removePods, existing)
}
}
case kubetypes.RESTORE:
glog.V(4).Infof("Restoring pods for source %s", source)
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}
restores = &kubetypes.PodUpdate{Op: kubetypes.RESTORE, Pods: copyPods(restorePods), Source: source}
return adds, updates, deletes, removes, reconciles, restores
}
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 {
// Pods from each source are assumed to have passed validation individually.
// This function only checks if there is any naming conflict.
name := kubecontainer.GetPodFullName(pod)
if names.Has(name) {
glog.Warningf("Pod[%d] (%s) from %s failed validation due to duplicate pod name %q, ignoring", i+1, format.Pod(pod), source, pod.Name)
recorder.Eventf(pod, v1.EventTypeWarning, events.FailedValidation, "Error validating pod %s from %s due to duplicate pod name %q, ignoring", format.Pod(pod), source, pod.Name)
continue
} else {
names.Insert(name)
}
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 {
pods = append(pods, podRef.DeepCopy())
}
}
return pods
}
func copyPods(sourcePods []*v1.Pod) []*v1.Pod {
pods := []*v1.Pod{}
for _, source := range sourcePods {
// Use a deep copy here just in case
pods = append(pods, source.DeepCopy())
}
return pods
}

View File

@ -0,0 +1,415 @@
/*
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"
"k8s.io/api/core/v1"
apiequality "k8s.io/apimachinery/pkg/api/equality"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/tools/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: metav1.ObjectMeta{
UID: types.UID(name + namespace), // 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(),
TerminationMessagePolicy: v1.TerminationMessageReadFile,
},
},
},
}
}
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(scheme.Scheme, 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 !apiequality.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
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "")))
config.Sync()
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.SET, kubetypes.AllSource, CreateValidPod("foo", "")))
}
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: metav1.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, TerminationMessagePolicy: v1.TerminationMessageReadFile}}
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, TerminationMessagePolicy: v1.TerminationMessageReadFile}}
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, TerminationMessagePolicy: v1.TerminationMessageReadFile}}
podUpdate = CreatePodUpdate(kubetypes.ADD, TestSource, pod)
channel <- podUpdate
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.UPDATE, TestSource, pod))
podUpdate = CreatePodUpdate(kubetypes.REMOVE, TestSource, CreateValidPod("foo", "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, TerminationMessagePolicy: v1.TerminationMessageReadFile}}
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 := pod.DeepCopy()
podUpdate := CreatePodUpdate(kubetypes.SET, TestSource, CreateValidPod("foo1", "new"), clone, CreateValidPod("foo3", "new"))
channel <- podUpdate
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo1", "new"), pod, CreateValidPod("foo3", "new")))
pod.Annotations["kubernetes.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 := pod.DeepCopy()
podUpdate := CreatePodUpdate(kubetypes.SET, TestSource, clone)
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))
}

View File

@ -0,0 +1,26 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package config
const (
DefaultKubeletPodsDirName = "pods"
DefaultKubeletVolumesDirName = "volumes"
DefaultKubeletVolumeDevicesDirName = "volumeDevices"
DefaultKubeletPluginsDirName = "plugins"
DefaultKubeletContainersDirName = "containers"
DefaultKubeletPluginContainersDirName = "plugin-containers"
)

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"

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

@ -0,0 +1,205 @@
/*
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"
"strings"
"time"
"github.com/golang/glog"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/tools/cache"
api "k8s.io/kubernetes/pkg/apis/core"
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{}) {
// "golang.org/x/exp/inotify" requires a path without trailing "/"
path = strings.TrimRight(path, string(os.PathSeparator))
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 manifest 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 manifests 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.Errorf("Can't get metadata for %q: %v", path, err)
continue
}
switch {
case statInfo.Mode().IsDir():
glog.Errorf("Not recursing into manifest path %q", path)
case statInfo.Mode().IsRegular():
pod, err := s.extractFromFile(path)
if err != nil {
glog.Errorf("Can't process manifest file %q: %v", path, err)
} else {
pods = append(pods, pod)
}
default:
glog.Errorf("Manifest 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 manifest 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: couldn't parse as pod(%v), please check manifest file.\n", filename, 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,131 @@
// +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"
"path/filepath"
"strings"
"github.com/golang/glog"
"golang.org/x/exp/inotify"
"k8s.io/api/core/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 manifest files already existing when starting
if err := s.resetStoreFromPath(); err != nil {
return fmt.Errorf("unable to read manifest 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 {
// Ignore file start with dots
if strings.HasPrefix(filepath.Base(e.Name), ".") {
glog.V(4).Infof("Ignored pod manifest: %s, because it starts with dots", e.Name)
return nil
}
var eventType podEventType
switch {
case (e.Mask & inotify.IN_ISDIR) > 0:
glog.Errorf("Not recursing into manifest 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 manifest 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,431 @@
// +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"
"k8s.io/api/core/v1"
apiequality "k8s.io/apimachinery/pkg/api/equality"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/wait"
utiltesting "k8s.io/client-go/util/testing"
"k8s.io/kubernetes/pkg/api/testapi"
api "k8s.io/kubernetes/pkg/apis/core"
k8s_api_v1 "k8s.io/kubernetes/pkg/apis/core/v1"
"k8s.io/kubernetes/pkg/apis/core/validation"
kubetypes "k8s.io/kubernetes/pkg/kubelet/types"
"k8s.io/kubernetes/pkg/securitycontext"
)
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 !apiequality.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_manifest", 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 := k8s_api_v1.Convert_v1_Pod_To_core_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 !apiequality.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_manifest")
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 !apiequality.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: metav1.ObjectMeta{
Name: "test",
UID: "12345",
Namespace: "mynamespace",
},
Spec: v1.PodSpec{
Containers: []v1.Container{{Name: "image", Image: "test/image", SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults()}},
SecurityContext: &v1.PodSecurityContext{},
SchedulerName: api.DefaultSchedulerName,
},
Status: v1.PodStatus{
Phase: v1.PodPending,
},
},
expected: CreatePodUpdate(kubetypes.SET, kubetypes.FileSource, &v1.Pod{
ObjectMeta: metav1.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,
Tolerations: []v1.Toleration{{
Operator: "Exists",
Effect: "NoExecute",
}},
Containers: []v1.Container{{
Name: "image",
Image: "test/image",
TerminationMessagePath: "/dev/termination-log",
ImagePullPolicy: "Always",
SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults(),
TerminationMessagePolicy: v1.TerminationMessageReadFile,
}},
SecurityContext: &v1.PodSecurityContext{},
SchedulerName: api.DefaultSchedulerName,
},
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_manifest"
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_manifest"
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 := k8s_api_v1.Convert_v1_Pod_To_core_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 !apiequality.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")
}

109
vendor/k8s.io/kubernetes/pkg/kubelet/config/flags.go generated vendored Normal file
View File

@ -0,0 +1,109 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package config
import (
"github.com/spf13/pflag"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
type ContainerRuntimeOptions struct {
// General options.
// ContainerRuntime is the container runtime to use.
ContainerRuntime string
// RuntimeCgroups that container runtime is expected to be isolated in.
RuntimeCgroups string
// Docker-specific options.
// DockershimRootDirectory is the path to the dockershim root directory. Defaults to
// /var/lib/dockershim if unset. Exposed for integration testing (e.g. in OpenShift).
DockershimRootDirectory string
// Enable dockershim only mode.
ExperimentalDockershim bool
// This flag, if set, disables use of a shared PID namespace for pods running in the docker CRI runtime.
// A shared PID namespace is the only option in non-docker runtimes and is required by the CRI. The ability to
// disable it for docker will be removed unless a compelling use case is discovered with widespread use.
// TODO: Remove once we no longer support disabling shared PID namespace (https://issues.k8s.io/41938)
DockerDisableSharedPID bool
// PodSandboxImage is the image whose network/ipc namespaces
// containers in each pod will use.
PodSandboxImage string
// DockerEndpoint is the path to the docker endpoint to communicate with.
DockerEndpoint string
// If no pulling progress is made before the deadline imagePullProgressDeadline,
// the image pulling will be cancelled. Defaults to 1m0s.
// +optional
ImagePullProgressDeadline metav1.Duration
// Network plugin options.
// networkPluginName is the name of the network plugin to be invoked for
// various events in kubelet/pod lifecycle
NetworkPluginName string
// NetworkPluginMTU is the MTU to be passed to the network plugin,
// and overrides the default MTU for cases where it cannot be automatically
// computed (such as IPSEC).
NetworkPluginMTU int32
// CNIConfDir is the full path of the directory in which to search for
// CNI config files
CNIConfDir string
// CNIBinDir is the full path of the directory in which to search for
// CNI plugin binaries
CNIBinDir string
// rkt-specific options.
// rktPath is the path of rkt binary. Leave empty to use the first rkt in $PATH.
RktPath string
// rktApiEndpoint is the endpoint of the rkt API service to communicate with.
RktAPIEndpoint string
// rktStage1Image is the image to use as stage1. Local paths and
// http/https URLs are supported.
RktStage1Image string
}
func (s *ContainerRuntimeOptions) AddFlags(fs *pflag.FlagSet) {
// General settings.
fs.StringVar(&s.ContainerRuntime, "container-runtime", s.ContainerRuntime, "The container runtime to use. Possible values: 'docker', 'rkt'.")
fs.StringVar(&s.RuntimeCgroups, "runtime-cgroups", s.RuntimeCgroups, "Optional absolute name of cgroups to create and run the runtime in.")
// Docker-specific settings.
fs.BoolVar(&s.ExperimentalDockershim, "experimental-dockershim", s.ExperimentalDockershim, "Enable dockershim only mode. In this mode, kubelet will only start dockershim without any other functionalities. This flag only serves test purpose, please do not use it unless you are conscious of what you are doing. [default=false]")
fs.MarkHidden("experimental-dockershim")
fs.StringVar(&s.DockershimRootDirectory, "experimental-dockershim-root-directory", s.DockershimRootDirectory, "Path to the dockershim root directory.")
fs.MarkHidden("experimental-dockershim-root-directory")
fs.BoolVar(&s.DockerDisableSharedPID, "docker-disable-shared-pid", s.DockerDisableSharedPID, "The Container Runtime Interface (CRI) defaults to using a shared PID namespace for containers in a pod when running with Docker 1.13.1 or higher. Setting this flag reverts to the previous behavior of isolated PID namespaces. This ability will be removed in a future Kubernetes release.")
fs.StringVar(&s.PodSandboxImage, "pod-infra-container-image", s.PodSandboxImage, "The image whose network/ipc namespaces containers in each pod will use.")
fs.StringVar(&s.DockerEndpoint, "docker-endpoint", s.DockerEndpoint, "Use this for the docker endpoint to communicate with")
fs.DurationVar(&s.ImagePullProgressDeadline.Duration, "image-pull-progress-deadline", s.ImagePullProgressDeadline.Duration, "If no pulling progress is made before this deadline, the image pulling will be cancelled.")
// Network plugin settings. Shared by both docker and rkt.
fs.StringVar(&s.NetworkPluginName, "network-plugin", s.NetworkPluginName, "<Warning: Alpha feature> The name of the network plugin to be invoked for various events in kubelet/pod lifecycle")
fs.StringVar(&s.CNIConfDir, "cni-conf-dir", s.CNIConfDir, "<Warning: Alpha feature> The full path of the directory in which to search for CNI config files. Default: /etc/cni/net.d")
fs.StringVar(&s.CNIBinDir, "cni-bin-dir", s.CNIBinDir, "<Warning: Alpha feature> The full path of the directory in which to search for CNI plugin binaries. Default: /opt/cni/bin")
fs.Int32Var(&s.NetworkPluginMTU, "network-plugin-mtu", s.NetworkPluginMTU, "<Warning: Alpha feature> The MTU to be passed to the network plugin, to override the default. Set to 0 to use the default 1460 MTU.")
// Rkt-specific settings.
fs.StringVar(&s.RktPath, "rkt-path", s.RktPath, "Path of rkt binary. Leave empty to use the first rkt in $PATH. Only used if --container-runtime='rkt'.")
fs.StringVar(&s.RktAPIEndpoint, "rkt-api-endpoint", s.RktAPIEndpoint, "The endpoint of the rkt API service to communicate with. Only used if --container-runtime='rkt'.")
fs.StringVar(&s.RktStage1Image, "rkt-stage1-image", s.RktStage1Image, "image to use as stage1. Local paths and http/https URLs are supported. If empty, the 'stage1.aci' in the same directory as '--rkt-path' will be used.")
fs.MarkDeprecated("rkt-stage1-image", "Will be removed in a future version. The default stage1 image will be specified by the rkt configurations, see https://github.com/coreos/rkt/blob/master/Documentation/configuration.md for more details.")
}

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/api/core/v1"
"k8s.io/apimachinery/pkg/util/wait"
api "k8s.io/kubernetes/pkg/apis/core"
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,373 @@
/*
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"
"k8s.io/api/core/v1"
apiequality "k8s.io/apimachinery/pkg/api/equality"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
utiltesting "k8s.io/client-go/util/testing"
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/api/testapi"
api "k8s.io/kubernetes/pkg/apis/core"
k8s_api_v1 "k8s.io/kubernetes/pkg/apis/core/v1"
"k8s.io/kubernetes/pkg/apis/core/validation"
kubetypes "k8s.io/kubernetes/pkg/kubelet/types"
)
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: legacyscheme.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: legacyscheme.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: legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion.String()},
Spec: v1.PodSpec{
Containers: []v1.Container{{Name: ""}},
},
},
},
{
desc: "Invalid container name",
pod: &v1.Pod{
TypeMeta: metav1.TypeMeta{APIVersion: legacyscheme.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: metav1.ObjectMeta{
Name: "foo",
UID: "111",
Namespace: "mynamespace",
},
Spec: v1.PodSpec{
NodeName: string(nodeName),
Containers: []v1.Container{{Name: "1", Image: "foo", ImagePullPolicy: v1.PullAlways, TerminationMessagePolicy: v1.TerminationMessageReadFile}},
SecurityContext: &v1.PodSecurityContext{},
SchedulerName: api.DefaultSchedulerName,
},
Status: v1.PodStatus{
Phase: v1.PodPending,
},
},
expected: CreatePodUpdate(kubetypes.SET,
kubetypes.HTTPSource,
&v1.Pod{
ObjectMeta: metav1.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,
SchedulerName: api.DefaultSchedulerName,
Containers: []v1.Container{{
Name: "1",
Image: "foo",
TerminationMessagePath: "/dev/termination-log",
ImagePullPolicy: "Always",
TerminationMessagePolicy: v1.TerminationMessageReadFile,
}},
},
Status: v1.PodStatus{
Phase: v1.PodPending,
},
}),
},
{
desc: "Multiple pods",
pods: &v1.PodList{
TypeMeta: metav1.TypeMeta{
Kind: "PodList",
APIVersion: "",
},
Items: []v1.Pod{
{
ObjectMeta: metav1.ObjectMeta{
Name: "foo",
UID: "111",
},
Spec: v1.PodSpec{
NodeName: nodeName,
Containers: []v1.Container{{Name: "1", Image: "foo", ImagePullPolicy: v1.PullAlways, TerminationMessagePolicy: v1.TerminationMessageReadFile}},
SecurityContext: &v1.PodSecurityContext{},
SchedulerName: api.DefaultSchedulerName,
},
Status: v1.PodStatus{
Phase: v1.PodPending,
},
},
{
ObjectMeta: metav1.ObjectMeta{
Name: "bar",
UID: "222",
},
Spec: v1.PodSpec{
NodeName: nodeName,
Containers: []v1.Container{{Name: "2", Image: "bar:bartag", ImagePullPolicy: "", TerminationMessagePolicy: v1.TerminationMessageReadFile}},
SecurityContext: &v1.PodSecurityContext{},
SchedulerName: api.DefaultSchedulerName,
},
Status: v1.PodStatus{
Phase: v1.PodPending,
},
},
},
},
expected: CreatePodUpdate(kubetypes.SET,
kubetypes.HTTPSource,
&v1.Pod{
ObjectMeta: metav1.ObjectMeta{
UID: "111",
Name: "foo" + "-" + nodeName,
Namespace: "default",
Annotations: map[string]string{kubetypes.ConfigHashAnnotationKey: "111"},
SelfLink: getSelfLink("foo-"+nodeName, metav1.NamespaceDefault),
},
Spec: v1.PodSpec{
NodeName: nodeName,
RestartPolicy: v1.RestartPolicyAlways,
DNSPolicy: v1.DNSClusterFirst,
TerminationGracePeriodSeconds: &grace,
SecurityContext: &v1.PodSecurityContext{},
SchedulerName: api.DefaultSchedulerName,
Containers: []v1.Container{{
Name: "1",
Image: "foo",
TerminationMessagePath: "/dev/termination-log",
ImagePullPolicy: "Always",
TerminationMessagePolicy: v1.TerminationMessageReadFile,
}},
},
Status: v1.PodStatus{
Phase: v1.PodPending,
},
},
&v1.Pod{
ObjectMeta: metav1.ObjectMeta{
UID: "222",
Name: "bar" + "-" + nodeName,
Namespace: "default",
Annotations: map[string]string{kubetypes.ConfigHashAnnotationKey: "222"},
SelfLink: getSelfLink("bar-"+nodeName, metav1.NamespaceDefault),
},
Spec: v1.PodSpec{
NodeName: nodeName,
RestartPolicy: v1.RestartPolicyAlways,
DNSPolicy: v1.DNSClusterFirst,
TerminationGracePeriodSeconds: &grace,
SecurityContext: &v1.PodSecurityContext{},
SchedulerName: api.DefaultSchedulerName,
Containers: []v1.Container{{
Name: "2",
Image: "bar:bartag",
TerminationMessagePath: "/dev/termination-log",
ImagePullPolicy: "IfNotPresent",
TerminationMessagePolicy: v1.TerminationMessageReadFile,
}},
},
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 !apiequality.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 := k8s_api_v1.Convert_v1_Pod_To_core_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: legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion.String(),
Kind: "Pod",
},
ObjectMeta: metav1.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)
}