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  }