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 }