github.com/hashicorp/vault/sdk@v0.13.0/physical/latency.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package physical
     5  
     6  import (
     7  	"context"
     8  	"math/rand"
     9  	"sync"
    10  	"time"
    11  
    12  	log "github.com/hashicorp/go-hclog"
    13  	uberAtomic "go.uber.org/atomic"
    14  )
    15  
    16  const (
    17  	// DefaultJitterPercent is used if no cache size is specified for NewCache
    18  	DefaultJitterPercent = 20
    19  )
    20  
    21  // LatencyInjector is used to add latency into underlying physical requests
    22  type LatencyInjector struct {
    23  	logger        log.Logger
    24  	backend       Backend
    25  	latency       *uberAtomic.Duration
    26  	jitterPercent int
    27  	randomLock    *sync.Mutex
    28  	random        *rand.Rand
    29  }
    30  
    31  // TransactionalLatencyInjector is the transactional version of the latency
    32  // injector
    33  type TransactionalLatencyInjector struct {
    34  	*LatencyInjector
    35  	Transactional
    36  }
    37  
    38  // Verify LatencyInjector satisfies the correct interfaces
    39  var (
    40  	_ Backend       = (*LatencyInjector)(nil)
    41  	_ Transactional = (*TransactionalLatencyInjector)(nil)
    42  )
    43  
    44  // NewLatencyInjector returns a wrapped physical backend to simulate latency
    45  func NewLatencyInjector(b Backend, latency time.Duration, jitter int, logger log.Logger) *LatencyInjector {
    46  	if jitter < 0 || jitter > 100 {
    47  		jitter = DefaultJitterPercent
    48  	}
    49  	logger.Info("creating latency injector")
    50  
    51  	return &LatencyInjector{
    52  		logger:        logger,
    53  		backend:       b,
    54  		latency:       uberAtomic.NewDuration(latency),
    55  		jitterPercent: jitter,
    56  		randomLock:    new(sync.Mutex),
    57  		random:        rand.New(rand.NewSource(int64(time.Now().Nanosecond()))),
    58  	}
    59  }
    60  
    61  // NewTransactionalLatencyInjector creates a new transactional LatencyInjector
    62  // jitter is the random percent that latency will vary between.
    63  // For example, if you specify latency = 50ms and jitter = 20, then for any
    64  // given operation, the latency will be 50ms +- 10ms (20% of 50), or between 40 and 60ms.
    65  func NewTransactionalLatencyInjector(b Backend, latency time.Duration, jitter int, logger log.Logger) *TransactionalLatencyInjector {
    66  	return &TransactionalLatencyInjector{
    67  		LatencyInjector: NewLatencyInjector(b, latency, jitter, logger),
    68  		Transactional:   b.(Transactional),
    69  	}
    70  }
    71  
    72  func (l *LatencyInjector) SetLatency(latency time.Duration) {
    73  	l.logger.Info("Changing backend latency", "latency", latency)
    74  	l.latency.Store(latency)
    75  }
    76  
    77  func (l *LatencyInjector) addLatency() {
    78  	// Calculate a value between 1 +- jitter%
    79  	percent := 100
    80  	if l.jitterPercent > 0 {
    81  		min := 100 - l.jitterPercent
    82  		max := 100 + l.jitterPercent
    83  		l.randomLock.Lock()
    84  		percent = l.random.Intn(max-min) + min
    85  		l.randomLock.Unlock()
    86  	}
    87  	latencyDuration := time.Duration(int(l.latency.Load()) * percent / 100)
    88  	time.Sleep(latencyDuration)
    89  }
    90  
    91  // Put is a latent put request
    92  func (l *LatencyInjector) Put(ctx context.Context, entry *Entry) error {
    93  	l.addLatency()
    94  	return l.backend.Put(ctx, entry)
    95  }
    96  
    97  // Get is a latent get request
    98  func (l *LatencyInjector) Get(ctx context.Context, key string) (*Entry, error) {
    99  	l.addLatency()
   100  	return l.backend.Get(ctx, key)
   101  }
   102  
   103  // Delete is a latent delete request
   104  func (l *LatencyInjector) Delete(ctx context.Context, key string) error {
   105  	l.addLatency()
   106  	return l.backend.Delete(ctx, key)
   107  }
   108  
   109  // List is a latent list request
   110  func (l *LatencyInjector) List(ctx context.Context, prefix string) ([]string, error) {
   111  	l.addLatency()
   112  	return l.backend.List(ctx, prefix)
   113  }
   114  
   115  // Transaction is a latent transaction request
   116  func (l *TransactionalLatencyInjector) Transaction(ctx context.Context, txns []*TxnEntry) error {
   117  	l.addLatency()
   118  	return l.Transactional.Transaction(ctx, txns)
   119  }
   120  
   121  // TransactionLimits implements physical.TransactionalLimits
   122  func (l *TransactionalLatencyInjector) TransactionLimits() (int, int) {
   123  	if tl, ok := l.Transactional.(TransactionalLimits); ok {
   124  		return tl.TransactionLimits()
   125  	}
   126  	// We don't have any specific limits of our own so return zeros to signal that
   127  	// the caller should use whatever reasonable defaults it would if it used a
   128  	// non-TransactionalLimits backend.
   129  	return 0, 0
   130  }