knative.dev/pkg@v0.0.0-20260602142205-ac97e43f6622/leaderelection/chaosduck/main.go (about)

     1  /*
     2  Copyright 2020 The Knative 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  // The chaosduck binary is an e2e testing tool for leader election, which loads
    18  // the leader election configuration within the system namespace and
    19  // periodically kills one of the leader pods for each HA component.
    20  package main
    21  
    22  import (
    23  	"context"
    24  	"errors"
    25  	"flag"
    26  	"log"
    27  	"regexp"
    28  	"strings"
    29  	"time"
    30  
    31  	"knative.dev/pkg/injection"
    32  
    33  	"golang.org/x/sync/errgroup"
    34  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    35  	"k8s.io/apimachinery/pkg/util/sets"
    36  	"k8s.io/apimachinery/pkg/util/wait"
    37  	"k8s.io/client-go/kubernetes"
    38  	kubeclient "knative.dev/pkg/client/injection/kube/client"
    39  	"knative.dev/pkg/kflag"
    40  	"knative.dev/pkg/signals"
    41  	"knative.dev/pkg/system"
    42  )
    43  
    44  // components is a mapping from component name to the collection of leader pod names.
    45  type components map[string]sets.Set[string]
    46  
    47  var (
    48  	disabledComponents      kflag.StringSet
    49  	disabledComponentsRegex kflag.StringSet
    50  	tributePeriod           = 20 * time.Second
    51  	tributeFactor           = 2.0
    52  )
    53  
    54  func init() {
    55  	// Note that we don't explicitly call flag.Parse() because ParseAndGetConfigOrDie below does this already.
    56  	flag.Var(&disabledComponents, "disable", "A repeatable flag to disable chaos for certain components.")
    57  	flag.Var(&disabledComponentsRegex, "disableRegex", "A repeatable flag to disable chaos for components matching one of the passed regexes.")
    58  	flag.DurationVar(&tributePeriod, "period", tributePeriod, "How frequently to terminate a leader pod per component (this is the base duration used with the jitter factor from -factor).")
    59  	flag.Float64Var(&tributeFactor, "factor", tributeFactor, "The jitter factor to apply to the period.")
    60  }
    61  
    62  func countingRFind(wr rune, wc int) func(rune) bool {
    63  	cnt := 0
    64  	return func(r rune) bool {
    65  		if r == wr {
    66  			cnt++
    67  		}
    68  		return cnt == wc
    69  	}
    70  }
    71  
    72  // This is a copy of test/ha/ha.go that avoids a dependency that pulls in a
    73  // redefinition of the kubeconfig flag.
    74  func extractDeployment(pod string) string {
    75  	if x := strings.LastIndexFunc(pod, countingRFind('-', 2)); x != -1 {
    76  		return pod[:x]
    77  	}
    78  	return ""
    79  }
    80  
    81  // buildComponents crawls the list of leases and builds a mapping from component names
    82  // to the set pod names that hold one or more leases.
    83  func buildComponents(ctx context.Context, kc kubernetes.Interface) (components, error) {
    84  	leases, err := kc.CoordinationV1().Leases(system.Namespace()).List(ctx, metav1.ListOptions{})
    85  	if err != nil {
    86  		return nil, err
    87  	}
    88  
    89  	cs := components{}
    90  	for _, lease := range leases.Items {
    91  		if lease.Spec.HolderIdentity == nil {
    92  			log.Printf("Found lease %q held by nobody!", lease.Name)
    93  			continue
    94  		}
    95  		pod := strings.SplitN(*lease.Spec.HolderIdentity, "_", 2)[0]
    96  		deploymentName := extractDeployment(pod)
    97  		if deploymentName == "" {
    98  			continue
    99  		}
   100  
   101  		set, ok := cs[deploymentName]
   102  		if !ok {
   103  			set = make(sets.Set[string], 1)
   104  			cs[deploymentName] = set
   105  		}
   106  		set.Insert(pod)
   107  	}
   108  	return cs, nil
   109  }
   110  
   111  // quack will kill one of the components leader pods.
   112  func quack(ctx context.Context, kc kubernetes.Interface, component string, leaders sets.Set[string]) error {
   113  	tribute, ok := leaders.PopAny()
   114  	if !ok {
   115  		return errors.New("this should not be possible, since components are only created when they have components")
   116  	}
   117  	log.Printf("Quacking at %q leader %q", component, tribute)
   118  
   119  	return kc.CoreV1().Pods(system.Namespace()).Delete(ctx, tribute, metav1.DeleteOptions{})
   120  }
   121  
   122  // matchesAny returns true if any of the given regexes matches the given string.
   123  func matchesAny(regexes []*regexp.Regexp, str string) bool {
   124  	for _, re := range regexes {
   125  		if re.MatchString(str) {
   126  			return true
   127  		}
   128  	}
   129  	return false
   130  }
   131  
   132  func main() {
   133  	ctx, _ := injection.EnableInjectionOrDie(signals.NewContext(), nil)
   134  	kc := kubeclient.Get(ctx)
   135  
   136  	regexes := make([]*regexp.Regexp, 0, len(disabledComponentsRegex.Value))
   137  	for re := range disabledComponentsRegex.Value {
   138  		regexes = append(regexes, regexp.MustCompile(re))
   139  	}
   140  
   141  	// Until we are shutdown, build up an index of components and kill
   142  	// of a leader at the specified frequency.
   143  	wait.JitterUntilWithContext(ctx, func(ctx context.Context) {
   144  		components, err := buildComponents(ctx, kc)
   145  		if err != nil {
   146  			log.Print("Error building components: ", err)
   147  		}
   148  		log.Printf("Got components: %#v", components)
   149  
   150  		eg, ctx := errgroup.WithContext(ctx)
   151  		for name, leaders := range components {
   152  			if disabledComponents.Value.Has(name) || matchesAny(regexes, name) {
   153  				continue
   154  			}
   155  
   156  			eg.Go(func() error {
   157  				return quack(ctx, kc, name, leaders)
   158  			})
   159  		}
   160  		if err := eg.Wait(); err != nil {
   161  			log.Print("Ended iteration with err: ", err)
   162  		}
   163  	}, tributePeriod, tributeFactor, true /* sliding: do not include the runtime of the above in the interval */)
   164  }