github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/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/errors" 11 "github.com/juju/retry" 12 "github.com/juju/utils/clock" 13 charmresource "gopkg.in/juju/charm.v6-unstable/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 // TODO(ericsnow) What else do we need? 50 } 51 52 func newCharmstoreOpener(st *state.State) *charmstoreOpener { 53 return &charmstoreOpener{st} 54 } 55 56 func newCharmStoreClient(st *state.State) (charmstore.Client, error) { 57 return charmstore.NewCachingClient(state.MacaroonCache{st}, nil) 58 } 59 60 // NewClient opens a new charm store client. 61 func (cs *charmstoreOpener) NewClient() (*CSRetryClient, error) { 62 // TODO(ericsnow) Use a valid charm store client. 63 client, err := newCharmStoreClient(cs.st) 64 if err != nil { 65 return nil, errors.Trace(err) 66 } 67 return newCSRetryClient(client), nil 68 } 69 70 // CSRetryClient is a wrapper around a Juju charm store client that 71 // retries GetResource() calls. 72 type CSRetryClient struct { 73 charmstore.Client 74 retryArgs retry.CallArgs 75 } 76 77 func newCSRetryClient(client charmstore.Client) *CSRetryClient { 78 retryArgs := retry.CallArgs{ 79 // The only error that stops the retry loop should be "not found". 80 IsFatalError: errors.IsNotFound, 81 // We want to retry until the charm store either gives us the 82 // resource (and we cache it) or the resource isn't found in the 83 // charm store. 84 Attempts: -1, // retry forever... 85 // A one minute gives enough time for potential connection 86 // issues to sort themselves out without making the caller wait 87 // for an exceptional amount of time. 88 Delay: 1 * time.Minute, 89 Clock: clock.WallClock, 90 } 91 return &CSRetryClient{ 92 Client: client, 93 retryArgs: retryArgs, 94 } 95 } 96 97 // GetResource returns a reader for the resource's data. 98 func (client CSRetryClient) GetResource(req charmstore.ResourceRequest) (charmstore.ResourceData, error) { 99 args := client.retryArgs // a copy 100 101 var data charmstore.ResourceData 102 args.Func = func() error { 103 var err error 104 data, err = client.Client.GetResource(req) 105 if err != nil { 106 return errors.Trace(err) 107 } 108 return nil 109 } 110 111 var lastErr error 112 args.NotifyFunc = func(err error, i int) { 113 // Remember the error we're hiding and then retry! 114 logger.Debugf("(attempt %d) retrying resource download from charm store due to error: %v", i, err) 115 lastErr = err 116 } 117 118 err := retry.Call(args) 119 if retry.IsAttemptsExceeded(err) { 120 return data, errors.Annotate(lastErr, "failed after retrying") 121 } 122 if err != nil { 123 return data, errors.Trace(err) 124 } 125 126 return data, nil 127 }