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 }