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

     1  // Copyright (C) 2020 Storj Labs, Inc.
     2  // See LICENSE for copying information.
     3  
     4  package uplink
     5  
     6  import (
     7  	"errors"
     8  	"fmt"
     9  	"io"
    10  	"strings"
    11  	_ "unsafe" // for go:linkname
    12  
    13  	"github.com/spacemonkeygo/monkit/v3"
    14  	"github.com/zeebo/errs"
    15  
    16  	"storj.io/common/encryption"
    17  	"storj.io/common/errs2"
    18  	"storj.io/common/rpc/rpcstatus"
    19  	"storj.io/eventkit"
    20  	"storj.io/uplink/private/metaclient"
    21  	"storj.io/uplink/private/piecestore"
    22  )
    23  
    24  var mon = monkit.Package()
    25  var evs = eventkit.Package()
    26  
    27  // We use packageError.Wrap/New instead of plain errs.Wrap/New to add a prefix "uplink" to every error
    28  // message emitted by the Uplink library.
    29  // It is private because it's not intended to be part of the public API.
    30  var packageError = errs.Class("uplink")
    31  
    32  // ErrTooManyRequests is returned when user has sent too many requests in a given amount of time.
    33  var ErrTooManyRequests = errors.New("too many requests")
    34  
    35  // ErrBandwidthLimitExceeded is returned when project will exceeded bandwidth limit.
    36  var ErrBandwidthLimitExceeded = errors.New("bandwidth limit exceeded")
    37  
    38  // ErrStorageLimitExceeded is returned when project will exceeded storage limit.
    39  var ErrStorageLimitExceeded = errors.New("storage limit exceeded")
    40  
    41  // ErrSegmentsLimitExceeded is returned when project will exceeded segments limit.
    42  var ErrSegmentsLimitExceeded = errors.New("segments limit exceeded")
    43  
    44  // ErrPermissionDenied is returned when the request is denied due to invalid permissions.
    45  var ErrPermissionDenied = errors.New("permission denied")
    46  
    47  //go:linkname convertKnownErrors
    48  func convertKnownErrors(err error, bucket, key string) error {
    49  	switch {
    50  	case errors.Is(err, io.EOF):
    51  		return err
    52  	case metaclient.ErrNoBucket.Has(err):
    53  		return errwrapf("%w (%q)", ErrBucketNameInvalid, bucket)
    54  	case metaclient.ErrNoPath.Has(err):
    55  		return errwrapf("%w (%q)", ErrObjectKeyInvalid, key)
    56  	case metaclient.ErrBucketNotFound.Has(err):
    57  		return errwrapf("%w (%q)", ErrBucketNotFound, bucket)
    58  	case metaclient.ErrObjectNotFound.Has(err):
    59  		return errwrapf("%w (%q)", ErrObjectNotFound, key)
    60  	case metaclient.ErrUploadIDInvalid.Has(err):
    61  		return errwrapf("%w (%q)", ErrUploadIDInvalid, key)
    62  	case encryption.ErrMissingEncryptionBase.Has(err):
    63  		return errwrapf("%w (%q)", ErrPermissionDenied, key)
    64  	case encryption.ErrMissingDecryptionBase.Has(err):
    65  		return errwrapf("%w (%q)", ErrPermissionDenied, key)
    66  	case errs2.IsRPC(err, rpcstatus.ResourceExhausted):
    67  		// TODO is a better way to do this?
    68  		message := errs.Unwrap(err).Error()
    69  		if strings.HasSuffix(message, "Exceeded Usage Limit") {
    70  			return packageError.Wrap(rpcstatus.Wrap(rpcstatus.ResourceExhausted, ErrBandwidthLimitExceeded))
    71  		} else if strings.HasSuffix(message, "Too Many Requests") {
    72  			return packageError.Wrap(rpcstatus.Wrap(rpcstatus.ResourceExhausted, ErrTooManyRequests))
    73  		} else if strings.Contains(message, "Exceeded Storage Limit") {
    74  			// contains used to have some flexibility in constructing error message on server-side
    75  			return packageError.Wrap(rpcstatus.Wrap(rpcstatus.ResourceExhausted, ErrStorageLimitExceeded))
    76  		} else if strings.Contains(message, "Exceeded Segments Limit") {
    77  			// contains used to have some flexibility in constructing error message on server-side
    78  			return packageError.Wrap(rpcstatus.Wrap(rpcstatus.ResourceExhausted, ErrSegmentsLimitExceeded))
    79  		}
    80  	case errs2.IsRPC(err, rpcstatus.NotFound):
    81  		const (
    82  			bucketNotFoundPrefix = "bucket not found"
    83  			objectNotFoundPrefix = "object not found"
    84  		)
    85  
    86  		message := errs.Unwrap(err).Error()
    87  		if strings.HasPrefix(message, bucketNotFoundPrefix) {
    88  			// remove error prefix + ": " from message
    89  			bucket := strings.TrimPrefix(message[len(bucketNotFoundPrefix):], ": ")
    90  			return errwrapf("%w (%q)", ErrBucketNotFound, bucket)
    91  		} else if strings.HasPrefix(message, objectNotFoundPrefix) {
    92  			return errwrapf("%w (%q)", ErrObjectNotFound, key)
    93  		}
    94  	case errs2.IsRPC(err, rpcstatus.PermissionDenied):
    95  		originalErr := err
    96  		wrappedErr := errwrapf("%w (%v)", ErrPermissionDenied, originalErr)
    97  		// TODO: once we have confirmed nothing downstream
    98  		// is using errs2.IsRPC(err, rpcstatus.PermissionDenied), we should
    99  		// just return wrappedErr instead of this.
   100  		return &joinedErr{main: wrappedErr, alt: originalErr, code: rpcstatus.PermissionDenied}
   101  	}
   102  
   103  	return packageError.Wrap(err)
   104  }
   105  
   106  func errwrapf(format string, err error, args ...interface{}) error {
   107  	var all []interface{}
   108  	all = append(all, err)
   109  	all = append(all, args...)
   110  	return packageError.Wrap(fmt.Errorf(format, all...))
   111  }
   112  
   113  type joinedErr struct {
   114  	main error
   115  	alt  error
   116  	code rpcstatus.StatusCode
   117  }
   118  
   119  func (err *joinedErr) Is(target error) bool {
   120  	return errors.Is(err.main, target) || errors.Is(err.alt, target)
   121  }
   122  
   123  func (err *joinedErr) As(target interface{}) bool {
   124  	if errors.As(err.main, target) {
   125  		return true
   126  	}
   127  	if errors.As(err.alt, target) {
   128  		return true
   129  	}
   130  	return false
   131  }
   132  
   133  func (err *joinedErr) Code() uint64 {
   134  	return uint64(err.code)
   135  }
   136  
   137  func (err *joinedErr) Unwrap() error {
   138  	return err.main
   139  }
   140  
   141  func (err *joinedErr) Error() string {
   142  	return err.main.Error()
   143  }
   144  
   145  // Ungroup works with errs2.IsRPC and errs.IsFunc.
   146  func (err *joinedErr) Ungroup() []error {
   147  	return []error{err.main, err.alt}
   148  }
   149  
   150  var noiseVersion = func() int64 {
   151  	if piecestore.NoiseEnabled {
   152  		// this is a number that indicates what noise support exists so far.
   153  		// 1 was our first implementation, but crucially had an errant round
   154  		// trip on uploads and downloads. 2 has the round trip fixed for
   155  		// downloads but not uploads. 3 has long tail cancelation for
   156  		// downloads fixed. 4 has round trips reduced for small uploads.
   157  		// we'll probably have future values here.
   158  		// we'll want to compare the performance of these different cases.
   159  		return 4
   160  	}
   161  	// 0 means no noise
   162  	return 0
   163  }()