k8s.io/perf-tests/clusterloader2@v0.0.0-20240304094227-64bdb12da87e/pkg/measurement/util/runtimeobjects/runtimeobjects.go (about) 1 /* 2 Copyright 2018 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 "strconv" 23 24 goerrors "github.com/go-errors/errors" 25 gocmp "github.com/google/go-cmp/cmp" 26 appsv1 "k8s.io/api/apps/v1" 27 batch "k8s.io/api/batch/v1" 28 corev1 "k8s.io/api/core/v1" 29 "k8s.io/apimachinery/pkg/api/equality" 30 "k8s.io/apimachinery/pkg/api/meta" 31 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 32 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 33 "k8s.io/apimachinery/pkg/labels" 34 "k8s.io/apimachinery/pkg/runtime" 35 "k8s.io/apimachinery/pkg/runtime/schema" 36 "k8s.io/client-go/dynamic" 37 clientset "k8s.io/client-go/kubernetes" 38 corev1helpers "k8s.io/component-helpers/scheduling/corev1" 39 v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper" 40 dsutil "k8s.io/kubernetes/pkg/controller/daemon/util" 41 "k8s.io/perf-tests/clusterloader2/pkg/framework/client" 42 ) 43 44 // ListRuntimeObjectsForKind returns objects of given kind that satisfy given namespace, labelSelector and fieldSelector. 45 func ListRuntimeObjectsForKind(d dynamic.Interface, gvr schema.GroupVersionResource, kind, namespace, labelSelector, fieldSelector string) ([]runtime.Object, error) { 46 var runtimeObjectsList []runtime.Object 47 var listFunc func() error 48 listOpts := metav1.ListOptions{ 49 LabelSelector: labelSelector, 50 FieldSelector: fieldSelector, 51 } 52 listFunc = func() error { 53 list, err := d.Resource(gvr).List(context.TODO(), listOpts) 54 if err != nil { 55 return err 56 } 57 runtimeObjectsList = make([]runtime.Object, len(list.Items)) 58 for i := range list.Items { 59 runtimeObjectsList[i] = &list.Items[i] 60 } 61 return nil 62 } 63 64 if err := client.RetryWithExponentialBackOff(client.RetryFunction(listFunc)); err != nil { 65 return nil, err 66 } 67 return runtimeObjectsList, nil 68 } 69 70 // GetResourceVersionFromRuntimeObject returns resource version of given runtime object. 71 func GetResourceVersionFromRuntimeObject(obj runtime.Object) (uint64, error) { 72 accessor, err := meta.Accessor(obj) 73 if err != nil { 74 return 0, fmt.Errorf("accessor error: %v", err) 75 } 76 version := accessor.GetResourceVersion() 77 if len(version) == 0 { 78 return 0, nil 79 } 80 return strconv.ParseUint(version, 10, 64) 81 } 82 83 // GetIsPodUpdatedPredicateFromRuntimeObject returns a func(*corev1.Pod) bool predicate 84 // that can be used to check if given pod represents the desired state of pod. 85 func GetIsPodUpdatedPredicateFromRuntimeObject(obj runtime.Object) (func(*corev1.Pod) error, error) { 86 switch typed := obj.(type) { 87 case *unstructured.Unstructured: 88 return getIsPodUpdatedPodPredicateFromUnstructured(typed) 89 default: 90 return nil, goerrors.Errorf("unsupported kind when getting updated pod predicate: %v", obj) 91 } 92 } 93 94 // Auxiliary error type for lazy evaluation of gocmp.Diff which is known to be 95 // computationally expensive. 96 type lazySpecDiffError struct { 97 templateSpec corev1.PodSpec 98 podSpec corev1.PodSpec 99 } 100 101 func (lsde *lazySpecDiffError) Error() string { 102 return fmt.Sprintf("Not matching templates, diff: %v", gocmp.Diff(lsde.templateSpec, lsde.podSpec)) 103 } 104 105 func getIsPodUpdatedPodPredicateFromUnstructured(obj *unstructured.Unstructured) (func(_ *corev1.Pod) error, error) { 106 templateMap, ok, err := unstructured.NestedMap(obj.UnstructuredContent(), "spec", "template") 107 if err != nil { 108 return nil, goerrors.Errorf("failed to get pod template: %v", err) 109 } 110 if !ok { 111 return nil, goerrors.Errorf("spec.template is not set in object %v", obj.UnstructuredContent()) 112 } 113 template := corev1.PodTemplateSpec{} 114 if err := runtime.DefaultUnstructuredConverter.FromUnstructured(templateMap, &template); err != nil { 115 return nil, goerrors.Errorf("failed to parse spec.teemplate as v1.PodTemplateSpec") 116 } 117 118 return func(pod *corev1.Pod) error { 119 if !equality.Semantic.DeepDerivative(template.Spec, pod.Spec) { 120 return &lazySpecDiffError{template.Spec, pod.Spec} 121 } 122 return nil 123 }, nil 124 } 125 126 // GetSpecFromRuntimeObject returns spec of given runtime object. 127 func GetSpecFromRuntimeObject(obj runtime.Object) (interface{}, error) { 128 if obj == nil { 129 return nil, nil 130 } 131 switch typed := obj.(type) { 132 case *unstructured.Unstructured: 133 return getSpecFromUnstrutured(typed) 134 case *corev1.ReplicationController: 135 return typed.Spec, nil 136 case *appsv1.ReplicaSet: 137 return typed.Spec, nil 138 case *appsv1.Deployment: 139 return typed.Spec, nil 140 case *appsv1.StatefulSet: 141 return typed.Spec, nil 142 case *appsv1.DaemonSet: 143 return typed.Spec, nil 144 case *batch.Job: 145 return typed.Spec, nil 146 default: 147 return nil, fmt.Errorf("unsupported kind when getting spec: %v", obj) 148 } 149 } 150 151 // Note: This function assumes each controller has field Spec. 152 func getSpecFromUnstrutured(obj *unstructured.Unstructured) (map[string]interface{}, error) { 153 spec, ok, err := unstructured.NestedMap(obj.UnstructuredContent(), "spec") 154 if err != nil { 155 return nil, fmt.Errorf("try to acquire spec failed, %v", err) 156 } 157 if !ok { 158 return nil, fmt.Errorf("try to acquire spec failed, no field spec for obj %s", obj.GetName()) 159 } 160 return spec, nil 161 } 162 163 // GetReplicasFromRuntimeObject returns replicas number from given runtime object. 164 func GetReplicasFromRuntimeObject(c clientset.Interface, obj runtime.Object) (ReplicasWatcher, error) { 165 if obj == nil { 166 return &ConstReplicas{0}, nil 167 } 168 switch typed := obj.(type) { 169 case *unstructured.Unstructured: 170 return getReplicasFromUnstrutured(c, typed) 171 case *corev1.ReplicationController: 172 if typed.Spec.Replicas != nil { 173 return &ConstReplicas{int(*typed.Spec.Replicas)}, nil 174 } 175 return &ConstReplicas{0}, nil 176 case *appsv1.ReplicaSet: 177 if typed.Spec.Replicas != nil { 178 return &ConstReplicas{int(*typed.Spec.Replicas)}, nil 179 } 180 return &ConstReplicas{0}, nil 181 case *appsv1.Deployment: 182 if typed.Spec.Replicas != nil { 183 return &ConstReplicas{int(*typed.Spec.Replicas)}, nil 184 } 185 return &ConstReplicas{0}, nil 186 case *appsv1.StatefulSet: 187 if typed.Spec.Replicas != nil { 188 return &ConstReplicas{int(*typed.Spec.Replicas)}, nil 189 } 190 return &ConstReplicas{0}, nil 191 case *appsv1.DaemonSet: 192 return getDaemonSetNumSchedulableNodes(c, &typed.Spec.Template.Spec) 193 case *batch.Job: 194 if typed.Spec.Parallelism != nil { 195 return &ConstReplicas{int(*typed.Spec.Parallelism)}, nil 196 } 197 return &ConstReplicas{0}, nil 198 default: 199 return nil, fmt.Errorf("unsupported kind when getting number of replicas: %v", obj) 200 } 201 } 202 203 // getDaemonSetNumSchedulableNodes returns the number of schedulable nodes matching both nodeSelector and NodeAffinity. 204 func getDaemonSetNumSchedulableNodes(c clientset.Interface, podSpec *corev1.PodSpec) (ReplicasWatcher, error) { 205 selector, err := metav1.LabelSelectorAsSelector(metav1.SetAsLabelSelector(podSpec.NodeSelector)) 206 if err != nil { 207 return nil, err 208 } 209 return NewNodeCounter(c, selector, podSpec.Affinity, podSpec.Tolerations), nil 210 } 211 212 // Note: This function assumes each controller has field Spec.Replicas, except DaemonSets and Job. 213 func getReplicasFromUnstrutured(c clientset.Interface, obj *unstructured.Unstructured) (ReplicasWatcher, error) { 214 spec, err := getSpecFromUnstrutured(obj) 215 if err != nil { 216 return nil, err 217 } 218 return tryAcquireReplicasFromUnstructuredSpec(c, spec, obj.GetKind()) 219 } 220 221 func tryAcquireReplicasFromUnstructuredSpec(c clientset.Interface, spec map[string]interface{}, kind string) (ReplicasWatcher, error) { 222 switch kind { 223 case "DaemonSet": 224 parser, err := newDaemonSetPodSpecParser(spec) 225 if err != nil { 226 return nil, err 227 } 228 var podSpec corev1.PodSpec 229 if err := parser.getDaemonSetNodeSelectorFromUnstructuredSpec(&podSpec); err != nil { 230 return nil, err 231 } 232 if err := parser.getDaemonSetAffinityFromUnstructuredSpec(&podSpec); err != nil { 233 return nil, err 234 } 235 if err := parser.getDaemonSetTolerationsFromUnstructuredSpec(&podSpec); err != nil { 236 return nil, err 237 } 238 return getDaemonSetNumSchedulableNodes(c, &podSpec) 239 case "Job": 240 replicas, found, err := unstructured.NestedInt64(spec, "parallelism") 241 if err != nil { 242 return nil, fmt.Errorf("try to acquire job parallelism failed, %v", err) 243 } 244 if !found { 245 return &ConstReplicas{0}, nil 246 } 247 return &ConstReplicas{int(replicas)}, nil 248 default: 249 replicas, found, err := unstructured.NestedInt64(spec, "replicas") 250 if err != nil { 251 return nil, fmt.Errorf("try to acquire replicas failed, %v", err) 252 } 253 if !found { 254 return &ConstReplicas{0}, nil 255 } 256 return &ConstReplicas{int(replicas)}, nil 257 } 258 } 259 260 type daemonSetPodSpecParser map[string]interface{} 261 262 func newDaemonSetPodSpecParser(spec map[string]interface{}) (daemonSetPodSpecParser, error) { 263 template, found, err := unstructured.NestedMap(spec, "template") 264 if err != nil || !found { 265 return nil, err 266 } 267 podSpec, found, err := unstructured.NestedMap(template, "spec") 268 if err != nil || !found { 269 return nil, err 270 } 271 return podSpec, nil 272 } 273 274 func (p daemonSetPodSpecParser) getDaemonSetNodeSelectorFromUnstructuredSpec(spec *corev1.PodSpec) error { 275 nodeSelector, _, err := unstructured.NestedStringMap(p, "nodeSelector") 276 spec.NodeSelector = nodeSelector 277 return err 278 } 279 280 func (p daemonSetPodSpecParser) getDaemonSetAffinityFromUnstructuredSpec(spec *corev1.PodSpec) error { 281 unstructuredAffinity, found, err := unstructured.NestedMap(p, "affinity") 282 if err != nil || !found { 283 return err 284 } 285 affinity := &corev1.Affinity{} 286 err = runtime.DefaultUnstructuredConverter.FromUnstructured(unstructuredAffinity, affinity) 287 spec.Affinity = affinity 288 return err 289 } 290 291 func (p daemonSetPodSpecParser) getDaemonSetTolerationsFromUnstructuredSpec(spec *corev1.PodSpec) error { 292 dsutil.AddOrUpdateDaemonPodTolerations(spec) 293 unstructuredTolerations, found, err := unstructured.NestedSlice(p, "tolerations") 294 if err != nil || !found { 295 return err 296 } 297 for _, unstructuredToleration := range unstructuredTolerations { 298 var toleration corev1.Toleration 299 err = runtime.DefaultUnstructuredConverter.FromUnstructured(unstructuredToleration.(map[string]interface{}), &toleration) 300 if err != nil { 301 break 302 } 303 v1helper.AddOrUpdateTolerationInPodSpec(spec, &toleration) 304 } 305 return err 306 } 307 308 // IsEqualRuntimeObjectsSpec returns true if given runtime objects have identical specs. 309 func IsEqualRuntimeObjectsSpec(runtimeObj1, runtimeObj2 runtime.Object) (bool, error) { 310 runtimeObj1Spec, err := GetSpecFromRuntimeObject(runtimeObj1) 311 if err != nil { 312 return false, err 313 } 314 runtimeObj2Spec, err := GetSpecFromRuntimeObject(runtimeObj2) 315 if err != nil { 316 return false, err 317 } 318 319 return equality.Semantic.DeepEqual(runtimeObj1Spec, runtimeObj2Spec), nil 320 } 321 322 // GetNumObjectsMatchingSelector returns number of objects matching the given selector. 323 func GetNumObjectsMatchingSelector(c dynamic.Interface, namespace string, resource schema.GroupVersionResource, labelSelector labels.Selector) (int, error) { 324 var numObjects int 325 listFunc := func() error { 326 list, err := c.Resource(resource).Namespace(namespace).List(context.TODO(), metav1.ListOptions{LabelSelector: labelSelector.String()}) 327 if err != nil { 328 return err 329 } 330 numObjects = len(list.Items) 331 return nil 332 } 333 err := client.RetryWithExponentialBackOff(client.RetryFunction(listFunc)) 334 return numObjects, err 335 } 336 337 // The pod can only schedule onto nodes that satisfy requirements in NodeAffinity. 338 func podMatchesNodeAffinity(affinity *corev1.Affinity, node *corev1.Node) (bool, error) { 339 // 1. nil NodeSelector matches all nodes (i.e. does not filter out any nodes) 340 // 2. nil []NodeSelectorTerm (equivalent to non-nil empty NodeSelector) matches no nodes 341 // 3. zero-length non-nil []NodeSelectorTerm matches no nodes also, just for simplicity 342 // 4. nil []NodeSelectorRequirement (equivalent to non-nil empty NodeSelectorTerm) matches no nodes 343 // 5. zero-length non-nil []NodeSelectorRequirement matches no nodes also, just for simplicity 344 // 6. non-nil empty NodeSelectorRequirement is not allowed 345 if affinity != nil && affinity.NodeAffinity != nil { 346 nodeAffinity := affinity.NodeAffinity 347 // if no required NodeAffinity requirements, will do no-op, means select all nodes. 348 if nodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution == nil { 349 return true, nil 350 } 351 return corev1helpers.MatchNodeSelectorTerms(node, nodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution) 352 } 353 return true, nil 354 }