mirror of
https://github.com/ceph/ceph-csi.git
synced 2025-06-13 18:43:34 +00:00
vendor files
This commit is contained in:
112
vendor/k8s.io/kubernetes/pkg/kubelet/config/BUILD
generated
vendored
Normal file
112
vendor/k8s.io/kubernetes/pkg/kubelet/config/BUILD
generated
vendored
Normal file
@ -0,0 +1,112 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"apiserver.go",
|
||||
"common.go",
|
||||
"config.go",
|
||||
"defaults.go",
|
||||
"doc.go",
|
||||
"file.go",
|
||||
"file_unsupported.go",
|
||||
"flags.go",
|
||||
"http.go",
|
||||
"sources.go",
|
||||
] + select({
|
||||
"@io_bazel_rules_go//go/platform:linux_amd64": [
|
||||
"file_linux.go",
|
||||
],
|
||||
"//conditions:default": [],
|
||||
}),
|
||||
importpath = "k8s.io/kubernetes/pkg/kubelet/config",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//pkg/api/legacyscheme:go_default_library",
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//pkg/apis/core/helper:go_default_library",
|
||||
"//pkg/apis/core/install:go_default_library",
|
||||
"//pkg/apis/core/v1:go_default_library",
|
||||
"//pkg/apis/core/validation:go_default_library",
|
||||
"//pkg/kubelet/checkpoint:go_default_library",
|
||||
"//pkg/kubelet/container:go_default_library",
|
||||
"//pkg/kubelet/events:go_default_library",
|
||||
"//pkg/kubelet/types:go_default_library",
|
||||
"//pkg/kubelet/util/format:go_default_library",
|
||||
"//pkg/util/config:go_default_library",
|
||||
"//pkg/util/hash:go_default_library",
|
||||
"//vendor/github.com/golang/glog:go_default_library",
|
||||
"//vendor/github.com/spf13/pflag:go_default_library",
|
||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/fields:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/yaml:go_default_library",
|
||||
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
|
||||
"//vendor/k8s.io/client-go/tools/cache:go_default_library",
|
||||
"//vendor/k8s.io/client-go/tools/record:go_default_library",
|
||||
] + select({
|
||||
"@io_bazel_rules_go//go/platform:linux_amd64": [
|
||||
"//vendor/golang.org/x/exp/inotify:go_default_library",
|
||||
],
|
||||
"//conditions:default": [],
|
||||
}),
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"apiserver_test.go",
|
||||
"common_test.go",
|
||||
"config_test.go",
|
||||
"http_test.go",
|
||||
] + select({
|
||||
"@io_bazel_rules_go//go/platform:linux_amd64": [
|
||||
"file_linux_test.go",
|
||||
],
|
||||
"//conditions:default": [],
|
||||
}),
|
||||
importpath = "k8s.io/kubernetes/pkg/kubelet/config",
|
||||
library = ":go_default_library",
|
||||
deps = [
|
||||
"//pkg/api/legacyscheme:go_default_library",
|
||||
"//pkg/api/testapi:go_default_library",
|
||||
"//pkg/apis/core:go_default_library",
|
||||
"//pkg/apis/core/v1:go_default_library",
|
||||
"//pkg/apis/core/validation:go_default_library",
|
||||
"//pkg/kubelet/types:go_default_library",
|
||||
"//pkg/securitycontext:go_default_library",
|
||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/watch:go_default_library",
|
||||
"//vendor/k8s.io/client-go/kubernetes/scheme:go_default_library",
|
||||
"//vendor/k8s.io/client-go/tools/cache:go_default_library",
|
||||
"//vendor/k8s.io/client-go/tools/record:go_default_library",
|
||||
"//vendor/k8s.io/client-go/util/testing:go_default_library",
|
||||
] + select({
|
||||
"@io_bazel_rules_go//go/platform:linux_amd64": [
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
|
||||
],
|
||||
"//conditions:default": [],
|
||||
}),
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
49
vendor/k8s.io/kubernetes/pkg/kubelet/config/apiserver.go
generated
vendored
Normal file
49
vendor/k8s.io/kubernetes/pkg/kubelet/config/apiserver.go
generated
vendored
Normal file
@ -0,0 +1,49 @@
|
||||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Reads the pod configuration from the Kubernetes apiserver.
|
||||
package config
|
||||
|
||||
import (
|
||||
"k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
kubetypes "k8s.io/kubernetes/pkg/kubelet/types"
|
||||
)
|
||||
|
||||
// NewSourceApiserver creates a config source that watches and pulls from the apiserver.
|
||||
func NewSourceApiserver(c clientset.Interface, nodeName types.NodeName, updates chan<- interface{}) {
|
||||
lw := cache.NewListWatchFromClient(c.CoreV1().RESTClient(), "pods", metav1.NamespaceAll, fields.OneTermEqualSelector(api.PodHostField, string(nodeName)))
|
||||
newSourceApiserverFromLW(lw, updates)
|
||||
}
|
||||
|
||||
// newSourceApiserverFromLW holds creates a config source that watches and pulls from the apiserver.
|
||||
func newSourceApiserverFromLW(lw cache.ListerWatcher, updates chan<- interface{}) {
|
||||
send := func(objs []interface{}) {
|
||||
var pods []*v1.Pod
|
||||
for _, o := range objs {
|
||||
pods = append(pods, o.(*v1.Pod))
|
||||
}
|
||||
updates <- kubetypes.PodUpdate{Pods: pods, Op: kubetypes.SET, Source: kubetypes.ApiserverSource}
|
||||
}
|
||||
r := cache.NewReflector(lw, &v1.Pod{}, cache.NewUndeltaStore(send, cache.MetaNamespaceKeyFunc), 0)
|
||||
go r.Run(wait.NeverStop)
|
||||
}
|
194
vendor/k8s.io/kubernetes/pkg/kubelet/config/apiserver_test.go
generated
vendored
Normal file
194
vendor/k8s.io/kubernetes/pkg/kubelet/config/apiserver_test.go
generated
vendored
Normal file
@ -0,0 +1,194 @@
|
||||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
kubetypes "k8s.io/kubernetes/pkg/kubelet/types"
|
||||
)
|
||||
|
||||
type fakePodLW struct {
|
||||
listResp runtime.Object
|
||||
watchResp watch.Interface
|
||||
}
|
||||
|
||||
func (lw fakePodLW) List(options metav1.ListOptions) (runtime.Object, error) {
|
||||
return lw.listResp, nil
|
||||
}
|
||||
|
||||
func (lw fakePodLW) Watch(options metav1.ListOptions) (watch.Interface, error) {
|
||||
return lw.watchResp, nil
|
||||
}
|
||||
|
||||
var _ cache.ListerWatcher = fakePodLW{}
|
||||
|
||||
func TestNewSourceApiserver_UpdatesAndMultiplePods(t *testing.T) {
|
||||
pod1v1 := &v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "p"},
|
||||
Spec: v1.PodSpec{Containers: []v1.Container{{Image: "image/one"}}}}
|
||||
pod1v2 := &v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "p"},
|
||||
Spec: v1.PodSpec{Containers: []v1.Container{{Image: "image/two"}}}}
|
||||
pod2 := &v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "q"},
|
||||
Spec: v1.PodSpec{Containers: []v1.Container{{Image: "image/blah"}}}}
|
||||
|
||||
// Setup fake api client.
|
||||
fakeWatch := watch.NewFake()
|
||||
lw := fakePodLW{
|
||||
listResp: &v1.PodList{Items: []v1.Pod{*pod1v1}},
|
||||
watchResp: fakeWatch,
|
||||
}
|
||||
|
||||
ch := make(chan interface{})
|
||||
|
||||
newSourceApiserverFromLW(lw, ch)
|
||||
|
||||
got, ok := <-ch
|
||||
if !ok {
|
||||
t.Errorf("Unable to read from channel when expected")
|
||||
}
|
||||
update := got.(kubetypes.PodUpdate)
|
||||
expected := CreatePodUpdate(kubetypes.SET, kubetypes.ApiserverSource, pod1v1)
|
||||
if !apiequality.Semantic.DeepEqual(expected, update) {
|
||||
t.Errorf("Expected %#v; Got %#v", expected, update)
|
||||
}
|
||||
|
||||
// Add another pod
|
||||
fakeWatch.Add(pod2)
|
||||
got, ok = <-ch
|
||||
if !ok {
|
||||
t.Errorf("Unable to read from channel when expected")
|
||||
}
|
||||
update = got.(kubetypes.PodUpdate)
|
||||
// Could be sorted either of these two ways:
|
||||
expectedA := CreatePodUpdate(kubetypes.SET, kubetypes.ApiserverSource, pod1v1, pod2)
|
||||
expectedB := CreatePodUpdate(kubetypes.SET, kubetypes.ApiserverSource, pod2, pod1v1)
|
||||
|
||||
if !apiequality.Semantic.DeepEqual(expectedA, update) && !apiequality.Semantic.DeepEqual(expectedB, update) {
|
||||
t.Errorf("Expected %#v or %#v, Got %#v", expectedA, expectedB, update)
|
||||
}
|
||||
|
||||
// Modify pod1
|
||||
fakeWatch.Modify(pod1v2)
|
||||
got, ok = <-ch
|
||||
if !ok {
|
||||
t.Errorf("Unable to read from channel when expected")
|
||||
}
|
||||
update = got.(kubetypes.PodUpdate)
|
||||
expectedA = CreatePodUpdate(kubetypes.SET, kubetypes.ApiserverSource, pod1v2, pod2)
|
||||
expectedB = CreatePodUpdate(kubetypes.SET, kubetypes.ApiserverSource, pod2, pod1v2)
|
||||
|
||||
if !apiequality.Semantic.DeepEqual(expectedA, update) && !apiequality.Semantic.DeepEqual(expectedB, update) {
|
||||
t.Errorf("Expected %#v or %#v, Got %#v", expectedA, expectedB, update)
|
||||
}
|
||||
|
||||
// Delete pod1
|
||||
fakeWatch.Delete(pod1v2)
|
||||
got, ok = <-ch
|
||||
if !ok {
|
||||
t.Errorf("Unable to read from channel when expected")
|
||||
}
|
||||
update = got.(kubetypes.PodUpdate)
|
||||
expected = CreatePodUpdate(kubetypes.SET, kubetypes.ApiserverSource, pod2)
|
||||
if !apiequality.Semantic.DeepEqual(expected, update) {
|
||||
t.Errorf("Expected %#v, Got %#v", expected, update)
|
||||
}
|
||||
|
||||
// Delete pod2
|
||||
fakeWatch.Delete(pod2)
|
||||
got, ok = <-ch
|
||||
if !ok {
|
||||
t.Errorf("Unable to read from channel when expected")
|
||||
}
|
||||
update = got.(kubetypes.PodUpdate)
|
||||
expected = CreatePodUpdate(kubetypes.SET, kubetypes.ApiserverSource)
|
||||
if !apiequality.Semantic.DeepEqual(expected, update) {
|
||||
t.Errorf("Expected %#v, Got %#v", expected, update)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewSourceApiserver_TwoNamespacesSameName(t *testing.T) {
|
||||
pod1 := v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "p", Namespace: "one"},
|
||||
Spec: v1.PodSpec{Containers: []v1.Container{{Image: "image/one"}}}}
|
||||
pod2 := v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "p", Namespace: "two"},
|
||||
Spec: v1.PodSpec{Containers: []v1.Container{{Image: "image/blah"}}}}
|
||||
|
||||
// Setup fake api client.
|
||||
fakeWatch := watch.NewFake()
|
||||
lw := fakePodLW{
|
||||
listResp: &v1.PodList{Items: []v1.Pod{pod1, pod2}},
|
||||
watchResp: fakeWatch,
|
||||
}
|
||||
|
||||
ch := make(chan interface{})
|
||||
|
||||
newSourceApiserverFromLW(lw, ch)
|
||||
|
||||
got, ok := <-ch
|
||||
if !ok {
|
||||
t.Errorf("Unable to read from channel when expected")
|
||||
}
|
||||
update := got.(kubetypes.PodUpdate)
|
||||
// Make sure that we get both pods. Catches bug #2294.
|
||||
if !(len(update.Pods) == 2) {
|
||||
t.Errorf("Expected %d, Got %d", 2, len(update.Pods))
|
||||
}
|
||||
|
||||
// Delete pod1
|
||||
fakeWatch.Delete(&pod1)
|
||||
got, ok = <-ch
|
||||
if !ok {
|
||||
t.Errorf("Unable to read from channel when expected")
|
||||
}
|
||||
update = got.(kubetypes.PodUpdate)
|
||||
if !(len(update.Pods) == 1) {
|
||||
t.Errorf("Expected %d, Got %d", 1, len(update.Pods))
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewSourceApiserverInitialEmptySendsEmptyPodUpdate(t *testing.T) {
|
||||
// Setup fake api client.
|
||||
fakeWatch := watch.NewFake()
|
||||
lw := fakePodLW{
|
||||
listResp: &v1.PodList{Items: []v1.Pod{}},
|
||||
watchResp: fakeWatch,
|
||||
}
|
||||
|
||||
ch := make(chan interface{})
|
||||
|
||||
newSourceApiserverFromLW(lw, ch)
|
||||
|
||||
got, ok := <-ch
|
||||
if !ok {
|
||||
t.Errorf("Unable to read from channel when expected")
|
||||
}
|
||||
update := got.(kubetypes.PodUpdate)
|
||||
expected := CreatePodUpdate(kubetypes.SET, kubetypes.ApiserverSource)
|
||||
if !apiequality.Semantic.DeepEqual(expected, update) {
|
||||
t.Errorf("Expected %#v; Got %#v", expected, update)
|
||||
}
|
||||
}
|
169
vendor/k8s.io/kubernetes/pkg/kubelet/config/common.go
generated
vendored
Normal file
169
vendor/k8s.io/kubernetes/pkg/kubelet/config/common.go
generated
vendored
Normal file
@ -0,0 +1,169 @@
|
||||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Common logic used by both http and file channels.
|
||||
package config
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
utilyaml "k8s.io/apimachinery/pkg/util/yaml"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
"k8s.io/kubernetes/pkg/apis/core/helper"
|
||||
// TODO: remove this import if
|
||||
// api.Registry.GroupOrDie(v1.GroupName).GroupVersion.String() is changed
|
||||
// to "v1"?
|
||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||
_ "k8s.io/kubernetes/pkg/apis/core/install"
|
||||
k8s_api_v1 "k8s.io/kubernetes/pkg/apis/core/v1"
|
||||
"k8s.io/kubernetes/pkg/apis/core/validation"
|
||||
kubetypes "k8s.io/kubernetes/pkg/kubelet/types"
|
||||
"k8s.io/kubernetes/pkg/util/hash"
|
||||
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
// Generate a pod name that is unique among nodes by appending the nodeName.
|
||||
func generatePodName(name string, nodeName types.NodeName) string {
|
||||
return fmt.Sprintf("%s-%s", name, nodeName)
|
||||
}
|
||||
|
||||
func applyDefaults(pod *api.Pod, source string, isFile bool, nodeName types.NodeName) error {
|
||||
if len(pod.UID) == 0 {
|
||||
hasher := md5.New()
|
||||
if isFile {
|
||||
fmt.Fprintf(hasher, "host:%s", nodeName)
|
||||
fmt.Fprintf(hasher, "file:%s", source)
|
||||
} else {
|
||||
fmt.Fprintf(hasher, "url:%s", source)
|
||||
}
|
||||
hash.DeepHashObject(hasher, pod)
|
||||
pod.UID = types.UID(hex.EncodeToString(hasher.Sum(nil)[0:]))
|
||||
glog.V(5).Infof("Generated UID %q pod %q from %s", pod.UID, pod.Name, source)
|
||||
}
|
||||
|
||||
pod.Name = generatePodName(pod.Name, nodeName)
|
||||
glog.V(5).Infof("Generated Name %q for UID %q from URL %s", pod.Name, pod.UID, source)
|
||||
|
||||
if pod.Namespace == "" {
|
||||
pod.Namespace = metav1.NamespaceDefault
|
||||
}
|
||||
glog.V(5).Infof("Using namespace %q for pod %q from %s", pod.Namespace, pod.Name, source)
|
||||
|
||||
// Set the Host field to indicate this pod is scheduled on the current node.
|
||||
pod.Spec.NodeName = string(nodeName)
|
||||
|
||||
pod.ObjectMeta.SelfLink = getSelfLink(pod.Name, pod.Namespace)
|
||||
|
||||
if pod.Annotations == nil {
|
||||
pod.Annotations = make(map[string]string)
|
||||
}
|
||||
// The generated UID is the hash of the file.
|
||||
pod.Annotations[kubetypes.ConfigHashAnnotationKey] = string(pod.UID)
|
||||
|
||||
if isFile {
|
||||
// Applying the default Taint tolerations to static pods,
|
||||
// so they are not evicted when there are node problems.
|
||||
helper.AddOrUpdateTolerationInPod(pod, &api.Toleration{
|
||||
Operator: "Exists",
|
||||
Effect: api.TaintEffectNoExecute,
|
||||
})
|
||||
}
|
||||
|
||||
// Set the default status to pending.
|
||||
pod.Status.Phase = api.PodPending
|
||||
return nil
|
||||
}
|
||||
|
||||
func getSelfLink(name, namespace string) string {
|
||||
var selfLink string
|
||||
if len(namespace) == 0 {
|
||||
namespace = metav1.NamespaceDefault
|
||||
}
|
||||
selfLink = fmt.Sprintf("/api/"+legacyscheme.Registry.GroupOrDie(api.GroupName).GroupVersion.Version+"/namespaces/%s/pods/%s", namespace, name)
|
||||
return selfLink
|
||||
}
|
||||
|
||||
type defaultFunc func(pod *api.Pod) error
|
||||
|
||||
func tryDecodeSinglePod(data []byte, defaultFn defaultFunc) (parsed bool, pod *v1.Pod, err error) {
|
||||
// JSON is valid YAML, so this should work for everything.
|
||||
json, err := utilyaml.ToJSON(data)
|
||||
if err != nil {
|
||||
return false, nil, err
|
||||
}
|
||||
obj, err := runtime.Decode(legacyscheme.Codecs.UniversalDecoder(), json)
|
||||
if err != nil {
|
||||
return false, pod, err
|
||||
}
|
||||
|
||||
newPod, ok := obj.(*api.Pod)
|
||||
// Check whether the object could be converted to single pod.
|
||||
if !ok {
|
||||
return false, pod, fmt.Errorf("invalid pod: %#v", obj)
|
||||
}
|
||||
|
||||
// Apply default values and validate the pod.
|
||||
if err = defaultFn(newPod); err != nil {
|
||||
return true, pod, err
|
||||
}
|
||||
if errs := validation.ValidatePod(newPod); len(errs) > 0 {
|
||||
return true, pod, fmt.Errorf("invalid pod: %v", errs)
|
||||
}
|
||||
v1Pod := &v1.Pod{}
|
||||
if err := k8s_api_v1.Convert_core_Pod_To_v1_Pod(newPod, v1Pod, nil); err != nil {
|
||||
glog.Errorf("Pod %q failed to convert to v1", newPod.Name)
|
||||
return true, nil, err
|
||||
}
|
||||
return true, v1Pod, nil
|
||||
}
|
||||
|
||||
func tryDecodePodList(data []byte, defaultFn defaultFunc) (parsed bool, pods v1.PodList, err error) {
|
||||
obj, err := runtime.Decode(legacyscheme.Codecs.UniversalDecoder(), data)
|
||||
if err != nil {
|
||||
return false, pods, err
|
||||
}
|
||||
|
||||
newPods, ok := obj.(*api.PodList)
|
||||
// Check whether the object could be converted to list of pods.
|
||||
if !ok {
|
||||
err = fmt.Errorf("invalid pods list: %#v", obj)
|
||||
return false, pods, err
|
||||
}
|
||||
|
||||
// Apply default values and validate pods.
|
||||
for i := range newPods.Items {
|
||||
newPod := &newPods.Items[i]
|
||||
if err = defaultFn(newPod); err != nil {
|
||||
return true, pods, err
|
||||
}
|
||||
if errs := validation.ValidatePod(newPod); len(errs) > 0 {
|
||||
err = fmt.Errorf("invalid pod: %v", errs)
|
||||
return true, pods, err
|
||||
}
|
||||
}
|
||||
v1Pods := &v1.PodList{}
|
||||
if err := k8s_api_v1.Convert_core_PodList_To_v1_PodList(newPods, v1Pods, nil); err != nil {
|
||||
return true, pods, err
|
||||
}
|
||||
return true, *v1Pods, err
|
||||
}
|
191
vendor/k8s.io/kubernetes/pkg/kubelet/config/common_test.go
generated
vendored
Normal file
191
vendor/k8s.io/kubernetes/pkg/kubelet/config/common_test.go
generated
vendored
Normal file
@ -0,0 +1,191 @@
|
||||
/*
|
||||
Copyright 2015 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||
"k8s.io/kubernetes/pkg/api/testapi"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
"k8s.io/kubernetes/pkg/securitycontext"
|
||||
)
|
||||
|
||||
func noDefault(*api.Pod) error { return nil }
|
||||
|
||||
func TestDecodeSinglePod(t *testing.T) {
|
||||
grace := int64(30)
|
||||
pod := &v1.Pod{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test",
|
||||
UID: "12345",
|
||||
Namespace: "mynamespace",
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
RestartPolicy: v1.RestartPolicyAlways,
|
||||
DNSPolicy: v1.DNSClusterFirst,
|
||||
TerminationGracePeriodSeconds: &grace,
|
||||
Containers: []v1.Container{{
|
||||
Name: "image",
|
||||
Image: "test/image",
|
||||
ImagePullPolicy: "IfNotPresent",
|
||||
TerminationMessagePath: "/dev/termination-log",
|
||||
TerminationMessagePolicy: v1.TerminationMessageReadFile,
|
||||
SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults(),
|
||||
}},
|
||||
SecurityContext: &v1.PodSecurityContext{},
|
||||
SchedulerName: api.DefaultSchedulerName,
|
||||
},
|
||||
}
|
||||
json, err := runtime.Encode(testapi.Default.Codec(), pod)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
parsed, podOut, err := tryDecodeSinglePod(json, noDefault)
|
||||
if !parsed {
|
||||
t.Errorf("expected to have parsed file: (%s)", string(json))
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v (%s)", err, string(json))
|
||||
}
|
||||
if !reflect.DeepEqual(pod, podOut) {
|
||||
t.Errorf("expected:\n%#v\ngot:\n%#v\n%s", pod, podOut, string(json))
|
||||
}
|
||||
|
||||
for _, gv := range legacyscheme.Registry.EnabledVersionsForGroup(v1.GroupName) {
|
||||
info, _ := runtime.SerializerInfoForMediaType(legacyscheme.Codecs.SupportedMediaTypes(), "application/yaml")
|
||||
encoder := legacyscheme.Codecs.EncoderForVersion(info.Serializer, gv)
|
||||
yaml, err := runtime.Encode(encoder, pod)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
parsed, podOut, err = tryDecodeSinglePod(yaml, noDefault)
|
||||
if !parsed {
|
||||
t.Errorf("expected to have parsed file: (%s)", string(yaml))
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v (%s)", err, string(yaml))
|
||||
}
|
||||
if !reflect.DeepEqual(pod, podOut) {
|
||||
t.Errorf("expected:\n%#v\ngot:\n%#v\n%s", pod, podOut, string(yaml))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDecodePodList(t *testing.T) {
|
||||
grace := int64(30)
|
||||
pod := &v1.Pod{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test",
|
||||
UID: "12345",
|
||||
Namespace: "mynamespace",
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
RestartPolicy: v1.RestartPolicyAlways,
|
||||
DNSPolicy: v1.DNSClusterFirst,
|
||||
TerminationGracePeriodSeconds: &grace,
|
||||
Containers: []v1.Container{{
|
||||
Name: "image",
|
||||
Image: "test/image",
|
||||
ImagePullPolicy: "IfNotPresent",
|
||||
TerminationMessagePath: "/dev/termination-log",
|
||||
TerminationMessagePolicy: v1.TerminationMessageReadFile,
|
||||
|
||||
SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults(),
|
||||
}},
|
||||
SecurityContext: &v1.PodSecurityContext{},
|
||||
SchedulerName: api.DefaultSchedulerName,
|
||||
},
|
||||
}
|
||||
podList := &v1.PodList{
|
||||
Items: []v1.Pod{*pod},
|
||||
}
|
||||
json, err := runtime.Encode(testapi.Default.Codec(), podList)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
parsed, podListOut, err := tryDecodePodList(json, noDefault)
|
||||
if !parsed {
|
||||
t.Errorf("expected to have parsed file: (%s)", string(json))
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v (%s)", err, string(json))
|
||||
}
|
||||
if !reflect.DeepEqual(podList, &podListOut) {
|
||||
t.Errorf("expected:\n%#v\ngot:\n%#v\n%s", podList, &podListOut, string(json))
|
||||
}
|
||||
|
||||
for _, gv := range legacyscheme.Registry.EnabledVersionsForGroup(v1.GroupName) {
|
||||
info, _ := runtime.SerializerInfoForMediaType(legacyscheme.Codecs.SupportedMediaTypes(), "application/yaml")
|
||||
encoder := legacyscheme.Codecs.EncoderForVersion(info.Serializer, gv)
|
||||
yaml, err := runtime.Encode(encoder, podList)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
parsed, podListOut, err = tryDecodePodList(yaml, noDefault)
|
||||
if !parsed {
|
||||
t.Errorf("expected to have parsed file: (%s): %v", string(yaml), err)
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v (%s)", err, string(yaml))
|
||||
continue
|
||||
}
|
||||
if !reflect.DeepEqual(podList, &podListOut) {
|
||||
t.Errorf("expected:\n%#v\ngot:\n%#v\n%s", pod, &podListOut, string(yaml))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetSelfLink(t *testing.T) {
|
||||
var testCases = []struct {
|
||||
desc string
|
||||
name string
|
||||
namespace string
|
||||
expectedSelfLink string
|
||||
}{
|
||||
{
|
||||
desc: "No namespace specified",
|
||||
name: "foo",
|
||||
namespace: "",
|
||||
expectedSelfLink: "/api/v1/namespaces/default/pods/foo",
|
||||
},
|
||||
{
|
||||
desc: "Namespace specified",
|
||||
name: "foo",
|
||||
namespace: "bar",
|
||||
expectedSelfLink: "/api/v1/namespaces/bar/pods/foo",
|
||||
},
|
||||
}
|
||||
for _, testCase := range testCases {
|
||||
selfLink := getSelfLink(testCase.name, testCase.namespace)
|
||||
if testCase.expectedSelfLink != selfLink {
|
||||
t.Errorf("%s: getSelfLink error, expected: %s, got: %s", testCase.desc, testCase.expectedSelfLink, selfLink)
|
||||
}
|
||||
}
|
||||
}
|
508
vendor/k8s.io/kubernetes/pkg/kubelet/config/config.go
generated
vendored
Normal file
508
vendor/k8s.io/kubernetes/pkg/kubelet/config/config.go
generated
vendored
Normal file
@ -0,0 +1,508 @@
|
||||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sync"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/client-go/tools/record"
|
||||
"k8s.io/kubernetes/pkg/kubelet/checkpoint"
|
||||
kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
|
||||
"k8s.io/kubernetes/pkg/kubelet/events"
|
||||
kubetypes "k8s.io/kubernetes/pkg/kubelet/types"
|
||||
"k8s.io/kubernetes/pkg/kubelet/util/format"
|
||||
"k8s.io/kubernetes/pkg/util/config"
|
||||
)
|
||||
|
||||
// PodConfigNotificationMode describes how changes are sent to the update channel.
|
||||
type PodConfigNotificationMode int
|
||||
|
||||
const (
|
||||
// PodConfigNotificationUnknown is the default value for
|
||||
// PodConfigNotificationMode when uninitialized.
|
||||
PodConfigNotificationUnknown = iota
|
||||
// PodConfigNotificationSnapshot delivers the full configuration as a SET whenever
|
||||
// any change occurs.
|
||||
PodConfigNotificationSnapshot
|
||||
// PodConfigNotificationSnapshotAndUpdates delivers an UPDATE and DELETE message whenever pods are
|
||||
// changed, and a SET message if there are any additions or removals.
|
||||
PodConfigNotificationSnapshotAndUpdates
|
||||
// PodConfigNotificationIncremental delivers ADD, UPDATE, DELETE, REMOVE, RECONCILE to the update channel.
|
||||
PodConfigNotificationIncremental
|
||||
)
|
||||
|
||||
// PodConfig is a configuration mux that merges many sources of pod configuration into a single
|
||||
// consistent structure, and then delivers incremental change notifications to listeners
|
||||
// in order.
|
||||
type PodConfig struct {
|
||||
pods *podStorage
|
||||
mux *config.Mux
|
||||
|
||||
// the channel of denormalized changes passed to listeners
|
||||
updates chan kubetypes.PodUpdate
|
||||
|
||||
// contains the list of all configured sources
|
||||
sourcesLock sync.Mutex
|
||||
sources sets.String
|
||||
checkpointManager checkpoint.Manager
|
||||
}
|
||||
|
||||
// NewPodConfig creates an object that can merge many configuration sources into a stream
|
||||
// of normalized updates to a pod configuration.
|
||||
func NewPodConfig(mode PodConfigNotificationMode, recorder record.EventRecorder) *PodConfig {
|
||||
updates := make(chan kubetypes.PodUpdate, 50)
|
||||
storage := newPodStorage(updates, mode, recorder)
|
||||
podConfig := &PodConfig{
|
||||
pods: storage,
|
||||
mux: config.NewMux(storage),
|
||||
updates: updates,
|
||||
sources: sets.String{},
|
||||
}
|
||||
return podConfig
|
||||
}
|
||||
|
||||
// Channel creates or returns a config source channel. The channel
|
||||
// only accepts PodUpdates
|
||||
func (c *PodConfig) Channel(source string) chan<- interface{} {
|
||||
c.sourcesLock.Lock()
|
||||
defer c.sourcesLock.Unlock()
|
||||
c.sources.Insert(source)
|
||||
return c.mux.Channel(source)
|
||||
}
|
||||
|
||||
// SeenAllSources returns true if seenSources contains all sources in the
|
||||
// config, and also this config has received a SET message from each source.
|
||||
func (c *PodConfig) SeenAllSources(seenSources sets.String) bool {
|
||||
if c.pods == nil {
|
||||
return false
|
||||
}
|
||||
glog.V(6).Infof("Looking for %v, have seen %v", c.sources.List(), seenSources)
|
||||
return seenSources.HasAll(c.sources.List()...) && c.pods.seenSources(c.sources.List()...)
|
||||
}
|
||||
|
||||
// Updates returns a channel of updates to the configuration, properly denormalized.
|
||||
func (c *PodConfig) Updates() <-chan kubetypes.PodUpdate {
|
||||
return c.updates
|
||||
}
|
||||
|
||||
// Sync requests the full configuration be delivered to the update channel.
|
||||
func (c *PodConfig) Sync() {
|
||||
c.pods.Sync()
|
||||
}
|
||||
|
||||
// Restore restores pods from the checkpoint path, *once*
|
||||
func (c *PodConfig) Restore(path string, updates chan<- interface{}) error {
|
||||
var err error
|
||||
if c.checkpointManager == nil {
|
||||
c.checkpointManager = checkpoint.NewCheckpointManager(path)
|
||||
pods, err := c.checkpointManager.LoadPods()
|
||||
if err == nil {
|
||||
updates <- kubetypes.PodUpdate{Pods: pods, Op: kubetypes.RESTORE, Source: kubetypes.ApiserverSource}
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// podStorage manages the current pod state at any point in time and ensures updates
|
||||
// to the channel are delivered in order. Note that this object is an in-memory source of
|
||||
// "truth" and on creation contains zero entries. Once all previously read sources are
|
||||
// available, then this object should be considered authoritative.
|
||||
type podStorage struct {
|
||||
podLock sync.RWMutex
|
||||
// map of source name to pod uid to pod reference
|
||||
pods map[string]map[types.UID]*v1.Pod
|
||||
mode PodConfigNotificationMode
|
||||
|
||||
// ensures that updates are delivered in strict order
|
||||
// on the updates channel
|
||||
updateLock sync.Mutex
|
||||
updates chan<- kubetypes.PodUpdate
|
||||
|
||||
// contains the set of all sources that have sent at least one SET
|
||||
sourcesSeenLock sync.RWMutex
|
||||
sourcesSeen sets.String
|
||||
|
||||
// the EventRecorder to use
|
||||
recorder record.EventRecorder
|
||||
}
|
||||
|
||||
// TODO: PodConfigNotificationMode could be handled by a listener to the updates channel
|
||||
// in the future, especially with multiple listeners.
|
||||
// TODO: allow initialization of the current state of the store with snapshotted version.
|
||||
func newPodStorage(updates chan<- kubetypes.PodUpdate, mode PodConfigNotificationMode, recorder record.EventRecorder) *podStorage {
|
||||
return &podStorage{
|
||||
pods: make(map[string]map[types.UID]*v1.Pod),
|
||||
mode: mode,
|
||||
updates: updates,
|
||||
sourcesSeen: sets.String{},
|
||||
recorder: recorder,
|
||||
}
|
||||
}
|
||||
|
||||
// Merge normalizes a set of incoming changes from different sources into a map of all Pods
|
||||
// and ensures that redundant changes are filtered out, and then pushes zero or more minimal
|
||||
// updates onto the update channel. Ensures that updates are delivered in order.
|
||||
func (s *podStorage) Merge(source string, change interface{}) error {
|
||||
s.updateLock.Lock()
|
||||
defer s.updateLock.Unlock()
|
||||
|
||||
seenBefore := s.sourcesSeen.Has(source)
|
||||
adds, updates, deletes, removes, reconciles, restores := s.merge(source, change)
|
||||
firstSet := !seenBefore && s.sourcesSeen.Has(source)
|
||||
|
||||
// deliver update notifications
|
||||
switch s.mode {
|
||||
case PodConfigNotificationIncremental:
|
||||
if len(removes.Pods) > 0 {
|
||||
s.updates <- *removes
|
||||
}
|
||||
if len(adds.Pods) > 0 {
|
||||
s.updates <- *adds
|
||||
}
|
||||
if len(updates.Pods) > 0 {
|
||||
s.updates <- *updates
|
||||
}
|
||||
if len(deletes.Pods) > 0 {
|
||||
s.updates <- *deletes
|
||||
}
|
||||
if len(restores.Pods) > 0 {
|
||||
s.updates <- *restores
|
||||
}
|
||||
if firstSet && len(adds.Pods) == 0 && len(updates.Pods) == 0 && len(deletes.Pods) == 0 {
|
||||
// Send an empty update when first seeing the source and there are
|
||||
// no ADD or UPDATE or DELETE pods from the source. This signals kubelet that
|
||||
// the source is ready.
|
||||
s.updates <- *adds
|
||||
}
|
||||
// Only add reconcile support here, because kubelet doesn't support Snapshot update now.
|
||||
if len(reconciles.Pods) > 0 {
|
||||
s.updates <- *reconciles
|
||||
}
|
||||
|
||||
case PodConfigNotificationSnapshotAndUpdates:
|
||||
if len(removes.Pods) > 0 || len(adds.Pods) > 0 || firstSet {
|
||||
s.updates <- kubetypes.PodUpdate{Pods: s.MergedState().([]*v1.Pod), Op: kubetypes.SET, Source: source}
|
||||
}
|
||||
if len(updates.Pods) > 0 {
|
||||
s.updates <- *updates
|
||||
}
|
||||
if len(deletes.Pods) > 0 {
|
||||
s.updates <- *deletes
|
||||
}
|
||||
|
||||
case PodConfigNotificationSnapshot:
|
||||
if len(updates.Pods) > 0 || len(deletes.Pods) > 0 || len(adds.Pods) > 0 || len(removes.Pods) > 0 || firstSet {
|
||||
s.updates <- kubetypes.PodUpdate{Pods: s.MergedState().([]*v1.Pod), Op: kubetypes.SET, Source: source}
|
||||
}
|
||||
|
||||
case PodConfigNotificationUnknown:
|
||||
fallthrough
|
||||
default:
|
||||
panic(fmt.Sprintf("unsupported PodConfigNotificationMode: %#v", s.mode))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *podStorage) merge(source string, change interface{}) (adds, updates, deletes, removes, reconciles, restores *kubetypes.PodUpdate) {
|
||||
s.podLock.Lock()
|
||||
defer s.podLock.Unlock()
|
||||
|
||||
addPods := []*v1.Pod{}
|
||||
updatePods := []*v1.Pod{}
|
||||
deletePods := []*v1.Pod{}
|
||||
removePods := []*v1.Pod{}
|
||||
reconcilePods := []*v1.Pod{}
|
||||
restorePods := []*v1.Pod{}
|
||||
|
||||
pods := s.pods[source]
|
||||
if pods == nil {
|
||||
pods = make(map[types.UID]*v1.Pod)
|
||||
}
|
||||
|
||||
// updatePodFunc is the local function which updates the pod cache *oldPods* with new pods *newPods*.
|
||||
// After updated, new pod will be stored in the pod cache *pods*.
|
||||
// Notice that *pods* and *oldPods* could be the same cache.
|
||||
updatePodsFunc := func(newPods []*v1.Pod, oldPods, pods map[types.UID]*v1.Pod) {
|
||||
filtered := filterInvalidPods(newPods, source, s.recorder)
|
||||
for _, ref := range filtered {
|
||||
// Annotate the pod with the source before any comparison.
|
||||
if ref.Annotations == nil {
|
||||
ref.Annotations = make(map[string]string)
|
||||
}
|
||||
ref.Annotations[kubetypes.ConfigSourceAnnotationKey] = source
|
||||
if existing, found := oldPods[ref.UID]; found {
|
||||
pods[ref.UID] = existing
|
||||
needUpdate, needReconcile, needGracefulDelete := checkAndUpdatePod(existing, ref)
|
||||
if needUpdate {
|
||||
updatePods = append(updatePods, existing)
|
||||
} else if needReconcile {
|
||||
reconcilePods = append(reconcilePods, existing)
|
||||
} else if needGracefulDelete {
|
||||
deletePods = append(deletePods, existing)
|
||||
}
|
||||
continue
|
||||
}
|
||||
recordFirstSeenTime(ref)
|
||||
pods[ref.UID] = ref
|
||||
addPods = append(addPods, ref)
|
||||
}
|
||||
}
|
||||
|
||||
update := change.(kubetypes.PodUpdate)
|
||||
switch update.Op {
|
||||
case kubetypes.ADD, kubetypes.UPDATE, kubetypes.DELETE:
|
||||
if update.Op == kubetypes.ADD {
|
||||
glog.V(4).Infof("Adding new pods from source %s : %v", source, update.Pods)
|
||||
} else if update.Op == kubetypes.DELETE {
|
||||
glog.V(4).Infof("Graceful deleting pods from source %s : %v", source, update.Pods)
|
||||
} else {
|
||||
glog.V(4).Infof("Updating pods from source %s : %v", source, update.Pods)
|
||||
}
|
||||
updatePodsFunc(update.Pods, pods, pods)
|
||||
|
||||
case kubetypes.REMOVE:
|
||||
glog.V(4).Infof("Removing pods from source %s : %v", source, update.Pods)
|
||||
for _, value := range update.Pods {
|
||||
if existing, found := pods[value.UID]; found {
|
||||
// this is a delete
|
||||
delete(pods, value.UID)
|
||||
removePods = append(removePods, existing)
|
||||
continue
|
||||
}
|
||||
// this is a no-op
|
||||
}
|
||||
|
||||
case kubetypes.SET:
|
||||
glog.V(4).Infof("Setting pods for source %s", source)
|
||||
s.markSourceSet(source)
|
||||
// Clear the old map entries by just creating a new map
|
||||
oldPods := pods
|
||||
pods = make(map[types.UID]*v1.Pod)
|
||||
updatePodsFunc(update.Pods, oldPods, pods)
|
||||
for uid, existing := range oldPods {
|
||||
if _, found := pods[uid]; !found {
|
||||
// this is a delete
|
||||
removePods = append(removePods, existing)
|
||||
}
|
||||
}
|
||||
case kubetypes.RESTORE:
|
||||
glog.V(4).Infof("Restoring pods for source %s", source)
|
||||
|
||||
default:
|
||||
glog.Warningf("Received invalid update type: %v", update)
|
||||
|
||||
}
|
||||
|
||||
s.pods[source] = pods
|
||||
|
||||
adds = &kubetypes.PodUpdate{Op: kubetypes.ADD, Pods: copyPods(addPods), Source: source}
|
||||
updates = &kubetypes.PodUpdate{Op: kubetypes.UPDATE, Pods: copyPods(updatePods), Source: source}
|
||||
deletes = &kubetypes.PodUpdate{Op: kubetypes.DELETE, Pods: copyPods(deletePods), Source: source}
|
||||
removes = &kubetypes.PodUpdate{Op: kubetypes.REMOVE, Pods: copyPods(removePods), Source: source}
|
||||
reconciles = &kubetypes.PodUpdate{Op: kubetypes.RECONCILE, Pods: copyPods(reconcilePods), Source: source}
|
||||
restores = &kubetypes.PodUpdate{Op: kubetypes.RESTORE, Pods: copyPods(restorePods), Source: source}
|
||||
|
||||
return adds, updates, deletes, removes, reconciles, restores
|
||||
}
|
||||
|
||||
func (s *podStorage) markSourceSet(source string) {
|
||||
s.sourcesSeenLock.Lock()
|
||||
defer s.sourcesSeenLock.Unlock()
|
||||
s.sourcesSeen.Insert(source)
|
||||
}
|
||||
|
||||
func (s *podStorage) seenSources(sources ...string) bool {
|
||||
s.sourcesSeenLock.RLock()
|
||||
defer s.sourcesSeenLock.RUnlock()
|
||||
return s.sourcesSeen.HasAll(sources...)
|
||||
}
|
||||
|
||||
func filterInvalidPods(pods []*v1.Pod, source string, recorder record.EventRecorder) (filtered []*v1.Pod) {
|
||||
names := sets.String{}
|
||||
for i, pod := range pods {
|
||||
// Pods from each source are assumed to have passed validation individually.
|
||||
// This function only checks if there is any naming conflict.
|
||||
name := kubecontainer.GetPodFullName(pod)
|
||||
if names.Has(name) {
|
||||
glog.Warningf("Pod[%d] (%s) from %s failed validation due to duplicate pod name %q, ignoring", i+1, format.Pod(pod), source, pod.Name)
|
||||
recorder.Eventf(pod, v1.EventTypeWarning, events.FailedValidation, "Error validating pod %s from %s due to duplicate pod name %q, ignoring", format.Pod(pod), source, pod.Name)
|
||||
continue
|
||||
} else {
|
||||
names.Insert(name)
|
||||
}
|
||||
|
||||
filtered = append(filtered, pod)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Annotations that the kubelet adds to the pod.
|
||||
var localAnnotations = []string{
|
||||
kubetypes.ConfigSourceAnnotationKey,
|
||||
kubetypes.ConfigMirrorAnnotationKey,
|
||||
kubetypes.ConfigFirstSeenAnnotationKey,
|
||||
}
|
||||
|
||||
func isLocalAnnotationKey(key string) bool {
|
||||
for _, localKey := range localAnnotations {
|
||||
if key == localKey {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// isAnnotationMapEqual returns true if the existing annotation Map is equal to candidate except
|
||||
// for local annotations.
|
||||
func isAnnotationMapEqual(existingMap, candidateMap map[string]string) bool {
|
||||
if candidateMap == nil {
|
||||
candidateMap = make(map[string]string)
|
||||
}
|
||||
for k, v := range candidateMap {
|
||||
if isLocalAnnotationKey(k) {
|
||||
continue
|
||||
}
|
||||
if existingValue, ok := existingMap[k]; ok && existingValue == v {
|
||||
continue
|
||||
}
|
||||
return false
|
||||
}
|
||||
for k := range existingMap {
|
||||
if isLocalAnnotationKey(k) {
|
||||
continue
|
||||
}
|
||||
// stale entry in existing map.
|
||||
if _, exists := candidateMap[k]; !exists {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// recordFirstSeenTime records the first seen time of this pod.
|
||||
func recordFirstSeenTime(pod *v1.Pod) {
|
||||
glog.V(4).Infof("Receiving a new pod %q", format.Pod(pod))
|
||||
pod.Annotations[kubetypes.ConfigFirstSeenAnnotationKey] = kubetypes.NewTimestamp().GetString()
|
||||
}
|
||||
|
||||
// updateAnnotations returns an Annotation map containing the api annotation map plus
|
||||
// locally managed annotations
|
||||
func updateAnnotations(existing, ref *v1.Pod) {
|
||||
annotations := make(map[string]string, len(ref.Annotations)+len(localAnnotations))
|
||||
for k, v := range ref.Annotations {
|
||||
annotations[k] = v
|
||||
}
|
||||
for _, k := range localAnnotations {
|
||||
if v, ok := existing.Annotations[k]; ok {
|
||||
annotations[k] = v
|
||||
}
|
||||
}
|
||||
existing.Annotations = annotations
|
||||
}
|
||||
|
||||
func podsDifferSemantically(existing, ref *v1.Pod) bool {
|
||||
if reflect.DeepEqual(existing.Spec, ref.Spec) &&
|
||||
reflect.DeepEqual(existing.Labels, ref.Labels) &&
|
||||
reflect.DeepEqual(existing.DeletionTimestamp, ref.DeletionTimestamp) &&
|
||||
reflect.DeepEqual(existing.DeletionGracePeriodSeconds, ref.DeletionGracePeriodSeconds) &&
|
||||
isAnnotationMapEqual(existing.Annotations, ref.Annotations) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// checkAndUpdatePod updates existing, and:
|
||||
// * if ref makes a meaningful change, returns needUpdate=true
|
||||
// * if ref makes a meaningful change, and this change is graceful deletion, returns needGracefulDelete=true
|
||||
// * if ref makes no meaningful change, but changes the pod status, returns needReconcile=true
|
||||
// * else return all false
|
||||
// Now, needUpdate, needGracefulDelete and needReconcile should never be both true
|
||||
func checkAndUpdatePod(existing, ref *v1.Pod) (needUpdate, needReconcile, needGracefulDelete bool) {
|
||||
|
||||
// 1. this is a reconcile
|
||||
// TODO: it would be better to update the whole object and only preserve certain things
|
||||
// like the source annotation or the UID (to ensure safety)
|
||||
if !podsDifferSemantically(existing, ref) {
|
||||
// this is not an update
|
||||
// Only check reconcile when it is not an update, because if the pod is going to
|
||||
// be updated, an extra reconcile is unnecessary
|
||||
if !reflect.DeepEqual(existing.Status, ref.Status) {
|
||||
// Pod with changed pod status needs reconcile, because kubelet should
|
||||
// be the source of truth of pod status.
|
||||
existing.Status = ref.Status
|
||||
needReconcile = true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Overwrite the first-seen time with the existing one. This is our own
|
||||
// internal annotation, there is no need to update.
|
||||
ref.Annotations[kubetypes.ConfigFirstSeenAnnotationKey] = existing.Annotations[kubetypes.ConfigFirstSeenAnnotationKey]
|
||||
|
||||
existing.Spec = ref.Spec
|
||||
existing.Labels = ref.Labels
|
||||
existing.DeletionTimestamp = ref.DeletionTimestamp
|
||||
existing.DeletionGracePeriodSeconds = ref.DeletionGracePeriodSeconds
|
||||
existing.Status = ref.Status
|
||||
updateAnnotations(existing, ref)
|
||||
|
||||
// 2. this is an graceful delete
|
||||
if ref.DeletionTimestamp != nil {
|
||||
needGracefulDelete = true
|
||||
} else {
|
||||
// 3. this is an update
|
||||
needUpdate = true
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Sync sends a copy of the current state through the update channel.
|
||||
func (s *podStorage) Sync() {
|
||||
s.updateLock.Lock()
|
||||
defer s.updateLock.Unlock()
|
||||
s.updates <- kubetypes.PodUpdate{Pods: s.MergedState().([]*v1.Pod), Op: kubetypes.SET, Source: kubetypes.AllSource}
|
||||
}
|
||||
|
||||
// Object implements config.Accessor
|
||||
func (s *podStorage) MergedState() interface{} {
|
||||
s.podLock.RLock()
|
||||
defer s.podLock.RUnlock()
|
||||
pods := make([]*v1.Pod, 0)
|
||||
for _, sourcePods := range s.pods {
|
||||
for _, podRef := range sourcePods {
|
||||
pods = append(pods, podRef.DeepCopy())
|
||||
}
|
||||
}
|
||||
return pods
|
||||
}
|
||||
|
||||
func copyPods(sourcePods []*v1.Pod) []*v1.Pod {
|
||||
pods := []*v1.Pod{}
|
||||
for _, source := range sourcePods {
|
||||
// Use a deep copy here just in case
|
||||
pods = append(pods, source.DeepCopy())
|
||||
}
|
||||
return pods
|
||||
}
|
415
vendor/k8s.io/kubernetes/pkg/kubelet/config/config_test.go
generated
vendored
Normal file
415
vendor/k8s.io/kubernetes/pkg/kubelet/config/config_test.go
generated
vendored
Normal file
@ -0,0 +1,415 @@
|
||||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
"k8s.io/client-go/tools/record"
|
||||
kubetypes "k8s.io/kubernetes/pkg/kubelet/types"
|
||||
"k8s.io/kubernetes/pkg/securitycontext"
|
||||
)
|
||||
|
||||
const (
|
||||
TestSource = "test"
|
||||
)
|
||||
|
||||
func expectEmptyChannel(t *testing.T, ch <-chan interface{}) {
|
||||
select {
|
||||
case update := <-ch:
|
||||
t.Errorf("Expected no update in channel, Got %v", update)
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
type sortedPods []*v1.Pod
|
||||
|
||||
func (s sortedPods) Len() int {
|
||||
return len(s)
|
||||
}
|
||||
func (s sortedPods) Swap(i, j int) {
|
||||
s[i], s[j] = s[j], s[i]
|
||||
}
|
||||
func (s sortedPods) Less(i, j int) bool {
|
||||
return s[i].Namespace < s[j].Namespace
|
||||
}
|
||||
|
||||
func CreateValidPod(name, namespace string) *v1.Pod {
|
||||
return &v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
UID: types.UID(name + namespace), // for the purpose of testing, this is unique enough
|
||||
Name: name,
|
||||
Namespace: namespace,
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
RestartPolicy: v1.RestartPolicyAlways,
|
||||
DNSPolicy: v1.DNSClusterFirst,
|
||||
Containers: []v1.Container{
|
||||
{
|
||||
Name: "ctr",
|
||||
Image: "image",
|
||||
ImagePullPolicy: "IfNotPresent",
|
||||
SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults(),
|
||||
TerminationMessagePolicy: v1.TerminationMessageReadFile,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func CreatePodUpdate(op kubetypes.PodOperation, source string, pods ...*v1.Pod) kubetypes.PodUpdate {
|
||||
return kubetypes.PodUpdate{Pods: pods, Op: op, Source: source}
|
||||
}
|
||||
|
||||
func createPodConfigTester(mode PodConfigNotificationMode) (chan<- interface{}, <-chan kubetypes.PodUpdate, *PodConfig) {
|
||||
eventBroadcaster := record.NewBroadcaster()
|
||||
config := NewPodConfig(mode, eventBroadcaster.NewRecorder(scheme.Scheme, v1.EventSource{Component: "kubelet"}))
|
||||
channel := config.Channel(TestSource)
|
||||
ch := config.Updates()
|
||||
return channel, ch, config
|
||||
}
|
||||
|
||||
func expectPodUpdate(t *testing.T, ch <-chan kubetypes.PodUpdate, expected ...kubetypes.PodUpdate) {
|
||||
for i := range expected {
|
||||
update := <-ch
|
||||
sort.Sort(sortedPods(update.Pods))
|
||||
sort.Sort(sortedPods(expected[i].Pods))
|
||||
// Make copies of the expected/actual update to compare all fields
|
||||
// except for "Pods", which are compared separately below.
|
||||
expectedCopy, updateCopy := expected[i], update
|
||||
expectedCopy.Pods, updateCopy.Pods = nil, nil
|
||||
if !apiequality.Semantic.DeepEqual(expectedCopy, updateCopy) {
|
||||
t.Fatalf("Expected %#v, Got %#v", expectedCopy, updateCopy)
|
||||
}
|
||||
|
||||
if len(expected[i].Pods) != len(update.Pods) {
|
||||
t.Fatalf("Expected %#v, Got %#v", expected[i], update)
|
||||
}
|
||||
// Compare pods one by one. This is necessary because we don't want to
|
||||
// compare local annotations.
|
||||
for j := range expected[i].Pods {
|
||||
if podsDifferSemantically(expected[i].Pods[j], update.Pods[j]) || !reflect.DeepEqual(expected[i].Pods[j].Status, update.Pods[j].Status) {
|
||||
t.Fatalf("Expected %#v, Got %#v", expected[i].Pods[j], update.Pods[j])
|
||||
}
|
||||
}
|
||||
}
|
||||
expectNoPodUpdate(t, ch)
|
||||
}
|
||||
|
||||
func expectNoPodUpdate(t *testing.T, ch <-chan kubetypes.PodUpdate) {
|
||||
select {
|
||||
case update := <-ch:
|
||||
t.Errorf("Expected no update in channel, Got %#v", update)
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewPodAdded(t *testing.T) {
|
||||
channel, ch, config := createPodConfigTester(PodConfigNotificationIncremental)
|
||||
|
||||
// see an update
|
||||
podUpdate := CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "new"))
|
||||
channel <- podUpdate
|
||||
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "new")))
|
||||
|
||||
config.Sync()
|
||||
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.SET, kubetypes.AllSource, CreateValidPod("foo", "new")))
|
||||
}
|
||||
|
||||
func TestNewPodAddedInvalidNamespace(t *testing.T) {
|
||||
channel, ch, config := createPodConfigTester(PodConfigNotificationIncremental)
|
||||
|
||||
// see an update
|
||||
podUpdate := CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", ""))
|
||||
channel <- podUpdate
|
||||
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "")))
|
||||
|
||||
config.Sync()
|
||||
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.SET, kubetypes.AllSource, CreateValidPod("foo", "")))
|
||||
}
|
||||
|
||||
func TestNewPodAddedDefaultNamespace(t *testing.T) {
|
||||
channel, ch, config := createPodConfigTester(PodConfigNotificationIncremental)
|
||||
|
||||
// see an update
|
||||
podUpdate := CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "default"))
|
||||
channel <- podUpdate
|
||||
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "default")))
|
||||
|
||||
config.Sync()
|
||||
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.SET, kubetypes.AllSource, CreateValidPod("foo", "default")))
|
||||
}
|
||||
|
||||
func TestNewPodAddedDifferentNamespaces(t *testing.T) {
|
||||
channel, ch, config := createPodConfigTester(PodConfigNotificationIncremental)
|
||||
|
||||
// see an update
|
||||
podUpdate := CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "default"))
|
||||
channel <- podUpdate
|
||||
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "default")))
|
||||
|
||||
// see an update in another namespace
|
||||
podUpdate = CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "new"))
|
||||
channel <- podUpdate
|
||||
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "new")))
|
||||
|
||||
config.Sync()
|
||||
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.SET, kubetypes.AllSource, CreateValidPod("foo", "default"), CreateValidPod("foo", "new")))
|
||||
}
|
||||
|
||||
func TestInvalidPodFiltered(t *testing.T) {
|
||||
channel, ch, _ := createPodConfigTester(PodConfigNotificationIncremental)
|
||||
|
||||
// see an update
|
||||
podUpdate := CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "new"))
|
||||
channel <- podUpdate
|
||||
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "new")))
|
||||
|
||||
// add an invalid update
|
||||
podUpdate = CreatePodUpdate(kubetypes.UPDATE, TestSource, &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}})
|
||||
channel <- podUpdate
|
||||
expectNoPodUpdate(t, ch)
|
||||
}
|
||||
|
||||
func TestNewPodAddedSnapshotAndUpdates(t *testing.T) {
|
||||
channel, ch, config := createPodConfigTester(PodConfigNotificationSnapshotAndUpdates)
|
||||
|
||||
// see an set
|
||||
podUpdate := CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "new"))
|
||||
channel <- podUpdate
|
||||
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.SET, TestSource, CreateValidPod("foo", "new")))
|
||||
|
||||
config.Sync()
|
||||
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.SET, kubetypes.AllSource, CreateValidPod("foo", "new")))
|
||||
|
||||
// container updates are separated as UPDATE
|
||||
pod := *podUpdate.Pods[0]
|
||||
pod.Spec.Containers = []v1.Container{{Name: "bar", Image: "test", ImagePullPolicy: v1.PullIfNotPresent, TerminationMessagePolicy: v1.TerminationMessageReadFile}}
|
||||
channel <- CreatePodUpdate(kubetypes.ADD, TestSource, &pod)
|
||||
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.UPDATE, TestSource, &pod))
|
||||
}
|
||||
|
||||
func TestNewPodAddedSnapshot(t *testing.T) {
|
||||
channel, ch, config := createPodConfigTester(PodConfigNotificationSnapshot)
|
||||
|
||||
// see an set
|
||||
podUpdate := CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "new"))
|
||||
channel <- podUpdate
|
||||
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.SET, TestSource, CreateValidPod("foo", "new")))
|
||||
|
||||
config.Sync()
|
||||
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.SET, kubetypes.AllSource, CreateValidPod("foo", "new")))
|
||||
|
||||
// container updates are separated as UPDATE
|
||||
pod := *podUpdate.Pods[0]
|
||||
pod.Spec.Containers = []v1.Container{{Name: "bar", Image: "test", ImagePullPolicy: v1.PullIfNotPresent, TerminationMessagePolicy: v1.TerminationMessageReadFile}}
|
||||
channel <- CreatePodUpdate(kubetypes.ADD, TestSource, &pod)
|
||||
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.SET, TestSource, &pod))
|
||||
}
|
||||
|
||||
func TestNewPodAddedUpdatedRemoved(t *testing.T) {
|
||||
channel, ch, _ := createPodConfigTester(PodConfigNotificationIncremental)
|
||||
|
||||
// should register an add
|
||||
podUpdate := CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "new"))
|
||||
channel <- podUpdate
|
||||
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "new")))
|
||||
|
||||
// should ignore ADDs that are identical
|
||||
expectNoPodUpdate(t, ch)
|
||||
|
||||
// an kubetypes.ADD should be converted to kubetypes.UPDATE
|
||||
pod := CreateValidPod("foo", "new")
|
||||
pod.Spec.Containers = []v1.Container{{Name: "bar", Image: "test", ImagePullPolicy: v1.PullIfNotPresent, TerminationMessagePolicy: v1.TerminationMessageReadFile}}
|
||||
podUpdate = CreatePodUpdate(kubetypes.ADD, TestSource, pod)
|
||||
channel <- podUpdate
|
||||
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.UPDATE, TestSource, pod))
|
||||
|
||||
podUpdate = CreatePodUpdate(kubetypes.REMOVE, TestSource, CreateValidPod("foo", "new"))
|
||||
channel <- podUpdate
|
||||
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.REMOVE, TestSource, pod))
|
||||
}
|
||||
|
||||
func TestNewPodAddedDelete(t *testing.T) {
|
||||
channel, ch, _ := createPodConfigTester(PodConfigNotificationIncremental)
|
||||
|
||||
// should register an add
|
||||
addedPod := CreateValidPod("foo", "new")
|
||||
podUpdate := CreatePodUpdate(kubetypes.ADD, TestSource, addedPod)
|
||||
channel <- podUpdate
|
||||
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.ADD, TestSource, addedPod))
|
||||
|
||||
// mark this pod as deleted
|
||||
timestamp := metav1.NewTime(time.Now())
|
||||
deletedPod := CreateValidPod("foo", "new")
|
||||
deletedPod.ObjectMeta.DeletionTimestamp = ×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, TerminationMessagePolicy: v1.TerminationMessageReadFile}}
|
||||
podUpdate = CreatePodUpdate(kubetypes.SET, TestSource, pod, CreateValidPod("foo3", "new"), CreateValidPod("foo4", "new"))
|
||||
channel <- podUpdate
|
||||
expectPodUpdate(t, ch,
|
||||
CreatePodUpdate(kubetypes.REMOVE, TestSource, CreateValidPod("foo", "new")),
|
||||
CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo4", "new")),
|
||||
CreatePodUpdate(kubetypes.UPDATE, TestSource, pod))
|
||||
}
|
||||
|
||||
func TestNewPodAddedSetReconciled(t *testing.T) {
|
||||
// Create and touch new test pods, return the new pods and touched pod. We should create new pod list
|
||||
// before touching to avoid data race.
|
||||
newTestPods := func(touchStatus, touchSpec bool) ([]*v1.Pod, *v1.Pod) {
|
||||
pods := []*v1.Pod{
|
||||
CreateValidPod("changeable-pod-0", "new"),
|
||||
CreateValidPod("constant-pod-1", "new"),
|
||||
CreateValidPod("constant-pod-2", "new"),
|
||||
}
|
||||
if touchStatus {
|
||||
pods[0].Status = v1.PodStatus{Message: strconv.Itoa(rand.Int())}
|
||||
}
|
||||
if touchSpec {
|
||||
pods[0].Spec.Containers[0].Name = strconv.Itoa(rand.Int())
|
||||
}
|
||||
return pods, pods[0]
|
||||
}
|
||||
for _, op := range []kubetypes.PodOperation{
|
||||
kubetypes.ADD,
|
||||
kubetypes.SET,
|
||||
} {
|
||||
var podWithStatusChange *v1.Pod
|
||||
pods, _ := newTestPods(false, false)
|
||||
channel, ch, _ := createPodConfigTester(PodConfigNotificationIncremental)
|
||||
|
||||
// Use SET to initialize the config, especially initialize the source set
|
||||
channel <- CreatePodUpdate(kubetypes.SET, TestSource, pods...)
|
||||
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.ADD, TestSource, pods...))
|
||||
|
||||
// If status is not changed, no reconcile should be triggered
|
||||
channel <- CreatePodUpdate(op, TestSource, pods...)
|
||||
expectNoPodUpdate(t, ch)
|
||||
|
||||
// If the pod status is changed and not updated, a reconcile should be triggered
|
||||
pods, podWithStatusChange = newTestPods(true, false)
|
||||
channel <- CreatePodUpdate(op, TestSource, pods...)
|
||||
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.RECONCILE, TestSource, podWithStatusChange))
|
||||
|
||||
// If the pod status is changed, but the pod is also updated, no reconcile should be triggered
|
||||
pods, podWithStatusChange = newTestPods(true, true)
|
||||
channel <- CreatePodUpdate(op, TestSource, pods...)
|
||||
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.UPDATE, TestSource, podWithStatusChange))
|
||||
}
|
||||
}
|
||||
|
||||
func TestInitialEmptySet(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
mode PodConfigNotificationMode
|
||||
op kubetypes.PodOperation
|
||||
}{
|
||||
{PodConfigNotificationIncremental, kubetypes.ADD},
|
||||
{PodConfigNotificationSnapshot, kubetypes.SET},
|
||||
{PodConfigNotificationSnapshotAndUpdates, kubetypes.SET},
|
||||
} {
|
||||
channel, ch, _ := createPodConfigTester(test.mode)
|
||||
|
||||
// should register an empty PodUpdate operation
|
||||
podUpdate := CreatePodUpdate(kubetypes.SET, TestSource)
|
||||
channel <- podUpdate
|
||||
expectPodUpdate(t, ch, CreatePodUpdate(test.op, TestSource))
|
||||
|
||||
// should ignore following empty sets
|
||||
podUpdate = CreatePodUpdate(kubetypes.SET, TestSource)
|
||||
channel <- podUpdate
|
||||
podUpdate = CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo", "new"))
|
||||
channel <- podUpdate
|
||||
expectPodUpdate(t, ch, CreatePodUpdate(test.op, TestSource, CreateValidPod("foo", "new")))
|
||||
}
|
||||
}
|
||||
|
||||
func TestPodUpdateAnnotations(t *testing.T) {
|
||||
channel, ch, _ := createPodConfigTester(PodConfigNotificationIncremental)
|
||||
|
||||
pod := CreateValidPod("foo2", "new")
|
||||
pod.Annotations = make(map[string]string, 0)
|
||||
pod.Annotations["kubernetes.io/blah"] = "blah"
|
||||
|
||||
clone := pod.DeepCopy()
|
||||
|
||||
podUpdate := CreatePodUpdate(kubetypes.SET, TestSource, CreateValidPod("foo1", "new"), clone, CreateValidPod("foo3", "new"))
|
||||
channel <- podUpdate
|
||||
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.ADD, TestSource, CreateValidPod("foo1", "new"), pod, CreateValidPod("foo3", "new")))
|
||||
|
||||
pod.Annotations["kubernetes.io/blah"] = "superblah"
|
||||
podUpdate = CreatePodUpdate(kubetypes.SET, TestSource, CreateValidPod("foo1", "new"), pod, CreateValidPod("foo3", "new"))
|
||||
channel <- podUpdate
|
||||
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.UPDATE, TestSource, pod))
|
||||
|
||||
pod.Annotations["kubernetes.io/otherblah"] = "doh"
|
||||
podUpdate = CreatePodUpdate(kubetypes.SET, TestSource, CreateValidPod("foo1", "new"), pod, CreateValidPod("foo3", "new"))
|
||||
channel <- podUpdate
|
||||
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.UPDATE, TestSource, pod))
|
||||
|
||||
delete(pod.Annotations, "kubernetes.io/blah")
|
||||
podUpdate = CreatePodUpdate(kubetypes.SET, TestSource, CreateValidPod("foo1", "new"), pod, CreateValidPod("foo3", "new"))
|
||||
channel <- podUpdate
|
||||
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.UPDATE, TestSource, pod))
|
||||
}
|
||||
|
||||
func TestPodUpdateLabels(t *testing.T) {
|
||||
channel, ch, _ := createPodConfigTester(PodConfigNotificationIncremental)
|
||||
|
||||
pod := CreateValidPod("foo2", "new")
|
||||
pod.Labels = make(map[string]string, 0)
|
||||
pod.Labels["key"] = "value"
|
||||
|
||||
clone := pod.DeepCopy()
|
||||
|
||||
podUpdate := CreatePodUpdate(kubetypes.SET, TestSource, clone)
|
||||
channel <- podUpdate
|
||||
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.ADD, TestSource, pod))
|
||||
|
||||
pod.Labels["key"] = "newValue"
|
||||
podUpdate = CreatePodUpdate(kubetypes.SET, TestSource, pod)
|
||||
channel <- podUpdate
|
||||
expectPodUpdate(t, ch, CreatePodUpdate(kubetypes.UPDATE, TestSource, pod))
|
||||
|
||||
}
|
26
vendor/k8s.io/kubernetes/pkg/kubelet/config/defaults.go
generated
vendored
Normal file
26
vendor/k8s.io/kubernetes/pkg/kubelet/config/defaults.go
generated
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package config
|
||||
|
||||
const (
|
||||
DefaultKubeletPodsDirName = "pods"
|
||||
DefaultKubeletVolumesDirName = "volumes"
|
||||
DefaultKubeletVolumeDevicesDirName = "volumeDevices"
|
||||
DefaultKubeletPluginsDirName = "plugins"
|
||||
DefaultKubeletContainersDirName = "containers"
|
||||
DefaultKubeletPluginContainersDirName = "plugin-containers"
|
||||
)
|
18
vendor/k8s.io/kubernetes/pkg/kubelet/config/doc.go
generated
vendored
Normal file
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"
|
205
vendor/k8s.io/kubernetes/pkg/kubelet/config/file.go
generated
vendored
Normal file
205
vendor/k8s.io/kubernetes/pkg/kubelet/config/file.go
generated
vendored
Normal file
@ -0,0 +1,205 @@
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Reads the pod configuration from file or a directory of files.
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
kubetypes "k8s.io/kubernetes/pkg/kubelet/types"
|
||||
)
|
||||
|
||||
type sourceFile struct {
|
||||
path string
|
||||
nodeName types.NodeName
|
||||
store cache.Store
|
||||
fileKeyMapping map[string]string
|
||||
updates chan<- interface{}
|
||||
}
|
||||
|
||||
func NewSourceFile(path string, nodeName types.NodeName, period time.Duration, updates chan<- interface{}) {
|
||||
// "golang.org/x/exp/inotify" requires a path without trailing "/"
|
||||
path = strings.TrimRight(path, string(os.PathSeparator))
|
||||
|
||||
config := new(path, nodeName, period, updates)
|
||||
glog.V(1).Infof("Watching path %q", path)
|
||||
go wait.Forever(config.run, period)
|
||||
}
|
||||
|
||||
func new(path string, nodeName types.NodeName, period time.Duration, updates chan<- interface{}) *sourceFile {
|
||||
send := func(objs []interface{}) {
|
||||
var pods []*v1.Pod
|
||||
for _, o := range objs {
|
||||
pods = append(pods, o.(*v1.Pod))
|
||||
}
|
||||
updates <- kubetypes.PodUpdate{Pods: pods, Op: kubetypes.SET, Source: kubetypes.FileSource}
|
||||
}
|
||||
store := cache.NewUndeltaStore(send, cache.MetaNamespaceKeyFunc)
|
||||
return &sourceFile{
|
||||
path: path,
|
||||
nodeName: nodeName,
|
||||
store: store,
|
||||
fileKeyMapping: map[string]string{},
|
||||
updates: updates,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *sourceFile) run() {
|
||||
if err := s.watch(); err != nil {
|
||||
glog.Errorf("Unable to read manifest path %q: %v", s.path, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *sourceFile) applyDefaults(pod *api.Pod, source string) error {
|
||||
return applyDefaults(pod, source, true, s.nodeName)
|
||||
}
|
||||
|
||||
func (s *sourceFile) resetStoreFromPath() error {
|
||||
path := s.path
|
||||
statInfo, err := os.Stat(path)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
// Emit an update with an empty PodList to allow FileSource to be marked as seen
|
||||
s.updates <- kubetypes.PodUpdate{Pods: []*v1.Pod{}, Op: kubetypes.SET, Source: kubetypes.FileSource}
|
||||
return fmt.Errorf("path does not exist, ignoring")
|
||||
}
|
||||
|
||||
switch {
|
||||
case statInfo.Mode().IsDir():
|
||||
pods, err := s.extractFromDir(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(pods) == 0 {
|
||||
// Emit an update with an empty PodList to allow FileSource to be marked as seen
|
||||
s.updates <- kubetypes.PodUpdate{Pods: pods, Op: kubetypes.SET, Source: kubetypes.FileSource}
|
||||
return nil
|
||||
}
|
||||
return s.replaceStore(pods...)
|
||||
|
||||
case statInfo.Mode().IsRegular():
|
||||
pod, err := s.extractFromFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.replaceStore(pod)
|
||||
|
||||
default:
|
||||
return fmt.Errorf("path is not a directory or file")
|
||||
}
|
||||
}
|
||||
|
||||
// Get as many pod manifests as we can from a directory. Return an error if and only if something
|
||||
// prevented us from reading anything at all. Do not return an error if only some files
|
||||
// were problematic.
|
||||
func (s *sourceFile) extractFromDir(name string) ([]*v1.Pod, error) {
|
||||
dirents, err := filepath.Glob(filepath.Join(name, "[^.]*"))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("glob failed: %v", err)
|
||||
}
|
||||
|
||||
pods := make([]*v1.Pod, 0)
|
||||
if len(dirents) == 0 {
|
||||
return pods, nil
|
||||
}
|
||||
|
||||
sort.Strings(dirents)
|
||||
for _, path := range dirents {
|
||||
statInfo, err := os.Stat(path)
|
||||
if err != nil {
|
||||
glog.Errorf("Can't get metadata for %q: %v", path, err)
|
||||
continue
|
||||
}
|
||||
|
||||
switch {
|
||||
case statInfo.Mode().IsDir():
|
||||
glog.Errorf("Not recursing into manifest path %q", path)
|
||||
case statInfo.Mode().IsRegular():
|
||||
pod, err := s.extractFromFile(path)
|
||||
if err != nil {
|
||||
glog.Errorf("Can't process manifest file %q: %v", path, err)
|
||||
} else {
|
||||
pods = append(pods, pod)
|
||||
}
|
||||
default:
|
||||
glog.Errorf("Manifest path %q is not a directory or file: %v", path, statInfo.Mode())
|
||||
}
|
||||
}
|
||||
return pods, nil
|
||||
}
|
||||
|
||||
func (s *sourceFile) extractFromFile(filename string) (pod *v1.Pod, err error) {
|
||||
glog.V(3).Infof("Reading manifest file %q", filename)
|
||||
defer func() {
|
||||
if err == nil && pod != nil {
|
||||
objKey, keyErr := cache.MetaNamespaceKeyFunc(pod)
|
||||
if keyErr != nil {
|
||||
err = keyErr
|
||||
return
|
||||
}
|
||||
s.fileKeyMapping[filename] = objKey
|
||||
}
|
||||
}()
|
||||
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return pod, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
data, err := ioutil.ReadAll(file)
|
||||
if err != nil {
|
||||
return pod, err
|
||||
}
|
||||
|
||||
defaultFn := func(pod *api.Pod) error {
|
||||
return s.applyDefaults(pod, filename)
|
||||
}
|
||||
|
||||
parsed, pod, podErr := tryDecodeSinglePod(data, defaultFn)
|
||||
if parsed {
|
||||
if podErr != nil {
|
||||
return pod, podErr
|
||||
}
|
||||
return pod, nil
|
||||
}
|
||||
|
||||
return pod, fmt.Errorf("%v: couldn't parse as pod(%v), please check manifest file.\n", filename, podErr)
|
||||
}
|
||||
|
||||
func (s *sourceFile) replaceStore(pods ...*v1.Pod) (err error) {
|
||||
objs := []interface{}{}
|
||||
for _, pod := range pods {
|
||||
objs = append(objs, pod)
|
||||
}
|
||||
return s.store.Replace(objs, "")
|
||||
}
|
131
vendor/k8s.io/kubernetes/pkg/kubelet/config/file_linux.go
generated
vendored
Normal file
131
vendor/k8s.io/kubernetes/pkg/kubelet/config/file_linux.go
generated
vendored
Normal file
@ -0,0 +1,131 @@
|
||||
// +build linux
|
||||
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Reads the pod configuration from file or a directory of files.
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"golang.org/x/exp/inotify"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
kubetypes "k8s.io/kubernetes/pkg/kubelet/types"
|
||||
)
|
||||
|
||||
type podEventType int
|
||||
|
||||
const (
|
||||
podAdd podEventType = iota
|
||||
podModify
|
||||
podDelete
|
||||
)
|
||||
|
||||
func (s *sourceFile) watch() error {
|
||||
_, err := os.Stat(s.path)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
// Emit an update with an empty PodList to allow FileSource to be marked as seen
|
||||
s.updates <- kubetypes.PodUpdate{Pods: []*v1.Pod{}, Op: kubetypes.SET, Source: kubetypes.FileSource}
|
||||
return fmt.Errorf("path does not exist, ignoring")
|
||||
}
|
||||
|
||||
w, err := inotify.NewWatcher()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create inotify: %v", err)
|
||||
}
|
||||
defer w.Close()
|
||||
|
||||
err = w.AddWatch(s.path, inotify.IN_DELETE_SELF|inotify.IN_CREATE|inotify.IN_MOVED_TO|inotify.IN_MODIFY|inotify.IN_MOVED_FROM|inotify.IN_DELETE)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to create inotify for path %q: %v", s.path, err)
|
||||
}
|
||||
|
||||
// Reset store with manifest files already existing when starting
|
||||
if err := s.resetStoreFromPath(); err != nil {
|
||||
return fmt.Errorf("unable to read manifest path %q: %v", s.path, err)
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case event := <-w.Event:
|
||||
err = s.processEvent(event)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error while processing event (%+v): %v", event, err)
|
||||
}
|
||||
case err = <-w.Error:
|
||||
return fmt.Errorf("error while watching %q: %v", s.path, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *sourceFile) processEvent(e *inotify.Event) error {
|
||||
// Ignore file start with dots
|
||||
if strings.HasPrefix(filepath.Base(e.Name), ".") {
|
||||
glog.V(4).Infof("Ignored pod manifest: %s, because it starts with dots", e.Name)
|
||||
return nil
|
||||
}
|
||||
var eventType podEventType
|
||||
switch {
|
||||
case (e.Mask & inotify.IN_ISDIR) > 0:
|
||||
glog.Errorf("Not recursing into manifest path %q", s.path)
|
||||
return nil
|
||||
case (e.Mask & inotify.IN_CREATE) > 0:
|
||||
eventType = podAdd
|
||||
case (e.Mask & inotify.IN_MOVED_TO) > 0:
|
||||
eventType = podAdd
|
||||
case (e.Mask & inotify.IN_MODIFY) > 0:
|
||||
eventType = podModify
|
||||
case (e.Mask & inotify.IN_DELETE) > 0:
|
||||
eventType = podDelete
|
||||
case (e.Mask & inotify.IN_MOVED_FROM) > 0:
|
||||
eventType = podDelete
|
||||
case (e.Mask & inotify.IN_DELETE_SELF) > 0:
|
||||
return fmt.Errorf("the watched path is deleted")
|
||||
default:
|
||||
// Ignore rest events
|
||||
return nil
|
||||
}
|
||||
|
||||
switch eventType {
|
||||
case podAdd, podModify:
|
||||
if pod, err := s.extractFromFile(e.Name); err != nil {
|
||||
glog.Errorf("Can't process manifest file %q: %v", e.Name, err)
|
||||
} else {
|
||||
return s.store.Add(pod)
|
||||
}
|
||||
case podDelete:
|
||||
if objKey, keyExist := s.fileKeyMapping[e.Name]; keyExist {
|
||||
pod, podExist, err := s.store.GetByKey(objKey)
|
||||
if err != nil {
|
||||
return err
|
||||
} else if !podExist {
|
||||
return fmt.Errorf("the pod with key %s doesn't exist in cache", objKey)
|
||||
} else {
|
||||
return s.store.Delete(pod)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
431
vendor/k8s.io/kubernetes/pkg/kubelet/config/file_linux_test.go
generated
vendored
Normal file
431
vendor/k8s.io/kubernetes/pkg/kubelet/config/file_linux_test.go
generated
vendored
Normal file
@ -0,0 +1,431 @@
|
||||
// +build linux
|
||||
|
||||
/*
|
||||
Copyright 2016 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
utiltesting "k8s.io/client-go/util/testing"
|
||||
"k8s.io/kubernetes/pkg/api/testapi"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
k8s_api_v1 "k8s.io/kubernetes/pkg/apis/core/v1"
|
||||
"k8s.io/kubernetes/pkg/apis/core/validation"
|
||||
kubetypes "k8s.io/kubernetes/pkg/kubelet/types"
|
||||
"k8s.io/kubernetes/pkg/securitycontext"
|
||||
)
|
||||
|
||||
func TestExtractFromNonExistentFile(t *testing.T) {
|
||||
ch := make(chan interface{}, 1)
|
||||
c := new("/some/fake/file", "localhost", time.Millisecond, ch)
|
||||
err := c.watch()
|
||||
if err == nil {
|
||||
t.Errorf("Expected error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateOnNonExistentFile(t *testing.T) {
|
||||
ch := make(chan interface{})
|
||||
NewSourceFile("random_non_existent_path", "localhost", time.Millisecond, ch)
|
||||
select {
|
||||
case got := <-ch:
|
||||
update := got.(kubetypes.PodUpdate)
|
||||
expected := CreatePodUpdate(kubetypes.SET, kubetypes.FileSource)
|
||||
if !apiequality.Semantic.DeepDerivative(expected, update) {
|
||||
t.Fatalf("expected %#v, Got %#v", expected, update)
|
||||
}
|
||||
|
||||
case <-time.After(wait.ForeverTestTimeout):
|
||||
t.Fatalf("expected update, timeout instead")
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadPodsFromFileExistAlready(t *testing.T) {
|
||||
hostname := types.NodeName("random-test-hostname")
|
||||
var testCases = getTestCases(hostname)
|
||||
|
||||
for _, testCase := range testCases {
|
||||
func() {
|
||||
dirName, err := utiltesting.MkTmpdir("file-test")
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create temp dir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(dirName)
|
||||
file := testCase.writeToFile(dirName, "test_pod_manifest", t)
|
||||
|
||||
ch := make(chan interface{})
|
||||
NewSourceFile(file, hostname, time.Millisecond, ch)
|
||||
select {
|
||||
case got := <-ch:
|
||||
update := got.(kubetypes.PodUpdate)
|
||||
for _, pod := range update.Pods {
|
||||
// TODO: remove the conversion when validation is performed on versioned objects.
|
||||
internalPod := &api.Pod{}
|
||||
if err := k8s_api_v1.Convert_v1_Pod_To_core_Pod(pod, internalPod, nil); err != nil {
|
||||
t.Fatalf("%s: Cannot convert pod %#v, %#v", testCase.desc, pod, err)
|
||||
}
|
||||
if errs := validation.ValidatePod(internalPod); len(errs) > 0 {
|
||||
t.Fatalf("%s: Invalid pod %#v, %#v", testCase.desc, internalPod, errs)
|
||||
}
|
||||
}
|
||||
if !apiequality.Semantic.DeepEqual(testCase.expected, update) {
|
||||
t.Fatalf("%s: Expected %#v, Got %#v", testCase.desc, testCase.expected, update)
|
||||
}
|
||||
case <-time.After(wait.ForeverTestTimeout):
|
||||
t.Fatalf("%s: Expected update, timeout instead", testCase.desc)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadPodsFromFileExistLater(t *testing.T) {
|
||||
watchFileAdded(false, t)
|
||||
}
|
||||
|
||||
func TestReadPodsFromFileChanged(t *testing.T) {
|
||||
watchFileChanged(false, t)
|
||||
}
|
||||
|
||||
func TestReadPodsFromFileInDirAdded(t *testing.T) {
|
||||
watchFileAdded(true, t)
|
||||
}
|
||||
|
||||
func TestReadPodsFromFileInDirChanged(t *testing.T) {
|
||||
watchFileChanged(true, t)
|
||||
}
|
||||
|
||||
func TestExtractFromBadDataFile(t *testing.T) {
|
||||
dirName, err := utiltesting.MkTmpdir("file-test")
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create temp dir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(dirName)
|
||||
|
||||
fileName := filepath.Join(dirName, "test_pod_manifest")
|
||||
err = ioutil.WriteFile(fileName, []byte{1, 2, 3}, 0555)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to write test file %#v", err)
|
||||
}
|
||||
|
||||
ch := make(chan interface{}, 1)
|
||||
c := new(fileName, "localhost", time.Millisecond, ch)
|
||||
err = c.resetStoreFromPath()
|
||||
if err == nil {
|
||||
t.Fatalf("expected error, got nil")
|
||||
}
|
||||
expectEmptyChannel(t, ch)
|
||||
}
|
||||
|
||||
func TestExtractFromEmptyDir(t *testing.T) {
|
||||
dirName, err := utiltesting.MkTmpdir("file-test")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(dirName)
|
||||
|
||||
ch := make(chan interface{}, 1)
|
||||
c := new(dirName, "localhost", time.Millisecond, ch)
|
||||
err = c.resetStoreFromPath()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
update := (<-ch).(kubetypes.PodUpdate)
|
||||
expected := CreatePodUpdate(kubetypes.SET, kubetypes.FileSource)
|
||||
if !apiequality.Semantic.DeepEqual(expected, update) {
|
||||
t.Fatalf("expected %#v, Got %#v", expected, update)
|
||||
}
|
||||
}
|
||||
|
||||
type testCase struct {
|
||||
desc string
|
||||
pod runtime.Object
|
||||
expected kubetypes.PodUpdate
|
||||
}
|
||||
|
||||
func getTestCases(hostname types.NodeName) []*testCase {
|
||||
grace := int64(30)
|
||||
return []*testCase{
|
||||
{
|
||||
desc: "Simple pod",
|
||||
pod: &v1.Pod{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Pod",
|
||||
APIVersion: "",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test",
|
||||
UID: "12345",
|
||||
Namespace: "mynamespace",
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{{Name: "image", Image: "test/image", SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults()}},
|
||||
SecurityContext: &v1.PodSecurityContext{},
|
||||
SchedulerName: api.DefaultSchedulerName,
|
||||
},
|
||||
Status: v1.PodStatus{
|
||||
Phase: v1.PodPending,
|
||||
},
|
||||
},
|
||||
expected: CreatePodUpdate(kubetypes.SET, kubetypes.FileSource, &v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-" + string(hostname),
|
||||
UID: "12345",
|
||||
Namespace: "mynamespace",
|
||||
Annotations: map[string]string{kubetypes.ConfigHashAnnotationKey: "12345"},
|
||||
SelfLink: getSelfLink("test-"+string(hostname), "mynamespace"),
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
NodeName: string(hostname),
|
||||
RestartPolicy: v1.RestartPolicyAlways,
|
||||
DNSPolicy: v1.DNSClusterFirst,
|
||||
TerminationGracePeriodSeconds: &grace,
|
||||
Tolerations: []v1.Toleration{{
|
||||
Operator: "Exists",
|
||||
Effect: "NoExecute",
|
||||
}},
|
||||
Containers: []v1.Container{{
|
||||
Name: "image",
|
||||
Image: "test/image",
|
||||
TerminationMessagePath: "/dev/termination-log",
|
||||
ImagePullPolicy: "Always",
|
||||
SecurityContext: securitycontext.ValidSecurityContextWithContainerDefaults(),
|
||||
TerminationMessagePolicy: v1.TerminationMessageReadFile,
|
||||
}},
|
||||
SecurityContext: &v1.PodSecurityContext{},
|
||||
SchedulerName: api.DefaultSchedulerName,
|
||||
},
|
||||
Status: v1.PodStatus{
|
||||
Phase: v1.PodPending,
|
||||
},
|
||||
}),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (tc *testCase) writeToFile(dir, name string, t *testing.T) string {
|
||||
var versionedPod runtime.Object
|
||||
err := testapi.Default.Converter().Convert(&tc.pod, &versionedPod, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("%s: error in versioning the pod: %v", tc.desc, err)
|
||||
}
|
||||
fileContents, err := runtime.Encode(testapi.Default.Codec(), versionedPod)
|
||||
if err != nil {
|
||||
t.Fatalf("%s: error in encoding the pod: %v", tc.desc, err)
|
||||
}
|
||||
|
||||
fileName := filepath.Join(dir, name)
|
||||
if err := writeFile(fileName, []byte(fileContents)); err != nil {
|
||||
t.Fatalf("unable to write test file %#v", err)
|
||||
}
|
||||
return fileName
|
||||
}
|
||||
|
||||
func watchFileAdded(watchDir bool, t *testing.T) {
|
||||
hostname := types.NodeName("random-test-hostname")
|
||||
var testCases = getTestCases(hostname)
|
||||
|
||||
fileNamePre := "test_pod_manifest"
|
||||
for index, testCase := range testCases {
|
||||
func() {
|
||||
dirName, err := utiltesting.MkTmpdir("dir-test")
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create temp dir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(dirName)
|
||||
fileName := fmt.Sprintf("%s_%d", fileNamePre, index)
|
||||
|
||||
ch := make(chan interface{})
|
||||
if watchDir {
|
||||
NewSourceFile(dirName, hostname, 100*time.Millisecond, ch)
|
||||
} else {
|
||||
NewSourceFile(filepath.Join(dirName, fileName), hostname, 100*time.Millisecond, ch)
|
||||
}
|
||||
expectEmptyUpdate(t, ch)
|
||||
|
||||
addFile := func() {
|
||||
// Add a file
|
||||
testCase.writeToFile(dirName, fileName, t)
|
||||
}
|
||||
|
||||
go addFile()
|
||||
|
||||
// For !watchDir: expect an update by SourceFile.resetStoreFromPath().
|
||||
// For watchDir: expect at least one update from CREATE & MODIFY inotify event.
|
||||
// Shouldn't expect two updates from CREATE & MODIFY because CREATE doesn't guarantee file written.
|
||||
// In that case no update will be sent from CREATE event.
|
||||
expectUpdate(t, ch, testCase)
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func watchFileChanged(watchDir bool, t *testing.T) {
|
||||
hostname := types.NodeName("random-test-hostname")
|
||||
var testCases = getTestCases(hostname)
|
||||
|
||||
fileNamePre := "test_pod_manifest"
|
||||
for index, testCase := range testCases {
|
||||
func() {
|
||||
dirName, err := utiltesting.MkTmpdir("dir-test")
|
||||
fileName := fmt.Sprintf("%s_%d", fileNamePre, index)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create temp dir: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(dirName)
|
||||
|
||||
var file string
|
||||
lock := &sync.Mutex{}
|
||||
ch := make(chan interface{})
|
||||
func() {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
file = testCase.writeToFile(dirName, fileName, t)
|
||||
}()
|
||||
|
||||
if watchDir {
|
||||
NewSourceFile(dirName, hostname, 100*time.Millisecond, ch)
|
||||
defer func() {
|
||||
// Remove the file
|
||||
deleteFile(dirName, fileName, ch, t)
|
||||
}()
|
||||
} else {
|
||||
NewSourceFile(file, hostname, 100*time.Millisecond, ch)
|
||||
}
|
||||
// expect an update by SourceFile.resetStoreFromPath()
|
||||
expectUpdate(t, ch, testCase)
|
||||
|
||||
changeFile := func() {
|
||||
// Edit the file content
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
|
||||
pod := testCase.pod.(*v1.Pod)
|
||||
pod.Spec.Containers[0].Name = "image2"
|
||||
|
||||
testCase.expected.Pods[0].Spec.Containers[0].Name = "image2"
|
||||
testCase.writeToFile(dirName, fileName, t)
|
||||
}
|
||||
|
||||
go changeFile()
|
||||
// expect an update by MODIFY inotify event
|
||||
expectUpdate(t, ch, testCase)
|
||||
|
||||
if watchDir {
|
||||
from := fileName
|
||||
fileName = fileName + "_ch"
|
||||
go changeFileName(dirName, from, fileName, t)
|
||||
// expect an update by MOVED_FROM inotify event cause changing file name
|
||||
expectEmptyUpdate(t, ch)
|
||||
// expect an update by MOVED_TO inotify event cause changing file name
|
||||
expectUpdate(t, ch, testCase)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func deleteFile(dir, file string, ch chan interface{}, t *testing.T) {
|
||||
go func() {
|
||||
path := filepath.Join(dir, file)
|
||||
err := os.Remove(path)
|
||||
if err != nil {
|
||||
t.Errorf("unable to remove test file %s: %s", path, err)
|
||||
}
|
||||
}()
|
||||
|
||||
expectEmptyUpdate(t, ch)
|
||||
}
|
||||
|
||||
func expectUpdate(t *testing.T, ch chan interface{}, testCase *testCase) {
|
||||
timer := time.After(5 * time.Second)
|
||||
for {
|
||||
select {
|
||||
case got := <-ch:
|
||||
update := got.(kubetypes.PodUpdate)
|
||||
for _, pod := range update.Pods {
|
||||
// TODO: remove the conversion when validation is performed on versioned objects.
|
||||
internalPod := &api.Pod{}
|
||||
if err := k8s_api_v1.Convert_v1_Pod_To_core_Pod(pod, internalPod, nil); err != nil {
|
||||
t.Fatalf("%s: Cannot convert pod %#v, %#v", testCase.desc, pod, err)
|
||||
}
|
||||
if errs := validation.ValidatePod(internalPod); len(errs) > 0 {
|
||||
t.Fatalf("%s: Invalid pod %#v, %#v", testCase.desc, internalPod, errs)
|
||||
}
|
||||
}
|
||||
|
||||
if !apiequality.Semantic.DeepEqual(testCase.expected, update) {
|
||||
t.Fatalf("%s: Expected: %#v, Got: %#v", testCase.desc, testCase.expected, update)
|
||||
}
|
||||
return
|
||||
case <-timer:
|
||||
t.Fatalf("%s: Expected update, timeout instead", testCase.desc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func expectEmptyUpdate(t *testing.T, ch chan interface{}) {
|
||||
timer := time.After(5 * time.Second)
|
||||
for {
|
||||
select {
|
||||
case got := <-ch:
|
||||
update := got.(kubetypes.PodUpdate)
|
||||
if len(update.Pods) != 0 {
|
||||
t.Fatalf("expected empty update, got %#v", update)
|
||||
}
|
||||
return
|
||||
case <-timer:
|
||||
t.Fatalf("expected empty update, timeout instead")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func writeFile(filename string, data []byte) error {
|
||||
f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE, 0666)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
n, err := f.Write(data)
|
||||
if err == nil && n < len(data) {
|
||||
err = io.ErrShortWrite
|
||||
}
|
||||
if err1 := f.Close(); err == nil {
|
||||
err = err1
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func changeFileName(dir, from, to string, t *testing.T) {
|
||||
fromPath := filepath.Join(dir, from)
|
||||
toPath := filepath.Join(dir, to)
|
||||
if err := exec.Command("mv", fromPath, toPath).Run(); err != nil {
|
||||
t.Errorf("Fail to change file name: %s", err)
|
||||
}
|
||||
}
|
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")
|
||||
}
|
109
vendor/k8s.io/kubernetes/pkg/kubelet/config/flags.go
generated
vendored
Normal file
109
vendor/k8s.io/kubernetes/pkg/kubelet/config/flags.go
generated
vendored
Normal file
@ -0,0 +1,109 @@
|
||||
/*
|
||||
Copyright 2017 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"github.com/spf13/pflag"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
type ContainerRuntimeOptions struct {
|
||||
|
||||
// General options.
|
||||
|
||||
// ContainerRuntime is the container runtime to use.
|
||||
ContainerRuntime string
|
||||
// RuntimeCgroups that container runtime is expected to be isolated in.
|
||||
RuntimeCgroups string
|
||||
|
||||
// Docker-specific options.
|
||||
|
||||
// DockershimRootDirectory is the path to the dockershim root directory. Defaults to
|
||||
// /var/lib/dockershim if unset. Exposed for integration testing (e.g. in OpenShift).
|
||||
DockershimRootDirectory string
|
||||
// Enable dockershim only mode.
|
||||
ExperimentalDockershim bool
|
||||
// This flag, if set, disables use of a shared PID namespace for pods running in the docker CRI runtime.
|
||||
// A shared PID namespace is the only option in non-docker runtimes and is required by the CRI. The ability to
|
||||
// disable it for docker will be removed unless a compelling use case is discovered with widespread use.
|
||||
// TODO: Remove once we no longer support disabling shared PID namespace (https://issues.k8s.io/41938)
|
||||
DockerDisableSharedPID bool
|
||||
// PodSandboxImage is the image whose network/ipc namespaces
|
||||
// containers in each pod will use.
|
||||
PodSandboxImage string
|
||||
// DockerEndpoint is the path to the docker endpoint to communicate with.
|
||||
DockerEndpoint string
|
||||
// If no pulling progress is made before the deadline imagePullProgressDeadline,
|
||||
// the image pulling will be cancelled. Defaults to 1m0s.
|
||||
// +optional
|
||||
ImagePullProgressDeadline metav1.Duration
|
||||
|
||||
// Network plugin options.
|
||||
|
||||
// networkPluginName is the name of the network plugin to be invoked for
|
||||
// various events in kubelet/pod lifecycle
|
||||
NetworkPluginName string
|
||||
// NetworkPluginMTU is the MTU to be passed to the network plugin,
|
||||
// and overrides the default MTU for cases where it cannot be automatically
|
||||
// computed (such as IPSEC).
|
||||
NetworkPluginMTU int32
|
||||
// CNIConfDir is the full path of the directory in which to search for
|
||||
// CNI config files
|
||||
CNIConfDir string
|
||||
// CNIBinDir is the full path of the directory in which to search for
|
||||
// CNI plugin binaries
|
||||
CNIBinDir string
|
||||
|
||||
// rkt-specific options.
|
||||
|
||||
// rktPath is the path of rkt binary. Leave empty to use the first rkt in $PATH.
|
||||
RktPath string
|
||||
// rktApiEndpoint is the endpoint of the rkt API service to communicate with.
|
||||
RktAPIEndpoint string
|
||||
// rktStage1Image is the image to use as stage1. Local paths and
|
||||
// http/https URLs are supported.
|
||||
RktStage1Image string
|
||||
}
|
||||
|
||||
func (s *ContainerRuntimeOptions) AddFlags(fs *pflag.FlagSet) {
|
||||
// General settings.
|
||||
fs.StringVar(&s.ContainerRuntime, "container-runtime", s.ContainerRuntime, "The container runtime to use. Possible values: 'docker', 'rkt'.")
|
||||
fs.StringVar(&s.RuntimeCgroups, "runtime-cgroups", s.RuntimeCgroups, "Optional absolute name of cgroups to create and run the runtime in.")
|
||||
|
||||
// Docker-specific settings.
|
||||
fs.BoolVar(&s.ExperimentalDockershim, "experimental-dockershim", s.ExperimentalDockershim, "Enable dockershim only mode. In this mode, kubelet will only start dockershim without any other functionalities. This flag only serves test purpose, please do not use it unless you are conscious of what you are doing. [default=false]")
|
||||
fs.MarkHidden("experimental-dockershim")
|
||||
fs.StringVar(&s.DockershimRootDirectory, "experimental-dockershim-root-directory", s.DockershimRootDirectory, "Path to the dockershim root directory.")
|
||||
fs.MarkHidden("experimental-dockershim-root-directory")
|
||||
fs.BoolVar(&s.DockerDisableSharedPID, "docker-disable-shared-pid", s.DockerDisableSharedPID, "The Container Runtime Interface (CRI) defaults to using a shared PID namespace for containers in a pod when running with Docker 1.13.1 or higher. Setting this flag reverts to the previous behavior of isolated PID namespaces. This ability will be removed in a future Kubernetes release.")
|
||||
fs.StringVar(&s.PodSandboxImage, "pod-infra-container-image", s.PodSandboxImage, "The image whose network/ipc namespaces containers in each pod will use.")
|
||||
fs.StringVar(&s.DockerEndpoint, "docker-endpoint", s.DockerEndpoint, "Use this for the docker endpoint to communicate with")
|
||||
fs.DurationVar(&s.ImagePullProgressDeadline.Duration, "image-pull-progress-deadline", s.ImagePullProgressDeadline.Duration, "If no pulling progress is made before this deadline, the image pulling will be cancelled.")
|
||||
|
||||
// Network plugin settings. Shared by both docker and rkt.
|
||||
fs.StringVar(&s.NetworkPluginName, "network-plugin", s.NetworkPluginName, "<Warning: Alpha feature> The name of the network plugin to be invoked for various events in kubelet/pod lifecycle")
|
||||
fs.StringVar(&s.CNIConfDir, "cni-conf-dir", s.CNIConfDir, "<Warning: Alpha feature> The full path of the directory in which to search for CNI config files. Default: /etc/cni/net.d")
|
||||
fs.StringVar(&s.CNIBinDir, "cni-bin-dir", s.CNIBinDir, "<Warning: Alpha feature> The full path of the directory in which to search for CNI plugin binaries. Default: /opt/cni/bin")
|
||||
fs.Int32Var(&s.NetworkPluginMTU, "network-plugin-mtu", s.NetworkPluginMTU, "<Warning: Alpha feature> The MTU to be passed to the network plugin, to override the default. Set to 0 to use the default 1460 MTU.")
|
||||
|
||||
// Rkt-specific settings.
|
||||
fs.StringVar(&s.RktPath, "rkt-path", s.RktPath, "Path of rkt binary. Leave empty to use the first rkt in $PATH. Only used if --container-runtime='rkt'.")
|
||||
fs.StringVar(&s.RktAPIEndpoint, "rkt-api-endpoint", s.RktAPIEndpoint, "The endpoint of the rkt API service to communicate with. Only used if --container-runtime='rkt'.")
|
||||
fs.StringVar(&s.RktStage1Image, "rkt-stage1-image", s.RktStage1Image, "image to use as stage1. Local paths and http/https URLs are supported. If empty, the 'stage1.aci' in the same directory as '--rkt-path' will be used.")
|
||||
fs.MarkDeprecated("rkt-stage1-image", "Will be removed in a future version. The default stage1 image will be specified by the rkt configurations, see https://github.com/coreos/rkt/blob/master/Documentation/configuration.md for more details.")
|
||||
|
||||
}
|
143
vendor/k8s.io/kubernetes/pkg/kubelet/config/http.go
generated
vendored
Normal file
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/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
kubetypes "k8s.io/kubernetes/pkg/kubelet/types"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
)
|
||||
|
||||
type sourceURL struct {
|
||||
url string
|
||||
header http.Header
|
||||
nodeName types.NodeName
|
||||
updates chan<- interface{}
|
||||
data []byte
|
||||
failureLogs int
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
func NewSourceURL(url string, header http.Header, nodeName types.NodeName, period time.Duration, updates chan<- interface{}) {
|
||||
config := &sourceURL{
|
||||
url: url,
|
||||
header: header,
|
||||
nodeName: nodeName,
|
||||
updates: updates,
|
||||
data: nil,
|
||||
// Timing out requests leads to retries. This client is only used to
|
||||
// read the manifest URL passed to kubelet.
|
||||
client: &http.Client{Timeout: 10 * time.Second},
|
||||
}
|
||||
glog.V(1).Infof("Watching URL %s", url)
|
||||
go wait.Until(config.run, period, wait.NeverStop)
|
||||
}
|
||||
|
||||
func (s *sourceURL) run() {
|
||||
if err := s.extractFromURL(); err != nil {
|
||||
// Don't log this multiple times per minute. The first few entries should be
|
||||
// enough to get the point across.
|
||||
if s.failureLogs < 3 {
|
||||
glog.Warningf("Failed to read pods from URL: %v", err)
|
||||
} else if s.failureLogs == 3 {
|
||||
glog.Warningf("Failed to read pods from URL. Dropping verbosity of this message to V(4): %v", err)
|
||||
} else {
|
||||
glog.V(4).Infof("Failed to read pods from URL: %v", err)
|
||||
}
|
||||
s.failureLogs++
|
||||
} else {
|
||||
if s.failureLogs > 0 {
|
||||
glog.Info("Successfully read pods from URL.")
|
||||
s.failureLogs = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *sourceURL) applyDefaults(pod *api.Pod) error {
|
||||
return applyDefaults(pod, s.url, false, s.nodeName)
|
||||
}
|
||||
|
||||
func (s *sourceURL) extractFromURL() error {
|
||||
req, err := http.NewRequest("GET", s.url, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header = s.header
|
||||
resp, err := s.client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
data, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("%v: %v", s.url, resp.Status)
|
||||
}
|
||||
if len(data) == 0 {
|
||||
// Emit an update with an empty PodList to allow HTTPSource to be marked as seen
|
||||
s.updates <- kubetypes.PodUpdate{Pods: []*v1.Pod{}, Op: kubetypes.SET, Source: kubetypes.HTTPSource}
|
||||
return fmt.Errorf("zero-length data received from %v", s.url)
|
||||
}
|
||||
// Short circuit if the data has not changed since the last time it was read.
|
||||
if bytes.Compare(data, s.data) == 0 {
|
||||
return nil
|
||||
}
|
||||
s.data = data
|
||||
|
||||
// First try as it is a single pod.
|
||||
parsed, pod, singlePodErr := tryDecodeSinglePod(data, s.applyDefaults)
|
||||
if parsed {
|
||||
if singlePodErr != nil {
|
||||
// It parsed but could not be used.
|
||||
return singlePodErr
|
||||
}
|
||||
s.updates <- kubetypes.PodUpdate{Pods: []*v1.Pod{pod}, Op: kubetypes.SET, Source: kubetypes.HTTPSource}
|
||||
return nil
|
||||
}
|
||||
|
||||
// That didn't work, so try a list of pods.
|
||||
parsed, podList, multiPodErr := tryDecodePodList(data, s.applyDefaults)
|
||||
if parsed {
|
||||
if multiPodErr != nil {
|
||||
// It parsed but could not be used.
|
||||
return multiPodErr
|
||||
}
|
||||
pods := make([]*v1.Pod, 0)
|
||||
for i := range podList.Items {
|
||||
pods = append(pods, &podList.Items[i])
|
||||
}
|
||||
s.updates <- kubetypes.PodUpdate{Pods: pods, Op: kubetypes.SET, Source: kubetypes.HTTPSource}
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("%v: received '%v', but couldn't parse as "+
|
||||
"single (%v) or multiple pods (%v).\n",
|
||||
s.url, string(data), singlePodErr, multiPodErr)
|
||||
}
|
373
vendor/k8s.io/kubernetes/pkg/kubelet/config/http_test.go
generated
vendored
Normal file
373
vendor/k8s.io/kubernetes/pkg/kubelet/config/http_test.go
generated
vendored
Normal file
@ -0,0 +1,373 @@
|
||||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package config
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
utiltesting "k8s.io/client-go/util/testing"
|
||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||
"k8s.io/kubernetes/pkg/api/testapi"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
k8s_api_v1 "k8s.io/kubernetes/pkg/apis/core/v1"
|
||||
"k8s.io/kubernetes/pkg/apis/core/validation"
|
||||
kubetypes "k8s.io/kubernetes/pkg/kubelet/types"
|
||||
)
|
||||
|
||||
func TestURLErrorNotExistNoUpdate(t *testing.T) {
|
||||
ch := make(chan interface{})
|
||||
NewSourceURL("http://localhost:49575/_not_found_", http.Header{}, "localhost", time.Millisecond, ch)
|
||||
select {
|
||||
case got := <-ch:
|
||||
t.Errorf("Expected no update, Got %#v", got)
|
||||
case <-time.After(2 * time.Millisecond):
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractFromHttpBadness(t *testing.T) {
|
||||
ch := make(chan interface{}, 1)
|
||||
c := sourceURL{"http://localhost:49575/_not_found_", http.Header{}, "other", ch, nil, 0, http.DefaultClient}
|
||||
if err := c.extractFromURL(); err == nil {
|
||||
t.Errorf("Expected error")
|
||||
}
|
||||
expectEmptyChannel(t, ch)
|
||||
}
|
||||
|
||||
func TestExtractInvalidPods(t *testing.T) {
|
||||
var testCases = []struct {
|
||||
desc string
|
||||
pod *v1.Pod
|
||||
}{
|
||||
{
|
||||
desc: "No version",
|
||||
pod: &v1.Pod{TypeMeta: metav1.TypeMeta{APIVersion: ""}},
|
||||
},
|
||||
{
|
||||
desc: "Invalid version",
|
||||
pod: &v1.Pod{TypeMeta: metav1.TypeMeta{APIVersion: "v1betta2"}},
|
||||
},
|
||||
{
|
||||
desc: "Invalid volume name",
|
||||
pod: &v1.Pod{
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion.String()},
|
||||
Spec: v1.PodSpec{
|
||||
Volumes: []v1.Volume{{Name: "_INVALID_"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Duplicate volume names",
|
||||
pod: &v1.Pod{
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion.String()},
|
||||
Spec: v1.PodSpec{
|
||||
Volumes: []v1.Volume{{Name: "repeated"}, {Name: "repeated"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Unspecified container name",
|
||||
pod: &v1.Pod{
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion.String()},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{{Name: ""}},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "Invalid container name",
|
||||
pod: &v1.Pod{
|
||||
TypeMeta: metav1.TypeMeta{APIVersion: legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion.String()},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{{Name: "_INVALID_"}},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, testCase := range testCases {
|
||||
data, err := json.Marshal(testCase.pod)
|
||||
if err != nil {
|
||||
t.Fatalf("%s: Some weird json problem: %v", testCase.desc, err)
|
||||
}
|
||||
fakeHandler := utiltesting.FakeHandler{
|
||||
StatusCode: 200,
|
||||
ResponseBody: string(data),
|
||||
}
|
||||
testServer := httptest.NewServer(&fakeHandler)
|
||||
defer testServer.Close()
|
||||
ch := make(chan interface{}, 1)
|
||||
c := sourceURL{testServer.URL, http.Header{}, "localhost", ch, nil, 0, http.DefaultClient}
|
||||
if err := c.extractFromURL(); err == nil {
|
||||
t.Errorf("%s: Expected error", testCase.desc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractPodsFromHTTP(t *testing.T) {
|
||||
nodeName := "different-value"
|
||||
|
||||
grace := int64(30)
|
||||
var testCases = []struct {
|
||||
desc string
|
||||
pods runtime.Object
|
||||
expected kubetypes.PodUpdate
|
||||
}{
|
||||
{
|
||||
desc: "Single pod",
|
||||
pods: &v1.Pod{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Pod",
|
||||
APIVersion: "",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "foo",
|
||||
UID: "111",
|
||||
Namespace: "mynamespace",
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
NodeName: string(nodeName),
|
||||
Containers: []v1.Container{{Name: "1", Image: "foo", ImagePullPolicy: v1.PullAlways, TerminationMessagePolicy: v1.TerminationMessageReadFile}},
|
||||
SecurityContext: &v1.PodSecurityContext{},
|
||||
SchedulerName: api.DefaultSchedulerName,
|
||||
},
|
||||
Status: v1.PodStatus{
|
||||
Phase: v1.PodPending,
|
||||
},
|
||||
},
|
||||
expected: CreatePodUpdate(kubetypes.SET,
|
||||
kubetypes.HTTPSource,
|
||||
&v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
UID: "111",
|
||||
Name: "foo" + "-" + nodeName,
|
||||
Namespace: "mynamespace",
|
||||
Annotations: map[string]string{kubetypes.ConfigHashAnnotationKey: "111"},
|
||||
SelfLink: getSelfLink("foo-"+nodeName, "mynamespace"),
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
NodeName: nodeName,
|
||||
RestartPolicy: v1.RestartPolicyAlways,
|
||||
DNSPolicy: v1.DNSClusterFirst,
|
||||
SecurityContext: &v1.PodSecurityContext{},
|
||||
TerminationGracePeriodSeconds: &grace,
|
||||
SchedulerName: api.DefaultSchedulerName,
|
||||
|
||||
Containers: []v1.Container{{
|
||||
Name: "1",
|
||||
Image: "foo",
|
||||
TerminationMessagePath: "/dev/termination-log",
|
||||
ImagePullPolicy: "Always",
|
||||
TerminationMessagePolicy: v1.TerminationMessageReadFile,
|
||||
}},
|
||||
},
|
||||
Status: v1.PodStatus{
|
||||
Phase: v1.PodPending,
|
||||
},
|
||||
}),
|
||||
},
|
||||
{
|
||||
desc: "Multiple pods",
|
||||
pods: &v1.PodList{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "PodList",
|
||||
APIVersion: "",
|
||||
},
|
||||
Items: []v1.Pod{
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "foo",
|
||||
UID: "111",
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
NodeName: nodeName,
|
||||
Containers: []v1.Container{{Name: "1", Image: "foo", ImagePullPolicy: v1.PullAlways, TerminationMessagePolicy: v1.TerminationMessageReadFile}},
|
||||
SecurityContext: &v1.PodSecurityContext{},
|
||||
SchedulerName: api.DefaultSchedulerName,
|
||||
},
|
||||
Status: v1.PodStatus{
|
||||
Phase: v1.PodPending,
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "bar",
|
||||
UID: "222",
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
NodeName: nodeName,
|
||||
Containers: []v1.Container{{Name: "2", Image: "bar:bartag", ImagePullPolicy: "", TerminationMessagePolicy: v1.TerminationMessageReadFile}},
|
||||
SecurityContext: &v1.PodSecurityContext{},
|
||||
SchedulerName: api.DefaultSchedulerName,
|
||||
},
|
||||
Status: v1.PodStatus{
|
||||
Phase: v1.PodPending,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: CreatePodUpdate(kubetypes.SET,
|
||||
kubetypes.HTTPSource,
|
||||
&v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
UID: "111",
|
||||
Name: "foo" + "-" + nodeName,
|
||||
Namespace: "default",
|
||||
Annotations: map[string]string{kubetypes.ConfigHashAnnotationKey: "111"},
|
||||
SelfLink: getSelfLink("foo-"+nodeName, metav1.NamespaceDefault),
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
NodeName: nodeName,
|
||||
RestartPolicy: v1.RestartPolicyAlways,
|
||||
DNSPolicy: v1.DNSClusterFirst,
|
||||
TerminationGracePeriodSeconds: &grace,
|
||||
SecurityContext: &v1.PodSecurityContext{},
|
||||
SchedulerName: api.DefaultSchedulerName,
|
||||
|
||||
Containers: []v1.Container{{
|
||||
Name: "1",
|
||||
Image: "foo",
|
||||
TerminationMessagePath: "/dev/termination-log",
|
||||
ImagePullPolicy: "Always",
|
||||
TerminationMessagePolicy: v1.TerminationMessageReadFile,
|
||||
}},
|
||||
},
|
||||
Status: v1.PodStatus{
|
||||
Phase: v1.PodPending,
|
||||
},
|
||||
},
|
||||
&v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
UID: "222",
|
||||
Name: "bar" + "-" + nodeName,
|
||||
Namespace: "default",
|
||||
Annotations: map[string]string{kubetypes.ConfigHashAnnotationKey: "222"},
|
||||
SelfLink: getSelfLink("bar-"+nodeName, metav1.NamespaceDefault),
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
NodeName: nodeName,
|
||||
RestartPolicy: v1.RestartPolicyAlways,
|
||||
DNSPolicy: v1.DNSClusterFirst,
|
||||
TerminationGracePeriodSeconds: &grace,
|
||||
SecurityContext: &v1.PodSecurityContext{},
|
||||
SchedulerName: api.DefaultSchedulerName,
|
||||
|
||||
Containers: []v1.Container{{
|
||||
Name: "2",
|
||||
Image: "bar:bartag",
|
||||
TerminationMessagePath: "/dev/termination-log",
|
||||
ImagePullPolicy: "IfNotPresent",
|
||||
TerminationMessagePolicy: v1.TerminationMessageReadFile,
|
||||
}},
|
||||
},
|
||||
Status: v1.PodStatus{
|
||||
Phase: v1.PodPending,
|
||||
},
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
var versionedPods runtime.Object
|
||||
err := testapi.Default.Converter().Convert(&testCase.pods, &versionedPods, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("%s: error in versioning the pods: %s", testCase.desc, err)
|
||||
}
|
||||
data, err := runtime.Encode(testapi.Default.Codec(), versionedPods)
|
||||
if err != nil {
|
||||
t.Fatalf("%s: error in encoding the pod: %v", testCase.desc, err)
|
||||
}
|
||||
fakeHandler := utiltesting.FakeHandler{
|
||||
StatusCode: 200,
|
||||
ResponseBody: string(data),
|
||||
}
|
||||
testServer := httptest.NewServer(&fakeHandler)
|
||||
defer testServer.Close()
|
||||
ch := make(chan interface{}, 1)
|
||||
c := sourceURL{testServer.URL, http.Header{}, types.NodeName(nodeName), ch, nil, 0, http.DefaultClient}
|
||||
if err := c.extractFromURL(); err != nil {
|
||||
t.Errorf("%s: Unexpected error: %v", testCase.desc, err)
|
||||
continue
|
||||
}
|
||||
update := (<-ch).(kubetypes.PodUpdate)
|
||||
|
||||
if !apiequality.Semantic.DeepEqual(testCase.expected, update) {
|
||||
t.Errorf("%s: Expected: %#v, Got: %#v", testCase.desc, testCase.expected, update)
|
||||
}
|
||||
for _, pod := range update.Pods {
|
||||
// TODO: remove the conversion when validation is performed on versioned objects.
|
||||
internalPod := &api.Pod{}
|
||||
if err := k8s_api_v1.Convert_v1_Pod_To_core_Pod(pod, internalPod, nil); err != nil {
|
||||
t.Fatalf("%s: Cannot convert pod %#v, %#v", testCase.desc, pod, err)
|
||||
}
|
||||
if errs := validation.ValidatePod(internalPod); len(errs) != 0 {
|
||||
t.Errorf("%s: Expected no validation errors on %#v, Got %v", testCase.desc, pod, errs.ToAggregate())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestURLWithHeader(t *testing.T) {
|
||||
pod := &v1.Pod{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: legacyscheme.Registry.GroupOrDie(v1.GroupName).GroupVersion.String(),
|
||||
Kind: "Pod",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "foo",
|
||||
UID: "111",
|
||||
Namespace: "mynamespace",
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
NodeName: "localhost",
|
||||
Containers: []v1.Container{{Name: "1", Image: "foo", ImagePullPolicy: v1.PullAlways}},
|
||||
},
|
||||
}
|
||||
data, err := json.Marshal(pod)
|
||||
if err != nil {
|
||||
t.Fatalf("Unexpected json marshalling error: %v", err)
|
||||
}
|
||||
fakeHandler := utiltesting.FakeHandler{
|
||||
StatusCode: 200,
|
||||
ResponseBody: string(data),
|
||||
}
|
||||
testServer := httptest.NewServer(&fakeHandler)
|
||||
defer testServer.Close()
|
||||
ch := make(chan interface{}, 1)
|
||||
header := make(http.Header)
|
||||
header.Set("Metadata-Flavor", "Google")
|
||||
c := sourceURL{testServer.URL, header, "localhost", ch, nil, 0, http.DefaultClient}
|
||||
if err := c.extractFromURL(); err != nil {
|
||||
t.Fatalf("Unexpected error extracting from URL: %v", err)
|
||||
}
|
||||
update := (<-ch).(kubetypes.PodUpdate)
|
||||
|
||||
headerVal := fakeHandler.RequestReceived.Header["Metadata-Flavor"]
|
||||
if len(headerVal) != 1 || headerVal[0] != "Google" {
|
||||
t.Errorf("Header missing expected entry %v. Got %v", header, fakeHandler.RequestReceived.Header)
|
||||
}
|
||||
if len(update.Pods) != 1 {
|
||||
t.Errorf("Received wrong number of pods, expected one: %v", update.Pods)
|
||||
}
|
||||
}
|
67
vendor/k8s.io/kubernetes/pkg/kubelet/config/sources.go
generated
vendored
Normal file
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)
|
||||
}
|
Reference in New Issue
Block a user