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

     1  /*
     2  Copyright 2022 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  	"sync"
    23  
    24  	appsv1 "k8s.io/api/apps/v1"
    25  	corev1 "k8s.io/api/core/v1"
    26  	"k8s.io/apimachinery/pkg/api/meta"
    27  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    28  	"k8s.io/apimachinery/pkg/types"
    29  	appsinformers "k8s.io/client-go/informers/apps/v1"
    30  	coreinformers "k8s.io/client-go/informers/core/v1"
    31  	"k8s.io/client-go/tools/cache"
    32  	"k8s.io/klog/v2"
    33  )
    34  
    35  const (
    36  	controllerUIDIndex = "controllerUID"
    37  )
    38  
    39  // ControlledPodsIndexer is able to efficiently find pods with ownerReference pointing a given controller object.
    40  // For Deployments, it performs indirect lookup with ReplicaSets in the middle.
    41  type ControlledPodsIndexer struct {
    42  	podsIndexer cache.Indexer
    43  	podsSynced  cache.InformerSynced
    44  	rsSynced    cache.InformerSynced
    45  
    46  	rsIndexer cache.Indexer
    47  
    48  	// lock is a lock for accessing rsPendingDeletion.
    49  	lock sync.Mutex
    50  	// rsPendingDeletion are replicasets that have been deleted, but there are still pods referencing them,
    51  	// so we have to postpone deletion from `rsIndexer`. They should be deleted as soon as the last pod
    52  	// referencing it is deleted.
    53  	rsPendingDeletion map[types.UID]bool
    54  }
    55  
    56  // ReplicaSetState stores information relevant to a specific ReplicaSet object,
    57  // i.e. how many pods it owns exist, whether the RS object itself exists
    58  // and its latest known owner's UID.
    59  type ReplicaSetState struct {
    60  	NumPods int
    61  	Exists  bool
    62  }
    63  
    64  // UIDSet is a collection of ReplicaSet objects UIDs.
    65  type UIDSet map[types.UID]bool
    66  
    67  func deletionHandlingUIDKeyFunc(obj interface{}) (string, error) {
    68  	if d, ok := obj.(cache.DeletedFinalStateUnknown); ok {
    69  		return d.Key, nil
    70  	}
    71  	return string(getObjUID(obj)), nil
    72  }
    73  
    74  // NewControlledPodsIndexer creates a new ControlledPodsIndexer instance.
    75  func NewControlledPodsIndexer(podsInformer coreinformers.PodInformer, rsInformer appsinformers.ReplicaSetInformer) (*ControlledPodsIndexer, error) {
    76  	if err := podsInformer.Informer().AddIndexers(cache.Indexers{controllerUIDIndex: controllerUIDIndexFunc}); err != nil {
    77  		return nil, fmt.Errorf("failed to register indexer: %w", err)
    78  	}
    79  
    80  	// We need a separate storage from rsInformer as we postpone deletion until all pods are removed.
    81  	rsIndexer := cache.NewIndexer(deletionHandlingUIDKeyFunc, cache.Indexers{controllerUIDIndex: controllerUIDIndexFunc})
    82  
    83  	cpi := &ControlledPodsIndexer{
    84  		podsIndexer:       podsInformer.Informer().GetIndexer(),
    85  		podsSynced:        podsInformer.Informer().HasSynced,
    86  		rsIndexer:         rsIndexer,
    87  		rsSynced:          rsInformer.Informer().HasSynced,
    88  		rsPendingDeletion: make(map[types.UID]bool),
    89  	}
    90  
    91  	podsInformer.Informer().AddEventHandler(
    92  		cache.ResourceEventHandlerFuncs{
    93  			UpdateFunc: func(oldObj, newObj interface{}) {
    94  				oldOwnerUID, _ := getControllerInfo(oldObj)
    95  				newOwnerUID, _ := getControllerInfo(newObj)
    96  				if oldOwnerUID == newOwnerUID {
    97  					return
    98  				}
    99  
   100  				cpi.lock.Lock()
   101  				defer cpi.lock.Unlock()
   102  				if err := cpi.clearRSDataIfPossibleLocked(oldOwnerUID); err != nil {
   103  					klog.Errorf("error while deleting %v: %v", oldOwnerUID, err)
   104  				}
   105  			},
   106  			DeleteFunc: func(obj interface{}) {
   107  				ownerUID, _ := getControllerInfo(obj)
   108  
   109  				cpi.lock.Lock()
   110  				defer cpi.lock.Unlock()
   111  				if err := cpi.clearRSDataIfPossibleLocked(ownerUID); err != nil {
   112  					klog.Errorf("error while deleting %v: %v", ownerUID, err)
   113  				}
   114  			},
   115  		},
   116  	)
   117  	rsInformer.Informer().AddEventHandler(
   118  		cache.ResourceEventHandlerFuncs{
   119  			AddFunc: func(obj interface{}) {
   120  				if err := rsIndexer.Add(obj); err != nil {
   121  					klog.Errorf("error while adding %v: %v", obj, err)
   122  				}
   123  			},
   124  			UpdateFunc: func(_, newObj interface{}) {
   125  				if err := rsIndexer.Update(newObj); err != nil {
   126  					klog.Errorf("error while updating %v: %v", newObj, err)
   127  				}
   128  			},
   129  			DeleteFunc: func(obj interface{}) {
   130  				rsUID := getObjUID(obj)
   131  				cpi.lock.Lock()
   132  				defer cpi.lock.Unlock()
   133  				cpi.rsPendingDeletion[rsUID] = true
   134  				if err := cpi.clearRSDataIfPossibleLocked(rsUID); err != nil {
   135  					klog.Errorf("error while deleting %v: %v", rsUID, err)
   136  				}
   137  			},
   138  		},
   139  	)
   140  
   141  	return cpi, nil
   142  }
   143  
   144  func getControllerInfo(obj interface{}) (types.UID, string) {
   145  	metaAccessor, err := meta.Accessor(obj)
   146  	if err != nil {
   147  		return "", ""
   148  	}
   149  	controller := metav1.GetControllerOf(metaAccessor)
   150  	if controller == nil {
   151  		return "", ""
   152  	}
   153  	return controller.UID, controller.Kind
   154  }
   155  
   156  func getObjUID(obj interface{}) types.UID {
   157  	metaAccessor, err := meta.Accessor(obj)
   158  	if err != nil {
   159  		return ""
   160  	}
   161  	return metaAccessor.GetUID()
   162  }
   163  
   164  func (p *ControlledPodsIndexer) clearRSDataIfPossibleLocked(rsUID types.UID) error {
   165  	if !p.rsPendingDeletion[rsUID] {
   166  		return nil
   167  	}
   168  	pods, err := p.appendPodsControlledBy(nil, rsUID)
   169  	if err != nil {
   170  		return fmt.Errorf("failed to list pods for %q: %w", rsUID, err)
   171  	}
   172  
   173  	if len(pods) != 0 {
   174  		return nil
   175  	}
   176  	delete(p.rsPendingDeletion, rsUID)
   177  	obj, exists, err := p.rsIndexer.GetByKey(string(rsUID))
   178  	if err != nil {
   179  		return err
   180  	}
   181  	if !exists {
   182  		return nil
   183  	}
   184  
   185  	return p.rsIndexer.Delete(obj)
   186  }
   187  
   188  func controllerUIDIndexFunc(obj interface{}) ([]string, error) {
   189  	meta, err := meta.Accessor(obj)
   190  	if err != nil {
   191  		return nil, fmt.Errorf("object has no meta: %v", err)
   192  	}
   193  	controllerRef := metav1.GetControllerOf(meta)
   194  	if controllerRef == nil {
   195  		return []string{}, nil
   196  	}
   197  	return []string{string(controllerRef.UID)}, nil
   198  }
   199  
   200  // WaitForCacheSync waits for all required informers to be initialized.
   201  func (p *ControlledPodsIndexer) WaitForCacheSync(ctx context.Context) bool {
   202  	return cache.WaitForNamedCacheSync("PodsIndexer", ctx.Done(), p.podsSynced, p.rsSynced)
   203  }
   204  
   205  // PodsControlledBy returns pods controlled by a given controller object.
   206  func (p *ControlledPodsIndexer) PodsControlledBy(obj interface{}) ([]*corev1.Pod, error) {
   207  	metaAccessor, err := meta.Accessor(obj)
   208  	if err != nil {
   209  		return nil, fmt.Errorf("object has no meta: %w", err)
   210  	}
   211  	typeAccessor, err := meta.TypeAccessor(obj)
   212  	if err != nil {
   213  		return nil, fmt.Errorf("object has unknown type: %w", err)
   214  	}
   215  
   216  	var podOwners []types.UID
   217  	switch typeAccessor.GetKind() {
   218  	case "Deployment":
   219  		replicaSets, err := p.rsIndexer.ByIndex(controllerUIDIndex, string(metaAccessor.GetUID()))
   220  		if err != nil {
   221  			return nil, fmt.Errorf("failed to get replicasets controlled by %v: %w", metaAccessor.GetUID(), err)
   222  		}
   223  		for _, replicaSet := range replicaSets {
   224  			replicaSet, ok := replicaSet.(*appsv1.ReplicaSet)
   225  			if !ok {
   226  				return nil, fmt.Errorf("expected *appsv1.ReplicaSet; got: %T", replicaSet)
   227  			}
   228  			podOwners = append(podOwners, replicaSet.GetUID())
   229  		}
   230  	default:
   231  		podOwners = append(podOwners, metaAccessor.GetUID())
   232  	}
   233  
   234  	var res []*corev1.Pod
   235  	for _, podOwner := range podOwners {
   236  		res, err = p.appendPodsControlledBy(res, podOwner)
   237  		if err != nil {
   238  			return nil, fmt.Errorf("failed to get pods controlled by %v: %w", podOwner, err)
   239  		}
   240  	}
   241  
   242  	return res, nil
   243  }
   244  
   245  func (p *ControlledPodsIndexer) appendPodsControlledBy(res []*corev1.Pod, uid types.UID) ([]*corev1.Pod, error) {
   246  	objs, err := p.podsIndexer.ByIndex(controllerUIDIndex, string(uid))
   247  	if err != nil {
   248  		return nil, fmt.Errorf("method ByIndex failed: %w", err)
   249  	}
   250  
   251  	for _, obj := range objs {
   252  		pod, ok := obj.(*corev1.Pod)
   253  		if !ok {
   254  			return nil, fmt.Errorf("expected *corev1.Pod; got: %T", obj)
   255  		}
   256  		res = append(res, pod)
   257  	}
   258  	return res, nil
   259  }