github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/resource/retryclient.go (about) 1 // Copyright 2020 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package resource 5 6 import ( 7 "fmt" 8 "time" 9 10 "github.com/juju/charm/v12" 11 "github.com/juju/clock" 12 "github.com/juju/errors" 13 "github.com/juju/retry" 14 ) 15 16 // ResourceRetryClient is a wrapper around a Juju repository client that 17 // retries GetResource() calls. 18 type ResourceRetryClient struct { 19 ResourceGetter 20 retryArgs retry.CallArgs 21 } 22 23 func newRetryClient(client ResourceGetter) *ResourceRetryClient { 24 retryArgs := retry.CallArgs{ 25 // (anastasiamac 2017-05-25) This might not work as the error types 26 // may be lost after a call to some clients. 27 IsFatalError: func(err error) bool { 28 return errors.Is(err, errors.NotFound) || errors.Is(err, errors.NotValid) 29 }, 30 // We don't want to retry for ever. 31 // If we cannot get a resource after trying a few times, 32 // most likely user intervention is needed. 33 Attempts: 3, 34 // A one minute gives enough time for potential connection 35 // issues to sort themselves out without making the caller wait 36 // for an exceptional amount of time. 37 Delay: 1 * time.Minute, 38 Clock: clock.WallClock, 39 } 40 return &ResourceRetryClient{ 41 ResourceGetter: client, 42 retryArgs: retryArgs, 43 } 44 } 45 46 // GetResource returns a reader for the resource's data. 47 func (client ResourceRetryClient) GetResource(req ResourceRequest) (ResourceData, error) { 48 args := client.retryArgs // a copy 49 50 var data ResourceData 51 args.Func = func() error { 52 var err error 53 data, err = client.ResourceGetter.GetResource(req) 54 if err != nil { 55 return errors.Trace(err) 56 } 57 return nil 58 } 59 60 var channelStr string 61 stChannel := req.CharmID.Origin.Channel 62 if stChannel != nil { 63 // Empty string is valid for CharmStore charms. 64 channel, err := charm.MakeChannel(stChannel.Track, stChannel.Risk, stChannel.Branch) 65 if err != nil { 66 return data, errors.Trace(err) 67 } 68 channelStr = fmt.Sprintf("channel (%v), ", channel.String()) 69 } 70 71 var lastErr error 72 args.NotifyFunc = func(err error, i int) { 73 // Remember the error we're hiding and then retry! 74 logger.Warningf("attempt %d/%d to download resource %q from charm store [%scharm (%v), resource revision (%v)] failed with error (will retry): %v", 75 i, client.retryArgs.Attempts, req.Name, channelStr, req.CharmID.URL, req.Revision, err) 76 logger.Tracef("resource get error stack: %v", errors.ErrorStack(err)) 77 lastErr = err 78 } 79 80 err := retry.Call(args) 81 if retry.IsAttemptsExceeded(err) { 82 return data, errors.Annotate(lastErr, "failed after retrying") 83 } 84 if err != nil { 85 return data, errors.Trace(err) 86 } 87 88 return data, nil 89 }