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 }