
Two problems: 1. Get is always using Existing nodes slice, and you will for sure miss any updated data 2. Each Update duplicates node entry in UpdatedNodes slice For the 1st, try to find a node in UpdatedNodes slice (same as for the List). 2nd - append only if there is no node with same name as updated, if there is just replace object. Change-Id: I9ef1cca2788ba946eee37fa1b037c124ad76074c
332 lines
8.4 KiB
Go
332 lines
8.4 KiB
Go
/*
|
|
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 node
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"sync"
|
|
"time"
|
|
|
|
"k8s.io/kubernetes/pkg/api"
|
|
apierrors "k8s.io/kubernetes/pkg/api/errors"
|
|
"k8s.io/kubernetes/pkg/api/resource"
|
|
"k8s.io/kubernetes/pkg/api/unversioned"
|
|
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
|
|
unversionedcore "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/unversioned"
|
|
"k8s.io/kubernetes/pkg/runtime"
|
|
"k8s.io/kubernetes/pkg/util/clock"
|
|
utilnode "k8s.io/kubernetes/pkg/util/node"
|
|
"k8s.io/kubernetes/pkg/util/sets"
|
|
"k8s.io/kubernetes/pkg/watch"
|
|
)
|
|
|
|
// FakeNodeHandler is a fake implementation of NodesInterface and NodeInterface. It
|
|
// allows test cases to have fine-grained control over mock behaviors. We also need
|
|
// PodsInterface and PodInterface to test list & delet pods, which is implemented in
|
|
// the embedded client.Fake field.
|
|
type FakeNodeHandler struct {
|
|
*fake.Clientset
|
|
|
|
// Input: Hooks determine if request is valid or not
|
|
CreateHook func(*FakeNodeHandler, *api.Node) bool
|
|
Existing []*api.Node
|
|
|
|
// Output
|
|
CreatedNodes []*api.Node
|
|
DeletedNodes []*api.Node
|
|
UpdatedNodes []*api.Node
|
|
UpdatedNodeStatuses []*api.Node
|
|
RequestCount int
|
|
|
|
// Synchronization
|
|
lock sync.Mutex
|
|
deleteWaitChan chan struct{}
|
|
}
|
|
|
|
type FakeLegacyHandler struct {
|
|
unversionedcore.CoreInterface
|
|
n *FakeNodeHandler
|
|
}
|
|
|
|
func (c *FakeNodeHandler) getUpdatedNodesCopy() []*api.Node {
|
|
c.lock.Lock()
|
|
defer c.lock.Unlock()
|
|
updatedNodesCopy := make([]*api.Node, len(c.UpdatedNodes), len(c.UpdatedNodes))
|
|
for i, ptr := range c.UpdatedNodes {
|
|
updatedNodesCopy[i] = ptr
|
|
}
|
|
return updatedNodesCopy
|
|
}
|
|
|
|
func (c *FakeNodeHandler) Core() unversionedcore.CoreInterface {
|
|
return &FakeLegacyHandler{c.Clientset.Core(), c}
|
|
}
|
|
|
|
func (m *FakeLegacyHandler) Nodes() unversionedcore.NodeInterface {
|
|
return m.n
|
|
}
|
|
|
|
func (m *FakeNodeHandler) Create(node *api.Node) (*api.Node, error) {
|
|
m.lock.Lock()
|
|
defer func() {
|
|
m.RequestCount++
|
|
m.lock.Unlock()
|
|
}()
|
|
for _, n := range m.Existing {
|
|
if n.Name == node.Name {
|
|
return nil, apierrors.NewAlreadyExists(api.Resource("nodes"), node.Name)
|
|
}
|
|
}
|
|
if m.CreateHook == nil || m.CreateHook(m, node) {
|
|
nodeCopy := *node
|
|
m.CreatedNodes = append(m.CreatedNodes, &nodeCopy)
|
|
return node, nil
|
|
} else {
|
|
return nil, errors.New("Create error.")
|
|
}
|
|
}
|
|
|
|
func (m *FakeNodeHandler) Get(name string) (*api.Node, error) {
|
|
m.lock.Lock()
|
|
defer func() {
|
|
m.RequestCount++
|
|
m.lock.Unlock()
|
|
}()
|
|
for i := range m.UpdatedNodes {
|
|
if m.UpdatedNodes[i].Name == name {
|
|
nodeCopy := *m.UpdatedNodes[i]
|
|
return &nodeCopy, nil
|
|
}
|
|
}
|
|
for i := range m.Existing {
|
|
if m.Existing[i].Name == name {
|
|
nodeCopy := *m.Existing[i]
|
|
return &nodeCopy, nil
|
|
}
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
func (m *FakeNodeHandler) List(opts api.ListOptions) (*api.NodeList, error) {
|
|
m.lock.Lock()
|
|
defer func() {
|
|
m.RequestCount++
|
|
m.lock.Unlock()
|
|
}()
|
|
var nodes []*api.Node
|
|
for i := 0; i < len(m.UpdatedNodes); i++ {
|
|
if !contains(m.UpdatedNodes[i], m.DeletedNodes) {
|
|
nodes = append(nodes, m.UpdatedNodes[i])
|
|
}
|
|
}
|
|
for i := 0; i < len(m.Existing); i++ {
|
|
if !contains(m.Existing[i], m.DeletedNodes) && !contains(m.Existing[i], nodes) {
|
|
nodes = append(nodes, m.Existing[i])
|
|
}
|
|
}
|
|
for i := 0; i < len(m.CreatedNodes); i++ {
|
|
if !contains(m.CreatedNodes[i], m.DeletedNodes) && !contains(m.CreatedNodes[i], nodes) {
|
|
nodes = append(nodes, m.CreatedNodes[i])
|
|
}
|
|
}
|
|
nodeList := &api.NodeList{}
|
|
for _, node := range nodes {
|
|
nodeList.Items = append(nodeList.Items, *node)
|
|
}
|
|
return nodeList, nil
|
|
}
|
|
|
|
func (m *FakeNodeHandler) Delete(id string, opt *api.DeleteOptions) error {
|
|
m.lock.Lock()
|
|
defer func() {
|
|
m.RequestCount++
|
|
if m.deleteWaitChan != nil {
|
|
m.deleteWaitChan <- struct{}{}
|
|
}
|
|
m.lock.Unlock()
|
|
}()
|
|
m.DeletedNodes = append(m.DeletedNodes, newNode(id))
|
|
return nil
|
|
}
|
|
|
|
func (m *FakeNodeHandler) DeleteCollection(opt *api.DeleteOptions, listOpts api.ListOptions) error {
|
|
return nil
|
|
}
|
|
|
|
func (m *FakeNodeHandler) Update(node *api.Node) (*api.Node, error) {
|
|
m.lock.Lock()
|
|
defer func() {
|
|
m.RequestCount++
|
|
m.lock.Unlock()
|
|
}()
|
|
nodeCopy := *node
|
|
for i, updateNode := range m.UpdatedNodes {
|
|
if updateNode.Name == nodeCopy.Name {
|
|
m.UpdatedNodes[i] = &nodeCopy
|
|
return node, nil
|
|
}
|
|
}
|
|
m.UpdatedNodes = append(m.UpdatedNodes, &nodeCopy)
|
|
return node, nil
|
|
}
|
|
|
|
func (m *FakeNodeHandler) UpdateStatus(node *api.Node) (*api.Node, error) {
|
|
m.lock.Lock()
|
|
defer func() {
|
|
m.RequestCount++
|
|
m.lock.Unlock()
|
|
}()
|
|
nodeCopy := *node
|
|
m.UpdatedNodeStatuses = append(m.UpdatedNodeStatuses, &nodeCopy)
|
|
return node, nil
|
|
}
|
|
|
|
func (m *FakeNodeHandler) PatchStatus(nodeName string, data []byte) (*api.Node, error) {
|
|
m.RequestCount++
|
|
return &api.Node{}, nil
|
|
}
|
|
|
|
func (m *FakeNodeHandler) Watch(opts api.ListOptions) (watch.Interface, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (m *FakeNodeHandler) Patch(name string, pt api.PatchType, data []byte, subresources ...string) (*api.Node, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
// FakeRecorder is used as a fake during testing.
|
|
type FakeRecorder struct {
|
|
source api.EventSource
|
|
events []*api.Event
|
|
clock clock.Clock
|
|
}
|
|
|
|
func (f *FakeRecorder) Event(obj runtime.Object, eventtype, reason, message string) {
|
|
f.generateEvent(obj, unversioned.Now(), eventtype, reason, message)
|
|
}
|
|
|
|
func (f *FakeRecorder) Eventf(obj runtime.Object, eventtype, reason, messageFmt string, args ...interface{}) {
|
|
f.Event(obj, eventtype, reason, fmt.Sprintf(messageFmt, args...))
|
|
}
|
|
|
|
func (f *FakeRecorder) PastEventf(obj runtime.Object, timestamp unversioned.Time, eventtype, reason, messageFmt string, args ...interface{}) {
|
|
}
|
|
|
|
func (f *FakeRecorder) generateEvent(obj runtime.Object, timestamp unversioned.Time, eventtype, reason, message string) {
|
|
ref, err := api.GetReference(obj)
|
|
if err != nil {
|
|
return
|
|
}
|
|
event := f.makeEvent(ref, eventtype, reason, message)
|
|
event.Source = f.source
|
|
if f.events != nil {
|
|
fmt.Println("write event")
|
|
f.events = append(f.events, event)
|
|
}
|
|
}
|
|
|
|
func (f *FakeRecorder) makeEvent(ref *api.ObjectReference, eventtype, reason, message string) *api.Event {
|
|
fmt.Println("make event")
|
|
t := unversioned.Time{Time: f.clock.Now()}
|
|
namespace := ref.Namespace
|
|
if namespace == "" {
|
|
namespace = api.NamespaceDefault
|
|
}
|
|
return &api.Event{
|
|
ObjectMeta: api.ObjectMeta{
|
|
Name: fmt.Sprintf("%v.%x", ref.Name, t.UnixNano()),
|
|
Namespace: namespace,
|
|
},
|
|
InvolvedObject: *ref,
|
|
Reason: reason,
|
|
Message: message,
|
|
FirstTimestamp: t,
|
|
LastTimestamp: t,
|
|
Count: 1,
|
|
Type: eventtype,
|
|
}
|
|
}
|
|
|
|
func NewFakeRecorder() *FakeRecorder {
|
|
return &FakeRecorder{
|
|
source: api.EventSource{Component: "nodeControllerTest"},
|
|
events: []*api.Event{},
|
|
clock: clock.NewFakeClock(time.Now()),
|
|
}
|
|
}
|
|
|
|
func newNode(name string) *api.Node {
|
|
return &api.Node{
|
|
ObjectMeta: api.ObjectMeta{Name: name},
|
|
Spec: api.NodeSpec{
|
|
ExternalID: name,
|
|
},
|
|
Status: api.NodeStatus{
|
|
Capacity: api.ResourceList{
|
|
api.ResourceName(api.ResourceCPU): resource.MustParse("10"),
|
|
api.ResourceName(api.ResourceMemory): resource.MustParse("10G"),
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
func newPod(name, host string) *api.Pod {
|
|
pod := &api.Pod{
|
|
ObjectMeta: api.ObjectMeta{
|
|
Namespace: "default",
|
|
Name: name,
|
|
},
|
|
Spec: api.PodSpec{
|
|
NodeName: host,
|
|
},
|
|
Status: api.PodStatus{
|
|
Conditions: []api.PodCondition{
|
|
{
|
|
Type: api.PodReady,
|
|
Status: api.ConditionTrue,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
return pod
|
|
}
|
|
|
|
func contains(node *api.Node, nodes []*api.Node) bool {
|
|
for i := 0; i < len(nodes); i++ {
|
|
if node.Name == nodes[i].Name {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Returns list of zones for all Nodes stored in FakeNodeHandler
|
|
func getZones(nodeHandler *FakeNodeHandler) []string {
|
|
nodes, _ := nodeHandler.List(api.ListOptions{})
|
|
zones := sets.NewString()
|
|
for _, node := range nodes.Items {
|
|
zones.Insert(utilnode.GetZoneKey(&node))
|
|
}
|
|
return zones.List()
|
|
}
|
|
|
|
func createZoneID(region, zone string) string {
|
|
return region + ":\x00:" + zone
|
|
}
|