storj.io/uplink@v1.13.0/private/testuplink/uplink.go (about)

     1  // Copyright (C) 2020 Storj Labs, Inc.
     2  // See LICENSE for copying information.
     3  
     4  package testuplink
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"io"
    10  	"sync"
    11  	"time"
    12  
    13  	"storj.io/common/memory"
    14  	"storj.io/uplink/private/eestream/scheduler"
    15  )
    16  
    17  type segmentSizeKey struct{}
    18  
    19  type plainSizeKey struct{}
    20  
    21  type listLimitKey struct{}
    22  
    23  type concurrentSegmentUploadsConfigKey struct{}
    24  
    25  type disableConcurrentSegmentUploadsKey struct{}
    26  
    27  type (
    28  	logWriterKey        struct{}
    29  	logWriterContextKey struct{}
    30  )
    31  
    32  // WithMaxSegmentSize creates context with max segment size for testing purposes.
    33  //
    34  // Created context needs to be used with uplink.OpenProject to manipulate default
    35  // segment size.
    36  func WithMaxSegmentSize(ctx context.Context, segmentSize memory.Size) context.Context {
    37  	return context.WithValue(ctx, segmentSizeKey{}, segmentSize)
    38  }
    39  
    40  // GetMaxSegmentSize returns max segment size from context if exists.
    41  func GetMaxSegmentSize(ctx context.Context) (memory.Size, bool) {
    42  	segmentSize, ok := ctx.Value(segmentSizeKey{}).(memory.Size)
    43  	return segmentSize, ok
    44  }
    45  
    46  // WithoutPlainSize creates context with information that segment plain size shouldn't be sent.
    47  // Only for testing purposes.
    48  func WithoutPlainSize(ctx context.Context) context.Context {
    49  	return context.WithValue(ctx, plainSizeKey{}, true)
    50  }
    51  
    52  // IsWithoutPlainSize returns true if information about not sending segment plain size exists in context.
    53  // Only for testing purposes.
    54  func IsWithoutPlainSize(ctx context.Context) bool {
    55  	withoutPlainSize, _ := ctx.Value(plainSizeKey{}).(bool)
    56  	return withoutPlainSize
    57  }
    58  
    59  // WithListLimit creates context with information about list limit that will be used with request.
    60  // Only for testing purposes.
    61  func WithListLimit(ctx context.Context, limit int) context.Context {
    62  	return context.WithValue(ctx, listLimitKey{}, limit)
    63  }
    64  
    65  // GetListLimit returns value for list limit if exists in context.
    66  // Only for testing purposes.
    67  func GetListLimit(ctx context.Context) int {
    68  	limit, _ := ctx.Value(listLimitKey{}).(int)
    69  	return limit
    70  }
    71  
    72  // ConcurrentSegmentUploadsConfig is the configuration for concurrent
    73  // segment uploads using the new upload codepath.
    74  type ConcurrentSegmentUploadsConfig struct {
    75  	// SchedulerOptions are the options for the scheduler used to place limits
    76  	// on the amount of concurrent piece limits per-upload, across all
    77  	// segments.
    78  	SchedulerOptions scheduler.Options
    79  
    80  	// LongTailMargin represents the maximum number of piece uploads beyond the
    81  	// optimal threshold that will be uploaded for a given segment. Once an
    82  	// upload has reached the optimal threshold, the remaining piece uploads
    83  	// are cancelled.
    84  	LongTailMargin int
    85  }
    86  
    87  // DefaultConcurrentSegmentUploadsConfig returns the default ConcurrentSegmentUploadsConfig.
    88  func DefaultConcurrentSegmentUploadsConfig() ConcurrentSegmentUploadsConfig {
    89  	return ConcurrentSegmentUploadsConfig{
    90  		SchedulerOptions: scheduler.Options{
    91  			MaximumConcurrent:        300,
    92  			MaximumConcurrentHandles: 10,
    93  		},
    94  		LongTailMargin: 50,
    95  	}
    96  }
    97  
    98  // WithConcurrentSegmentUploadsDefaultConfig creates a context that enables the
    99  // new concurrent segment upload codepath for testing purposes using the
   100  // default configuration.
   101  //
   102  // The context needs to be used with uplink.OpenProject to have effect.
   103  func WithConcurrentSegmentUploadsDefaultConfig(ctx context.Context) context.Context {
   104  	return WithConcurrentSegmentUploadsConfig(ctx, DefaultConcurrentSegmentUploadsConfig())
   105  }
   106  
   107  // WithConcurrentSegmentUploadsConfig creates a context that enables the
   108  // new concurrent segment upload codepath for testing purposes using the
   109  // given scheduler options.
   110  //
   111  // The context needs to be used with uplink.OpenProject to have effect.
   112  func WithConcurrentSegmentUploadsConfig(ctx context.Context, config ConcurrentSegmentUploadsConfig) context.Context {
   113  	return context.WithValue(ctx, concurrentSegmentUploadsConfigKey{}, config)
   114  }
   115  
   116  // DisableConcurrentSegmentUploads creates a context that disables the new
   117  // concurrent segment upload codepath.
   118  func DisableConcurrentSegmentUploads(ctx context.Context) context.Context {
   119  	return context.WithValue(ctx, disableConcurrentSegmentUploadsKey{}, struct{}{})
   120  }
   121  
   122  // GetConcurrentSegmentUploadsConfig returns the scheduler options to
   123  // use with the new concurrent segment upload codepath, if no scheduler
   124  // options have been set it will return default configuration. Concurrent
   125  // segment upload code path can be disabled with DisableConcurrentSegmentUploads.
   126  func GetConcurrentSegmentUploadsConfig(ctx context.Context) *ConcurrentSegmentUploadsConfig {
   127  	if value := ctx.Value(disableConcurrentSegmentUploadsKey{}); value != nil {
   128  		return nil
   129  	}
   130  	if config, ok := ctx.Value(concurrentSegmentUploadsConfigKey{}).(ConcurrentSegmentUploadsConfig); ok {
   131  		return &config
   132  	}
   133  	config := DefaultConcurrentSegmentUploadsConfig()
   134  	return &config
   135  }
   136  
   137  // WithLogWriter creates context with information about upload log file.
   138  func WithLogWriter(ctx context.Context, w io.Writer) context.Context {
   139  	return context.WithValue(ctx, logWriterKey{}, w)
   140  }
   141  
   142  // GetLogWriter returns upload log file from context if exists.
   143  func GetLogWriter(ctx context.Context) io.Writer {
   144  	if w, ok := ctx.Value(logWriterKey{}).(io.Writer); ok {
   145  		return w
   146  	}
   147  	return nil
   148  }
   149  
   150  type contextKeyList struct {
   151  	key  string
   152  	val  string
   153  	next *contextKeyList
   154  }
   155  
   156  // WithLogWriterContext appends the key/val pair to the context that is logged with
   157  // each Log call.
   158  func WithLogWriterContext(ctx context.Context, kvs ...string) context.Context {
   159  	for i := 0; i+1 < len(kvs); i += 2 {
   160  		ctx = context.WithValue(ctx, logWriterContextKey{}, &contextKeyList{
   161  			key:  kvs[i],
   162  			val:  kvs[i+1],
   163  			next: getLogWriterContext(ctx),
   164  		})
   165  	}
   166  	return ctx
   167  }
   168  
   169  func getLogWriterContext(ctx context.Context) *contextKeyList {
   170  	l, _ := ctx.Value(logWriterContextKey{}).(*contextKeyList)
   171  	return l
   172  }
   173  
   174  var (
   175  	logMu    sync.Mutex
   176  	logStart = time.Now()
   177  )
   178  
   179  // Log writes to upload log file if exists.
   180  func Log(ctx context.Context, args ...interface{}) {
   181  	w := GetLogWriter(ctx)
   182  	if w == nil {
   183  		return
   184  	}
   185  
   186  	logMu.Lock()
   187  	defer logMu.Unlock()
   188  
   189  	now := time.Now()
   190  
   191  	_, _ = io.WriteString(w, now.Truncate(0).Format(time.StampNano))
   192  	_, _ = io.WriteString(w, " (")
   193  	_, _ = fmt.Fprintf(w, "%-12s", now.Sub(logStart).String())
   194  	_, _ = io.WriteString(w, ")")
   195  
   196  	l, first := getLogWriterContext(ctx), true
   197  	for ; l != nil; l, first = l.next, false {
   198  		if first {
   199  			_, _ = io.WriteString(w, " [")
   200  		} else {
   201  			_, _ = io.WriteString(w, ", ")
   202  		}
   203  		_, _ = io.WriteString(w, l.key)
   204  		_, _ = io.WriteString(w, "=")
   205  		_, _ = io.WriteString(w, l.val)
   206  	}
   207  	if !first {
   208  		_, _ = io.WriteString(w, "]")
   209  	}
   210  
   211  	_, _ = io.WriteString(w, ": ")
   212  	_, _ = fmt.Fprintln(w, args...)
   213  }