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  }