123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516 |
- /*
- 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 testutil
- import (
- "context"
- "encoding/json"
- "errors"
- "fmt"
- "reflect"
- "sync"
- "testing"
- "time"
- apierrors "k8s.io/apimachinery/pkg/api/errors"
- "k8s.io/apimachinery/pkg/api/resource"
- metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
- "k8s.io/apimachinery/pkg/runtime"
- "k8s.io/apimachinery/pkg/types"
- "k8s.io/apimachinery/pkg/util/sets"
- "k8s.io/apimachinery/pkg/util/strategicpatch"
- "k8s.io/apimachinery/pkg/watch"
- "k8s.io/apimachinery/pkg/util/clock"
- ref "k8s.io/client-go/tools/reference"
- v1 "k8s.io/api/core/v1"
- "k8s.io/client-go/kubernetes/fake"
- v1core "k8s.io/client-go/kubernetes/typed/core/v1"
- "k8s.io/client-go/tools/cache"
- "k8s.io/kubernetes/pkg/api/legacyscheme"
- api "k8s.io/kubernetes/pkg/apis/core"
- utilnode "k8s.io/kubernetes/pkg/util/node"
- jsonpatch "github.com/evanphx/json-patch"
- "k8s.io/klog"
- )
- var (
- keyFunc = cache.DeletionHandlingMetaNamespaceKeyFunc
- )
- // 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 & delete 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, *v1.Node) bool
- Existing []*v1.Node
- // Output
- CreatedNodes []*v1.Node
- DeletedNodes []*v1.Node
- UpdatedNodes []*v1.Node
- UpdatedNodeStatuses []*v1.Node
- RequestCount int
- // Synchronization
- lock sync.Mutex
- DeleteWaitChan chan struct{}
- PatchWaitChan chan struct{}
- }
- // FakeLegacyHandler is a fake implementation of CoreV1Interface.
- type FakeLegacyHandler struct {
- v1core.CoreV1Interface
- n *FakeNodeHandler
- }
- // GetUpdatedNodesCopy returns a slice of Nodes with updates applied.
- func (m *FakeNodeHandler) GetUpdatedNodesCopy() []*v1.Node {
- m.lock.Lock()
- defer m.lock.Unlock()
- updatedNodesCopy := make([]*v1.Node, len(m.UpdatedNodes), len(m.UpdatedNodes))
- for i, ptr := range m.UpdatedNodes {
- updatedNodesCopy[i] = ptr
- }
- return updatedNodesCopy
- }
- // Core returns fake CoreInterface.
- func (m *FakeNodeHandler) Core() v1core.CoreV1Interface {
- return &FakeLegacyHandler{m.Clientset.CoreV1(), m}
- }
- // CoreV1 returns fake CoreV1Interface
- func (m *FakeNodeHandler) CoreV1() v1core.CoreV1Interface {
- return &FakeLegacyHandler{m.Clientset.CoreV1(), m}
- }
- // Nodes return fake NodeInterfaces.
- func (m *FakeLegacyHandler) Nodes() v1core.NodeInterface {
- return m.n
- }
- // Create adds a new Node to the fake store.
- func (m *FakeNodeHandler) Create(_ context.Context, node *v1.Node, _ metav1.CreateOptions) (*v1.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
- }
- return nil, errors.New("create error")
- }
- // Get returns a Node from the fake store.
- func (m *FakeNodeHandler) Get(_ context.Context, name string, opts metav1.GetOptions) (*v1.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
- }
- // List returns a list of Nodes from the fake store.
- func (m *FakeNodeHandler) List(_ context.Context, opts metav1.ListOptions) (*v1.NodeList, error) {
- m.lock.Lock()
- defer func() {
- m.RequestCount++
- m.lock.Unlock()
- }()
- var nodes []*v1.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 := &v1.NodeList{}
- for _, node := range nodes {
- nodeList.Items = append(nodeList.Items, *node)
- }
- return nodeList, nil
- }
- // Delete deletes a Node from the fake store.
- func (m *FakeNodeHandler) Delete(_ context.Context, id string, opt *metav1.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
- }
- // DeleteCollection deletes a collection of Nodes from the fake store.
- func (m *FakeNodeHandler) DeleteCollection(_ context.Context, opt *metav1.DeleteOptions, listOpts metav1.ListOptions) error {
- return nil
- }
- // Update updates a Node in the fake store.
- func (m *FakeNodeHandler) Update(_ context.Context, node *v1.Node, _ metav1.UpdateOptions) (*v1.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
- }
- // UpdateStatus updates a status of a Node in the fake store.
- func (m *FakeNodeHandler) UpdateStatus(_ context.Context, node *v1.Node, _ metav1.UpdateOptions) (*v1.Node, error) {
- m.lock.Lock()
- defer func() {
- m.RequestCount++
- m.lock.Unlock()
- }()
- var origNodeCopy v1.Node
- found := false
- for i := range m.Existing {
- if m.Existing[i].Name == node.Name {
- origNodeCopy = *m.Existing[i]
- found = true
- break
- }
- }
- updatedNodeIndex := -1
- for i := range m.UpdatedNodes {
- if m.UpdatedNodes[i].Name == node.Name {
- origNodeCopy = *m.UpdatedNodes[i]
- updatedNodeIndex = i
- found = true
- break
- }
- }
- if !found {
- return nil, fmt.Errorf("not found node %v", node)
- }
- origNodeCopy.Status = node.Status
- if updatedNodeIndex < 0 {
- m.UpdatedNodes = append(m.UpdatedNodes, &origNodeCopy)
- } else {
- m.UpdatedNodes[updatedNodeIndex] = &origNodeCopy
- }
- nodeCopy := *node
- m.UpdatedNodeStatuses = append(m.UpdatedNodeStatuses, &nodeCopy)
- return node, nil
- }
- // PatchStatus patches a status of a Node in the fake store.
- func (m *FakeNodeHandler) PatchStatus(ctx context.Context, nodeName string, data []byte) (*v1.Node, error) {
- m.RequestCount++
- return m.Patch(ctx, nodeName, types.StrategicMergePatchType, data, metav1.PatchOptions{}, "status")
- }
- // Watch watches Nodes in a fake store.
- func (m *FakeNodeHandler) Watch(_ context.Context, opts metav1.ListOptions) (watch.Interface, error) {
- return watch.NewFake(), nil
- }
- // Patch patches a Node in the fake store.
- func (m *FakeNodeHandler) Patch(_ context.Context, name string, pt types.PatchType, data []byte, _ metav1.PatchOptions, subresources ...string) (*v1.Node, error) {
- m.lock.Lock()
- defer func() {
- m.RequestCount++
- if m.PatchWaitChan != nil {
- m.PatchWaitChan <- struct{}{}
- }
- m.lock.Unlock()
- }()
- var nodeCopy v1.Node
- for i := range m.Existing {
- if m.Existing[i].Name == name {
- nodeCopy = *m.Existing[i]
- }
- }
- updatedNodeIndex := -1
- for i := range m.UpdatedNodes {
- if m.UpdatedNodes[i].Name == name {
- nodeCopy = *m.UpdatedNodes[i]
- updatedNodeIndex = i
- }
- }
- originalObjJS, err := json.Marshal(nodeCopy)
- if err != nil {
- klog.Errorf("Failed to marshal %v", nodeCopy)
- return nil, nil
- }
- var originalNode v1.Node
- if err = json.Unmarshal(originalObjJS, &originalNode); err != nil {
- klog.Errorf("Failed to unmarshal original object: %v", err)
- return nil, nil
- }
- var patchedObjJS []byte
- switch pt {
- case types.JSONPatchType:
- patchObj, err := jsonpatch.DecodePatch(data)
- if err != nil {
- klog.Error(err.Error())
- return nil, nil
- }
- if patchedObjJS, err = patchObj.Apply(originalObjJS); err != nil {
- klog.Error(err.Error())
- return nil, nil
- }
- case types.MergePatchType:
- if patchedObjJS, err = jsonpatch.MergePatch(originalObjJS, data); err != nil {
- klog.Error(err.Error())
- return nil, nil
- }
- case types.StrategicMergePatchType:
- if patchedObjJS, err = strategicpatch.StrategicMergePatch(originalObjJS, data, originalNode); err != nil {
- klog.Error(err.Error())
- return nil, nil
- }
- default:
- klog.Errorf("unknown Content-Type header for patch: %v", pt)
- return nil, nil
- }
- var updatedNode v1.Node
- if err = json.Unmarshal(patchedObjJS, &updatedNode); err != nil {
- klog.Errorf("Failed to unmarshal patched object: %v", err)
- return nil, nil
- }
- if updatedNodeIndex < 0 {
- m.UpdatedNodes = append(m.UpdatedNodes, &updatedNode)
- } else {
- m.UpdatedNodes[updatedNodeIndex] = &updatedNode
- }
- return &updatedNode, nil
- }
- // FakeRecorder is used as a fake during testing.
- type FakeRecorder struct {
- sync.Mutex
- source v1.EventSource
- Events []*v1.Event
- clock clock.Clock
- }
- // Event emits a fake event to the fake recorder
- func (f *FakeRecorder) Event(obj runtime.Object, eventtype, reason, message string) {
- f.generateEvent(obj, metav1.Now(), eventtype, reason, message)
- }
- // Eventf emits a fake formatted event to the fake recorder
- func (f *FakeRecorder) Eventf(obj runtime.Object, eventtype, reason, messageFmt string, args ...interface{}) {
- f.Event(obj, eventtype, reason, fmt.Sprintf(messageFmt, args...))
- }
- // AnnotatedEventf emits a fake formatted event to the fake recorder
- func (f *FakeRecorder) AnnotatedEventf(obj runtime.Object, annotations map[string]string, eventtype, reason, messageFmt string, args ...interface{}) {
- f.Eventf(obj, eventtype, reason, messageFmt, args...)
- }
- func (f *FakeRecorder) generateEvent(obj runtime.Object, timestamp metav1.Time, eventtype, reason, message string) {
- f.Lock()
- defer f.Unlock()
- ref, err := ref.GetReference(legacyscheme.Scheme, obj)
- if err != nil {
- klog.Errorf("Encountered error while getting reference: %v", err)
- return
- }
- event := f.makeEvent(ref, eventtype, reason, message)
- event.Source = f.source
- if f.Events != nil {
- f.Events = append(f.Events, event)
- }
- }
- func (f *FakeRecorder) makeEvent(ref *v1.ObjectReference, eventtype, reason, message string) *v1.Event {
- t := metav1.Time{Time: f.clock.Now()}
- namespace := ref.Namespace
- if namespace == "" {
- namespace = metav1.NamespaceDefault
- }
- clientref := v1.ObjectReference{
- Kind: ref.Kind,
- Namespace: ref.Namespace,
- Name: ref.Name,
- UID: ref.UID,
- APIVersion: ref.APIVersion,
- ResourceVersion: ref.ResourceVersion,
- FieldPath: ref.FieldPath,
- }
- return &v1.Event{
- ObjectMeta: metav1.ObjectMeta{
- Name: fmt.Sprintf("%v.%x", ref.Name, t.UnixNano()),
- Namespace: namespace,
- },
- InvolvedObject: clientref,
- Reason: reason,
- Message: message,
- FirstTimestamp: t,
- LastTimestamp: t,
- Count: 1,
- Type: eventtype,
- }
- }
- // NewFakeRecorder returns a pointer to a newly constructed FakeRecorder.
- func NewFakeRecorder() *FakeRecorder {
- return &FakeRecorder{
- source: v1.EventSource{Component: "nodeControllerTest"},
- Events: []*v1.Event{},
- clock: clock.NewFakeClock(time.Now()),
- }
- }
- // NewNode is a helper function for creating Nodes for testing.
- func NewNode(name string) *v1.Node {
- return &v1.Node{
- ObjectMeta: metav1.ObjectMeta{Name: name},
- Status: v1.NodeStatus{
- Capacity: v1.ResourceList{
- v1.ResourceName(v1.ResourceCPU): resource.MustParse("10"),
- v1.ResourceName(v1.ResourceMemory): resource.MustParse("10G"),
- },
- },
- }
- }
- // NewPod is a helper function for creating Pods for testing.
- func NewPod(name, host string) *v1.Pod {
- pod := &v1.Pod{
- ObjectMeta: metav1.ObjectMeta{
- Namespace: "default",
- Name: name,
- },
- Spec: v1.PodSpec{
- NodeName: host,
- },
- Status: v1.PodStatus{
- Conditions: []v1.PodCondition{
- {
- Type: v1.PodReady,
- Status: v1.ConditionTrue,
- },
- },
- },
- }
- return pod
- }
- func contains(node *v1.Node, nodes []*v1.Node) bool {
- for i := 0; i < len(nodes); i++ {
- if node.Name == nodes[i].Name {
- return true
- }
- }
- return false
- }
- // GetZones returns list of zones for all Nodes stored in FakeNodeHandler
- func GetZones(nodeHandler *FakeNodeHandler) []string {
- nodes, _ := nodeHandler.List(context.TODO(), metav1.ListOptions{})
- zones := sets.NewString()
- for _, node := range nodes.Items {
- zones.Insert(utilnode.GetZoneKey(&node))
- }
- return zones.List()
- }
- // CreateZoneID returns a single zoneID for a given region and zone.
- func CreateZoneID(region, zone string) string {
- return region + ":\x00:" + zone
- }
- // GetKey is a helper function used by controllers unit tests to get the
- // key for a given kubernetes resource.
- func GetKey(obj interface{}, t *testing.T) string {
- tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
- if ok {
- // if tombstone , try getting the value from tombstone.Obj
- obj = tombstone.Obj
- }
- val := reflect.ValueOf(obj).Elem()
- name := val.FieldByName("Name").String()
- kind := val.FieldByName("Kind").String()
- // Note kind is not always set in the tests, so ignoring that for now
- if len(name) == 0 || len(kind) == 0 {
- t.Errorf("Unexpected object %v", obj)
- }
- key, err := keyFunc(obj)
- if err != nil {
- t.Errorf("Unexpected error getting key for %v %v: %v", kind, name, err)
- return ""
- }
- return key
- }
|