kubernetes/pkg/kubelet/mirror_manager_test.go
Yu-Ju Hong 929fb63b33 Sync static pods from Kubelet to the API server
Currently, API server is not aware of the static pods (manifests from
sources other than the API server, e.g. file and http) at all. This is
inconvenient since users cannot check the static pods through kubectl.
It is also sub-optimal because scheduler is unaware of the resource
consumption by these static pods on the node.

This change syncs the information back to the API server by creating a
mirror pod via API server for each static pod.

 - Kubelet creates containers for the static pod, as it would do
   normally.

 - If a mirror pod gets deleted, Kubelet will re-create one. The
   containers are sync'd to the static pods, so they will not be
   affected.

 - If a static pod gets removed from the source (e.g. manifest file
   removed from the directory), the orphaned mirror pod will be deleted.

Note that because events are associated with UID, and the mirror pod has
a different UID than the original static pod, the events will not be
shown for the mirror pod when running `kubectl describe pod
<mirror_pod>`.
2015-03-17 08:45:56 -07:00

159 lines
4.4 KiB
Go

/*
Copyright 2015 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 kubelet
import (
"reflect"
"sync"
"testing"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
)
type fakeMirrorManager struct {
mirrorPodLock sync.RWMutex
// Note that a real mirror manager does not store the mirror pods in
// itself. This fake manager does this to track calls.
mirrorPods util.StringSet
createCounts map[string]int
deleteCounts map[string]int
}
func (self *fakeMirrorManager) CreateMirrorPod(pod api.Pod, _ string) error {
self.mirrorPodLock.Lock()
defer self.mirrorPodLock.Unlock()
podFullName := GetPodFullName(&pod)
self.mirrorPods.Insert(podFullName)
self.createCounts[podFullName]++
return nil
}
func (self *fakeMirrorManager) DeleteMirrorPod(podFullName string) error {
self.mirrorPodLock.Lock()
defer self.mirrorPodLock.Unlock()
self.mirrorPods.Delete(podFullName)
self.deleteCounts[podFullName]++
return nil
}
func newFakeMirrorMananger() *fakeMirrorManager {
m := fakeMirrorManager{}
m.mirrorPods = util.NewStringSet()
m.createCounts = make(map[string]int)
m.deleteCounts = make(map[string]int)
return &m
}
func (self *fakeMirrorManager) HasPod(podFullName string) bool {
self.mirrorPodLock.RLock()
defer self.mirrorPodLock.RUnlock()
return self.mirrorPods.Has(podFullName)
}
func (self *fakeMirrorManager) NumOfPods() int {
self.mirrorPodLock.RLock()
defer self.mirrorPodLock.RUnlock()
return self.mirrorPods.Len()
}
func (self *fakeMirrorManager) GetPods() []string {
self.mirrorPodLock.RLock()
defer self.mirrorPodLock.RUnlock()
return self.mirrorPods.List()
}
func (self *fakeMirrorManager) GetCounts(podFullName string) (int, int) {
self.mirrorPodLock.RLock()
defer self.mirrorPodLock.RUnlock()
return self.createCounts[podFullName], self.deleteCounts[podFullName]
}
// Tests that mirror pods are filtered out properly from the pod update.
func TestFilterOutMirrorPods(t *testing.T) {
mirrorPod := api.Pod{
ObjectMeta: api.ObjectMeta{
UID: "987654321",
Name: "bar",
Namespace: "default",
Annotations: map[string]string{
ConfigSourceAnnotationKey: "api",
ConfigMirrorAnnotationKey: "mirror",
},
},
}
staticPod := api.Pod{
ObjectMeta: api.ObjectMeta{
UID: "123456789",
Name: "bar",
Namespace: "default",
Annotations: map[string]string{ConfigSourceAnnotationKey: "file"},
},
}
expectedPods := []api.Pod{
{
ObjectMeta: api.ObjectMeta{
UID: "999999999",
Name: "taco",
Namespace: "default",
Annotations: map[string]string{ConfigSourceAnnotationKey: "api"},
},
},
staticPod,
}
updates := append(expectedPods, mirrorPod)
actualPods, actualMirrorPods := filterAndCategorizePods(updates)
if !reflect.DeepEqual(expectedPods, actualPods) {
t.Errorf("expected %#v, got %#v", expectedPods, actualPods)
}
if !actualMirrorPods.Has(GetPodFullName(&mirrorPod)) {
t.Errorf("mirror pod is not recorded")
}
}
func TestParsePodFullName(t *testing.T) {
type nameTuple struct {
Name string
Namespace string
}
successfulCases := map[string]nameTuple{
"bar_foo": {Name: "bar", Namespace: "foo"},
"bar.org_foo.com": {Name: "bar.org", Namespace: "foo.com"},
"bar-bar_foo": {Name: "bar-bar", Namespace: "foo"},
}
failedCases := []string{"barfoo", "bar_foo_foo", ""}
for podFullName, expected := range successfulCases {
name, namespace, err := ParsePodFullName(podFullName)
if err != nil {
t.Errorf("unexpected error when parsing the full name: %v", err)
continue
}
if name != expected.Name || namespace != expected.Namespace {
t.Errorf("expected name %q, namespace %q; got name %q, namespace %q",
expected.Name, expected.Namespace, name, namespace)
}
}
for _, podFullName := range failedCases {
_, _, err := ParsePodFullName(podFullName)
if err == nil {
t.Errorf("expected error when parsing the full name, got none")
}
}
}