github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/resource/resourceadapters/charmstore.go (about) 1 // Copyright 2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package resourceadapters 5 6 import ( 7 "io" 8 "time" 9 10 "github.com/juju/clock" 11 "github.com/juju/errors" 12 "github.com/juju/retry" 13 charmresource "gopkg.in/juju/charm.v6/resource" 14 "gopkg.in/juju/names.v2" 15 16 "github.com/juju/juju/charmstore" 17 "github.com/juju/juju/resource" 18 "github.com/juju/juju/state" 19 ) 20 21 // charmstoreEntityCache adapts between resource state and charmstore.EntityCache. 22 type charmstoreEntityCache struct { 23 st state.Resources 24 userID names.Tag 25 unit resource.Unit 26 applicationID string 27 } 28 29 // GetResource implements charmstore.EntityCache. 30 func (cache *charmstoreEntityCache) GetResource(name string) (resource.Resource, error) { 31 return cache.st.GetResource(cache.applicationID, name) 32 } 33 34 // SetResource implements charmstore.EntityCache. 35 func (cache *charmstoreEntityCache) SetResource(chRes charmresource.Resource, reader io.Reader) (resource.Resource, error) { 36 return cache.st.SetResource(cache.applicationID, cache.userID.Id(), chRes, reader) 37 } 38 39 // OpenResource implements charmstore.EntityCache. 40 func (cache *charmstoreEntityCache) OpenResource(name string) (resource.Resource, io.ReadCloser, error) { 41 if cache.unit == nil { 42 return resource.Resource{}, nil, errors.NotImplementedf("") 43 } 44 return cache.st.OpenResourceForUniter(cache.unit, name) 45 } 46 47 type charmstoreOpener struct { 48 st *state.State 49 } 50 51 func newCharmstoreOpener(st *state.State) *charmstoreOpener { 52 return &charmstoreOpener{st} 53 } 54 55 func newCharmStoreClient(st *state.State) (charmstore.Client, error) { 56 controllerCfg, err := st.ControllerConfig() 57 if err != nil { 58 return charmstore.Client{}, errors.Trace(err) 59 } 60 return charmstore.NewCachingClient(state.MacaroonCache{st}, controllerCfg.CharmStoreURL()) 61 } 62 63 // NewClient opens a new charm store client. 64 func (cs *charmstoreOpener) NewClient() (*CSRetryClient, error) { 65 // TODO(ericsnow) Use a valid charm store client. 66 client, err := newCharmStoreClient(cs.st) 67 if err != nil { 68 return nil, errors.Trace(err) 69 } 70 return newCSRetryClient(client), nil 71 } 72 73 // ResourceClient defines a set of functionality that a client 74 // needs to define to support resources. 75 type ResourceClient interface { 76 GetResource(req charmstore.ResourceRequest) (data charmstore.ResourceData, err error) 77 } 78 79 // CSRetryClient is a wrapper around a Juju charm store client that 80 // retries GetResource() calls. 81 type CSRetryClient struct { 82 ResourceClient 83 retryArgs retry.CallArgs 84 } 85 86 func newCSRetryClient(client ResourceClient) *CSRetryClient { 87 retryArgs := retry.CallArgs{ 88 // (anastasiamac 2017-05-25) This might not work as the error types 89 // may be lost after a call to some clients. 90 IsFatalError: func(err error) bool { 91 return errors.IsNotFound(err) || errors.IsNotValid(err) 92 }, 93 // We don't want to retry for ever. 94 // If we cannot get a resource after trying a few times, 95 // most likely user intervention is needed. 96 Attempts: 3, 97 // A one minute gives enough time for potential connection 98 // issues to sort themselves out without making the caller wait 99 // for an exceptional amount of time. 100 Delay: 1 * time.Minute, 101 Clock: clock.WallClock, 102 } 103 return &CSRetryClient{ 104 ResourceClient: client, 105 retryArgs: retryArgs, 106 } 107 } 108 109 // GetResource returns a reader for the resource's data. 110 func (client CSRetryClient) GetResource(req charmstore.ResourceRequest) (charmstore.ResourceData, error) { 111 args := client.retryArgs // a copy 112 113 var data charmstore.ResourceData 114 args.Func = func() error { 115 var err error 116 data, err = client.ResourceClient.GetResource(req) 117 if err != nil { 118 return errors.Trace(err) 119 } 120 return nil 121 } 122 123 var lastErr error 124 args.NotifyFunc = func(err error, i int) { 125 // Remember the error we're hiding and then retry! 126 logger.Warningf("attempt %d/%d to download resource %q from charm store [channel (%v), charm (%v), resource revision (%v)] failed with error (will retry): %v", 127 i, client.retryArgs.Attempts, req.Name, req.Channel, req.Charm, req.Revision, err) 128 logger.Tracef("resource get error stack: %v", errors.ErrorStack(err)) 129 lastErr = err 130 } 131 132 err := retry.Call(args) 133 if retry.IsAttemptsExceeded(err) { 134 return data, errors.Annotate(lastErr, "failed after retrying") 135 } 136 if err != nil { 137 return data, errors.Trace(err) 138 } 139 140 return data, nil 141 }