github.com/lfch/etcd-io/tests/v3@v3.0.0-20221004140520-eac99acd3e9d/functional/tester/checker_lease_expire.go (about) 1 // Copyright 2018 The etcd Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package tester 16 17 import ( 18 "context" 19 "fmt" 20 "time" 21 22 "github.com/lfch/etcd-io/api/v3/v3rpc/rpctypes" 23 "github.com/lfch/etcd-io/client/v3" 24 "github.com/lfch/etcd-io/tests/v3/functional/rpcpb" 25 26 "go.uber.org/zap" 27 "google.golang.org/grpc" 28 ) 29 30 type leaseExpireChecker struct { 31 ctype rpcpb.Checker 32 lg *zap.Logger 33 m *rpcpb.Member 34 ls *leaseStresser 35 cli *clientv3.Client 36 } 37 38 func newLeaseExpireChecker(ls *leaseStresser) Checker { 39 return &leaseExpireChecker{ 40 ctype: rpcpb.Checker_LEASE_EXPIRE, 41 lg: ls.lg, 42 m: ls.m, 43 ls: ls, 44 } 45 } 46 47 func (lc *leaseExpireChecker) Type() rpcpb.Checker { 48 return lc.ctype 49 } 50 51 func (lc *leaseExpireChecker) EtcdClientEndpoints() []string { 52 return []string{lc.m.EtcdClientEndpoint} 53 } 54 55 func (lc *leaseExpireChecker) Check() error { 56 if lc.ls == nil { 57 return nil 58 } 59 if lc.ls != nil && 60 (lc.ls.revokedLeases == nil || 61 lc.ls.aliveLeases == nil || 62 lc.ls.shortLivedLeases == nil) { 63 return nil 64 } 65 66 cli, err := lc.m.CreateEtcdClient(grpc.WithBackoffMaxDelay(time.Second)) 67 if err != nil { 68 return fmt.Errorf("%v (%q)", err, lc.m.EtcdClientEndpoint) 69 } 70 defer func() { 71 if cli != nil { 72 cli.Close() 73 } 74 }() 75 lc.cli = cli 76 77 if err := check(lc.lg, lc.cli, true, lc.ls.revokedLeases.leases); err != nil { 78 return err 79 } 80 if err := check(lc.lg, lc.cli, false, lc.ls.aliveLeases.leases); err != nil { 81 return err 82 } 83 84 return lc.checkShortLivedLeases() 85 } 86 87 const leaseExpireCheckerTimeout = 10 * time.Second 88 89 // checkShortLivedLeases ensures leases expire. 90 func (lc *leaseExpireChecker) checkShortLivedLeases() error { 91 ctx, cancel := context.WithTimeout(context.Background(), leaseExpireCheckerTimeout) 92 errc := make(chan error) 93 defer cancel() 94 for leaseID := range lc.ls.shortLivedLeases.leases { 95 go func(id int64) { 96 errc <- lc.checkShortLivedLease(ctx, id) 97 }(leaseID) 98 } 99 100 var errs []error 101 for range lc.ls.shortLivedLeases.leases { 102 if err := <-errc; err != nil { 103 errs = append(errs, err) 104 } 105 } 106 return errsToError(errs) 107 } 108 109 func (lc *leaseExpireChecker) checkShortLivedLease(ctx context.Context, leaseID int64) (err error) { 110 // retry in case of transient failure or lease is expired but not yet revoked due to the fact that etcd cluster didn't have enought time to delete it. 111 var resp *clientv3.LeaseTimeToLiveResponse 112 for i := 0; i < retries; i++ { 113 resp, err = getLeaseByID(ctx, lc.cli, leaseID) 114 // lease not found, for ~v3.1 compatibilities, check ErrLeaseNotFound 115 if (err == nil && resp.TTL == -1) || (err != nil && rpctypes.Error(err) == rpctypes.ErrLeaseNotFound) { 116 return nil 117 } 118 if err != nil { 119 lc.lg.Debug( 120 "retrying; Lease TimeToLive failed", 121 zap.Int("retries", i), 122 zap.String("lease-id", fmt.Sprintf("%016x", leaseID)), 123 zap.Error(err), 124 ) 125 continue 126 } 127 if resp.TTL > 0 { 128 dur := time.Duration(resp.TTL) * time.Second 129 lc.lg.Debug( 130 "lease has not been expired, wait until expire", 131 zap.String("lease-id", fmt.Sprintf("%016x", leaseID)), 132 zap.Int64("ttl", resp.TTL), 133 zap.Duration("wait-duration", dur), 134 ) 135 time.Sleep(dur) 136 } else { 137 lc.lg.Debug( 138 "lease expired but not yet revoked", 139 zap.Int("retries", i), 140 zap.String("lease-id", fmt.Sprintf("%016x", leaseID)), 141 zap.Int64("ttl", resp.TTL), 142 zap.Duration("wait-duration", time.Second), 143 ) 144 time.Sleep(time.Second) 145 } 146 if err = checkLease(ctx, lc.lg, lc.cli, false, leaseID); err != nil { 147 continue 148 } 149 return nil 150 } 151 return err 152 } 153 154 func checkLease(ctx context.Context, lg *zap.Logger, cli *clientv3.Client, expired bool, leaseID int64) error { 155 keysExpired, err := hasKeysAttachedToLeaseExpired(ctx, lg, cli, leaseID) 156 if err != nil { 157 lg.Warn( 158 "hasKeysAttachedToLeaseExpired failed", 159 zap.Any("endpoint", cli.Endpoints()), 160 zap.Error(err), 161 ) 162 return err 163 } 164 leaseExpired, err := hasLeaseExpired(ctx, lg, cli, leaseID) 165 if err != nil { 166 lg.Warn( 167 "hasLeaseExpired failed", 168 zap.Any("endpoint", cli.Endpoints()), 169 zap.Error(err), 170 ) 171 return err 172 } 173 if leaseExpired != keysExpired { 174 return fmt.Errorf("lease %v expiration mismatch (lease expired=%v, keys expired=%v)", leaseID, leaseExpired, keysExpired) 175 } 176 if leaseExpired != expired { 177 return fmt.Errorf("lease %v expected expired=%v, got %v", leaseID, expired, leaseExpired) 178 } 179 return nil 180 } 181 182 func check(lg *zap.Logger, cli *clientv3.Client, expired bool, leases map[int64]time.Time) error { 183 ctx, cancel := context.WithTimeout(context.Background(), leaseExpireCheckerTimeout) 184 defer cancel() 185 for leaseID := range leases { 186 if err := checkLease(ctx, lg, cli, expired, leaseID); err != nil { 187 return err 188 } 189 } 190 return nil 191 } 192 193 // TODO: handle failures from "grpc.WaitForReady(true)" 194 func getLeaseByID(ctx context.Context, cli *clientv3.Client, leaseID int64) (*clientv3.LeaseTimeToLiveResponse, error) { 195 return cli.TimeToLive( 196 ctx, 197 clientv3.LeaseID(leaseID), 198 clientv3.WithAttachedKeys(), 199 ) 200 } 201 202 func hasLeaseExpired(ctx context.Context, lg *zap.Logger, cli *clientv3.Client, leaseID int64) (bool, error) { 203 // keep retrying until lease's state is known or ctx is being canceled 204 for ctx.Err() == nil { 205 resp, err := getLeaseByID(ctx, cli, leaseID) 206 if err != nil { 207 // for ~v3.1 compatibilities 208 if rpctypes.Error(err) == rpctypes.ErrLeaseNotFound { 209 return true, nil 210 } 211 } else { 212 return resp.TTL == -1, nil 213 } 214 lg.Warn( 215 "hasLeaseExpired getLeaseByID failed", 216 zap.Any("endpoint", cli.Endpoints()), 217 zap.String("lease-id", fmt.Sprintf("%016x", leaseID)), 218 zap.Error(err), 219 ) 220 } 221 return false, ctx.Err() 222 } 223 224 // The keys attached to the lease has the format of "<leaseID>_<idx>" where idx is the ordering key creation 225 // Since the format of keys contains about leaseID, finding keys base on "<leaseID>" prefix 226 // determines whether the attached keys for a given leaseID has been deleted or not 227 func hasKeysAttachedToLeaseExpired(ctx context.Context, lg *zap.Logger, cli *clientv3.Client, leaseID int64) (bool, error) { 228 resp, err := cli.Get(ctx, fmt.Sprintf("%d", leaseID), clientv3.WithPrefix()) 229 if err != nil { 230 lg.Warn( 231 "hasKeysAttachedToLeaseExpired failed", 232 zap.Any("endpoint", cli.Endpoints()), 233 zap.String("lease-id", fmt.Sprintf("%016x", leaseID)), 234 zap.Error(err), 235 ) 236 return false, err 237 } 238 return len(resp.Kvs) == 0, nil 239 }