Switch to github.com/golang/dep for vendoring
Signed-off-by: Mrunal Patel <mrunalp@gmail.com>
This commit is contained in:
parent
d6ab91be27
commit
8e5b17cf13
15431 changed files with 3971413 additions and 8881 deletions
90
vendor/k8s.io/kubernetes/pkg/kubelet/config/BUILD
generated
vendored
Normal file
90
vendor/k8s.io/kubernetes/pkg/kubelet/config/BUILD
generated
vendored
Normal file
|
@ -0,0 +1,90 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"apiserver.go",
|
||||
"common.go",
|
||||
"config.go",
|
||||
"doc.go",
|
||||
"file.go",
|
||||
"file_linux.go",
|
||||
"http.go",
|
||||
"sources.go",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/api/v1:go_default_library",
|
||||
"//pkg/api/v1/pod:go_default_library",
|
||||
"//pkg/api/validation:go_default_library",
|
||||
"//pkg/client/cache:go_default_library",
|
||||
"//pkg/client/clientset_generated/clientset:go_default_library",
|
||||
"//pkg/client/record:go_default_library",
|
||||
"//pkg/fields:go_default_library",
|
||||
"//pkg/kubelet/container:go_default_library",
|
||||
"//pkg/kubelet/events:go_default_library",
|
||||
"//pkg/kubelet/types:go_default_library",
|
||||
"//pkg/kubelet/util/format:go_default_library",
|
||||
"//pkg/util/config:go_default_library",
|
||||
"//pkg/util/hash:go_default_library",
|
||||
"//vendor:github.com/golang/glog",
|
||||
"//vendor:golang.org/x/exp/inotify",
|
||||
"//vendor:k8s.io/apimachinery/pkg/runtime",
|
||||
"//vendor:k8s.io/apimachinery/pkg/types",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/sets",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/validation/field",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/wait",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/yaml",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"apiserver_test.go",
|
||||
"common_test.go",
|
||||
"config_test.go",
|
||||
"file_linux_test.go",
|
||||
"http_test.go",
|
||||
],
|
||||
library = ":go_default_library",
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/api/testapi:go_default_library",
|
||||
"//pkg/api/v1:go_default_library",
|
||||
"//pkg/api/validation:go_default_library",
|
||||
"//pkg/client/cache:go_default_library",
|
||||
"//pkg/client/record:go_default_library",
|
||||
"//pkg/kubelet/types:go_default_library",
|
||||
"//pkg/securitycontext:go_default_library",
|
||||
"//pkg/util/testing:go_default_library",
|
||||
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
|
||||
"//vendor:k8s.io/apimachinery/pkg/runtime",
|
||||
"//vendor:k8s.io/apimachinery/pkg/types",
|
||||
"//vendor:k8s.io/apimachinery/pkg/util/wait",
|
||||
"//vendor:k8s.io/apimachinery/pkg/watch",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
46
vendor/k8s.io/kubernetes/pkg/kubelet/config/apiserver.go
generated
vendored
Normal file
46
vendor/k8s.io/kubernetes/pkg/kubelet/config/apiserver.go
generated
vendored
Normal file
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Reads the pod configuration from the Kubernetes apiserver.
|
||||
package config
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/v1"
|
||||
"k8s.io/kubernetes/pkg/client/cache"
|
||||
"k8s.io/kubernetes/pkg/client/clientset_generated/clientset"
|
||||
"k8s.io/kubernetes/pkg/fields"
|
||||
kubetypes "k8s.io/kubernetes/pkg/kubelet/types"
|
||||
)
|
||||
|
||||
// NewSourceApiserver creates a config source that watches and pulls from the apiserver.
|
||||
func NewSourceApiserver(c *clientset.Clientset, nodeName types.NodeName, updates chan<- interface{}) {
|
||||
lw := cache.NewListWatchFromClient(c.Core().RESTClient(), "pods", v1.NamespaceAll, fields.OneTermEqualSelector(api.PodHostField, string(nodeName)))
|
||||
newSourceApiserverFromLW(lw, updates)
|
||||
}
|
||||
|
||||
// newSourceApiserverFromLW holds creates a config source that watches and pulls from the apiserver.
|
||||
func newSourceApiserverFromLW(lw cache.ListerWatcher, updates chan<- interface{}) {
|
||||
send := func(objs []interface{}) {
|
||||
var pods []*v1.Pod
|
||||
for _, o := range objs {
|
||||
pods = append(pods, o.(*v1.Pod))
|
||||
}
|
||||
updates <- kubetypes.PodUpdate{Pods: pods, Op: kubetypes.SET, Source: kubetypes.ApiserverSource}
|
||||
}
|
||||
cache.NewReflector(lw, &v1.Pod{}, cache.NewUndeltaStore(send, cache.MetaNamespaceKeyFunc), 0).Run()
|
||||
}
|
193
vendor/k8s.io/kubernetes/pkg/kubelet/config/apiserver_test.go
generated
vendored
Normal file
193
vendor/k8s.io/kubernetes/pkg/kubelet/config/apiserver_test.go
generated
vendored
Normal file
|
@ -0,0 +1,193 @@
|
|||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/v1"
|
||||
"k8s.io/kubernetes/pkg/client/cache"
|
||||
kubetypes "k8s.io/kubernetes/pkg/kubelet/types"
|
||||
)
|
||||
|
||||
type fakePodLW struct {
|
||||
listResp runtime.Object
|
||||
watchResp watch.Interface
|
||||
}
|
||||
|
||||
func (lw fakePodLW) List(options v1.ListOptions) (runtime.Object, error) {
|
||||
return lw.listResp, nil
|
||||
}
|
||||
|
||||
func (lw fakePodLW) Watch(options v1.ListOptions) (watch.Interface, error) {
|
||||
return lw.watchResp, nil
|
||||
}
|
||||
|
||||
var _ cache.ListerWatcher = fakePodLW{}
|
||||
|
||||
func TestNewSourceApiserver_UpdatesAndMultiplePods(t *testing.T) {
|
||||
pod1v1 := &v1.Pod{
|
||||
ObjectMeta: v1.ObjectMeta{Name: "p"},
|
||||
Spec: v1.PodSpec{Containers: []v1.Container{{Image: "image/one"}}}}
|
||||
pod1v2 := &v1.Pod{
|
||||
ObjectMeta: v1.ObjectMeta{Name: "p"},
|
||||
Spec: v1.PodSpec{Containers: []v1.Container{{Image: "image/two"}}}}
|
||||
pod2 := &v1.Pod{
|
||||
ObjectMeta: v1.ObjectMeta{Name: "q"},
|
||||
Spec: v1.PodSpec{Containers: []v1.Container{{Image: "image/blah"}}}}
|
||||
|
||||
// Setup fake api client.
|
||||
fakeWatch := watch.NewFake()
|
||||
lw := fakePodLW{
|
||||
listResp: &v1.PodList{Items: []v1.Pod{*pod1v1}},
|
||||
watchResp: fakeWatch,
|
||||
}
|
||||
|
||||
ch := make(chan interface{})
|
||||
|
||||
newSourceApiserverFromLW(lw, ch)
|
||||
|
||||
got, ok := <-ch
|
||||
if !ok {
|
||||
t.Errorf("Unable to read from channel when expected")
|
||||
}
|
||||
update := got.(kubetypes.PodUpdate)
|
||||
expected := CreatePodUpdate(kubetypes.SET, kubetypes.ApiserverSource, pod1v1)
|
||||
if !api.Semantic.DeepEqual(expected, update) {
|
||||
t.Errorf("Expected %#v; Got %#v", expected, update)
|
||||
}
|
||||
|
||||
// Add another pod
|
||||
fakeWatch.Add(pod2)
|
||||
got, ok = <-ch
|
||||
if !ok {
|
||||
t.Errorf("Unable to read from channel when expected")
|
||||
}
|
||||
update = got.(kubetypes.PodUpdate)
|
||||
// Could be sorted either of these two ways:
|
||||
expectedA := CreatePodUpdate(kubetypes.SET, kubetypes.ApiserverSource, pod1v1, pod2)
|
||||
expectedB := CreatePodUpdate(kubetypes.SET, kubetypes.ApiserverSource, pod2, pod1v1)
|
||||
|
||||
if !api.Semantic.DeepEqual(expectedA, update) && !api.Semantic.DeepEqual(expectedB, update) {
|
||||
t.Errorf("Expected %#v or %#v, Got %#v", expectedA, expectedB, update)
|
||||
}
|
||||
|
||||
// Modify pod1
|
||||
fakeWatch.Modify(pod1v2)
|
||||
got, ok = <-ch
|
||||
if !ok {
|
||||
t.Errorf("Unable to read from channel when expected")
|
||||
}
|
||||
update = got.(kubetypes.PodUpdate)
|
||||
expectedA = CreatePodUpdate(kubetypes.SET, kubetypes.ApiserverSource, pod1v2, pod2)
|
||||
expectedB = CreatePodUpdate(kubetypes.SET, kubetypes.ApiserverSource, pod2, pod1v2)
|
||||
|
||||
if !api.Semantic.DeepEqual(expectedA, update) && !api.Semantic.DeepEqual(expectedB, update) {
|
||||
t.Errorf("Expected %#v or %#v, Got %#v", expectedA, expectedB, update)
|
||||
}
|
||||
|
||||
// Delete pod1
|
||||
fakeWatch.Delete(pod1v2)
|
||||
got, ok = <-ch
|
||||
if !ok {
|
||||
t.Errorf("Unable to read from channel when expected")
|
||||
}
|
||||
update = got.(kubetypes.PodUpdate)
|
||||
expected = CreatePodUpdate(kubetypes.SET, kubetypes.ApiserverSource, pod2)
|
||||
if !api.Semantic.DeepEqual(expected, update) {
|
||||
t.Errorf("Expected %#v, Got %#v", expected, update)
|
||||
}
|
||||
|
||||
// Delete pod2
|
||||
fakeWatch.Delete(pod2)
|
||||
got, ok = <-ch
|
||||
if !ok {
|
||||
t.Errorf("Unable to read from channel when expected")
|
||||
}
|
||||
update = got.(kubetypes.PodUpdate)
|
||||
expected = CreatePodUpdate(kubetypes.SET, kubetypes.ApiserverSource)
|
||||
if !api.Semantic.DeepEqual(expected, update) {
|
||||
t.Errorf("Expected %#v, Got %#v", expected, update)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewSourceApiserver_TwoNamespacesSameName(t *testing.T) {
|
||||
pod1 := v1.Pod{
|
||||
ObjectMeta: v1.ObjectMeta{Name: "p", Namespace: "one"},
|
||||
Spec: v1.PodSpec{Containers: []v1.Container{{Image: "image/one"}}}}
|
||||
pod2 := v1.Pod{
|
||||
ObjectMeta: v1.ObjectMeta{Name: "p", Namespace: "two"},
|
||||
Spec: v1.PodSpec{Containers: []v1.Container{{Image: "image/blah"}}}}
|
||||
|
||||
// Setup fake api client.
|
||||
fakeWatch := watch.NewFake()
|
||||
lw := fakePodLW{
|
||||
listResp: &v1.PodList{Items: []v1.Pod{pod1, pod2}},
|
||||
watchResp: fakeWatch,
|
||||
}
|
||||
|
||||
ch := make(chan interface{})
|
||||
|
||||
newSourceApiserverFromLW(lw, ch)
|
||||
|
||||
got, ok := <-ch
|
||||
if !ok {
|
||||
t.Errorf("Unable to read from channel when expected")
|
||||
}
|
||||
update := got.(kubetypes.PodUpdate)
|
||||
// Make sure that we get both pods. Catches bug #2294.
|
||||
if !(len(update.Pods) == 2) {
|
||||
t.Errorf("Expected %d, Got %d", 2, len(update.Pods))
|
||||
}
|
||||
|
||||
// Delete pod1
|
||||
fakeWatch.Delete(&pod1)
|
||||
got, ok = <-ch
|
||||
if !ok {
|
||||
t.Errorf("Unable to read from channel when expected")
|
||||
}
|
||||
update = got.(kubetypes.PodUpdate)
|
||||
if !(len(update.Pods) == 1) {
|
||||
t.Errorf("Expected %d, Got %d", 1, len(update.Pods))
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewSourceApiserverInitialEmptySendsEmptyPodUpdate(t *testing.T) {
|
||||
// Setup fake api client.
|
||||
fakeWatch := watch.NewFake()
|
||||
lw := fakePodLW{
|
||||
listResp: &v1.PodList{Items: []v1.Pod{}},
|
||||
watchResp: fakeWatch,
|
||||
}
|
||||
|
||||
ch := make(chan interface{})
|
||||
|
||||
newSourceApiserverFromLW(lw, ch)
|
||||
|
||||
got, ok := <-ch
|
||||
if !ok {
|
||||
t.Errorf("Unable to read from channel when expected")
|
||||
}
|
||||
update := got.(kubetypes.PodUpdate)
|
||||
expected := CreatePodUpdate(kubetypes.SET, kubetypes.ApiserverSource)
|
||||
if !api.Semantic.DeepEqual(expected, update) {
|
||||
t.Errorf("Expected %#v; Got %#v", expected, update)
|
||||
}
|
||||
}
|
149
vendor/k8s.io/kubernetes/pkg/kubelet/config/common.go
generated
vendored
Normal file
149
vendor/k8s.io/kubernetes/pkg/kubelet/config/common.go
generated
vendored
Normal file
|
@ -0,0 +1,149 @@
|
|||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Common logic used by both http and file channels.
|
||||
package config
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
utilyaml "k8s.io/apimachinery/pkg/util/yaml"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/v1"
|
||||
"k8s.io/kubernetes/pkg/api/validation"
|
||||
kubetypes "k8s.io/kubernetes/pkg/kubelet/types"
|
||||
"k8s.io/kubernetes/pkg/util/hash"
|
||||
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
// Generate a pod name that is unique among nodes by appending the nodeName.
|
||||
func generatePodName(name string, nodeName types.NodeName) string {
|
||||
return fmt.Sprintf("%s-%s", name, nodeName)
|
||||
}
|
||||
|
||||
func applyDefaults(pod *api.Pod, source string, isFile bool, nodeName types.NodeName) error {
|
||||
if len(pod.UID) == 0 {
|
||||
hasher := md5.New()
|
||||
if isFile {
|
||||
fmt.Fprintf(hasher, "host:%s", nodeName)
|
||||
fmt.Fprintf(hasher, "file:%s", source)
|
||||
} else {
|
||||
fmt.Fprintf(hasher, "url:%s", source)
|
||||
}
|
||||
hash.DeepHashObject(hasher, pod)
|
||||
pod.UID = types.UID(hex.EncodeToString(hasher.Sum(nil)[0:]))
|
||||
glog.V(5).Infof("Generated UID %q pod %q from %s", pod.UID, pod.Name, source)
|
||||
}
|
||||
|
||||
pod.Name = generatePodName(pod.Name, nodeName)
|
||||
glog.V(5).Infof("Generated Name %q for UID %q from URL %s", pod.Name, pod.UID, source)
|
||||
|
||||
if pod.Namespace == "" {
|
||||
pod.Namespace = kubetypes.NamespaceDefault
|
||||
}
|
||||
glog.V(5).Infof("Using namespace %q for pod %q from %s", pod.Namespace, pod.Name, source)
|
||||
|
||||
// Set the Host field to indicate this pod is scheduled on the current node.
|
||||
pod.Spec.NodeName = string(nodeName)
|
||||
|
||||
pod.ObjectMeta.SelfLink = getSelfLink(pod.Name, pod.Namespace)
|
||||
|
||||
if pod.Annotations == nil {
|
||||
pod.Annotations = make(map[string]string)
|
||||
}
|
||||
// The generated UID is the hash of the file.
|
||||
pod.Annotations[kubetypes.ConfigHashAnnotationKey] = string(pod.UID)
|
||||
|
||||
// Set the default status to pending.
|
||||
pod.Status.Phase = api.PodPending
|
||||
return nil
|
||||
}
|
||||
|
||||
func getSelfLink(name, namespace string) string {
|
||||
var selfLink string
|
||||
if len(namespace) == 0 {
|
||||
namespace = api.NamespaceDefault
|
||||
}
|
||||
selfLink = fmt.Sprintf("/api/"+api.Registry.GroupOrDie(api.GroupName).GroupVersion.Version+"/pods/namespaces/%s/%s", name, namespace)
|
||||
return selfLink
|
||||
}
|
||||
|
||||
type defaultFunc func(pod *api.Pod) error
|
||||
|
||||
func tryDecodeSinglePod(data []byte, defaultFn defaultFunc) (parsed bool, pod *v1.Pod, err error) {
|
||||
// JSON is valid YAML, so this should work for everything.
|
||||
json, err := utilyaml.ToJSON(data)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
obj, err := runtime.Decode(api.Codecs.UniversalDecoder(), json)
|
||||
if err != nil {
|
||||
return false, pod, err
|
||||
}
|
||||
// Check whether the object could be converted to single pod.
|
||||
if _, ok := obj.(*api.Pod); !ok {
|
||||
err = fmt.Errorf("invalid pod: %#v", obj)
|
||||
return false, pod, err
|
||||
}
|
||||
newPod := obj.(*api.Pod)
|
||||
// Apply default values and validate the pod.
|
||||
if err = defaultFn(newPod); err != nil {
|
||||
return true, pod, err
|
||||
}
|
||||
if errs := validation.ValidatePod(newPod); len(errs) > 0 {
|
||||
err = fmt.Errorf("invalid pod: %v", errs)
|
||||
return true, pod, err
|
||||
}
|
||||
v1Pod := &v1.Pod{}
|
||||
if err := v1.Convert_api_Pod_To_v1_Pod(newPod, v1Pod, nil); err != nil {
|
||||
return true, nil, err
|
||||
}
|
||||
return true, v1Pod, nil
|
||||
}
|
||||
|
||||
func tryDecodePodList(data []byte, defaultFn defaultFunc) (parsed bool, pods v1.PodList, err error) {
|
||||
obj, err := runtime.Decode(api.Codecs.UniversalDecoder(), data)
|
||||
if err != nil {
|
||||
return false, pods, err
|
||||
}
|
||||
// Check whether the object could be converted to list of pods.
|
||||
if _, ok := obj.(*api.PodList); !ok {
|
||||
err = fmt.Errorf("invalid pods list: %#v", obj)
|
||||
return false, pods, err
|
||||
}
|
||||
newPods := obj.(*api.PodList)
|
||||
// Apply default values and validate pods.
|
||||
for i := range newPods.Items {
|
||||
newPod := &newPods.Items[i]
|
||||
if err = defaultFn(newPod); err != nil {
|
||||
return true, pods, err
|
||||
}
|
||||
if errs := validation.ValidatePod(newPod); len(errs) > 0 {
|
||||
err = fmt.Errorf("invalid pod: %v", errs)
|
||||
return true, pods, err
|
||||
}
|
||||
}
|
||||
v1Pods := &v1.PodList{}
|
||||
if err := v1.Convert_api_PodList_To_v1_PodList(newPods, v1Pods, nil); err != nil {
|
||||
return true, pods, err
|
||||
}
|
||||
return true, *v1Pods, err
|
||||
}
|
157
vendor/k8s.io/kubernetes/pkg/kubelet/config/common_test.go
generated
vendored
Normal file
157
vendor/k8s.io/kubernetes/pkg/kubelet/config/common_test.go
generated
vendored
Normal file
|
@ -0,0 +1,157 @@
|
|||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/testapi"
|
||||
"k8s.io/kubernetes/pkg/api/v1"
|
||||
"k8s.io/kubernetes/pkg/securitycontext"
|
||||
)
|
||||
|
||||
func noDefault(*api.Pod) error { return nil }
|
||||
|
||||
func TestDecodeSinglePod(t *testing.T) {
|
||||
grace := int64(30)
|
||||
pod := &v1.Pod{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "",
|
||||
},
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "test",
|
||||
UID: "12345",
|
||||
Namespace: "mynamespace",
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
RestartPolicy: v1.RestartPolicyAlways,
|
||||
DNSPolicy: v1.DNSClusterFirst,
|
||||
TerminationGracePeriodSeconds: &grace,
|
||||
Containers: []v1.Container{{
|
||||
Name: "image",
|
||||
Image: "test/image",
|
||||
ImagePullPolicy: "IfNotPresent",
|
||||
TerminationMessagePath: "/dev/termination-log",
|
||||
SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults(),
|
||||
}},
|
||||
SecurityContext: &v1.PodSecurityContext{},
|
||||
},
|
||||
}
|
||||
json, err := runtime.Encode(testapi.Default.Codec(), pod)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
parsed, podOut, err := tryDecodeSinglePod(json, noDefault)
|
||||
if !parsed {
|
||||
t.Errorf("expected to have parsed file: (%s)", string(json))
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v (%s)", err, string(json))
|
||||
}
|
||||
if !reflect.DeepEqual(pod, podOut) {
|
||||
t.Errorf("expected:\n%#v\ngot:\n%#v\n%s", pod, podOut, string(json))
|
||||
}
|
||||
|
||||
for _, gv := range api.Registry.EnabledVersionsForGroup(v1.GroupName) {
|
||||
info, _ := runtime.SerializerInfoForMediaType(api.Codecs.SupportedMediaTypes(), "application/yaml")
|
||||
encoder := api.Codecs.EncoderForVersion(info.Serializer, gv)
|
||||
yaml, err := runtime.Encode(encoder, pod)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
parsed, podOut, err = tryDecodeSinglePod(yaml, noDefault)
|
||||
if !parsed {
|
||||
t.Errorf("expected to have parsed file: (%s)", string(yaml))
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v (%s)", err, string(yaml))
|
||||
}
|
||||
if !reflect.DeepEqual(pod, podOut) {
|
||||
t.Errorf("expected:\n%#v\ngot:\n%#v\n%s", pod, podOut, string(yaml))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodePodList(t *testing.T) {
|
||||
grace := int64(30)
|
||||
pod := &v1.Pod{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "",
|
||||
},
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "test",
|
||||
UID: "12345",
|
||||
Namespace: "mynamespace",
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
RestartPolicy: v1.RestartPolicyAlways,
|
||||
DNSPolicy: v1.DNSClusterFirst,
|
||||
TerminationGracePeriodSeconds: &grace,
|
||||
Containers: []v1.Container{{
|
||||
Name: "image",
|
||||
Image: "test/image",
|
||||
ImagePullPolicy: "IfNotPresent",
|
||||
TerminationMessagePath: "/dev/termination-log",
|
||||
SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults(),
|
||||
}},
|
||||
SecurityContext: &v1.PodSecurityContext{},
|
||||
},
|
||||
}
|
||||
podList := &v1.PodList{
|
||||
Items: []v1.Pod{*pod},
|
||||
}
|
||||
json, err := runtime.Encode(testapi.Default.Codec(), podList)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
parsed, podListOut, err := tryDecodePodList(json, noDefault)
|
||||
if !parsed {
|
||||
t.Errorf("expected to have parsed file: (%s)", string(json))
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v (%s)", err, string(json))
|
||||
}
|
||||
if !reflect.DeepEqual(podList, &podListOut) {
|
||||
t.Errorf("expected:\n%#v\ngot:\n%#v\n%s", podList, &podListOut, string(json))
|
||||
}
|
||||
|
||||
for _, gv := range api.Registry.EnabledVersionsForGroup(v1.GroupName) {
|
||||
info, _ := runtime.SerializerInfoForMediaType(api.Codecs.SupportedMediaTypes(), "application/yaml")
|
||||
encoder := api.Codecs.EncoderForVersion(info.Serializer, gv)
|
||||
yaml, err := runtime.Encode(encoder, podList)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
parsed, podListOut, err = tryDecodePodList(yaml, noDefault)
|
||||
if !parsed {
|
||||
t.Errorf("expected to have parsed file: (%s): %v", string(yaml), err)
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v (%s)", err, string(yaml))
|
||||
continue
|
||||
}
|
||||
if !reflect.DeepEqual(podList, &podListOut) {
|
||||
t.Errorf("expected:\n%#v\ngot:\n%#v\n%s", pod, &podListOut, string(yaml))
|
||||
}
|
||||
}
|
||||
}
|
541
vendor/k8s.io/kubernetes/pkg/kubelet/config/config.go
generated
vendored
Normal file
541
vendor/k8s.io/kubernetes/pkg/kubelet/config/config.go
generated
vendored
Normal file
|
@ -0,0 +1,541 @@
|
|||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sync"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/v1"
|
||||
podutil "k8s.io/kubernetes/pkg/api/v1/pod"
|
||||
"k8s.io/kubernetes/pkg/api/validation"
|
||||
"k8s.io/kubernetes/pkg/client/record"
|
||||
kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
|
||||
"k8s.io/kubernetes/pkg/kubelet/events"
|
||||
kubetypes "k8s.io/kubernetes/pkg/kubelet/types"
|
||||
"k8s.io/kubernetes/pkg/kubelet/util/format"
|
||||
"k8s.io/kubernetes/pkg/util/config"
|
||||
)
|
||||
|
||||
// PodConfigNotificationMode describes how changes are sent to the update channel.
|
||||
type PodConfigNotificationMode int
|
||||
|
||||
const (
|
||||
// PodConfigNotificationUnknown is the default value for
|
||||
// PodConfigNotificationMode when uninitialized.
|
||||
PodConfigNotificationUnknown = iota
|
||||
// PodConfigNotificationSnapshot delivers the full configuration as a SET whenever
|
||||
// any change occurs.
|
||||
PodConfigNotificationSnapshot
|
||||
// PodConfigNotificationSnapshotAndUpdates delivers an UPDATE and DELETE message whenever pods are
|
||||
// changed, and a SET message if there are any additions or removals.
|
||||
PodConfigNotificationSnapshotAndUpdates
|
||||
// PodConfigNotificationIncremental delivers ADD, UPDATE, DELETE, REMOVE, RECONCILE to the update channel.
|
||||
PodConfigNotificationIncremental
|
||||
)
|
||||
|
||||
// PodConfig is a configuration mux that merges many sources of pod configuration into a single
|
||||
// consistent structure, and then delivers incremental change notifications to listeners
|
||||
// in order.
|
||||
type PodConfig struct {
|
||||
pods *podStorage
|
||||
mux *config.Mux
|
||||
|
||||
// the channel of denormalized changes passed to listeners
|
||||
updates chan kubetypes.PodUpdate
|
||||
|
||||
// contains the list of all configured sources
|
||||
sourcesLock sync.Mutex
|
||||
sources sets.String
|
||||
}
|
||||
|
||||
// NewPodConfig creates an object that can merge many configuration sources into a stream
|
||||
// of normalized updates to a pod configuration.
|
||||
func NewPodConfig(mode PodConfigNotificationMode, recorder record.EventRecorder) *PodConfig {
|
||||
updates := make(chan kubetypes.PodUpdate, 50)
|
||||
storage := newPodStorage(updates, mode, recorder)
|
||||
podConfig := &PodConfig{
|
||||
pods: storage,
|
||||
mux: config.NewMux(storage),
|
||||
updates: updates,
|
||||
sources: sets.String{},
|
||||
}
|
||||
return podConfig
|
||||
}
|
||||
|
||||
// Channel creates or returns a config source channel. The channel
|
||||
// only accepts PodUpdates
|
||||
func (c *PodConfig) Channel(source string) chan<- interface{} {
|
||||
c.sourcesLock.Lock()
|
||||
defer c.sourcesLock.Unlock()
|
||||
c.sources.Insert(source)
|
||||
return c.mux.Channel(source)
|
||||
}
|
||||
|
||||
// SeenAllSources returns true if seenSources contains all sources in the
|
||||
// config, and also this config has received a SET message from each source.
|
||||
func (c *PodConfig) SeenAllSources(seenSources sets.String) bool {
|
||||
if c.pods == nil {
|
||||
return false
|
||||
}
|
||||
glog.V(6).Infof("Looking for %v, have seen %v", c.sources.List(), seenSources)
|
||||
return seenSources.HasAll(c.sources.List()...) && c.pods.seenSources(c.sources.List()...)
|
||||
}
|
||||
|
||||
// Updates returns a channel of updates to the configuration, properly denormalized.
|
||||
func (c *PodConfig) Updates() <-chan kubetypes.PodUpdate {
|
||||
return c.updates
|
||||
}
|
||||
|
||||
// Sync requests the full configuration be delivered to the update channel.
|
||||
func (c *PodConfig) Sync() {
|
||||
c.pods.Sync()
|
||||
}
|
||||
|
||||
// podStorage manages the current pod state at any point in time and ensures updates
|
||||
// to the channel are delivered in order. Note that this object is an in-memory source of
|
||||
// "truth" and on creation contains zero entries. Once all previously read sources are
|
||||
// available, then this object should be considered authoritative.
|
||||
type podStorage struct {
|
||||
podLock sync.RWMutex
|
||||
// map of source name to pod name to pod reference
|
||||
pods map[string]map[string]*v1.Pod
|
||||
mode PodConfigNotificationMode
|
||||
|
||||
// ensures that updates are delivered in strict order
|
||||
// on the updates channel
|
||||
updateLock sync.Mutex
|
||||
updates chan<- kubetypes.PodUpdate
|
||||
|
||||
// contains the set of all sources that have sent at least one SET
|
||||
sourcesSeenLock sync.RWMutex
|
||||
sourcesSeen sets.String
|
||||
|
||||
// the EventRecorder to use
|
||||
recorder record.EventRecorder
|
||||
}
|
||||
|
||||
// TODO: PodConfigNotificationMode could be handled by a listener to the updates channel
|
||||
// in the future, especially with multiple listeners.
|
||||
// TODO: allow initialization of the current state of the store with snapshotted version.
|
||||
func newPodStorage(updates chan<- kubetypes.PodUpdate, mode PodConfigNotificationMode, recorder record.EventRecorder) *podStorage {
|
||||
return &podStorage{
|
||||
pods: make(map[string]map[string]*v1.Pod),
|
||||
mode: mode,
|
||||
updates: updates,
|
||||
sourcesSeen: sets.String{},
|
||||
recorder: recorder,
|
||||
}
|
||||
}
|
||||
|
||||
// Merge normalizes a set of incoming changes from different sources into a map of all Pods
|
||||
// and ensures that redundant changes are filtered out, and then pushes zero or more minimal
|
||||
// updates onto the update channel. Ensures that updates are delivered in order.
|
||||
func (s *podStorage) Merge(source string, change interface{}) error {
|
||||
s.updateLock.Lock()
|
||||
defer s.updateLock.Unlock()
|
||||
|
||||
seenBefore := s.sourcesSeen.Has(source)
|
||||
adds, updates, deletes, removes, reconciles := s.merge(source, change)
|
||||
firstSet := !seenBefore && s.sourcesSeen.Has(source)
|
||||
|
||||
// deliver update notifications
|
||||
switch s.mode {
|
||||
case PodConfigNotificationIncremental:
|
||||
if len(removes.Pods) > 0 {
|
||||
s.updates <- *removes
|
||||
}
|
||||
if len(adds.Pods) > 0 {
|
||||
s.updates <- *adds
|
||||
}
|
||||
if len(updates.Pods) > 0 {
|
||||
s.updates <- *updates
|
||||
}
|
||||
if len(deletes.Pods) > 0 {
|
||||
s.updates <- *deletes
|
||||
}
|
||||
if firstSet && len(adds.Pods) == 0 && len(updates.Pods) == 0 && len(deletes.Pods) == 0 {
|
||||
// Send an empty update when first seeing the source and there are
|
||||
// no ADD or UPDATE or DELETE pods from the source. This signals kubelet that
|
||||
// the source is ready.
|
||||
s.updates <- *adds
|
||||
}
|
||||
// Only add reconcile support here, because kubelet doesn't support Snapshot update now.
|
||||
if len(reconciles.Pods) > 0 {
|
||||
s.updates <- *reconciles
|
||||
}
|
||||
|
||||
case PodConfigNotificationSnapshotAndUpdates:
|
||||
if len(removes.Pods) > 0 || len(adds.Pods) > 0 || firstSet {
|
||||
s.updates <- kubetypes.PodUpdate{Pods: s.MergedState().([]*v1.Pod), Op: kubetypes.SET, Source: source}
|
||||
}
|
||||
if len(updates.Pods) > 0 {
|
||||
s.updates <- *updates
|
||||
}
|
||||
if len(deletes.Pods) > 0 {
|
||||
s.updates <- *deletes
|
||||
}
|
||||
|
||||
case PodConfigNotificationSnapshot:
|
||||
if len(updates.Pods) > 0 || len(deletes.Pods) > 0 || len(adds.Pods) > 0 || len(removes.Pods) > 0 || firstSet {
|
||||
s.updates <- kubetypes.PodUpdate{Pods: s.MergedState().([]*v1.Pod), Op: kubetypes.SET, Source: source}
|
||||
}
|
||||
|
||||
case PodConfigNotificationUnknown:
|
||||
fallthrough
|
||||
default:
|
||||
panic(fmt.Sprintf("unsupported PodConfigNotificationMode: %#v", s.mode))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *podStorage) merge(source string, change interface{}) (adds, updates, deletes, removes, reconciles *kubetypes.PodUpdate) {
|
||||
s.podLock.Lock()
|
||||
defer s.podLock.Unlock()
|
||||
|
||||
addPods := []*v1.Pod{}
|
||||
updatePods := []*v1.Pod{}
|
||||
deletePods := []*v1.Pod{}
|
||||
removePods := []*v1.Pod{}
|
||||
reconcilePods := []*v1.Pod{}
|
||||
|
||||
pods := s.pods[source]
|
||||
if pods == nil {
|
||||
pods = make(map[string]*v1.Pod)
|
||||
}
|
||||
|
||||
// updatePodFunc is the local function which updates the pod cache *oldPods* with new pods *newPods*.
|
||||
// After updated, new pod will be stored in the pod cache *pods*.
|
||||
// Notice that *pods* and *oldPods* could be the same cache.
|
||||
updatePodsFunc := func(newPods []*v1.Pod, oldPods, pods map[string]*v1.Pod) {
|
||||
filtered := filterInvalidPods(newPods, source, s.recorder)
|
||||
for _, ref := range filtered {
|
||||
name := kubecontainer.GetPodFullName(ref)
|
||||
// Annotate the pod with the source before any comparison.
|
||||
if ref.Annotations == nil {
|
||||
ref.Annotations = make(map[string]string)
|
||||
}
|
||||
ref.Annotations[kubetypes.ConfigSourceAnnotationKey] = source
|
||||
if existing, found := oldPods[name]; found {
|
||||
pods[name] = existing
|
||||
needUpdate, needReconcile, needGracefulDelete := checkAndUpdatePod(existing, ref)
|
||||
if needUpdate {
|
||||
updatePods = append(updatePods, existing)
|
||||
} else if needReconcile {
|
||||
reconcilePods = append(reconcilePods, existing)
|
||||
} else if needGracefulDelete {
|
||||
deletePods = append(deletePods, existing)
|
||||
}
|
||||
continue
|
||||
}
|
||||
recordFirstSeenTime(ref)
|
||||
pods[name] = ref
|
||||
addPods = append(addPods, ref)
|
||||
}
|
||||
}
|
||||
|
||||
update := change.(kubetypes.PodUpdate)
|
||||
// The InitContainers and InitContainerStatuses fields are lost during
|
||||
// serialization and deserialization. They are conveyed via Annotations.
|
||||
// Setting these fields here so that kubelet doesn't have to check for
|
||||
// annotations.
|
||||
if source == kubetypes.ApiserverSource {
|
||||
for _, pod := range update.Pods {
|
||||
if err := podutil.SetInitContainersAndStatuses(pod); err != nil {
|
||||
glog.Error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
switch update.Op {
|
||||
case kubetypes.ADD, kubetypes.UPDATE, kubetypes.DELETE:
|
||||
if update.Op == kubetypes.ADD {
|
||||
glog.V(4).Infof("Adding new pods from source %s : %v", source, update.Pods)
|
||||
} else if update.Op == kubetypes.DELETE {
|
||||
glog.V(4).Infof("Graceful deleting pods from source %s : %v", source, update.Pods)
|
||||
} else {
|
||||
glog.V(4).Infof("Updating pods from source %s : %v", source, update.Pods)
|
||||
}
|
||||
updatePodsFunc(update.Pods, pods, pods)
|
||||
|
||||
case kubetypes.REMOVE:
|
||||
glog.V(4).Infof("Removing pods from source %s : %v", source, update.Pods)
|
||||
for _, value := range update.Pods {
|
||||
name := kubecontainer.GetPodFullName(value)
|
||||
if existing, found := pods[name]; found {
|
||||
// this is a delete
|
||||
delete(pods, name)
|
||||
removePods = append(removePods, existing)
|
||||
continue
|
||||
}
|
||||
// this is a no-op
|
||||
}
|
||||
|
||||
case kubetypes.SET:
|
||||
glog.V(4).Infof("Setting pods for source %s", source)
|
||||
s.markSourceSet(source)
|
||||
// Clear the old map entries by just creating a new map
|
||||
oldPods := pods
|
||||
pods = make(map[string]*v1.Pod)
|
||||
updatePodsFunc(update.Pods, oldPods, pods)
|
||||
for name, existing := range oldPods {
|
||||
if _, found := pods[name]; !found {
|
||||
// this is a delete
|
||||
removePods = append(removePods, existing)
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
glog.Warningf("Received invalid update type: %v", update)
|
||||
|
||||
}
|
||||
|
||||
s.pods[source] = pods
|
||||
|
||||
adds = &kubetypes.PodUpdate{Op: kubetypes.ADD, Pods: copyPods(addPods), Source: source}
|
||||
updates = &kubetypes.PodUpdate{Op: kubetypes.UPDATE, Pods: copyPods(updatePods), Source: source}
|
||||
deletes = &kubetypes.PodUpdate{Op: kubetypes.DELETE, Pods: copyPods(deletePods), Source: source}
|
||||
removes = &kubetypes.PodUpdate{Op: kubetypes.REMOVE, Pods: copyPods(removePods), Source: source}
|
||||
reconciles = &kubetypes.PodUpdate{Op: kubetypes.RECONCILE, Pods: copyPods(reconcilePods), Source: source}
|
||||
|
||||
return adds, updates, deletes, removes, reconciles
|
||||
}
|
||||
|
||||
func (s *podStorage) markSourceSet(source string) {
|
||||
s.sourcesSeenLock.Lock()
|
||||
defer s.sourcesSeenLock.Unlock()
|
||||
s.sourcesSeen.Insert(source)
|
||||
}
|
||||
|
||||
func (s *podStorage) seenSources(sources ...string) bool {
|
||||
s.sourcesSeenLock.RLock()
|
||||
defer s.sourcesSeenLock.RUnlock()
|
||||
return s.sourcesSeen.HasAll(sources...)
|
||||
}
|
||||
|
||||
func filterInvalidPods(pods []*v1.Pod, source string, recorder record.EventRecorder) (filtered []*v1.Pod) {
|
||||
names := sets.String{}
|
||||
for i, pod := range pods {
|
||||
var errlist field.ErrorList
|
||||
// TODO: remove the conversion when validation is performed on versioned objects.
|
||||
internalPod := &api.Pod{}
|
||||
if err := v1.Convert_v1_Pod_To_api_Pod(pod, internalPod, nil); err != nil {
|
||||
name := kubecontainer.GetPodFullName(pod)
|
||||
glog.Warningf("Pod[%d] (%s) from %s failed to convert to v1, ignoring: %v", i+1, name, source, err)
|
||||
recorder.Eventf(pod, v1.EventTypeWarning, "FailedConversion", "Error converting pod %s from %s, ignoring: %v", name, source, err)
|
||||
continue
|
||||
}
|
||||
if errs := validation.ValidatePod(internalPod); len(errs) != 0 {
|
||||
errlist = append(errlist, errs...)
|
||||
// If validation fails, don't trust it any further -
|
||||
// even Name could be bad.
|
||||
} else {
|
||||
name := kubecontainer.GetPodFullName(pod)
|
||||
if names.Has(name) {
|
||||
// TODO: when validation becomes versioned, this gets a bit
|
||||
// more complicated.
|
||||
errlist = append(errlist, field.Duplicate(field.NewPath("metadata", "name"), pod.Name))
|
||||
} else {
|
||||
names.Insert(name)
|
||||
}
|
||||
}
|
||||
if len(errlist) > 0 {
|
||||
name := bestPodIdentString(pod)
|
||||
err := errlist.ToAggregate()
|
||||
glog.Warningf("Pod[%d] (%s) from %s failed validation, ignoring: %v", i+1, name, source, err)
|
||||
recorder.Eventf(pod, v1.EventTypeWarning, events.FailedValidation, "Error validating pod %s from %s, ignoring: %v", name, source, err)
|
||||
continue
|
||||
}
|
||||
filtered = append(filtered, pod)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Annotations that the kubelet adds to the pod.
|
||||
var localAnnotations = []string{
|
||||
kubetypes.ConfigSourceAnnotationKey,
|
||||
kubetypes.ConfigMirrorAnnotationKey,
|
||||
kubetypes.ConfigFirstSeenAnnotationKey,
|
||||
}
|
||||
|
||||
func isLocalAnnotationKey(key string) bool {
|
||||
for _, localKey := range localAnnotations {
|
||||
if key == localKey {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// isAnnotationMapEqual returns true if the existing annotation Map is equal to candidate except
|
||||
// for local annotations.
|
||||
func isAnnotationMapEqual(existingMap, candidateMap map[string]string) bool {
|
||||
if candidateMap == nil {
|
||||
candidateMap = make(map[string]string)
|
||||
}
|
||||
for k, v := range candidateMap {
|
||||
if isLocalAnnotationKey(k) {
|
||||
continue
|
||||
}
|
||||
if existingValue, ok := existingMap[k]; ok && existingValue == v {
|
||||
continue
|
||||
}
|
||||
return false
|
||||
}
|
||||
for k := range existingMap {
|
||||
if isLocalAnnotationKey(k) {
|
||||
continue
|
||||
}
|
||||
// stale entry in existing map.
|
||||
if _, exists := candidateMap[k]; !exists {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// recordFirstSeenTime records the first seen time of this pod.
|
||||
func recordFirstSeenTime(pod *v1.Pod) {
|
||||
glog.V(4).Infof("Receiving a new pod %q", format.Pod(pod))
|
||||
pod.Annotations[kubetypes.ConfigFirstSeenAnnotationKey] = kubetypes.NewTimestamp().GetString()
|
||||
}
|
||||
|
||||
// updateAnnotations returns an Annotation map containing the api annotation map plus
|
||||
// locally managed annotations
|
||||
func updateAnnotations(existing, ref *v1.Pod) {
|
||||
annotations := make(map[string]string, len(ref.Annotations)+len(localAnnotations))
|
||||
for k, v := range ref.Annotations {
|
||||
annotations[k] = v
|
||||
}
|
||||
for _, k := range localAnnotations {
|
||||
if v, ok := existing.Annotations[k]; ok {
|
||||
annotations[k] = v
|
||||
}
|
||||
}
|
||||
existing.Annotations = annotations
|
||||
}
|
||||
|
||||
func podsDifferSemantically(existing, ref *v1.Pod) bool {
|
||||
if reflect.DeepEqual(existing.Spec, ref.Spec) &&
|
||||
reflect.DeepEqual(existing.Labels, ref.Labels) &&
|
||||
reflect.DeepEqual(existing.DeletionTimestamp, ref.DeletionTimestamp) &&
|
||||
reflect.DeepEqual(existing.DeletionGracePeriodSeconds, ref.DeletionGracePeriodSeconds) &&
|
||||
isAnnotationMapEqual(existing.Annotations, ref.Annotations) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// checkAndUpdatePod updates existing, and:
|
||||
// * if ref makes a meaningful change, returns needUpdate=true
|
||||
// * if ref makes a meaningful change, and this change is graceful deletion, returns needGracefulDelete=true
|
||||
// * if ref makes no meaningful change, but changes the pod status, returns needReconcile=true
|
||||
// * else return all false
|
||||
// Now, needUpdate, needGracefulDelete and needReconcile should never be both true
|
||||
func checkAndUpdatePod(existing, ref *v1.Pod) (needUpdate, needReconcile, needGracefulDelete bool) {
|
||||
|
||||
// 1. this is a reconcile
|
||||
// TODO: it would be better to update the whole object and only preserve certain things
|
||||
// like the source annotation or the UID (to ensure safety)
|
||||
if !podsDifferSemantically(existing, ref) {
|
||||
// this is not an update
|
||||
// Only check reconcile when it is not an update, because if the pod is going to
|
||||
// be updated, an extra reconcile is unnecessary
|
||||
if !reflect.DeepEqual(existing.Status, ref.Status) {
|
||||
// Pod with changed pod status needs reconcile, because kubelet should
|
||||
// be the source of truth of pod status.
|
||||
existing.Status = ref.Status
|
||||
needReconcile = true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Overwrite the first-seen time with the existing one. This is our own
|
||||
// internal annotation, there is no need to update.
|
||||
ref.Annotations[kubetypes.ConfigFirstSeenAnnotationKey] = existing.Annotations[kubetypes.ConfigFirstSeenAnnotationKey]
|
||||
|
||||
existing.Spec = ref.Spec
|
||||
existing.Labels = ref.Labels
|
||||
existing.DeletionTimestamp = ref.DeletionTimestamp
|
||||
existing.DeletionGracePeriodSeconds = ref.DeletionGracePeriodSeconds
|
||||
existing.Status = ref.Status
|
||||
updateAnnotations(existing, ref)
|
||||
|
||||
// 2. this is an graceful delete
|
||||
if ref.DeletionTimestamp != nil {
|
||||
needGracefulDelete = true
|
||||
} else {
|
||||
// 3. this is an update
|
||||
needUpdate = true
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Sync sends a copy of the current state through the update channel.
|
||||
func (s *podStorage) Sync() {
|
||||
s.updateLock.Lock()
|
||||
defer s.updateLock.Unlock()
|
||||
s.updates <- kubetypes.PodUpdate{Pods: s.MergedState().([]*v1.Pod), Op: kubetypes.SET, Source: kubetypes.AllSource}
|
||||
}
|
||||
|
||||
// Object implements config.Accessor
|
||||
func (s *podStorage) MergedState() interface{} {
|
||||
s.podLock.RLock()
|
||||
defer s.podLock.RUnlock()
|
||||
pods := make([]*v1.Pod, 0)
|
||||
for _, sourcePods := range s.pods {
|
||||
for _, podRef := range sourcePods {
|
||||
pod, err := api.Scheme.Copy(podRef)
|
||||
if err != nil {
|
||||
glog.Errorf("unable to copy pod: %v", err)
|
||||
}
|
||||
pods = append(pods, pod.(*v1.Pod))
|
||||
}
|
||||
}
|
||||
return pods
|
||||
}
|
||||
|
||||
func bestPodIdentString(pod *v1.Pod) string {
|
||||
namespace := pod.Namespace
|
||||
if namespace == "" {
|
||||
namespace = "<empty-namespace>"
|
||||
}
|
||||
name := pod.Name
|
||||
if name == "" {
|
||||
name = "<empty-name>"
|
||||
}
|
||||
return fmt.Sprintf("%s.%s", name, namespace)
|
||||
}
|
||||
|
||||
func copyPods(sourcePods []*v1.Pod) []*v1.Pod {
|
||||
pods := []*v1.Pod{}
|
||||
for _, source := range sourcePods {
|
||||
// Use a deep copy here just in case
|
||||
pod, err := api.Scheme.Copy(source)
|
||||
if err != nil {
|
||||
glog.Errorf("unable to copy pod: %v", err)
|
||||
}
|
||||
pods = append(pods, pod.(*v1.Pod))
|
||||
}
|
||||
return pods
|
||||
}
|
417
vendor/k8s.io/kubernetes/pkg/kubelet/config/config_test.go
generated
vendored
Normal file
417
vendor/k8s.io/kubernetes/pkg/kubelet/config/config_test.go
generated
vendored
Normal file
|
@ -0,0 +1,417 @@
|
|||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/v1"
|
||||
"k8s.io/kubernetes/pkg/client/record"
|
||||
kubetypes "k8s.io/kubernetes/pkg/kubelet/types"
|
||||
"k8s.io/kubernetes/pkg/securitycontext"
|
||||
)
|
||||
|
||||
const (
|
||||
TestSource = "test"
|
||||
)
|
||||
|
||||
func expectEmptyChannel(t *testing.T, ch <-chan interface{}) {
|
||||
select {
|
||||
case update := <-ch:
|
||||
t.Errorf("Expected no update in channel, Got %v", update)
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
type sortedPods []*v1.Pod
|
||||
|
||||
func (s sortedPods) Len() int {
|
||||
return len(s)
|
||||
}
|
||||
func (s sortedPods) Swap(i, j int) {
|
||||
s[i], s[j] = s[j], s[i]
|
||||
}
|
||||
func (s sortedPods) Less(i, j int) bool {
|
||||
return s[i].Namespace < s[j].Namespace
|
||||
}
|
||||
|
||||
func CreateValidPod(name, namespace string) *v1.Pod {
|
||||
return &v1.Pod{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
UID: types.UID(name), // for the purpose of testing, this is unique enough
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
RestartPolicy: v1.RestartPolicyAlways,
|
||||
DNSPolicy: v1.DNSClusterFirst,
|
||||
Containers: []v1.Container{
|
||||
{
|
||||
Name: "ctr",
|
||||
Image: "image",
|
||||
ImagePullPolicy: "IfNotPresent",
|
||||
SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults(),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func CreatePodUpdate(op kubetypes.PodOperation, source string, pods ...*v1.Pod) kubetypes.PodUpdate {
|
||||
return kubetypes.PodUpdate{Pods: pods, Op: op, Source: source}
|
||||
}
|
||||
|
||||
func createPodConfigTester(mode PodConfigNotificationMode) (chan<- interface{}, <-chan kubetypes.PodUpdate, *PodConfig) {
|
||||
eventBroadcaster := record.NewBroadcaster()
|
||||
config := NewPodConfig(mode, eventBroadcaster.NewRecorder(v1.EventSource{Component: "kubelet"}))
|
||||
channel := config.Channel(TestSource)
|
||||
ch := config.Updates()
|
||||
return channel, ch, config
|
||||
}
|
||||
|
||||
func expectPodUpdate(t *testing.T, ch <-chan kubetypes.PodUpdate, expected ...kubetypes.PodUpdate) {
|
||||
for i := range expected {
|
||||
update := <-ch
|
||||
sort.Sort(sortedPods(update.Pods))
|
||||
sort.Sort(sortedPods(expected[i].Pods))
|
||||
// Make copies of the expected/actual update to compare all fields
|
||||
// except for "Pods", which are compared separately below.
|
||||
expectedCopy, updateCopy := expected[i], update
|
||||
expectedCopy.Pods, updateCopy.Pods = nil, nil
|
||||
if !api.Semantic.DeepEqual(expectedCopy, updateCopy) {
|
||||
t.Fatalf("Expected %#v, Got %#v", expectedCopy, updateCopy)
|
||||
}
|
||||
|
||||
if len(expected[i].Pods) != len(update.Pods) {
|
||||
t.Fatalf("Expected %#v, Got %#v", expected[i], update)
|
||||
}
|
||||
// Compare pods one by one. This is necessary because we don't want to
|
||||
// compare local annotations.
|
||||
for j := range expected[i].Pods {
|
||||
if podsDifferSemantically(expected[i].Pods[j], update.Pods[j]) || !reflect.DeepEqual(expected[i].Pods[j].Status, update.Pods[j].Status) {
|
||||
t.Fatalf("Expected %#v, Got %#v", expected[i].Pods[j], update.Pods[j])
|
||||
}
|
||||
}
|
||||
}
|
||||
expectNoPodUpdate(t, ch)
|
||||
}
|
||||
|
||||
func expectNoPodUpdate(t *testing.T, ch <-chan kubetypes.PodUpdate) {
|
||||
select {
|
||||
case update := <-ch:
|
||||
t.Errorf("Expected no update in channel, Got %#v", update)
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewPodAdded(t *testing.T) {
|
||||
channel, ch, config := createPodConfigTester(PodConfigNotificationIncremental)
|
||||
|
||||
// see an update
|
||||
podUpdate := CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "new"))
|
||||
channel <- podUpdate
|
||||
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "new")))
|
||||
|
||||
config.Sync()
|
||||
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.SET, kubetypes.AllSource, CreateValidPod("foo", "new")))
|
||||
}
|
||||
|
||||
func TestNewPodAddedInvalidNamespace(t *testing.T) {
|
||||
channel, ch, config := createPodConfigTester(PodConfigNotificationIncremental)
|
||||
|
||||
// see an update
|
||||
podUpdate := CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", ""))
|
||||
channel <- podUpdate
|
||||
config.Sync()
|
||||
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.SET, kubetypes.AllSource))
|
||||
}
|
||||
|
||||
func TestNewPodAddedDefaultNamespace(t *testing.T) {
|
||||
channel, ch, config := createPodConfigTester(PodConfigNotificationIncremental)
|
||||
|
||||
// see an update
|
||||
podUpdate := CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "default"))
|
||||
channel <- podUpdate
|
||||
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "default")))
|
||||
|
||||
config.Sync()
|
||||
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.SET, kubetypes.AllSource, CreateValidPod("foo", "default")))
|
||||
}
|
||||
|
||||
func TestNewPodAddedDifferentNamespaces(t *testing.T) {
|
||||
channel, ch, config := createPodConfigTester(PodConfigNotificationIncremental)
|
||||
|
||||
// see an update
|
||||
podUpdate := CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "default"))
|
||||
channel <- podUpdate
|
||||
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "default")))
|
||||
|
||||
// see an update in another namespace
|
||||
podUpdate = CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "new"))
|
||||
channel <- podUpdate
|
||||
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "new")))
|
||||
|
||||
config.Sync()
|
||||
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.SET, kubetypes.AllSource, CreateValidPod("foo", "default"), CreateValidPod("foo", "new")))
|
||||
}
|
||||
|
||||
func TestInvalidPodFiltered(t *testing.T) {
|
||||
channel, ch, _ := createPodConfigTester(PodConfigNotificationIncremental)
|
||||
|
||||
// see an update
|
||||
podUpdate := CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "new"))
|
||||
channel <- podUpdate
|
||||
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "new")))
|
||||
|
||||
// add an invalid update
|
||||
podUpdate = CreatePodUpdate(kubetypes.UPDATE, TestSource, &v1.Pod{ObjectMeta: v1.ObjectMeta{Name: "foo"}})
|
||||
channel <- podUpdate
|
||||
expectNoPodUpdate(t, ch)
|
||||
}
|
||||
|
||||
func TestNewPodAddedSnapshotAndUpdates(t *testing.T) {
|
||||
channel, ch, config := createPodConfigTester(PodConfigNotificationSnapshotAndUpdates)
|
||||
|
||||
// see an set
|
||||
podUpdate := CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "new"))
|
||||
channel <- podUpdate
|
||||
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.SET, TestSource, CreateValidPod("foo", "new")))
|
||||
|
||||
config.Sync()
|
||||
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.SET, kubetypes.AllSource, CreateValidPod("foo", "new")))
|
||||
|
||||
// container updates are separated as UPDATE
|
||||
pod := *podUpdate.Pods[0]
|
||||
pod.Spec.Containers = []v1.Container{{Name: "bar", Image: "test", ImagePullPolicy: v1.PullIfNotPresent}}
|
||||
channel <- CreatePodUpdate(kubetypes.ADD, TestSource, &pod)
|
||||
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.UPDATE, TestSource, &pod))
|
||||
}
|
||||
|
||||
func TestNewPodAddedSnapshot(t *testing.T) {
|
||||
channel, ch, config := createPodConfigTester(PodConfigNotificationSnapshot)
|
||||
|
||||
// see an set
|
||||
podUpdate := CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "new"))
|
||||
channel <- podUpdate
|
||||
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.SET, TestSource, CreateValidPod("foo", "new")))
|
||||
|
||||
config.Sync()
|
||||
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.SET, kubetypes.AllSource, CreateValidPod("foo", "new")))
|
||||
|
||||
// container updates are separated as UPDATE
|
||||
pod := *podUpdate.Pods[0]
|
||||
pod.Spec.Containers = []v1.Container{{Name: "bar", Image: "test", ImagePullPolicy: v1.PullIfNotPresent}}
|
||||
channel <- CreatePodUpdate(kubetypes.ADD, TestSource, &pod)
|
||||
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.SET, TestSource, &pod))
|
||||
}
|
||||
|
||||
func TestNewPodAddedUpdatedRemoved(t *testing.T) {
|
||||
channel, ch, _ := createPodConfigTester(PodConfigNotificationIncremental)
|
||||
|
||||
// should register an add
|
||||
podUpdate := CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "new"))
|
||||
channel <- podUpdate
|
||||
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "new")))
|
||||
|
||||
// should ignore ADDs that are identical
|
||||
expectNoPodUpdate(t, ch)
|
||||
|
||||
// an kubetypes.ADD should be converted to kubetypes.UPDATE
|
||||
pod := CreateValidPod("foo", "new")
|
||||
pod.Spec.Containers = []v1.Container{{Name: "bar", Image: "test", ImagePullPolicy: v1.PullIfNotPresent}}
|
||||
podUpdate = CreatePodUpdate(kubetypes.ADD, TestSource, pod)
|
||||
channel <- podUpdate
|
||||
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.UPDATE, TestSource, pod))
|
||||
|
||||
podUpdate = CreatePodUpdate(kubetypes.REMOVE, TestSource, &v1.Pod{ObjectMeta: v1.ObjectMeta{Name: "foo", Namespace: "new"}})
|
||||
channel <- podUpdate
|
||||
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.REMOVE, TestSource, pod))
|
||||
}
|
||||
|
||||
func TestNewPodAddedDelete(t *testing.T) {
|
||||
channel, ch, _ := createPodConfigTester(PodConfigNotificationIncremental)
|
||||
|
||||
// should register an add
|
||||
addedPod := CreateValidPod("foo", "new")
|
||||
podUpdate := CreatePodUpdate(kubetypes.ADD, TestSource, addedPod)
|
||||
channel <- podUpdate
|
||||
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.ADD, TestSource, addedPod))
|
||||
|
||||
// mark this pod as deleted
|
||||
timestamp := metav1.NewTime(time.Now())
|
||||
deletedPod := CreateValidPod("foo", "new")
|
||||
deletedPod.ObjectMeta.DeletionTimestamp = ×tamp
|
||||
podUpdate = CreatePodUpdate(kubetypes.DELETE, TestSource, deletedPod)
|
||||
channel <- podUpdate
|
||||
// the existing pod should be gracefully deleted
|
||||
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.DELETE, TestSource, addedPod))
|
||||
}
|
||||
|
||||
func TestNewPodAddedUpdatedSet(t *testing.T) {
|
||||
channel, ch, _ := createPodConfigTester(PodConfigNotificationIncremental)
|
||||
|
||||
// should register an add
|
||||
podUpdate := CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "new"), CreateValidPod("foo2", "new"), CreateValidPod("foo3", "new"))
|
||||
channel <- podUpdate
|
||||
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "new"), CreateValidPod("foo2", "new"), CreateValidPod("foo3", "new")))
|
||||
|
||||
// should ignore ADDs that are identical
|
||||
expectNoPodUpdate(t, ch)
|
||||
|
||||
// should be converted to an kubetypes.ADD, kubetypes.REMOVE, and kubetypes.UPDATE
|
||||
pod := CreateValidPod("foo2", "new")
|
||||
pod.Spec.Containers = []v1.Container{{Name: "bar", Image: "test", ImagePullPolicy: v1.PullIfNotPresent}}
|
||||
podUpdate = CreatePodUpdate(kubetypes.SET, TestSource, pod, CreateValidPod("foo3", "new"), CreateValidPod("foo4", "new"))
|
||||
channel <- podUpdate
|
||||
expectPodUpdate(t, ch,
|
||||
CreatePodUpdate(kubetypes.REMOVE, TestSource, CreateValidPod("foo", "new")),
|
||||
CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo4", "new")),
|
||||
CreatePodUpdate(kubetypes.UPDATE, TestSource, pod))
|
||||
}
|
||||
|
||||
func TestNewPodAddedSetReconciled(t *testing.T) {
|
||||
// Create and touch new test pods, return the new pods and touched pod. We should create new pod list
|
||||
// before touching to avoid data race.
|
||||
newTestPods := func(touchStatus, touchSpec bool) ([]*v1.Pod, *v1.Pod) {
|
||||
pods := []*v1.Pod{
|
||||
CreateValidPod("changeable-pod-0", "new"),
|
||||
CreateValidPod("constant-pod-1", "new"),
|
||||
CreateValidPod("constant-pod-2", "new"),
|
||||
}
|
||||
if touchStatus {
|
||||
pods[0].Status = v1.PodStatus{Message: strconv.Itoa(rand.Int())}
|
||||
}
|
||||
if touchSpec {
|
||||
pods[0].Spec.Containers[0].Name = strconv.Itoa(rand.Int())
|
||||
}
|
||||
return pods, pods[0]
|
||||
}
|
||||
for _, op := range []kubetypes.PodOperation{
|
||||
kubetypes.ADD,
|
||||
kubetypes.SET,
|
||||
} {
|
||||
var podWithStatusChange *v1.Pod
|
||||
pods, _ := newTestPods(false, false)
|
||||
channel, ch, _ := createPodConfigTester(PodConfigNotificationIncremental)
|
||||
|
||||
// Use SET to initialize the config, especially initialize the source set
|
||||
channel <- CreatePodUpdate(kubetypes.SET, TestSource, pods...)
|
||||
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.ADD, TestSource, pods...))
|
||||
|
||||
// If status is not changed, no reconcile should be triggered
|
||||
channel <- CreatePodUpdate(op, TestSource, pods...)
|
||||
expectNoPodUpdate(t, ch)
|
||||
|
||||
// If the pod status is changed and not updated, a reconcile should be triggered
|
||||
pods, podWithStatusChange = newTestPods(true, false)
|
||||
channel <- CreatePodUpdate(op, TestSource, pods...)
|
||||
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.RECONCILE, TestSource, podWithStatusChange))
|
||||
|
||||
// If the pod status is changed, but the pod is also updated, no reconcile should be triggered
|
||||
pods, podWithStatusChange = newTestPods(true, true)
|
||||
channel <- CreatePodUpdate(op, TestSource, pods...)
|
||||
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.UPDATE, TestSource, podWithStatusChange))
|
||||
}
|
||||
}
|
||||
|
||||
func TestInitialEmptySet(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
mode PodConfigNotificationMode
|
||||
op kubetypes.PodOperation
|
||||
}{
|
||||
{PodConfigNotificationIncremental, kubetypes.ADD},
|
||||
{PodConfigNotificationSnapshot, kubetypes.SET},
|
||||
{PodConfigNotificationSnapshotAndUpdates, kubetypes.SET},
|
||||
} {
|
||||
channel, ch, _ := createPodConfigTester(test.mode)
|
||||
|
||||
// should register an empty PodUpdate operation
|
||||
podUpdate := CreatePodUpdate(kubetypes.SET, TestSource)
|
||||
channel <- podUpdate
|
||||
expectPodUpdate(t, ch, CreatePodUpdate(test.op, TestSource))
|
||||
|
||||
// should ignore following empty sets
|
||||
podUpdate = CreatePodUpdate(kubetypes.SET, TestSource)
|
||||
channel <- podUpdate
|
||||
podUpdate = CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "new"))
|
||||
channel <- podUpdate
|
||||
expectPodUpdate(t, ch, CreatePodUpdate(test.op, TestSource, CreateValidPod("foo", "new")))
|
||||
}
|
||||
}
|
||||
|
||||
func TestPodUpdateAnnotations(t *testing.T) {
|
||||
channel, ch, _ := createPodConfigTester(PodConfigNotificationIncremental)
|
||||
|
||||
pod := CreateValidPod("foo2", "new")
|
||||
pod.Annotations = make(map[string]string, 0)
|
||||
pod.Annotations["kubernetes.io/blah"] = "blah"
|
||||
|
||||
clone, err := api.Scheme.DeepCopy(pod)
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
|
||||
podUpdate := CreatePodUpdate(kubetypes.SET, TestSource, CreateValidPod("foo1", "new"), clone.(*v1.Pod), CreateValidPod("foo3", "new"))
|
||||
channel <- podUpdate
|
||||
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo1", "new"), pod, CreateValidPod("foo3", "new")))
|
||||
|
||||
pod.Annotations["kubenetes.io/blah"] = "superblah"
|
||||
podUpdate = CreatePodUpdate(kubetypes.SET, TestSource, CreateValidPod("foo1", "new"), pod, CreateValidPod("foo3", "new"))
|
||||
channel <- podUpdate
|
||||
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.UPDATE, TestSource, pod))
|
||||
|
||||
pod.Annotations["kubernetes.io/otherblah"] = "doh"
|
||||
podUpdate = CreatePodUpdate(kubetypes.SET, TestSource, CreateValidPod("foo1", "new"), pod, CreateValidPod("foo3", "new"))
|
||||
channel <- podUpdate
|
||||
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.UPDATE, TestSource, pod))
|
||||
|
||||
delete(pod.Annotations, "kubernetes.io/blah")
|
||||
podUpdate = CreatePodUpdate(kubetypes.SET, TestSource, CreateValidPod("foo1", "new"), pod, CreateValidPod("foo3", "new"))
|
||||
channel <- podUpdate
|
||||
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.UPDATE, TestSource, pod))
|
||||
}
|
||||
|
||||
func TestPodUpdateLabels(t *testing.T) {
|
||||
channel, ch, _ := createPodConfigTester(PodConfigNotificationIncremental)
|
||||
|
||||
pod := CreateValidPod("foo2", "new")
|
||||
pod.Labels = make(map[string]string, 0)
|
||||
pod.Labels["key"] = "value"
|
||||
|
||||
clone, err := api.Scheme.DeepCopy(pod)
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
|
||||
podUpdate := CreatePodUpdate(kubetypes.SET, TestSource, clone.(*v1.Pod))
|
||||
channel <- podUpdate
|
||||
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.ADD, TestSource, pod))
|
||||
|
||||
pod.Labels["key"] = "newValue"
|
||||
podUpdate = CreatePodUpdate(kubetypes.SET, TestSource, pod)
|
||||
channel <- podUpdate
|
||||
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.UPDATE, TestSource, pod))
|
||||
|
||||
}
|
18
vendor/k8s.io/kubernetes/pkg/kubelet/config/doc.go
generated
vendored
Normal file
18
vendor/k8s.io/kubernetes/pkg/kubelet/config/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Package config implements the pod configuration readers.
|
||||
package config // import "k8s.io/kubernetes/pkg/kubelet/config"
|
202
vendor/k8s.io/kubernetes/pkg/kubelet/config/file.go
generated
vendored
Normal file
202
vendor/k8s.io/kubernetes/pkg/kubelet/config/file.go
generated
vendored
Normal file
|
@ -0,0 +1,202 @@
|
|||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Reads the pod configuration from file or a directory of files.
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/v1"
|
||||
"k8s.io/kubernetes/pkg/client/cache"
|
||||
kubetypes "k8s.io/kubernetes/pkg/kubelet/types"
|
||||
)
|
||||
|
||||
type sourceFile struct {
|
||||
path string
|
||||
nodeName types.NodeName
|
||||
store cache.Store
|
||||
fileKeyMapping map[string]string
|
||||
updates chan<- interface{}
|
||||
}
|
||||
|
||||
func NewSourceFile(path string, nodeName types.NodeName, period time.Duration, updates chan<- interface{}) {
|
||||
config := new(path, nodeName, period, updates)
|
||||
glog.V(1).Infof("Watching path %q", path)
|
||||
go wait.Forever(config.run, period)
|
||||
}
|
||||
|
||||
func new(path string, nodeName types.NodeName, period time.Duration, updates chan<- interface{}) *sourceFile {
|
||||
send := func(objs []interface{}) {
|
||||
var pods []*v1.Pod
|
||||
for _, o := range objs {
|
||||
pods = append(pods, o.(*v1.Pod))
|
||||
}
|
||||
updates <- kubetypes.PodUpdate{Pods: pods, Op: kubetypes.SET, Source: kubetypes.FileSource}
|
||||
}
|
||||
store := cache.NewUndeltaStore(send, cache.MetaNamespaceKeyFunc)
|
||||
return &sourceFile{
|
||||
path: path,
|
||||
nodeName: nodeName,
|
||||
store: store,
|
||||
fileKeyMapping: map[string]string{},
|
||||
updates: updates,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *sourceFile) run() {
|
||||
if err := s.watch(); err != nil {
|
||||
glog.Errorf("unable to read config path %q: %v", s.path, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *sourceFile) applyDefaults(pod *api.Pod, source string) error {
|
||||
return applyDefaults(pod, source, true, s.nodeName)
|
||||
}
|
||||
|
||||
func (s *sourceFile) resetStoreFromPath() error {
|
||||
path := s.path
|
||||
statInfo, err := os.Stat(path)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
// Emit an update with an empty PodList to allow FileSource to be marked as seen
|
||||
s.updates <- kubetypes.PodUpdate{Pods: []*v1.Pod{}, Op: kubetypes.SET, Source: kubetypes.FileSource}
|
||||
return fmt.Errorf("path does not exist, ignoring")
|
||||
}
|
||||
|
||||
switch {
|
||||
case statInfo.Mode().IsDir():
|
||||
pods, err := s.extractFromDir(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(pods) == 0 {
|
||||
// Emit an update with an empty PodList to allow FileSource to be marked as seen
|
||||
s.updates <- kubetypes.PodUpdate{Pods: pods, Op: kubetypes.SET, Source: kubetypes.FileSource}
|
||||
return nil
|
||||
}
|
||||
return s.replaceStore(pods...)
|
||||
|
||||
case statInfo.Mode().IsRegular():
|
||||
pod, err := s.extractFromFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.replaceStore(pod)
|
||||
|
||||
default:
|
||||
return fmt.Errorf("path is not a directory or file")
|
||||
}
|
||||
}
|
||||
|
||||
// Get as many pod configs as we can from a directory. Return an error if and only if something
|
||||
// prevented us from reading anything at all. Do not return an error if only some files
|
||||
// were problematic.
|
||||
func (s *sourceFile) extractFromDir(name string) ([]*v1.Pod, error) {
|
||||
dirents, err := filepath.Glob(filepath.Join(name, "[^.]*"))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("glob failed: %v", err)
|
||||
}
|
||||
|
||||
pods := make([]*v1.Pod, 0)
|
||||
if len(dirents) == 0 {
|
||||
return pods, nil
|
||||
}
|
||||
|
||||
sort.Strings(dirents)
|
||||
for _, path := range dirents {
|
||||
statInfo, err := os.Stat(path)
|
||||
if err != nil {
|
||||
glog.V(1).Infof("Can't get metadata for %q: %v", path, err)
|
||||
continue
|
||||
}
|
||||
|
||||
switch {
|
||||
case statInfo.Mode().IsDir():
|
||||
glog.V(1).Infof("Not recursing into config path %q", path)
|
||||
case statInfo.Mode().IsRegular():
|
||||
pod, err := s.extractFromFile(path)
|
||||
if err != nil {
|
||||
glog.V(1).Infof("Can't process config file %q: %v", path, err)
|
||||
} else {
|
||||
pods = append(pods, pod)
|
||||
}
|
||||
default:
|
||||
glog.V(1).Infof("Config path %q is not a directory or file: %v", path, statInfo.Mode())
|
||||
}
|
||||
}
|
||||
return pods, nil
|
||||
}
|
||||
|
||||
func (s *sourceFile) extractFromFile(filename string) (pod *v1.Pod, err error) {
|
||||
glog.V(3).Infof("Reading config file %q", filename)
|
||||
defer func() {
|
||||
if err == nil && pod != nil {
|
||||
objKey, keyErr := cache.MetaNamespaceKeyFunc(pod)
|
||||
if keyErr != nil {
|
||||
err = keyErr
|
||||
return
|
||||
}
|
||||
s.fileKeyMapping[filename] = objKey
|
||||
}
|
||||
}()
|
||||
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return pod, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
data, err := ioutil.ReadAll(file)
|
||||
if err != nil {
|
||||
return pod, err
|
||||
}
|
||||
|
||||
defaultFn := func(pod *api.Pod) error {
|
||||
return s.applyDefaults(pod, filename)
|
||||
}
|
||||
|
||||
parsed, pod, podErr := tryDecodeSinglePod(data, defaultFn)
|
||||
if parsed {
|
||||
if podErr != nil {
|
||||
return pod, podErr
|
||||
}
|
||||
return pod, nil
|
||||
}
|
||||
|
||||
return pod, fmt.Errorf("%v: read '%v', but couldn't parse as pod(%v).\n",
|
||||
filename, string(data), podErr)
|
||||
}
|
||||
|
||||
func (s *sourceFile) replaceStore(pods ...*v1.Pod) (err error) {
|
||||
objs := []interface{}{}
|
||||
for _, pod := range pods {
|
||||
objs = append(objs, pod)
|
||||
}
|
||||
return s.store.Replace(objs, "")
|
||||
}
|
124
vendor/k8s.io/kubernetes/pkg/kubelet/config/file_linux.go
generated
vendored
Normal file
124
vendor/k8s.io/kubernetes/pkg/kubelet/config/file_linux.go
generated
vendored
Normal file
|
@ -0,0 +1,124 @@
|
|||
// +build linux
|
||||
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Reads the pod configuration from file or a directory of files.
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"golang.org/x/exp/inotify"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api/v1"
|
||||
kubetypes "k8s.io/kubernetes/pkg/kubelet/types"
|
||||
)
|
||||
|
||||
type podEventType int
|
||||
|
||||
const (
|
||||
podAdd podEventType = iota
|
||||
podModify
|
||||
podDelete
|
||||
)
|
||||
|
||||
func (s *sourceFile) watch() error {
|
||||
_, err := os.Stat(s.path)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
// Emit an update with an empty PodList to allow FileSource to be marked as seen
|
||||
s.updates <- kubetypes.PodUpdate{Pods: []*v1.Pod{}, Op: kubetypes.SET, Source: kubetypes.FileSource}
|
||||
return fmt.Errorf("path does not exist, ignoring")
|
||||
}
|
||||
|
||||
w, err := inotify.NewWatcher()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create inotify: %v", err)
|
||||
}
|
||||
defer w.Close()
|
||||
|
||||
err = w.AddWatch(s.path, inotify.IN_DELETE_SELF|inotify.IN_CREATE|inotify.IN_MOVED_TO|inotify.IN_MODIFY|inotify.IN_MOVED_FROM|inotify.IN_DELETE)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create inotify for path %q: %v", s.path, err)
|
||||
}
|
||||
|
||||
// Reset store with config files already existing when starting
|
||||
if err := s.resetStoreFromPath(); err != nil {
|
||||
return fmt.Errorf("unable to read config path %q: %v", s.path, err)
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case event := <-w.Event:
|
||||
err = s.processEvent(event)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while processing event (%+v): %v", event, err)
|
||||
}
|
||||
case err = <-w.Error:
|
||||
return fmt.Errorf("error while watching %q: %v", s.path, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *sourceFile) processEvent(e *inotify.Event) error {
|
||||
var eventType podEventType
|
||||
switch {
|
||||
case (e.Mask & inotify.IN_ISDIR) > 0:
|
||||
glog.V(1).Infof("Not recursing into config path %q", s.path)
|
||||
return nil
|
||||
case (e.Mask & inotify.IN_CREATE) > 0:
|
||||
eventType = podAdd
|
||||
case (e.Mask & inotify.IN_MOVED_TO) > 0:
|
||||
eventType = podAdd
|
||||
case (e.Mask & inotify.IN_MODIFY) > 0:
|
||||
eventType = podModify
|
||||
case (e.Mask & inotify.IN_DELETE) > 0:
|
||||
eventType = podDelete
|
||||
case (e.Mask & inotify.IN_MOVED_FROM) > 0:
|
||||
eventType = podDelete
|
||||
case (e.Mask & inotify.IN_DELETE_SELF) > 0:
|
||||
return fmt.Errorf("the watched path is deleted")
|
||||
default:
|
||||
// Ignore rest events
|
||||
return nil
|
||||
}
|
||||
|
||||
switch eventType {
|
||||
case podAdd, podModify:
|
||||
if pod, err := s.extractFromFile(e.Name); err != nil {
|
||||
glog.Errorf("can't process config file %q: %v", e.Name, err)
|
||||
} else {
|
||||
return s.store.Add(pod)
|
||||
}
|
||||
case podDelete:
|
||||
if objKey, keyExist := s.fileKeyMapping[e.Name]; keyExist {
|
||||
pod, podExist, err := s.store.GetByKey(objKey)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if !podExist {
|
||||
return fmt.Errorf("the pod with key %s doesn't exist in cache", objKey)
|
||||
} else {
|
||||
return s.store.Delete(pod)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
421
vendor/k8s.io/kubernetes/pkg/kubelet/config/file_linux_test.go
generated
vendored
Normal file
421
vendor/k8s.io/kubernetes/pkg/kubelet/config/file_linux_test.go
generated
vendored
Normal file
|
@ -0,0 +1,421 @@
|
|||
// +build linux
|
||||
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/testapi"
|
||||
"k8s.io/kubernetes/pkg/api/v1"
|
||||
"k8s.io/kubernetes/pkg/api/validation"
|
||||
kubetypes "k8s.io/kubernetes/pkg/kubelet/types"
|
||||
"k8s.io/kubernetes/pkg/securitycontext"
|
||||
utiltesting "k8s.io/kubernetes/pkg/util/testing"
|
||||
)
|
||||
|
||||
func TestExtractFromNonExistentFile(t *testing.T) {
|
||||
ch := make(chan interface{}, 1)
|
||||
c := new("/some/fake/file", "localhost", time.Millisecond, ch)
|
||||
err := c.watch()
|
||||
if err == nil {
|
||||
t.Errorf("Expected error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateOnNonExistentFile(t *testing.T) {
|
||||
ch := make(chan interface{})
|
||||
NewSourceFile("random_non_existent_path", "localhost", time.Millisecond, ch)
|
||||
select {
|
||||
case got := <-ch:
|
||||
update := got.(kubetypes.PodUpdate)
|
||||
expected := CreatePodUpdate(kubetypes.SET, kubetypes.FileSource)
|
||||
if !api.Semantic.DeepDerivative(expected, update) {
|
||||
t.Fatalf("expected %#v, Got %#v", expected, update)
|
||||
}
|
||||
|
||||
case <-time.After(wait.ForeverTestTimeout):
|
||||
t.Fatalf("expected update, timeout instead")
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadPodsFromFileExistAlready(t *testing.T) {
|
||||
hostname := types.NodeName("random-test-hostname")
|
||||
var testCases = getTestCases(hostname)
|
||||
|
||||
for _, testCase := range testCases {
|
||||
func() {
|
||||
dirName, err := utiltesting.MkTmpdir("file-test")
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create temp dir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(dirName)
|
||||
file := testCase.writeToFile(dirName, "test_pod_config", t)
|
||||
|
||||
ch := make(chan interface{})
|
||||
NewSourceFile(file, hostname, time.Millisecond, ch)
|
||||
select {
|
||||
case got := <-ch:
|
||||
update := got.(kubetypes.PodUpdate)
|
||||
for _, pod := range update.Pods {
|
||||
// TODO: remove the conversion when validation is performed on versioned objects.
|
||||
internalPod := &api.Pod{}
|
||||
if err := v1.Convert_v1_Pod_To_api_Pod(pod, internalPod, nil); err != nil {
|
||||
t.Fatalf("%s: Cannot convert pod %#v, %#v", testCase.desc, pod, err)
|
||||
}
|
||||
if errs := validation.ValidatePod(internalPod); len(errs) > 0 {
|
||||
t.Fatalf("%s: Invalid pod %#v, %#v", testCase.desc, internalPod, errs)
|
||||
}
|
||||
}
|
||||
if !api.Semantic.DeepEqual(testCase.expected, update) {
|
||||
t.Fatalf("%s: Expected %#v, Got %#v", testCase.desc, testCase.expected, update)
|
||||
}
|
||||
case <-time.After(wait.ForeverTestTimeout):
|
||||
t.Fatalf("%s: Expected update, timeout instead", testCase.desc)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadPodsFromFileExistLater(t *testing.T) {
|
||||
watchFileAdded(false, t)
|
||||
}
|
||||
|
||||
func TestReadPodsFromFileChanged(t *testing.T) {
|
||||
watchFileChanged(false, t)
|
||||
}
|
||||
|
||||
func TestReadPodsFromFileInDirAdded(t *testing.T) {
|
||||
watchFileAdded(true, t)
|
||||
}
|
||||
|
||||
func TestReadPodsFromFileInDirChanged(t *testing.T) {
|
||||
watchFileChanged(true, t)
|
||||
}
|
||||
|
||||
func TestExtractFromBadDataFile(t *testing.T) {
|
||||
dirName, err := utiltesting.MkTmpdir("file-test")
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create temp dir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(dirName)
|
||||
|
||||
fileName := filepath.Join(dirName, "test_pod_config")
|
||||
err = ioutil.WriteFile(fileName, []byte{1, 2, 3}, 0555)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to write test file %#v", err)
|
||||
}
|
||||
|
||||
ch := make(chan interface{}, 1)
|
||||
c := new(fileName, "localhost", time.Millisecond, ch)
|
||||
err = c.resetStoreFromPath()
|
||||
if err == nil {
|
||||
t.Fatalf("expected error, got nil")
|
||||
}
|
||||
expectEmptyChannel(t, ch)
|
||||
}
|
||||
|
||||
func TestExtractFromEmptyDir(t *testing.T) {
|
||||
dirName, err := utiltesting.MkTmpdir("file-test")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(dirName)
|
||||
|
||||
ch := make(chan interface{}, 1)
|
||||
c := new(dirName, "localhost", time.Millisecond, ch)
|
||||
err = c.resetStoreFromPath()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
update := (<-ch).(kubetypes.PodUpdate)
|
||||
expected := CreatePodUpdate(kubetypes.SET, kubetypes.FileSource)
|
||||
if !api.Semantic.DeepEqual(expected, update) {
|
||||
t.Fatalf("expected %#v, Got %#v", expected, update)
|
||||
}
|
||||
}
|
||||
|
||||
type testCase struct {
|
||||
desc string
|
||||
pod runtime.Object
|
||||
expected kubetypes.PodUpdate
|
||||
}
|
||||
|
||||
func getTestCases(hostname types.NodeName) []*testCase {
|
||||
grace := int64(30)
|
||||
return []*testCase{
|
||||
{
|
||||
desc: "Simple pod",
|
||||
pod: &v1.Pod{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Pod",
|
||||
APIVersion: "",
|
||||
},
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "test",
|
||||
UID: "12345",
|
||||
Namespace: "mynamespace",
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{{Name: "image", Image: "test/image", SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults()}},
|
||||
SecurityContext: &v1.PodSecurityContext{},
|
||||
},
|
||||
Status: v1.PodStatus{
|
||||
Phase: v1.PodPending,
|
||||
},
|
||||
},
|
||||
expected: CreatePodUpdate(kubetypes.SET, kubetypes.FileSource, &v1.Pod{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "test-" + string(hostname),
|
||||
UID: "12345",
|
||||
Namespace: "mynamespace",
|
||||
Annotations: map[string]string{kubetypes.ConfigHashAnnotationKey: "12345"},
|
||||
SelfLink: getSelfLink("test-"+string(hostname), "mynamespace"),
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
NodeName: string(hostname),
|
||||
RestartPolicy: v1.RestartPolicyAlways,
|
||||
DNSPolicy: v1.DNSClusterFirst,
|
||||
TerminationGracePeriodSeconds: &grace,
|
||||
Containers: []v1.Container{{
|
||||
Name: "image",
|
||||
Image: "test/image",
|
||||
TerminationMessagePath: "/dev/termination-log",
|
||||
ImagePullPolicy: "Always",
|
||||
SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults()}},
|
||||
SecurityContext: &v1.PodSecurityContext{},
|
||||
},
|
||||
Status: v1.PodStatus{
|
||||
Phase: v1.PodPending,
|
||||
},
|
||||
}),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (tc *testCase) writeToFile(dir, name string, t *testing.T) string {
|
||||
var versionedPod runtime.Object
|
||||
err := testapi.Default.Converter().Convert(&tc.pod, &versionedPod, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("%s: error in versioning the pod: %v", tc.desc, err)
|
||||
}
|
||||
fileContents, err := runtime.Encode(testapi.Default.Codec(), versionedPod)
|
||||
if err != nil {
|
||||
t.Fatalf("%s: error in encoding the pod: %v", tc.desc, err)
|
||||
}
|
||||
|
||||
fileName := filepath.Join(dir, name)
|
||||
if err := writeFile(fileName, []byte(fileContents)); err != nil {
|
||||
t.Fatalf("unable to write test file %#v", err)
|
||||
}
|
||||
return fileName
|
||||
}
|
||||
|
||||
func watchFileAdded(watchDir bool, t *testing.T) {
|
||||
hostname := types.NodeName("random-test-hostname")
|
||||
var testCases = getTestCases(hostname)
|
||||
|
||||
fileNamePre := "test_pod_config"
|
||||
for index, testCase := range testCases {
|
||||
func() {
|
||||
dirName, err := utiltesting.MkTmpdir("dir-test")
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create temp dir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(dirName)
|
||||
fileName := fmt.Sprintf("%s_%d", fileNamePre, index)
|
||||
|
||||
ch := make(chan interface{})
|
||||
if watchDir {
|
||||
NewSourceFile(dirName, hostname, 100*time.Millisecond, ch)
|
||||
} else {
|
||||
NewSourceFile(filepath.Join(dirName, fileName), hostname, 100*time.Millisecond, ch)
|
||||
}
|
||||
expectEmptyUpdate(t, ch)
|
||||
|
||||
addFile := func() {
|
||||
// Add a file
|
||||
testCase.writeToFile(dirName, fileName, t)
|
||||
}
|
||||
|
||||
go addFile()
|
||||
|
||||
// For !watchDir: expect an update by SourceFile.resetStoreFromPath().
|
||||
// For watchDir: expect at least one update from CREATE & MODIFY inotify event.
|
||||
// Shouldn't expect two updates from CREATE & MODIFY because CREATE doesn't guarantee file written.
|
||||
// In that case no update will be sent from CREATE event.
|
||||
expectUpdate(t, ch, testCase)
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func watchFileChanged(watchDir bool, t *testing.T) {
|
||||
hostname := types.NodeName("random-test-hostname")
|
||||
var testCases = getTestCases(hostname)
|
||||
|
||||
fileNamePre := "test_pod_config"
|
||||
for index, testCase := range testCases {
|
||||
func() {
|
||||
dirName, err := utiltesting.MkTmpdir("dir-test")
|
||||
fileName := fmt.Sprintf("%s_%d", fileNamePre, index)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create temp dir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(dirName)
|
||||
|
||||
var file string
|
||||
lock := &sync.Mutex{}
|
||||
ch := make(chan interface{})
|
||||
func() {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
file = testCase.writeToFile(dirName, fileName, t)
|
||||
}()
|
||||
|
||||
if watchDir {
|
||||
NewSourceFile(dirName, hostname, 100*time.Millisecond, ch)
|
||||
defer func() {
|
||||
// Remove the file
|
||||
deleteFile(dirName, fileName, ch, t)
|
||||
}()
|
||||
} else {
|
||||
NewSourceFile(file, hostname, 100*time.Millisecond, ch)
|
||||
}
|
||||
// expect an update by SourceFile.resetStoreFromPath()
|
||||
expectUpdate(t, ch, testCase)
|
||||
|
||||
changeFile := func() {
|
||||
// Edit the file content
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
|
||||
pod := testCase.pod.(*v1.Pod)
|
||||
pod.Spec.Containers[0].Name = "image2"
|
||||
|
||||
testCase.expected.Pods[0].Spec.Containers[0].Name = "image2"
|
||||
testCase.writeToFile(dirName, fileName, t)
|
||||
}
|
||||
|
||||
go changeFile()
|
||||
// expect an update by MODIFY inotify event
|
||||
expectUpdate(t, ch, testCase)
|
||||
|
||||
if watchDir {
|
||||
from := fileName
|
||||
fileName = fileName + "_ch"
|
||||
go changeFileName(dirName, from, fileName, t)
|
||||
// expect an update by MOVED_FROM inotify event cause changing file name
|
||||
expectEmptyUpdate(t, ch)
|
||||
// expect an update by MOVED_TO inotify event cause changing file name
|
||||
expectUpdate(t, ch, testCase)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func deleteFile(dir, file string, ch chan interface{}, t *testing.T) {
|
||||
go func() {
|
||||
path := filepath.Join(dir, file)
|
||||
err := os.Remove(path)
|
||||
if err != nil {
|
||||
t.Errorf("unable to remove test file %s: %s", path, err)
|
||||
}
|
||||
}()
|
||||
|
||||
expectEmptyUpdate(t, ch)
|
||||
}
|
||||
|
||||
func expectUpdate(t *testing.T, ch chan interface{}, testCase *testCase) {
|
||||
timer := time.After(5 * time.Second)
|
||||
for {
|
||||
select {
|
||||
case got := <-ch:
|
||||
update := got.(kubetypes.PodUpdate)
|
||||
for _, pod := range update.Pods {
|
||||
// TODO: remove the conversion when validation is performed on versioned objects.
|
||||
internalPod := &api.Pod{}
|
||||
if err := v1.Convert_v1_Pod_To_api_Pod(pod, internalPod, nil); err != nil {
|
||||
t.Fatalf("%s: Cannot convert pod %#v, %#v", testCase.desc, pod, err)
|
||||
}
|
||||
if errs := validation.ValidatePod(internalPod); len(errs) > 0 {
|
||||
t.Fatalf("%s: Invalid pod %#v, %#v", testCase.desc, internalPod, errs)
|
||||
}
|
||||
}
|
||||
|
||||
if !api.Semantic.DeepEqual(testCase.expected, update) {
|
||||
t.Fatalf("%s: Expected: %#v, Got: %#v", testCase.desc, testCase.expected, update)
|
||||
}
|
||||
return
|
||||
case <-timer:
|
||||
t.Fatalf("%s: Expected update, timeout instead", testCase.desc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func expectEmptyUpdate(t *testing.T, ch chan interface{}) {
|
||||
timer := time.After(5 * time.Second)
|
||||
for {
|
||||
select {
|
||||
case got := <-ch:
|
||||
update := got.(kubetypes.PodUpdate)
|
||||
if len(update.Pods) != 0 {
|
||||
t.Fatalf("expected empty update, got %#v", update)
|
||||
}
|
||||
return
|
||||
case <-timer:
|
||||
t.Fatalf("expected empty update, timeout instead")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func writeFile(filename string, data []byte) error {
|
||||
f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE, 0666)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
n, err := f.Write(data)
|
||||
if err == nil && n < len(data) {
|
||||
err = io.ErrShortWrite
|
||||
}
|
||||
if err1 := f.Close(); err == nil {
|
||||
err = err1
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func changeFileName(dir, from, to string, t *testing.T) {
|
||||
fromPath := filepath.Join(dir, from)
|
||||
toPath := filepath.Join(dir, to)
|
||||
if err := exec.Command("mv", fromPath, toPath).Run(); err != nil {
|
||||
t.Errorf("Fail to change file name: %s", err)
|
||||
}
|
||||
}
|
26
vendor/k8s.io/kubernetes/pkg/kubelet/config/file_unsupported.go
generated
vendored
Normal file
26
vendor/k8s.io/kubernetes/pkg/kubelet/config/file_unsupported.go
generated
vendored
Normal file
|
@ -0,0 +1,26 @@
|
|||
// +build !linux
|
||||
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Reads the pod configuration from file or a directory of files.
|
||||
package config
|
||||
|
||||
import "errors"
|
||||
|
||||
func (s *sourceFile) watch() error {
|
||||
return errors.New("source file is unsupported in this build")
|
||||
}
|
143
vendor/k8s.io/kubernetes/pkg/kubelet/config/http.go
generated
vendored
Normal file
143
vendor/k8s.io/kubernetes/pkg/kubelet/config/http.go
generated
vendored
Normal file
|
@ -0,0 +1,143 @@
|
|||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Reads the pod configuration from an HTTP GET response.
|
||||
package config
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/v1"
|
||||
kubetypes "k8s.io/kubernetes/pkg/kubelet/types"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
)
|
||||
|
||||
type sourceURL struct {
|
||||
url string
|
||||
header http.Header
|
||||
nodeName types.NodeName
|
||||
updates chan<- interface{}
|
||||
data []byte
|
||||
failureLogs int
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
func NewSourceURL(url string, header http.Header, nodeName types.NodeName, period time.Duration, updates chan<- interface{}) {
|
||||
config := &sourceURL{
|
||||
url: url,
|
||||
header: header,
|
||||
nodeName: nodeName,
|
||||
updates: updates,
|
||||
data: nil,
|
||||
// Timing out requests leads to retries. This client is only used to
|
||||
// read the manifest URL passed to kubelet.
|
||||
client: &http.Client{Timeout: 10 * time.Second},
|
||||
}
|
||||
glog.V(1).Infof("Watching URL %s", url)
|
||||
go wait.Until(config.run, period, wait.NeverStop)
|
||||
}
|
||||
|
||||
func (s *sourceURL) run() {
|
||||
if err := s.extractFromURL(); err != nil {
|
||||
// Don't log this multiple times per minute. The first few entries should be
|
||||
// enough to get the point across.
|
||||
if s.failureLogs < 3 {
|
||||
glog.Warningf("Failed to read pods from URL: %v", err)
|
||||
} else if s.failureLogs == 3 {
|
||||
glog.Warningf("Failed to read pods from URL. Dropping verbosity of this message to V(4): %v", err)
|
||||
} else {
|
||||
glog.V(4).Infof("Failed to read pods from URL: %v", err)
|
||||
}
|
||||
s.failureLogs++
|
||||
} else {
|
||||
if s.failureLogs > 0 {
|
||||
glog.Info("Successfully read pods from URL.")
|
||||
s.failureLogs = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *sourceURL) applyDefaults(pod *api.Pod) error {
|
||||
return applyDefaults(pod, s.url, false, s.nodeName)
|
||||
}
|
||||
|
||||
func (s *sourceURL) extractFromURL() error {
|
||||
req, err := http.NewRequest("GET", s.url, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header = s.header
|
||||
resp, err := s.client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("%v: %v", s.url, resp.Status)
|
||||
}
|
||||
if len(data) == 0 {
|
||||
// Emit an update with an empty PodList to allow HTTPSource to be marked as seen
|
||||
s.updates <- kubetypes.PodUpdate{Pods: []*v1.Pod{}, Op: kubetypes.SET, Source: kubetypes.HTTPSource}
|
||||
return fmt.Errorf("zero-length data received from %v", s.url)
|
||||
}
|
||||
// Short circuit if the data has not changed since the last time it was read.
|
||||
if bytes.Compare(data, s.data) == 0 {
|
||||
return nil
|
||||
}
|
||||
s.data = data
|
||||
|
||||
// First try as it is a single pod.
|
||||
parsed, pod, singlePodErr := tryDecodeSinglePod(data, s.applyDefaults)
|
||||
if parsed {
|
||||
if singlePodErr != nil {
|
||||
// It parsed but could not be used.
|
||||
return singlePodErr
|
||||
}
|
||||
s.updates <- kubetypes.PodUpdate{Pods: []*v1.Pod{pod}, Op: kubetypes.SET, Source: kubetypes.HTTPSource}
|
||||
return nil
|
||||
}
|
||||
|
||||
// That didn't work, so try a list of pods.
|
||||
parsed, podList, multiPodErr := tryDecodePodList(data, s.applyDefaults)
|
||||
if parsed {
|
||||
if multiPodErr != nil {
|
||||
// It parsed but could not be used.
|
||||
return multiPodErr
|
||||
}
|
||||
pods := make([]*v1.Pod, 0)
|
||||
for i := range podList.Items {
|
||||
pods = append(pods, &podList.Items[i])
|
||||
}
|
||||
s.updates <- kubetypes.PodUpdate{Pods: pods, Op: kubetypes.SET, Source: kubetypes.HTTPSource}
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("%v: received '%v', but couldn't parse as "+
|
||||
"single (%v) or multiple pods (%v).\n",
|
||||
s.url, string(data), singlePodErr, multiPodErr)
|
||||
}
|
361
vendor/k8s.io/kubernetes/pkg/kubelet/config/http_test.go
generated
vendored
Normal file
361
vendor/k8s.io/kubernetes/pkg/kubelet/config/http_test.go
generated
vendored
Normal file
|
@ -0,0 +1,361 @@
|
|||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/testapi"
|
||||
"k8s.io/kubernetes/pkg/api/v1"
|
||||
"k8s.io/kubernetes/pkg/api/validation"
|
||||
kubetypes "k8s.io/kubernetes/pkg/kubelet/types"
|
||||
utiltesting "k8s.io/kubernetes/pkg/util/testing"
|
||||
)
|
||||
|
||||
func TestURLErrorNotExistNoUpdate(t *testing.T) {
|
||||
ch := make(chan interface{})
|
||||
NewSourceURL("http://localhost:49575/_not_found_", http.Header{}, "localhost", time.Millisecond, ch)
|
||||
select {
|
||||
case got := <-ch:
|
||||
t.Errorf("Expected no update, Got %#v", got)
|
||||
case <-time.After(2 * time.Millisecond):
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractFromHttpBadness(t *testing.T) {
|
||||
ch := make(chan interface{}, 1)
|
||||
c := sourceURL{"http://localhost:49575/_not_found_", http.Header{}, "other", ch, nil, 0, http.DefaultClient}
|
||||
if err := c.extractFromURL(); err == nil {
|
||||
t.Errorf("Expected error")
|
||||
}
|
||||
expectEmptyChannel(t, ch)
|
||||
}
|
||||
|
||||
func TestExtractInvalidPods(t *testing.T) {
|
||||
var testCases = []struct {
|
||||
desc string
|
||||
pod *v1.Pod
|
||||
}{
|
||||
{
|
||||
desc: "No version",
|
||||
pod: &v1.Pod{TypeMeta: metav1.TypeMeta{APIVersion: ""}},
|
||||
},
|
||||
{
|
||||
desc: "Invalid version",
|
||||
pod: &v1.Pod{TypeMeta: metav1.TypeMeta{APIVersion: "v1betta2"}},
|
||||
},
|
||||
{
|
||||
desc: "Invalid volume name",
|
||||
pod: &v1.Pod{
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: api.Registry.GroupOrDie(v1.GroupName).GroupVersion.String()},
|
||||
Spec: v1.PodSpec{
|
||||
Volumes: []v1.Volume{{Name: "_INVALID_"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Duplicate volume names",
|
||||
pod: &v1.Pod{
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: api.Registry.GroupOrDie(v1.GroupName).GroupVersion.String()},
|
||||
Spec: v1.PodSpec{
|
||||
Volumes: []v1.Volume{{Name: "repeated"}, {Name: "repeated"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Unspecified container name",
|
||||
pod: &v1.Pod{
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: api.Registry.GroupOrDie(v1.GroupName).GroupVersion.String()},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{{Name: ""}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Invalid container name",
|
||||
pod: &v1.Pod{
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: api.Registry.GroupOrDie(v1.GroupName).GroupVersion.String()},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{{Name: "_INVALID_"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, testCase := range testCases {
|
||||
data, err := json.Marshal(testCase.pod)
|
||||
if err != nil {
|
||||
t.Fatalf("%s: Some weird json problem: %v", testCase.desc, err)
|
||||
}
|
||||
fakeHandler := utiltesting.FakeHandler{
|
||||
StatusCode: 200,
|
||||
ResponseBody: string(data),
|
||||
}
|
||||
testServer := httptest.NewServer(&fakeHandler)
|
||||
defer testServer.Close()
|
||||
ch := make(chan interface{}, 1)
|
||||
c := sourceURL{testServer.URL, http.Header{}, "localhost", ch, nil, 0, http.DefaultClient}
|
||||
if err := c.extractFromURL(); err == nil {
|
||||
t.Errorf("%s: Expected error", testCase.desc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractPodsFromHTTP(t *testing.T) {
|
||||
nodeName := "different-value"
|
||||
|
||||
grace := int64(30)
|
||||
var testCases = []struct {
|
||||
desc string
|
||||
pods runtime.Object
|
||||
expected kubetypes.PodUpdate
|
||||
}{
|
||||
{
|
||||
desc: "Single pod",
|
||||
pods: &v1.Pod{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Pod",
|
||||
APIVersion: "",
|
||||
},
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "foo",
|
||||
UID: "111",
|
||||
Namespace: "mynamespace",
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
NodeName: string(nodeName),
|
||||
Containers: []v1.Container{{Name: "1", Image: "foo", ImagePullPolicy: v1.PullAlways}},
|
||||
SecurityContext: &v1.PodSecurityContext{},
|
||||
},
|
||||
Status: v1.PodStatus{
|
||||
Phase: v1.PodPending,
|
||||
},
|
||||
},
|
||||
expected: CreatePodUpdate(kubetypes.SET,
|
||||
kubetypes.HTTPSource,
|
||||
&v1.Pod{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
UID: "111",
|
||||
Name: "foo" + "-" + nodeName,
|
||||
Namespace: "mynamespace",
|
||||
Annotations: map[string]string{kubetypes.ConfigHashAnnotationKey: "111"},
|
||||
SelfLink: getSelfLink("foo-"+nodeName, "mynamespace"),
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
NodeName: nodeName,
|
||||
RestartPolicy: v1.RestartPolicyAlways,
|
||||
DNSPolicy: v1.DNSClusterFirst,
|
||||
SecurityContext: &v1.PodSecurityContext{},
|
||||
TerminationGracePeriodSeconds: &grace,
|
||||
|
||||
Containers: []v1.Container{{
|
||||
Name: "1",
|
||||
Image: "foo",
|
||||
TerminationMessagePath: "/dev/termination-log",
|
||||
ImagePullPolicy: "Always",
|
||||
}},
|
||||
},
|
||||
Status: v1.PodStatus{
|
||||
Phase: v1.PodPending,
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
desc: "Multiple pods",
|
||||
pods: &v1.PodList{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "PodList",
|
||||
APIVersion: "",
|
||||
},
|
||||
Items: []v1.Pod{
|
||||
{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "foo",
|
||||
UID: "111",
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
NodeName: nodeName,
|
||||
Containers: []v1.Container{{Name: "1", Image: "foo", ImagePullPolicy: v1.PullAlways}},
|
||||
SecurityContext: &v1.PodSecurityContext{},
|
||||
},
|
||||
Status: v1.PodStatus{
|
||||
Phase: v1.PodPending,
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "bar",
|
||||
UID: "222",
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
NodeName: nodeName,
|
||||
Containers: []v1.Container{{Name: "2", Image: "bar:bartag", ImagePullPolicy: ""}},
|
||||
SecurityContext: &v1.PodSecurityContext{},
|
||||
},
|
||||
Status: v1.PodStatus{
|
||||
Phase: v1.PodPending,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: CreatePodUpdate(kubetypes.SET,
|
||||
kubetypes.HTTPSource,
|
||||
&v1.Pod{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
UID: "111",
|
||||
Name: "foo" + "-" + nodeName,
|
||||
Namespace: "default",
|
||||
Annotations: map[string]string{kubetypes.ConfigHashAnnotationKey: "111"},
|
||||
SelfLink: getSelfLink("foo-"+nodeName, kubetypes.NamespaceDefault),
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
NodeName: nodeName,
|
||||
RestartPolicy: v1.RestartPolicyAlways,
|
||||
DNSPolicy: v1.DNSClusterFirst,
|
||||
TerminationGracePeriodSeconds: &grace,
|
||||
SecurityContext: &v1.PodSecurityContext{},
|
||||
|
||||
Containers: []v1.Container{{
|
||||
Name: "1",
|
||||
Image: "foo",
|
||||
TerminationMessagePath: "/dev/termination-log",
|
||||
ImagePullPolicy: "Always",
|
||||
}},
|
||||
},
|
||||
Status: v1.PodStatus{
|
||||
Phase: v1.PodPending,
|
||||
},
|
||||
},
|
||||
&v1.Pod{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
UID: "222",
|
||||
Name: "bar" + "-" + nodeName,
|
||||
Namespace: "default",
|
||||
Annotations: map[string]string{kubetypes.ConfigHashAnnotationKey: "222"},
|
||||
SelfLink: getSelfLink("bar-"+nodeName, kubetypes.NamespaceDefault),
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
NodeName: nodeName,
|
||||
RestartPolicy: v1.RestartPolicyAlways,
|
||||
DNSPolicy: v1.DNSClusterFirst,
|
||||
TerminationGracePeriodSeconds: &grace,
|
||||
SecurityContext: &v1.PodSecurityContext{},
|
||||
|
||||
Containers: []v1.Container{{
|
||||
Name: "2",
|
||||
Image: "bar:bartag",
|
||||
TerminationMessagePath: "/dev/termination-log",
|
||||
ImagePullPolicy: "IfNotPresent",
|
||||
}},
|
||||
},
|
||||
Status: v1.PodStatus{
|
||||
Phase: v1.PodPending,
|
||||
},
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
var versionedPods runtime.Object
|
||||
err := testapi.Default.Converter().Convert(&testCase.pods, &versionedPods, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("%s: error in versioning the pods: %s", testCase.desc, err)
|
||||
}
|
||||
data, err := runtime.Encode(testapi.Default.Codec(), versionedPods)
|
||||
if err != nil {
|
||||
t.Fatalf("%s: error in encoding the pod: %v", testCase.desc, err)
|
||||
}
|
||||
fakeHandler := utiltesting.FakeHandler{
|
||||
StatusCode: 200,
|
||||
ResponseBody: string(data),
|
||||
}
|
||||
testServer := httptest.NewServer(&fakeHandler)
|
||||
defer testServer.Close()
|
||||
ch := make(chan interface{}, 1)
|
||||
c := sourceURL{testServer.URL, http.Header{}, types.NodeName(nodeName), ch, nil, 0, http.DefaultClient}
|
||||
if err := c.extractFromURL(); err != nil {
|
||||
t.Errorf("%s: Unexpected error: %v", testCase.desc, err)
|
||||
continue
|
||||
}
|
||||
update := (<-ch).(kubetypes.PodUpdate)
|
||||
|
||||
if !api.Semantic.DeepEqual(testCase.expected, update) {
|
||||
t.Errorf("%s: Expected: %#v, Got: %#v", testCase.desc, testCase.expected, update)
|
||||
}
|
||||
for _, pod := range update.Pods {
|
||||
// TODO: remove the conversion when validation is performed on versioned objects.
|
||||
internalPod := &api.Pod{}
|
||||
if err := v1.Convert_v1_Pod_To_api_Pod(pod, internalPod, nil); err != nil {
|
||||
t.Fatalf("%s: Cannot convert pod %#v, %#v", testCase.desc, pod, err)
|
||||
}
|
||||
if errs := validation.ValidatePod(internalPod); len(errs) != 0 {
|
||||
t.Errorf("%s: Expected no validation errors on %#v, Got %v", testCase.desc, pod, errs.ToAggregate())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestURLWithHeader(t *testing.T) {
|
||||
pod := &v1.Pod{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: api.Registry.GroupOrDie(v1.GroupName).GroupVersion.String(),
|
||||
Kind: "Pod",
|
||||
},
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Name: "foo",
|
||||
UID: "111",
|
||||
Namespace: "mynamespace",
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
NodeName: "localhost",
|
||||
Containers: []v1.Container{{Name: "1", Image: "foo", ImagePullPolicy: v1.PullAlways}},
|
||||
},
|
||||
}
|
||||
data, err := json.Marshal(pod)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected json marshalling error: %v", err)
|
||||
}
|
||||
fakeHandler := utiltesting.FakeHandler{
|
||||
StatusCode: 200,
|
||||
ResponseBody: string(data),
|
||||
}
|
||||
testServer := httptest.NewServer(&fakeHandler)
|
||||
defer testServer.Close()
|
||||
ch := make(chan interface{}, 1)
|
||||
header := make(http.Header)
|
||||
header.Set("Metadata-Flavor", "Google")
|
||||
c := sourceURL{testServer.URL, header, "localhost", ch, nil, 0, http.DefaultClient}
|
||||
if err := c.extractFromURL(); err != nil {
|
||||
t.Fatalf("Unexpected error extracting from URL: %v", err)
|
||||
}
|
||||
update := (<-ch).(kubetypes.PodUpdate)
|
||||
|
||||
headerVal := fakeHandler.RequestReceived.Header["Metadata-Flavor"]
|
||||
if len(headerVal) != 1 || headerVal[0] != "Google" {
|
||||
t.Errorf("Header missing expected entry %v. Got %v", header, fakeHandler.RequestReceived.Header)
|
||||
}
|
||||
if len(update.Pods) != 1 {
|
||||
t.Errorf("Received wrong number of pods, expected one: %v", update.Pods)
|
||||
}
|
||||
}
|
67
vendor/k8s.io/kubernetes/pkg/kubelet/config/sources.go
generated
vendored
Normal file
67
vendor/k8s.io/kubernetes/pkg/kubelet/config/sources.go
generated
vendored
Normal 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)
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue