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  }