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  }