k8s.io/perf-tests/clusterloader2@v0.0.0-20240304094227-64bdb12da87e/pkg/measurement/util/wait_for_conditions.go (about) 1 /* 2 Copyright 2023 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 util 18 19 import ( 20 "context" 21 "fmt" 22 "strings" 23 "time" 24 25 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 "k8s.io/apimachinery/pkg/runtime/schema" 27 "k8s.io/client-go/dynamic" 28 "k8s.io/klog/v2" 29 ) 30 31 // WaitForGenericK8sObjectsOptions is an options object used by WaitForGenericK8sObjectsNodes methods. 32 type WaitForGenericK8sObjectsOptions struct { 33 // GroupVersionResource identifies the resource to fetch. 34 GroupVersionResource schema.GroupVersionResource 35 // Namespaces identifies namespaces which should be observed. 36 Namespaces NamespacesRange 37 // SuccessfulConditions lists conditions to look for in the objects denoting good objects. 38 // Formatted as `ConditionType=ConditionStatus`, e.g. `Scheduled=true`. 39 SuccessfulConditions []string 40 // SuccessfulConditions lists conditions to look for in the objects denoting good objects. 41 // Formatted as `ConditionType=ConditionStatus`, e.g. `Scheduled=true`. 42 FailedConditions []string 43 // MinDesiredObjectCount describes minimum number of objects that should contain 44 // successful or failed condition. 45 MinDesiredObjectCount int 46 // MaxFailedObjectCount describes maximum number of objects that could contain failed condition. 47 MaxFailedObjectCount int 48 // CallerName identifies the measurement making the calls. 49 CallerName string 50 // WaitInterval contains interval for which the function waits between refreshes. 51 WaitInterval time.Duration 52 } 53 54 // NamespacesRange represents namespace range which will be queried. 55 type NamespacesRange struct { 56 Prefix string 57 Min int 58 Max int 59 } 60 61 // Summary returns summary which should be included in all logs. 62 func (o *WaitForGenericK8sObjectsOptions) Summary() string { 63 return fmt.Sprintf("%s: objects: %q, namespaces: %q", o.CallerName, o.GroupVersionResource.String(), o.Namespaces.String()) 64 } 65 66 // String returns printable representation of the namespaces range. 67 func (nr *NamespacesRange) String() string { 68 return fmt.Sprintf("%s-(%d-%d)", nr.Prefix, nr.Min, nr.Max) 69 } 70 71 // getMap returns a map with namespaces which should be queried. 72 func (nr *NamespacesRange) getMap() map[string]bool { 73 result := map[string]bool{} 74 for i := nr.Min; i <= nr.Max; i++ { 75 result[fmt.Sprintf("%s-%d", nr.Prefix, i)] = true 76 } 77 return result 78 } 79 80 // WaitForGenericK8sObjects waits till the desired number of k8s objects 81 // fulfills given conditions requirements, ctx.Done() channel is used to 82 // wait for timeout. 83 func WaitForGenericK8sObjects(ctx context.Context, dynamicClient dynamic.Interface, options *WaitForGenericK8sObjectsOptions) error { 84 store, err := NewDynamicObjectStore(ctx, dynamicClient, options.GroupVersionResource, options.Namespaces.getMap()) 85 if err != nil { 86 return err 87 } 88 89 objects, err := store.ListObjectSimplifications() 90 if err != nil { 91 return err 92 } 93 successful, failed, count := countObjectsMatchingConditions(objects, options.SuccessfulConditions, options.FailedConditions) 94 for { 95 select { 96 case <-ctx.Done(): 97 return fmt.Errorf("%s: timeout while waiting for %d objects to be successful or failed - currently there are: successful=%d failed=%d count=%d", 98 options.Summary(), options.MinDesiredObjectCount, len(successful), len(failed), count) 99 case <-time.After(options.WaitInterval): 100 objects, err := store.ListObjectSimplifications() 101 if err != nil { 102 return err 103 } 104 successful, failed, count = countObjectsMatchingConditions(objects, options.SuccessfulConditions, options.FailedConditions) 105 106 klog.V(2).Infof("%s: successful=%d failed=%d count=%d", options.Summary(), len(successful), len(failed), count) 107 if options.MinDesiredObjectCount <= len(successful)+len(failed) { 108 if options.MaxFailedObjectCount < len(failed) { 109 return fmt.Errorf("%s: too many failed objects, expected at most %d - currently there are: successful=%d failed=%d count=%d failed-objects=[%s]", 110 options.Summary(), options.MaxFailedObjectCount, len(successful), len(failed), count, strings.Join(failed, ",")) 111 } 112 return nil 113 } 114 } 115 } 116 } 117 118 // countObjectsMatchingConditions counts objects that have a successful or failed condition. 119 // Function assumes the conditions it looks for are mutually exclusive. 120 func countObjectsMatchingConditions(objects []ObjectSimplification, successfulConditions []string, failedConditions []string) (successful []string, failed []string, count int) { 121 successfulMap := map[string]bool{} 122 for _, c := range successfulConditions { 123 successfulMap[c] = true 124 } 125 failedMap := map[string]bool{} 126 for _, c := range failedConditions { 127 failedMap[c] = true 128 } 129 130 count = len(objects) 131 for _, object := range objects { 132 for _, c := range object.Status.Conditions { 133 if successfulMap[conditionToKey(c)] { 134 successful = append(successful, object.String()) 135 break 136 } 137 if failedMap[conditionToKey(c)] { 138 failed = append(failed, object.String()) 139 break 140 } 141 } 142 } 143 return 144 } 145 146 func conditionToKey(c metav1.Condition) string { 147 return fmt.Sprintf("%s=%s", c.Type, c.Status) 148 }