go.etcd.io/etcd@v3.3.27+incompatible/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/coreos/etcd/clientv3" 23 "github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes" 24 "github.com/coreos/etcd/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 := lc.check(true, lc.ls.revokedLeases.leases); err != nil { 78 return err 79 } 80 if err := lc.check(false, lc.ls.aliveLeases.leases); err != nil { 81 return err 82 } 83 return lc.checkShortLivedLeases() 84 } 85 86 const leaseExpireCheckerTimeout = 10 * time.Second 87 88 // checkShortLivedLeases ensures leases expire. 89 func (lc *leaseExpireChecker) checkShortLivedLeases() error { 90 ctx, cancel := context.WithTimeout(context.Background(), leaseExpireCheckerTimeout) 91 errc := make(chan error) 92 defer cancel() 93 for leaseID := range lc.ls.shortLivedLeases.leases { 94 go func(id int64) { 95 errc <- lc.checkShortLivedLease(ctx, id) 96 }(leaseID) 97 } 98 99 var errs []error 100 for range lc.ls.shortLivedLeases.leases { 101 if err := <-errc; err != nil { 102 errs = append(errs, err) 103 } 104 } 105 return errsToError(errs) 106 } 107 108 func (lc *leaseExpireChecker) checkShortLivedLease(ctx context.Context, leaseID int64) (err error) { 109 // 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. 110 var resp *clientv3.LeaseTimeToLiveResponse 111 for i := 0; i < retries; i++ { 112 resp, err = lc.getLeaseByID(ctx, leaseID) 113 // lease not found, for ~v3.1 compatibilities, check ErrLeaseNotFound 114 if (err == nil && resp.TTL == -1) || (err != nil && rpctypes.Error(err) == rpctypes.ErrLeaseNotFound) { 115 return nil 116 } 117 if err != nil { 118 lc.lg.Debug( 119 "retrying; Lease TimeToLive failed", 120 zap.Int("retries", i), 121 zap.String("lease-id", fmt.Sprintf("%016x", leaseID)), 122 zap.Error(err), 123 ) 124 continue 125 } 126 if resp.TTL > 0 { 127 dur := time.Duration(resp.TTL) * time.Second 128 lc.lg.Debug( 129 "lease has not been expired, wait until expire", 130 zap.String("lease-id", fmt.Sprintf("%016x", leaseID)), 131 zap.Int64("ttl", resp.TTL), 132 zap.Duration("wait-duration", dur), 133 ) 134 time.Sleep(dur) 135 } else { 136 lc.lg.Debug( 137 "lease expired but not yet revoked", 138 zap.Int("retries", i), 139 zap.String("lease-id", fmt.Sprintf("%016x", leaseID)), 140 zap.Int64("ttl", resp.TTL), 141 zap.Duration("wait-duration", time.Second), 142 ) 143 time.Sleep(time.Second) 144 } 145 if err = lc.checkLease(ctx, false, leaseID); err != nil { 146 continue 147 } 148 return nil 149 } 150 return err 151 } 152 153 func (lc *leaseExpireChecker) checkLease(ctx context.Context, expired bool, leaseID int64) error { 154 keysExpired, err := lc.hasKeysAttachedToLeaseExpired(ctx, leaseID) 155 if err != nil { 156 lc.lg.Warn( 157 "hasKeysAttachedToLeaseExpired failed", 158 zap.String("endpoint", lc.m.EtcdClientEndpoint), 159 zap.Error(err), 160 ) 161 return err 162 } 163 leaseExpired, err := lc.hasLeaseExpired(ctx, leaseID) 164 if err != nil { 165 lc.lg.Warn( 166 "hasLeaseExpired failed", 167 zap.String("endpoint", lc.m.EtcdClientEndpoint), 168 zap.Error(err), 169 ) 170 return err 171 } 172 if leaseExpired != keysExpired { 173 return fmt.Errorf("lease %v expiration mismatch (lease expired=%v, keys expired=%v)", leaseID, leaseExpired, keysExpired) 174 } 175 if leaseExpired != expired { 176 return fmt.Errorf("lease %v expected expired=%v, got %v", leaseID, expired, leaseExpired) 177 } 178 return nil 179 } 180 181 func (lc *leaseExpireChecker) check(expired bool, leases map[int64]time.Time) error { 182 ctx, cancel := context.WithTimeout(context.Background(), leaseExpireCheckerTimeout) 183 defer cancel() 184 for leaseID := range leases { 185 if err := lc.checkLease(ctx, expired, leaseID); err != nil { 186 return err 187 } 188 } 189 return nil 190 } 191 192 // TODO: handle failures from "grpc.FailFast(false)" 193 func (lc *leaseExpireChecker) getLeaseByID(ctx context.Context, leaseID int64) (*clientv3.LeaseTimeToLiveResponse, error) { 194 return lc.cli.TimeToLive( 195 ctx, 196 clientv3.LeaseID(leaseID), 197 clientv3.WithAttachedKeys(), 198 ) 199 } 200 201 func (lc *leaseExpireChecker) hasLeaseExpired(ctx context.Context, leaseID int64) (bool, error) { 202 // keep retrying until lease's state is known or ctx is being canceled 203 for ctx.Err() == nil { 204 resp, err := lc.getLeaseByID(ctx, leaseID) 205 if err != nil { 206 // for ~v3.1 compatibilities 207 if rpctypes.Error(err) == rpctypes.ErrLeaseNotFound { 208 return true, nil 209 } 210 } else { 211 return resp.TTL == -1, nil 212 } 213 lc.lg.Warn( 214 "hasLeaseExpired getLeaseByID failed", 215 zap.String("endpoint", lc.m.EtcdClientEndpoint), 216 zap.String("lease-id", fmt.Sprintf("%016x", leaseID)), 217 zap.Error(err), 218 ) 219 } 220 return false, ctx.Err() 221 } 222 223 // The keys attached to the lease has the format of "<leaseID>_<idx>" where idx is the ordering key creation 224 // Since the format of keys contains about leaseID, finding keys base on "<leaseID>" prefix 225 // determines whether the attached keys for a given leaseID has been deleted or not 226 func (lc *leaseExpireChecker) hasKeysAttachedToLeaseExpired(ctx context.Context, leaseID int64) (bool, error) { 227 resp, err := lc.cli.Get(ctx, fmt.Sprintf("%d", leaseID), clientv3.WithPrefix()) 228 if err != nil { 229 lc.lg.Warn( 230 "hasKeysAttachedToLeaseExpired failed", 231 zap.String("endpoint", lc.m.EtcdClientEndpoint), 232 zap.String("lease-id", fmt.Sprintf("%016x", leaseID)), 233 zap.Error(err), 234 ) 235 return false, err 236 } 237 return len(resp.Kvs) == 0, nil 238 }