mirror of https://github.com/k3s-io/k3s
526 lines
14 KiB
Go
526 lines
14 KiB
Go
/*
|
|
Copyright 2014 Google Inc. All rights reserved.
|
|
|
|
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 master
|
|
|
|
import (
|
|
"reflect"
|
|
"sync"
|
|
"testing"
|
|
|
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/leaky"
|
|
"github.com/GoogleCloudPlatform/kubernetes/pkg/registry/registrytest"
|
|
)
|
|
|
|
type podInfoCall struct {
|
|
host string
|
|
namespace string
|
|
name string
|
|
}
|
|
|
|
type podInfoResponse struct {
|
|
useCount int
|
|
data api.PodStatusResult
|
|
err error
|
|
}
|
|
|
|
type podInfoCalls map[podInfoCall]*podInfoResponse
|
|
|
|
type FakePodInfoGetter struct {
|
|
calls podInfoCalls
|
|
lock sync.Mutex
|
|
|
|
// default data/error to return, or you can add
|
|
// responses to specific calls-- that will take precedence.
|
|
data api.PodStatusResult
|
|
err error
|
|
}
|
|
|
|
func (f *FakePodInfoGetter) GetPodStatus(host, namespace, name string) (api.PodStatusResult, error) {
|
|
f.lock.Lock()
|
|
defer f.lock.Unlock()
|
|
|
|
if f.calls == nil {
|
|
f.calls = podInfoCalls{}
|
|
}
|
|
|
|
key := podInfoCall{host, namespace, name}
|
|
call, ok := f.calls[key]
|
|
if !ok {
|
|
f.calls[key] = &podInfoResponse{
|
|
0, f.data, f.err,
|
|
}
|
|
call = f.calls[key]
|
|
}
|
|
call.useCount++
|
|
return call.data, call.err
|
|
}
|
|
|
|
func TestPodCacheGetDifferentNamespace(t *testing.T) {
|
|
cache := NewPodCache(nil, nil, nil)
|
|
|
|
expectedDefault := api.PodStatus{
|
|
Info: api.PodInfo{
|
|
"foo": api.ContainerStatus{},
|
|
},
|
|
}
|
|
expectedOther := api.PodStatus{
|
|
Info: api.PodInfo{
|
|
"bar": api.ContainerStatus{},
|
|
},
|
|
}
|
|
|
|
cache.podStatus[objKey{api.NamespaceDefault, "foo"}] = expectedDefault
|
|
cache.podStatus[objKey{"other", "foo"}] = expectedOther
|
|
|
|
info, err := cache.GetPodStatus(api.NamespaceDefault, "foo")
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %+v", err)
|
|
}
|
|
if !reflect.DeepEqual(info, &expectedDefault) {
|
|
t.Errorf("Unexpected mismatch. Expected: %+v, Got: %+v", &expectedOther, info)
|
|
}
|
|
|
|
info, err = cache.GetPodStatus("other", "foo")
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %+v", err)
|
|
}
|
|
if !reflect.DeepEqual(info, &expectedOther) {
|
|
t.Errorf("Unexpected mismatch. Expected: %+v, Got: %+v", &expectedOther, info)
|
|
}
|
|
}
|
|
|
|
func TestPodCacheGet(t *testing.T) {
|
|
cache := NewPodCache(nil, nil, nil)
|
|
|
|
expected := api.PodStatus{
|
|
Info: api.PodInfo{
|
|
"foo": api.ContainerStatus{},
|
|
},
|
|
}
|
|
cache.podStatus[objKey{api.NamespaceDefault, "foo"}] = expected
|
|
|
|
info, err := cache.GetPodStatus(api.NamespaceDefault, "foo")
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %+v", err)
|
|
}
|
|
if !reflect.DeepEqual(info, &expected) {
|
|
t.Errorf("Unexpected mismatch. Expected: %+v, Got: %+v", &expected, info)
|
|
}
|
|
}
|
|
|
|
func TestPodCacheDelete(t *testing.T) {
|
|
config := podCacheTestConfig{
|
|
err: client.ErrPodInfoNotAvailable,
|
|
}
|
|
cache := config.Construct()
|
|
|
|
expected := api.PodStatus{
|
|
Info: api.PodInfo{
|
|
"foo": api.ContainerStatus{},
|
|
},
|
|
}
|
|
cache.podStatus[objKey{api.NamespaceDefault, "foo"}] = expected
|
|
|
|
info, err := cache.GetPodStatus(api.NamespaceDefault, "foo")
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %+v", err)
|
|
}
|
|
if !reflect.DeepEqual(info, &expected) {
|
|
t.Errorf("Unexpected mismatch. Expected: %+v, Got: %+v", &expected, info)
|
|
}
|
|
|
|
cache.ClearPodStatus(api.NamespaceDefault, "foo")
|
|
|
|
_, err = cache.GetPodStatus(api.NamespaceDefault, "foo")
|
|
if err == nil {
|
|
t.Errorf("Unexpected non-error after deleting")
|
|
}
|
|
if err != client.ErrPodInfoNotAvailable {
|
|
t.Errorf("Unexpected error: %v, expecting: %v", err, client.ErrPodInfoNotAvailable)
|
|
}
|
|
}
|
|
|
|
func TestPodCacheGetMissing(t *testing.T) {
|
|
pod1 := makePod(api.NamespaceDefault, "foo", "machine", "bar")
|
|
config := podCacheTestConfig{
|
|
kubeletContainerInfo: api.PodStatus{
|
|
Info: api.PodInfo{"bar": api.ContainerStatus{}}},
|
|
nodes: []api.Node{*makeHealthyNode("machine", "1.2.3.5")},
|
|
pod: pod1,
|
|
}
|
|
cache := config.Construct()
|
|
|
|
status, err := cache.GetPodStatus(api.NamespaceDefault, "foo")
|
|
if err != nil {
|
|
t.Errorf("Unexpected error: %+v", err)
|
|
}
|
|
if status == nil {
|
|
t.Errorf("Unexpected non-status.")
|
|
}
|
|
}
|
|
|
|
type podCacheTestConfig struct {
|
|
nodes []api.Node
|
|
pods []api.Pod
|
|
pod *api.Pod
|
|
err error
|
|
kubeletContainerInfo api.PodStatus
|
|
|
|
// Construct will fill in these fields
|
|
fakePodInfo *FakePodInfoGetter
|
|
fakeNodes *client.Fake
|
|
fakePods *registrytest.PodRegistry
|
|
}
|
|
|
|
func (c *podCacheTestConfig) Construct() *PodCache {
|
|
c.fakePodInfo = &FakePodInfoGetter{
|
|
data: api.PodStatusResult{
|
|
Status: c.kubeletContainerInfo,
|
|
},
|
|
}
|
|
c.fakeNodes = &client.Fake{
|
|
MinionsList: api.NodeList{
|
|
Items: c.nodes,
|
|
},
|
|
}
|
|
c.fakePods = registrytest.NewPodRegistry(&api.PodList{Items: c.pods})
|
|
c.fakePods.Pod = c.pod
|
|
c.fakePods.Err = c.err
|
|
return NewPodCache(
|
|
c.fakePodInfo,
|
|
c.fakeNodes.Nodes(),
|
|
c.fakePods,
|
|
)
|
|
}
|
|
|
|
func makePod(namespace, name, host string, containers ...string) *api.Pod {
|
|
pod := &api.Pod{
|
|
ObjectMeta: api.ObjectMeta{Namespace: namespace, Name: name},
|
|
Status: api.PodStatus{Host: host},
|
|
}
|
|
for _, c := range containers {
|
|
pod.Spec.Containers = append(pod.Spec.Containers, api.Container{Name: c})
|
|
}
|
|
return pod
|
|
}
|
|
|
|
func makeHealthyNode(name string, ip string) *api.Node {
|
|
return &api.Node{
|
|
ObjectMeta: api.ObjectMeta{Name: name},
|
|
Status: api.NodeStatus{
|
|
Addresses: []api.NodeAddress{
|
|
{Type: api.NodeLegacyHostIP, Address: ip},
|
|
},
|
|
Conditions: []api.NodeCondition{
|
|
{Type: api.NodeReady, Status: api.ConditionFull},
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func makeUnhealthyNode(name string) *api.Node {
|
|
return &api.Node{
|
|
ObjectMeta: api.ObjectMeta{Name: name},
|
|
Status: api.NodeStatus{Conditions: []api.NodeCondition{
|
|
{Type: api.NodeReady, Status: api.ConditionNone},
|
|
}},
|
|
}
|
|
}
|
|
|
|
func TestPodUpdateAllContainersClearsNodeStatus(t *testing.T) {
|
|
node := makeHealthyNode("machine", "1.2.3.5")
|
|
pod1 := makePod(api.NamespaceDefault, "foo", "machine", "bar")
|
|
pod2 := makePod(api.NamespaceDefault, "baz", "machine", "qux")
|
|
config := podCacheTestConfig{
|
|
kubeletContainerInfo: api.PodStatus{
|
|
Info: api.PodInfo{"bar": api.ContainerStatus{}}},
|
|
nodes: []api.Node{*node},
|
|
pods: []api.Pod{*pod1, *pod2},
|
|
}
|
|
cache := config.Construct()
|
|
|
|
if len(cache.currentNodes) != 0 {
|
|
t.Errorf("unexpected node cache: %v", cache.currentNodes)
|
|
}
|
|
key := objKey{"", "machine"}
|
|
cache.currentNodes[key] = makeUnhealthyNode("machine").Status
|
|
|
|
cache.UpdateAllContainers()
|
|
|
|
if len(cache.currentNodes) != 1 {
|
|
t.Errorf("unexpected empty node cache: %v", cache.currentNodes)
|
|
}
|
|
|
|
if !reflect.DeepEqual(cache.currentNodes[key], node.Status) {
|
|
t.Errorf("unexpected status:\n%#v\nexpected:\n%#v\n", cache.currentNodes[key], node.Status)
|
|
}
|
|
}
|
|
|
|
func TestPodUpdateAllContainers(t *testing.T) {
|
|
pod1 := makePod(api.NamespaceDefault, "foo", "machine", "bar")
|
|
pod2 := makePod(api.NamespaceDefault, "baz", "machine", "qux")
|
|
config := podCacheTestConfig{
|
|
kubeletContainerInfo: api.PodStatus{
|
|
Info: api.PodInfo{"bar": api.ContainerStatus{}}},
|
|
nodes: []api.Node{*makeHealthyNode("machine", "1.2.3.5")},
|
|
pods: []api.Pod{*pod1, *pod2},
|
|
}
|
|
cache := config.Construct()
|
|
|
|
cache.UpdateAllContainers()
|
|
|
|
call1 := config.fakePodInfo.calls[podInfoCall{"machine", api.NamespaceDefault, "foo"}]
|
|
call2 := config.fakePodInfo.calls[podInfoCall{"machine", api.NamespaceDefault, "baz"}]
|
|
if call1 == nil || call1.useCount != 1 {
|
|
t.Errorf("Expected 1 call for 'foo': %+v", config.fakePodInfo.calls)
|
|
}
|
|
if call2 == nil || call2.useCount != 1 {
|
|
t.Errorf("Expected 1 call for 'baz': %+v", config.fakePodInfo.calls)
|
|
}
|
|
if len(config.fakePodInfo.calls) != 2 {
|
|
t.Errorf("Expected 2 calls: %+v", config.fakePodInfo.calls)
|
|
}
|
|
|
|
status, err := cache.GetPodStatus(api.NamespaceDefault, "foo")
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %+v", err)
|
|
}
|
|
if e, a := config.kubeletContainerInfo.Info, status.Info; !reflect.DeepEqual(e, a) {
|
|
t.Errorf("Unexpected mismatch. Expected: %+v, Got: %+v", e, a)
|
|
}
|
|
if e, a := "1.2.3.5", status.HostIP; e != a {
|
|
t.Errorf("Unexpected mismatch. Expected: %+v, Got: %+v", e, a)
|
|
}
|
|
|
|
if e, a := 1, len(config.fakeNodes.Actions); e != a {
|
|
t.Errorf("Expected: %v, Got: %v; %+v", e, a, config.fakeNodes.Actions)
|
|
} else {
|
|
if e, a := "get-minion", config.fakeNodes.Actions[0].Action; e != a {
|
|
t.Errorf("Expected: %v, Got: %v; %+v", e, a, config.fakeNodes.Actions)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFillPodStatusNoHost(t *testing.T) {
|
|
pod := makePod(api.NamespaceDefault, "foo", "", "bar")
|
|
config := podCacheTestConfig{
|
|
kubeletContainerInfo: api.PodStatus{},
|
|
nodes: []api.Node{*makeHealthyNode("machine", "")},
|
|
pods: []api.Pod{*pod},
|
|
}
|
|
cache := config.Construct()
|
|
err := cache.updatePodStatus(&config.pods[0])
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %+v", err)
|
|
}
|
|
|
|
status, err := cache.GetPodStatus(pod.Namespace, pod.Name)
|
|
if e, a := api.PodPending, status.Phase; e != a {
|
|
t.Errorf("Expected: %+v, Got %+v", e, a)
|
|
}
|
|
}
|
|
|
|
func TestFillPodStatusMissingMachine(t *testing.T) {
|
|
pod := makePod(api.NamespaceDefault, "foo", "machine", "bar")
|
|
config := podCacheTestConfig{
|
|
kubeletContainerInfo: api.PodStatus{},
|
|
nodes: []api.Node{},
|
|
pods: []api.Pod{*pod},
|
|
}
|
|
cache := config.Construct()
|
|
err := cache.updatePodStatus(&config.pods[0])
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %+v", err)
|
|
}
|
|
|
|
status, err := cache.GetPodStatus(pod.Namespace, pod.Name)
|
|
if e, a := api.PodUnknown, status.Phase; e != a {
|
|
t.Errorf("Expected: %+v, Got %+v", e, a)
|
|
}
|
|
}
|
|
|
|
func TestFillPodInfoNoData(t *testing.T) {
|
|
pod := makePod(api.NamespaceDefault, "foo", "machine", "bar")
|
|
expectedIP := ""
|
|
config := podCacheTestConfig{
|
|
kubeletContainerInfo: api.PodStatus{
|
|
Phase: api.PodPending,
|
|
Host: "machine",
|
|
HostIP: "ip of machine",
|
|
Info: api.PodInfo{
|
|
leaky.PodInfraContainerName: {},
|
|
},
|
|
},
|
|
nodes: []api.Node{*makeHealthyNode("machine", "ip of machine")},
|
|
pods: []api.Pod{*pod},
|
|
}
|
|
cache := config.Construct()
|
|
err := cache.updatePodStatus(&config.pods[0])
|
|
if err != nil {
|
|
t.Fatalf("Unexpected error: %+v", err)
|
|
}
|
|
|
|
status, err := cache.GetPodStatus(pod.Namespace, pod.Name)
|
|
if e, a := &config.kubeletContainerInfo, status; !reflect.DeepEqual(e, a) {
|
|
t.Errorf("Expected: %+v, Got %+v", e, a)
|
|
}
|
|
if status.PodIP != expectedIP {
|
|
t.Errorf("Expected %s, Got %s", expectedIP, status.PodIP)
|
|
}
|
|
}
|
|
|
|
func TestPodPhaseWithBadNode(t *testing.T) {
|
|
desiredState := api.PodSpec{
|
|
Containers: []api.Container{
|
|
{Name: "containerA"},
|
|
{Name: "containerB"},
|
|
},
|
|
RestartPolicy: api.RestartPolicy{Always: &api.RestartPolicyAlways{}},
|
|
}
|
|
runningState := api.ContainerStatus{
|
|
State: api.ContainerState{
|
|
Running: &api.ContainerStateRunning{},
|
|
},
|
|
}
|
|
stoppedState := api.ContainerStatus{
|
|
State: api.ContainerState{
|
|
Termination: &api.ContainerStateTerminated{},
|
|
},
|
|
}
|
|
|
|
tests := []struct {
|
|
pod *api.Pod
|
|
nodes []api.Node
|
|
status api.PodPhase
|
|
test string
|
|
}{
|
|
{
|
|
&api.Pod{
|
|
Spec: desiredState,
|
|
Status: api.PodStatus{
|
|
Host: "machine-two",
|
|
},
|
|
},
|
|
[]api.Node{},
|
|
api.PodUnknown,
|
|
"no info, but bad machine",
|
|
},
|
|
{
|
|
&api.Pod{
|
|
Spec: desiredState,
|
|
Status: api.PodStatus{
|
|
Info: map[string]api.ContainerStatus{
|
|
"containerA": runningState,
|
|
"containerB": runningState,
|
|
},
|
|
Host: "machine-two",
|
|
},
|
|
},
|
|
[]api.Node{},
|
|
api.PodUnknown,
|
|
"all running but minion is missing",
|
|
},
|
|
{
|
|
&api.Pod{
|
|
Spec: desiredState,
|
|
Status: api.PodStatus{
|
|
Info: map[string]api.ContainerStatus{
|
|
"containerA": stoppedState,
|
|
"containerB": stoppedState,
|
|
},
|
|
Host: "machine-two",
|
|
},
|
|
},
|
|
[]api.Node{},
|
|
api.PodUnknown,
|
|
"all stopped but minion missing",
|
|
},
|
|
{
|
|
&api.Pod{
|
|
Spec: desiredState,
|
|
Status: api.PodStatus{
|
|
Info: map[string]api.ContainerStatus{
|
|
"containerA": runningState,
|
|
"containerB": runningState,
|
|
},
|
|
Host: "machine-two",
|
|
},
|
|
},
|
|
[]api.Node{*makeUnhealthyNode("machine-two")},
|
|
api.PodUnknown,
|
|
"all running but minion is unhealthy",
|
|
},
|
|
{
|
|
&api.Pod{
|
|
Spec: desiredState,
|
|
Status: api.PodStatus{
|
|
Info: map[string]api.ContainerStatus{
|
|
"containerA": stoppedState,
|
|
"containerB": stoppedState,
|
|
},
|
|
Host: "machine-two",
|
|
},
|
|
},
|
|
[]api.Node{*makeUnhealthyNode("machine-two")},
|
|
api.PodUnknown,
|
|
"all stopped but minion is unhealthy",
|
|
},
|
|
}
|
|
for _, test := range tests {
|
|
config := podCacheTestConfig{
|
|
kubeletContainerInfo: test.pod.Status,
|
|
nodes: test.nodes,
|
|
pods: []api.Pod{*test.pod},
|
|
}
|
|
cache := config.Construct()
|
|
cache.UpdateAllContainers()
|
|
status, err := cache.GetPodStatus(test.pod.Namespace, test.pod.Name)
|
|
if err != nil {
|
|
t.Errorf("%v: Unexpected error %v", test.test, err)
|
|
continue
|
|
}
|
|
if e, a := test.status, status.Phase; e != a {
|
|
t.Errorf("In test %s, expected %v, got %v", test.test, e, a)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestGarbageCollection(t *testing.T) {
|
|
pod1 := makePod(api.NamespaceDefault, "foo", "machine", "bar")
|
|
pod2 := makePod(api.NamespaceDefault, "baz", "machine", "qux")
|
|
config := podCacheTestConfig{
|
|
pods: []api.Pod{*pod1, *pod2},
|
|
}
|
|
cache := config.Construct()
|
|
|
|
expected := api.PodStatus{
|
|
Info: api.PodInfo{
|
|
"extra": api.ContainerStatus{},
|
|
},
|
|
}
|
|
cache.podStatus[objKey{api.NamespaceDefault, "extra"}] = expected
|
|
|
|
cache.GarbageCollectPodStatus()
|
|
|
|
status, found := cache.podStatus[objKey{api.NamespaceDefault, "extra"}]
|
|
if found {
|
|
t.Errorf("unexpectedly found: %v for key %v", status, objKey{api.NamespaceDefault, "extra"})
|
|
}
|
|
}
|