k8s.io/perf-tests/clusterloader2@v0.0.0-20240304094227-64bdb12da87e/pkg/measurement/util/runtimeobjects/replicaswatcher.go (about)

     1  /*
     2  Copyright 2021 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 runtimeobjects
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"sync"
    23  	"time"
    24  
    25  	corev1 "k8s.io/api/core/v1"
    26  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    27  	"k8s.io/apimachinery/pkg/labels"
    28  	"k8s.io/apimachinery/pkg/runtime"
    29  	"k8s.io/apimachinery/pkg/watch"
    30  	clientset "k8s.io/client-go/kubernetes"
    31  	"k8s.io/client-go/tools/cache"
    32  	corev1helpers "k8s.io/component-helpers/scheduling/corev1"
    33  	"k8s.io/klog/v2"
    34  	"k8s.io/perf-tests/clusterloader2/pkg/measurement/util/informer"
    35  )
    36  
    37  const (
    38  	informerSyncTimeout = time.Minute
    39  )
    40  
    41  // ReplicasWatcher is a struct that allows to check a number of replicas at a given time.
    42  // Usage:
    43  // var rw ReplicasWatcher = (...)
    44  //
    45  //	if err := rw.Start(stopCh); err != nil {
    46  //	  panic(err);
    47  //	}
    48  //
    49  // // Get number of replicas as needed.
    50  // val = rw.Replicas()
    51  // ...
    52  // val = rw.Replicas()
    53  type ReplicasWatcher interface {
    54  	Replicas() int
    55  	// Start must block until Replicas() returns a correct value.
    56  	Start(stopCh <-chan struct{}) error
    57  }
    58  
    59  // ConstReplicas is a ReplicasWatcher implementation that returns a constant value.
    60  type ConstReplicas struct {
    61  	ReplicasCount int
    62  }
    63  
    64  func (c *ConstReplicas) Replicas() int {
    65  	return c.ReplicasCount
    66  }
    67  
    68  func (c *ConstReplicas) Start(_ <-chan struct{}) error {
    69  	return nil
    70  }
    71  
    72  var _ ReplicasWatcher = &ConstReplicas{}
    73  
    74  // NodeCounter counts a number of node objects matching nodeSelector and affinity.
    75  type NodeCounter struct {
    76  	client       clientset.Interface
    77  	nodeSelector labels.Selector
    78  	affinity     *corev1.Affinity
    79  
    80  	mu       sync.Mutex
    81  	replicas int
    82  
    83  	tolerations []corev1.Toleration
    84  }
    85  
    86  var _ ReplicasWatcher = &NodeCounter{}
    87  
    88  // NewNodeCounter returns a new nodeCounter that return a number of objects matching nodeSelector and affinity.
    89  func NewNodeCounter(client clientset.Interface, nodeSelector labels.Selector, affinity *corev1.Affinity, tolerations []corev1.Toleration) *NodeCounter {
    90  	return &NodeCounter{
    91  		client:       client,
    92  		nodeSelector: nodeSelector,
    93  		affinity:     affinity,
    94  		tolerations:  tolerations,
    95  	}
    96  }
    97  
    98  func (n *NodeCounter) Start(stopCh <-chan struct{}) error {
    99  	i := informer.NewInformer(
   100  		&cache.ListWatch{
   101  			ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
   102  				options.LabelSelector = n.nodeSelector.String()
   103  				return n.client.CoreV1().Nodes().List(context.TODO(), options)
   104  			},
   105  			WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
   106  				options.LabelSelector = n.nodeSelector.String()
   107  				return n.client.CoreV1().Nodes().Watch(context.TODO(), options)
   108  			},
   109  		},
   110  		func(oldObj, newObj interface{}) {
   111  			if err := n.handleObject(oldObj, newObj); err != nil {
   112  				klog.Errorf("Error while processing node: %v", err)
   113  			}
   114  		},
   115  	)
   116  	// StartAndSync blocks until elements from initial list call are processed.
   117  	return informer.StartAndSync(i, stopCh, informerSyncTimeout)
   118  }
   119  
   120  func (n *NodeCounter) handleObject(oldObj, newObj interface{}) error {
   121  	old, err := n.shouldRun(oldObj)
   122  	if err != nil {
   123  		return err
   124  	}
   125  	new, err := n.shouldRun(newObj)
   126  	if err != nil {
   127  		return err
   128  	}
   129  	if new == old {
   130  		return nil
   131  	}
   132  	n.mu.Lock()
   133  	defer n.mu.Unlock()
   134  	if new && !old {
   135  		n.replicas++
   136  	} else {
   137  		n.replicas--
   138  	}
   139  	return nil
   140  }
   141  
   142  func (n *NodeCounter) Replicas() int {
   143  	n.mu.Lock()
   144  	defer n.mu.Unlock()
   145  	return n.replicas
   146  }
   147  
   148  func (n *NodeCounter) shouldRun(obj interface{}) (bool, error) {
   149  	if obj == nil {
   150  		return false, nil
   151  	}
   152  	node, ok := obj.(*corev1.Node)
   153  	if !ok {
   154  		return false, fmt.Errorf("unexpected type of obj: %v. got %T, want *corev1.Node", obj, obj)
   155  	}
   156  	matched, err := podMatchesNodeAffinity(n.affinity, node)
   157  	// refer to k8s.io/kubernetes@v1.22.15/pkg/controller/nodelifecycle/node_lifecycle_controller.go:633
   158  	// refer to k8s.io/kubernetes@v1.22.15/pkg/controller/daemon/daemon_controller.go:1247
   159  	_, hasUntoleratedTaint := corev1helpers.FindMatchingUntoleratedTaint(node.Spec.Taints, n.tolerations, func(t *corev1.Taint) bool {
   160  		return t.Effect == corev1.TaintEffectNoExecute || t.Effect == corev1.TaintEffectNoSchedule
   161  	})
   162  	return !hasUntoleratedTaint && matched, err
   163  }
   164  
   165  // GetReplicasOnce starts ReplicasWatcher and gets a number of replicas.
   166  func GetReplicasOnce(rw ReplicasWatcher) (int, error) {
   167  	stopCh := make(chan struct{})
   168  	defer close(stopCh)
   169  	if err := rw.Start(stopCh); err != nil {
   170  		return 0, err
   171  	}
   172  	return rw.Replicas(), nil
   173  }