k8s.io/kubernetes@v1.29.3/pkg/controller/testutil/test_utils.go (about)

     1  /*
     2  Copyright 2016 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package testutil
    18  
    19  import (
    20  	"context"
    21  	"encoding/json"
    22  	"errors"
    23  	"fmt"
    24  	"reflect"
    25  	"sync"
    26  	"testing"
    27  	"time"
    28  
    29  	v1 "k8s.io/api/core/v1"
    30  	apierrors "k8s.io/apimachinery/pkg/api/errors"
    31  	"k8s.io/apimachinery/pkg/api/resource"
    32  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    33  	"k8s.io/apimachinery/pkg/runtime"
    34  	"k8s.io/apimachinery/pkg/runtime/schema"
    35  	"k8s.io/apimachinery/pkg/types"
    36  	"k8s.io/apimachinery/pkg/util/sets"
    37  	"k8s.io/apimachinery/pkg/util/strategicpatch"
    38  	"k8s.io/apimachinery/pkg/watch"
    39  	v1apply "k8s.io/client-go/applyconfigurations/core/v1"
    40  	"k8s.io/client-go/kubernetes/fake"
    41  	v1core "k8s.io/client-go/kubernetes/typed/core/v1"
    42  	"k8s.io/client-go/tools/cache"
    43  	ref "k8s.io/client-go/tools/reference"
    44  	utilnode "k8s.io/component-helpers/node/topology"
    45  	"k8s.io/klog/v2"
    46  	"k8s.io/kubernetes/pkg/api/legacyscheme"
    47  	api "k8s.io/kubernetes/pkg/apis/core"
    48  	"k8s.io/utils/clock"
    49  	testingclock "k8s.io/utils/clock/testing"
    50  
    51  	jsonpatch "github.com/evanphx/json-patch"
    52  )
    53  
    54  var (
    55  	keyFunc = cache.DeletionHandlingMetaNamespaceKeyFunc
    56  )
    57  
    58  // FakeNodeHandler is a fake implementation of NodesInterface and NodeInterface. It
    59  // allows test cases to have fine-grained control over mock behaviors. We also need
    60  // PodsInterface and PodInterface to test list & delete pods, which is implemented in
    61  // the embedded client.Fake field.
    62  type FakeNodeHandler struct {
    63  	*fake.Clientset
    64  
    65  	// Input: Hooks determine if request is valid or not
    66  	CreateHook func(*FakeNodeHandler, *v1.Node) bool
    67  	Existing   []*v1.Node
    68  	AsyncCalls []func(*FakeNodeHandler)
    69  
    70  	// Output
    71  	CreatedNodes        []*v1.Node
    72  	DeletedNodes        []*v1.Node
    73  	UpdatedNodes        []*v1.Node
    74  	UpdatedNodeStatuses []*v1.Node
    75  	RequestCount        int
    76  
    77  	// Synchronization
    78  	lock           sync.Mutex
    79  	DeleteWaitChan chan struct{}
    80  	PatchWaitChan  chan struct{}
    81  }
    82  
    83  // FakeLegacyHandler is a fake implementation of CoreV1Interface.
    84  type FakeLegacyHandler struct {
    85  	v1core.CoreV1Interface
    86  	n *FakeNodeHandler
    87  }
    88  
    89  // GetUpdatedNodesCopy returns a slice of Nodes with updates applied.
    90  func (m *FakeNodeHandler) GetUpdatedNodesCopy() []*v1.Node {
    91  	m.lock.Lock()
    92  	defer m.lock.Unlock()
    93  	updatedNodesCopy := make([]*v1.Node, len(m.UpdatedNodes), len(m.UpdatedNodes))
    94  	for i, ptr := range m.UpdatedNodes {
    95  		updatedNodesCopy[i] = ptr
    96  	}
    97  	return updatedNodesCopy
    98  }
    99  
   100  // Core returns fake CoreInterface.
   101  func (m *FakeNodeHandler) Core() v1core.CoreV1Interface {
   102  	return &FakeLegacyHandler{m.Clientset.CoreV1(), m}
   103  }
   104  
   105  // CoreV1 returns fake CoreV1Interface
   106  func (m *FakeNodeHandler) CoreV1() v1core.CoreV1Interface {
   107  	return &FakeLegacyHandler{m.Clientset.CoreV1(), m}
   108  }
   109  
   110  // Nodes return fake NodeInterfaces.
   111  func (m *FakeLegacyHandler) Nodes() v1core.NodeInterface {
   112  	return m.n
   113  }
   114  
   115  // Create adds a new Node to the fake store.
   116  func (m *FakeNodeHandler) Create(_ context.Context, node *v1.Node, _ metav1.CreateOptions) (*v1.Node, error) {
   117  	m.lock.Lock()
   118  	defer func() {
   119  		m.RequestCount++
   120  		m.lock.Unlock()
   121  	}()
   122  	for _, n := range m.Existing {
   123  		if n.Name == node.Name {
   124  			return nil, apierrors.NewAlreadyExists(api.Resource("nodes"), node.Name)
   125  		}
   126  	}
   127  	if m.CreateHook == nil || m.CreateHook(m, node) {
   128  		nodeCopy := *node
   129  		m.CreatedNodes = append(m.CreatedNodes, &nodeCopy)
   130  		return node, nil
   131  	}
   132  	return nil, errors.New("create error")
   133  }
   134  
   135  // Get returns a Node from the fake store.
   136  func (m *FakeNodeHandler) Get(ctx context.Context, name string, opts metav1.GetOptions) (*v1.Node, error) {
   137  	m.lock.Lock()
   138  	defer func() {
   139  		m.RequestCount++
   140  		m.runAsyncCalls()
   141  		m.lock.Unlock()
   142  	}()
   143  	for i := range m.UpdatedNodes {
   144  		if m.UpdatedNodes[i].Name == name {
   145  			nodeCopy := *m.UpdatedNodes[i]
   146  			return &nodeCopy, nil
   147  		}
   148  	}
   149  	for i := range m.Existing {
   150  		if m.Existing[i].Name == name {
   151  			nodeCopy := *m.Existing[i]
   152  			return &nodeCopy, nil
   153  		}
   154  	}
   155  	return nil, apierrors.NewNotFound(schema.GroupResource{Resource: "nodes"}, name)
   156  }
   157  
   158  func (m *FakeNodeHandler) runAsyncCalls() {
   159  	for _, a := range m.AsyncCalls {
   160  		a(m)
   161  	}
   162  }
   163  
   164  // List returns a list of Nodes from the fake store.
   165  func (m *FakeNodeHandler) List(_ context.Context, opts metav1.ListOptions) (*v1.NodeList, error) {
   166  	m.lock.Lock()
   167  	defer func() {
   168  		m.RequestCount++
   169  		m.lock.Unlock()
   170  	}()
   171  	var nodes []*v1.Node
   172  	for i := 0; i < len(m.UpdatedNodes); i++ {
   173  		if !contains(m.UpdatedNodes[i], m.DeletedNodes) {
   174  			nodes = append(nodes, m.UpdatedNodes[i])
   175  		}
   176  	}
   177  	for i := 0; i < len(m.Existing); i++ {
   178  		if !contains(m.Existing[i], m.DeletedNodes) && !contains(m.Existing[i], nodes) {
   179  			nodes = append(nodes, m.Existing[i])
   180  		}
   181  	}
   182  	for i := 0; i < len(m.CreatedNodes); i++ {
   183  		if !contains(m.CreatedNodes[i], m.DeletedNodes) && !contains(m.CreatedNodes[i], nodes) {
   184  			nodes = append(nodes, m.CreatedNodes[i])
   185  		}
   186  	}
   187  	nodeList := &v1.NodeList{}
   188  	for _, node := range nodes {
   189  		nodeList.Items = append(nodeList.Items, *node)
   190  	}
   191  	return nodeList, nil
   192  }
   193  
   194  // Delete deletes a Node from the fake store.
   195  func (m *FakeNodeHandler) Delete(_ context.Context, id string, opt metav1.DeleteOptions) error {
   196  	m.lock.Lock()
   197  	defer func() {
   198  		m.RequestCount++
   199  		if m.DeleteWaitChan != nil {
   200  			m.DeleteWaitChan <- struct{}{}
   201  		}
   202  		m.lock.Unlock()
   203  	}()
   204  	m.DeletedNodes = append(m.DeletedNodes, NewNode(id))
   205  	return nil
   206  }
   207  
   208  // DeleteCollection deletes a collection of Nodes from the fake store.
   209  func (m *FakeNodeHandler) DeleteCollection(_ context.Context, opt metav1.DeleteOptions, listOpts metav1.ListOptions) error {
   210  	return nil
   211  }
   212  
   213  // Update updates a Node in the fake store.
   214  func (m *FakeNodeHandler) Update(_ context.Context, node *v1.Node, _ metav1.UpdateOptions) (*v1.Node, error) {
   215  	m.lock.Lock()
   216  	defer func() {
   217  		m.RequestCount++
   218  		m.lock.Unlock()
   219  	}()
   220  
   221  	nodeCopy := *node
   222  	for i, updateNode := range m.UpdatedNodes {
   223  		if updateNode.Name == nodeCopy.Name {
   224  			if updateNode.GetObjectMeta().GetResourceVersion() != nodeCopy.GetObjectMeta().GetResourceVersion() {
   225  				return nil, apierrors.NewConflict(schema.GroupResource{}, "fake conflict", nil)
   226  			}
   227  			m.UpdatedNodes[i] = &nodeCopy
   228  			return node, nil
   229  		}
   230  	}
   231  	m.UpdatedNodes = append(m.UpdatedNodes, &nodeCopy)
   232  	return node, nil
   233  }
   234  
   235  // UpdateStatus updates a status of a Node in the fake store.
   236  func (m *FakeNodeHandler) UpdateStatus(_ context.Context, node *v1.Node, _ metav1.UpdateOptions) (*v1.Node, error) {
   237  	m.lock.Lock()
   238  	defer func() {
   239  		m.RequestCount++
   240  		m.lock.Unlock()
   241  	}()
   242  
   243  	var origNodeCopy v1.Node
   244  	found := false
   245  	for i := range m.Existing {
   246  		if m.Existing[i].Name == node.Name {
   247  			origNodeCopy = *m.Existing[i]
   248  			found = true
   249  			break
   250  		}
   251  	}
   252  	updatedNodeIndex := -1
   253  	for i := range m.UpdatedNodes {
   254  		if m.UpdatedNodes[i].Name == node.Name {
   255  			origNodeCopy = *m.UpdatedNodes[i]
   256  			updatedNodeIndex = i
   257  			found = true
   258  			break
   259  		}
   260  	}
   261  
   262  	if !found {
   263  		return nil, fmt.Errorf("not found node %v", node)
   264  	}
   265  
   266  	origNodeCopy.Status = node.Status
   267  	if updatedNodeIndex < 0 {
   268  		m.UpdatedNodes = append(m.UpdatedNodes, &origNodeCopy)
   269  	} else {
   270  		m.UpdatedNodes[updatedNodeIndex] = &origNodeCopy
   271  	}
   272  
   273  	nodeCopy := *node
   274  	m.UpdatedNodeStatuses = append(m.UpdatedNodeStatuses, &nodeCopy)
   275  	return node, nil
   276  }
   277  
   278  // PatchStatus patches a status of a Node in the fake store.
   279  func (m *FakeNodeHandler) PatchStatus(ctx context.Context, nodeName string, data []byte) (*v1.Node, error) {
   280  	m.RequestCount++
   281  	return m.Patch(ctx, nodeName, types.StrategicMergePatchType, data, metav1.PatchOptions{}, "status")
   282  }
   283  
   284  // Watch watches Nodes in a fake store.
   285  func (m *FakeNodeHandler) Watch(_ context.Context, opts metav1.ListOptions) (watch.Interface, error) {
   286  	return watch.NewFake(), nil
   287  }
   288  
   289  // Patch patches a Node in the fake store.
   290  func (m *FakeNodeHandler) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, _ metav1.PatchOptions, subresources ...string) (*v1.Node, error) {
   291  	m.lock.Lock()
   292  	defer func() {
   293  		m.RequestCount++
   294  		if m.PatchWaitChan != nil {
   295  			m.PatchWaitChan <- struct{}{}
   296  		}
   297  		m.lock.Unlock()
   298  	}()
   299  	var nodeCopy v1.Node
   300  	for i := range m.Existing {
   301  		if m.Existing[i].Name == name {
   302  			nodeCopy = *m.Existing[i]
   303  		}
   304  	}
   305  	updatedNodeIndex := -1
   306  	for i := range m.UpdatedNodes {
   307  		if m.UpdatedNodes[i].Name == name {
   308  			nodeCopy = *m.UpdatedNodes[i]
   309  			updatedNodeIndex = i
   310  		}
   311  	}
   312  
   313  	originalObjJS, err := json.Marshal(nodeCopy)
   314  	if err != nil {
   315  		klog.FromContext(ctx).Error(nil, "Failed to marshal", "node", klog.KObj(&nodeCopy))
   316  		return nil, nil
   317  	}
   318  	var originalNode v1.Node
   319  	if err = json.Unmarshal(originalObjJS, &originalNode); err != nil {
   320  		klog.FromContext(ctx).Error(err, "Failed to unmarshal original object")
   321  		return nil, nil
   322  	}
   323  
   324  	var patchedObjJS []byte
   325  	switch pt {
   326  	case types.JSONPatchType:
   327  		patchObj, err := jsonpatch.DecodePatch(data)
   328  		if err != nil {
   329  			klog.FromContext(ctx).Error(err, "")
   330  			return nil, nil
   331  		}
   332  		if patchedObjJS, err = patchObj.Apply(originalObjJS); err != nil {
   333  			klog.FromContext(ctx).Error(err, "")
   334  			return nil, nil
   335  		}
   336  	case types.MergePatchType:
   337  		if patchedObjJS, err = jsonpatch.MergePatch(originalObjJS, data); err != nil {
   338  			klog.FromContext(ctx).Error(err, "")
   339  			return nil, nil
   340  		}
   341  	case types.StrategicMergePatchType:
   342  		if patchedObjJS, err = strategicpatch.StrategicMergePatch(originalObjJS, data, originalNode); err != nil {
   343  			klog.FromContext(ctx).Error(err, "")
   344  			return nil, nil
   345  		}
   346  	default:
   347  		klog.FromContext(ctx).Error(nil, "Unknown Content-Type header", "patch", pt)
   348  		return nil, nil
   349  	}
   350  
   351  	var updatedNode v1.Node
   352  	if err = json.Unmarshal(patchedObjJS, &updatedNode); err != nil {
   353  		klog.FromContext(ctx).Error(err, "Failed to unmarshal patched object")
   354  		return nil, nil
   355  	}
   356  
   357  	if updatedNodeIndex < 0 {
   358  		m.UpdatedNodes = append(m.UpdatedNodes, &updatedNode)
   359  	} else {
   360  		if updatedNode.GetObjectMeta().GetResourceVersion() != m.UpdatedNodes[updatedNodeIndex].GetObjectMeta().GetResourceVersion() {
   361  			return nil, apierrors.NewConflict(schema.GroupResource{}, "fake conflict", nil)
   362  		}
   363  		m.UpdatedNodes[updatedNodeIndex] = &updatedNode
   364  	}
   365  
   366  	return &updatedNode, nil
   367  }
   368  
   369  // Apply applies a NodeApplyConfiguration to a Node in the fake store.
   370  func (m *FakeNodeHandler) Apply(ctx context.Context, node *v1apply.NodeApplyConfiguration, opts metav1.ApplyOptions) (*v1.Node, error) {
   371  	patchOpts := opts.ToPatchOptions()
   372  	data, err := json.Marshal(node)
   373  	if err != nil {
   374  		return nil, err
   375  	}
   376  	name := node.Name
   377  	if name == nil {
   378  		return nil, fmt.Errorf("deployment.Name must be provided to Apply")
   379  	}
   380  
   381  	return m.Patch(ctx, *name, types.ApplyPatchType, data, patchOpts)
   382  }
   383  
   384  // ApplyStatus applies a status of a Node in the fake store.
   385  func (m *FakeNodeHandler) ApplyStatus(ctx context.Context, node *v1apply.NodeApplyConfiguration, opts metav1.ApplyOptions) (*v1.Node, error) {
   386  	patchOpts := opts.ToPatchOptions()
   387  	data, err := json.Marshal(node)
   388  	if err != nil {
   389  		return nil, err
   390  	}
   391  	name := node.Name
   392  	if name == nil {
   393  		return nil, fmt.Errorf("deployment.Name must be provided to Apply")
   394  	}
   395  
   396  	return m.Patch(ctx, *name, types.ApplyPatchType, data, patchOpts, "status")
   397  }
   398  
   399  // FakeRecorder is used as a fake during testing.
   400  type FakeRecorder struct {
   401  	sync.Mutex
   402  	source v1.EventSource
   403  	Events []*v1.Event
   404  	clock  clock.Clock
   405  }
   406  
   407  // Event emits a fake event to the fake recorder
   408  func (f *FakeRecorder) Event(obj runtime.Object, eventtype, reason, message string) {
   409  	f.generateEvent(obj, metav1.Now(), eventtype, reason, message)
   410  }
   411  
   412  // Eventf emits a fake formatted event to the fake recorder
   413  func (f *FakeRecorder) Eventf(obj runtime.Object, eventtype, reason, messageFmt string, args ...interface{}) {
   414  	f.Event(obj, eventtype, reason, fmt.Sprintf(messageFmt, args...))
   415  }
   416  
   417  // AnnotatedEventf emits a fake formatted event to the fake recorder
   418  func (f *FakeRecorder) AnnotatedEventf(obj runtime.Object, annotations map[string]string, eventtype, reason, messageFmt string, args ...interface{}) {
   419  	f.Eventf(obj, eventtype, reason, messageFmt, args...)
   420  }
   421  
   422  func (f *FakeRecorder) generateEvent(obj runtime.Object, timestamp metav1.Time, eventtype, reason, message string) {
   423  	f.Lock()
   424  	defer f.Unlock()
   425  	ctx := context.TODO()
   426  	ref, err := ref.GetReference(legacyscheme.Scheme, obj)
   427  	if err != nil {
   428  		klog.FromContext(ctx).Error(err, "Encountered error while getting reference")
   429  		return
   430  	}
   431  	event := f.makeEvent(ref, eventtype, reason, message)
   432  	event.Source = f.source
   433  	if f.Events != nil {
   434  		f.Events = append(f.Events, event)
   435  	}
   436  }
   437  
   438  func (f *FakeRecorder) makeEvent(ref *v1.ObjectReference, eventtype, reason, message string) *v1.Event {
   439  	t := metav1.Time{Time: f.clock.Now()}
   440  	namespace := ref.Namespace
   441  	if namespace == "" {
   442  		namespace = metav1.NamespaceDefault
   443  	}
   444  
   445  	clientref := v1.ObjectReference{
   446  		Kind:            ref.Kind,
   447  		Namespace:       ref.Namespace,
   448  		Name:            ref.Name,
   449  		UID:             ref.UID,
   450  		APIVersion:      ref.APIVersion,
   451  		ResourceVersion: ref.ResourceVersion,
   452  		FieldPath:       ref.FieldPath,
   453  	}
   454  
   455  	return &v1.Event{
   456  		ObjectMeta: metav1.ObjectMeta{
   457  			Name:      fmt.Sprintf("%v.%x", ref.Name, t.UnixNano()),
   458  			Namespace: namespace,
   459  		},
   460  		InvolvedObject: clientref,
   461  		Reason:         reason,
   462  		Message:        message,
   463  		FirstTimestamp: t,
   464  		LastTimestamp:  t,
   465  		Count:          1,
   466  		Type:           eventtype,
   467  	}
   468  }
   469  
   470  // NewFakeRecorder returns a pointer to a newly constructed FakeRecorder.
   471  func NewFakeRecorder() *FakeRecorder {
   472  	return &FakeRecorder{
   473  		source: v1.EventSource{Component: "nodeControllerTest"},
   474  		Events: []*v1.Event{},
   475  		clock:  testingclock.NewFakeClock(time.Now()),
   476  	}
   477  }
   478  
   479  // NewNode is a helper function for creating Nodes for testing.
   480  func NewNode(name string) *v1.Node {
   481  	return &v1.Node{
   482  		ObjectMeta: metav1.ObjectMeta{Name: name},
   483  		Status: v1.NodeStatus{
   484  			Capacity: v1.ResourceList{
   485  				v1.ResourceName(v1.ResourceCPU):    resource.MustParse("10"),
   486  				v1.ResourceName(v1.ResourceMemory): resource.MustParse("10G"),
   487  			},
   488  		},
   489  	}
   490  }
   491  
   492  // NewPod is a helper function for creating Pods for testing.
   493  func NewPod(name, host string) *v1.Pod {
   494  	pod := &v1.Pod{
   495  		ObjectMeta: metav1.ObjectMeta{
   496  			Namespace: "default",
   497  			Name:      name,
   498  		},
   499  		Spec: v1.PodSpec{
   500  			NodeName: host,
   501  		},
   502  		Status: v1.PodStatus{
   503  			Conditions: []v1.PodCondition{
   504  				{
   505  					Type:   v1.PodReady,
   506  					Status: v1.ConditionTrue,
   507  				},
   508  			},
   509  		},
   510  	}
   511  
   512  	return pod
   513  }
   514  
   515  func contains(node *v1.Node, nodes []*v1.Node) bool {
   516  	for i := 0; i < len(nodes); i++ {
   517  		if node.Name == nodes[i].Name {
   518  			return true
   519  		}
   520  	}
   521  	return false
   522  }
   523  
   524  // GetZones returns list of zones for all Nodes stored in FakeNodeHandler
   525  func GetZones(nodeHandler *FakeNodeHandler) []string {
   526  	nodes, _ := nodeHandler.List(context.TODO(), metav1.ListOptions{})
   527  	zones := sets.NewString()
   528  	for _, node := range nodes.Items {
   529  		zones.Insert(utilnode.GetZoneKey(&node))
   530  	}
   531  	return zones.List()
   532  }
   533  
   534  // CreateZoneID returns a single zoneID for a given region and zone.
   535  func CreateZoneID(region, zone string) string {
   536  	return region + ":\x00:" + zone
   537  }
   538  
   539  // GetKey is a helper function used by controllers unit tests to get the
   540  // key for a given kubernetes resource.
   541  func GetKey(obj interface{}, t *testing.T) string {
   542  	t.Helper()
   543  	tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
   544  	if ok {
   545  		// if tombstone , try getting the value from tombstone.Obj
   546  		obj = tombstone.Obj
   547  	}
   548  	val := reflect.ValueOf(obj).Elem()
   549  	name := val.FieldByName("Name").String()
   550  	if len(name) == 0 {
   551  		t.Errorf("Unexpected object %v", obj)
   552  	}
   553  
   554  	key, err := keyFunc(obj)
   555  	if err != nil {
   556  		t.Errorf("Unexpected error getting key for %T %v: %v", val.Interface(), name, err)
   557  		return ""
   558  	}
   559  	return key
   560  }