github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/api/crossmodelrelations/crossmodelrelations.go (about)

     1  // Copyright 2017 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package crossmodelrelations
     5  
     6  import (
     7  	"github.com/juju/clock"
     8  	"github.com/juju/errors"
     9  	"github.com/juju/loggo"
    10  	"gopkg.in/macaroon.v2-unstable"
    11  
    12  	"github.com/juju/juju/api/base"
    13  	apiwatcher "github.com/juju/juju/api/watcher"
    14  	"github.com/juju/juju/apiserver/params"
    15  	"github.com/juju/juju/core/watcher"
    16  )
    17  
    18  var logger = loggo.GetLogger("juju.api.crossmodelrelations")
    19  
    20  // Client provides access to the crossmodelrelations api facade.
    21  type Client struct {
    22  	base.ClientFacade
    23  	facade base.FacadeCaller
    24  
    25  	cache *MacaroonCache
    26  }
    27  
    28  // NewClient creates a new client-side CrossModelRelations facade.
    29  func NewClient(caller base.APICallCloser) *Client {
    30  	return NewClientWithCache(caller, NewMacaroonCache(clock.WallClock))
    31  }
    32  
    33  // NewClientWithCache creates a new client-side CrossModelRelations facade
    34  // with the specified cache.
    35  func NewClientWithCache(caller base.APICallCloser, cache *MacaroonCache) *Client {
    36  	frontend, backend := base.NewClientFacade(caller, "CrossModelRelations")
    37  	return &Client{
    38  		ClientFacade: frontend,
    39  		facade:       backend,
    40  		cache:        cache,
    41  	}
    42  }
    43  
    44  // handleError is used to process an error obtained when making a facade call.
    45  // If the error indicates that a macaroon discharge is required, this is done
    46  // and the resulting discharge macaroons passed back so the api call can be retried.
    47  func (c *Client) handleError(apiErr error) (macaroon.Slice, error) {
    48  	if params.ErrCode(apiErr) != params.CodeDischargeRequired {
    49  		return nil, apiErr
    50  	}
    51  	errResp := errors.Cause(apiErr).(*params.Error)
    52  	if errResp.Info == nil {
    53  		return nil, errors.Annotatef(apiErr, "no error info found in discharge-required response error")
    54  	}
    55  	logger.Debugf("attempting to discharge macaroon due to error: %v", apiErr)
    56  	ms, err := c.facade.RawAPICaller().BakeryClient().DischargeAll(errResp.Info.Macaroon)
    57  	if err == nil && logger.IsTraceEnabled() {
    58  		logger.Tracef("discharge macaroon ids:")
    59  		for _, m := range ms {
    60  			logger.Tracef("  - %v", m.Id())
    61  		}
    62  	}
    63  	if err != nil {
    64  		return nil, errors.Wrap(apiErr, err)
    65  	}
    66  	return ms, err
    67  }
    68  
    69  func (c *Client) getCachedMacaroon(opName, token string) (macaroon.Slice, bool) {
    70  	ms, ok := c.cache.Get(token)
    71  	if ok {
    72  		logger.Debugf("%s using cached macaroons for %s", opName, token)
    73  		if logger.IsTraceEnabled() {
    74  			for _, m := range ms {
    75  				logger.Tracef("  - %v", m.Id())
    76  			}
    77  		}
    78  	}
    79  	return ms, ok
    80  }
    81  
    82  // PublishRelationChange publishes relation changes to the
    83  // model hosting the remote application involved in the relation.
    84  func (c *Client) PublishRelationChange(change params.RemoteRelationChangeEvent) error {
    85  	args := params.RemoteRelationsChanges{
    86  		Changes: []params.RemoteRelationChangeEvent{change},
    87  	}
    88  	// Use any previously cached discharge macaroons.
    89  	if ms, ok := c.getCachedMacaroon("publish relation changed", change.RelationToken); ok {
    90  		args.Changes[0].Macaroons = ms
    91  	}
    92  
    93  	apiCall := func() error {
    94  		var results params.ErrorResults
    95  		if err := c.facade.FacadeCall("PublishRelationChanges", args, &results); err != nil {
    96  			return errors.Trace(err)
    97  		}
    98  		err := results.OneError()
    99  		if params.IsCodeNotFound(err) {
   100  			return errors.NotFoundf("relation for event %v", change)
   101  		}
   102  		return err
   103  	}
   104  	// Make the api call the first time.
   105  	err := apiCall()
   106  	if err == nil || errors.IsNotFound(err) {
   107  		return errors.Trace(err)
   108  	}
   109  
   110  	// On error, possibly discharge the macaroon and retry.
   111  	mac, err2 := c.handleError(err)
   112  	if err2 != nil {
   113  		return errors.Trace(err2)
   114  	}
   115  	args.Changes[0].Macaroons = mac
   116  	c.cache.Upsert(args.Changes[0].RelationToken, mac)
   117  	return apiCall()
   118  }
   119  
   120  func (c *Client) PublishIngressNetworkChange(change params.IngressNetworksChangeEvent) error {
   121  	args := params.IngressNetworksChanges{
   122  		Changes: []params.IngressNetworksChangeEvent{change},
   123  	}
   124  	// Use any previously cached discharge macaroons.
   125  	if ms, ok := c.getCachedMacaroon("publish ingress network change", change.RelationToken); ok {
   126  		args.Changes[0].Macaroons = ms
   127  	}
   128  
   129  	apiCall := func() error {
   130  		var results params.ErrorResults
   131  		if err := c.facade.FacadeCall("PublishIngressNetworkChanges", args, &results); err != nil {
   132  			return errors.Trace(err)
   133  		}
   134  		return results.OneError()
   135  	}
   136  
   137  	// Make the api call the first time.
   138  	err := apiCall()
   139  	if err == nil {
   140  		return nil
   141  	}
   142  
   143  	// On error, possibly discharge the macaroon and retry.
   144  	mac, err2 := c.handleError(err)
   145  	if err2 != nil {
   146  		return errors.Trace(err2)
   147  	}
   148  	args.Changes[0].Macaroons = mac
   149  	c.cache.Upsert(args.Changes[0].RelationToken, mac)
   150  	return apiCall()
   151  }
   152  
   153  // RegisterRemoteRelations sets up the remote model to participate
   154  // in the specified relations.
   155  func (c *Client) RegisterRemoteRelations(relations ...params.RegisterRemoteRelationArg) ([]params.RegisterRemoteRelationResult, error) {
   156  	var (
   157  		args         params.RegisterRemoteRelationArgs
   158  		retryIndices []int
   159  	)
   160  
   161  	args = params.RegisterRemoteRelationArgs{Relations: relations}
   162  	// Use any previously cached discharge macaroons.
   163  	for i, arg := range relations {
   164  		if ms, ok := c.getCachedMacaroon("register remote relation", arg.RelationToken); ok {
   165  			newArg := arg
   166  			newArg.Macaroons = ms
   167  			args.Relations[i] = newArg
   168  		}
   169  	}
   170  
   171  	var results params.RegisterRemoteRelationResults
   172  	apiCall := func() error {
   173  		// Reset the results struct before each api call.
   174  		results = params.RegisterRemoteRelationResults{}
   175  		err := c.facade.FacadeCall("RegisterRemoteRelations", args, &results)
   176  		if err != nil {
   177  			return errors.Trace(err)
   178  		}
   179  		if len(results.Results) != len(args.Relations) {
   180  			return errors.Errorf("expected %d result(s), got %d", len(args.Relations), len(results.Results))
   181  		}
   182  		return nil
   183  	}
   184  
   185  	// Make the api call the first time.
   186  	if err := apiCall(); err != nil {
   187  		return nil, errors.Trace(err)
   188  	}
   189  	// On error, possibly discharge the macaroon and retry.
   190  	result := results.Results
   191  	args = params.RegisterRemoteRelationArgs{}
   192  	// Separate the successful calls from those needing a retry.
   193  	for i, res := range results.Results {
   194  		if res.Error == nil {
   195  			continue
   196  		}
   197  		mac, err := c.handleError(res.Error)
   198  		if err != nil {
   199  			resCopy := res
   200  			resCopy.Error.Message = err.Error()
   201  			result[i] = resCopy
   202  			continue
   203  		}
   204  		retryArg := relations[i]
   205  		retryArg.Macaroons = mac
   206  		args.Relations = append(args.Relations, retryArg)
   207  		retryIndices = append(retryIndices, i)
   208  		c.cache.Upsert(retryArg.RelationToken, mac)
   209  	}
   210  	// Nothing to retry so return the original result.
   211  	if len(args.Relations) == 0 {
   212  		return result, nil
   213  	}
   214  
   215  	if err := apiCall(); err != nil {
   216  		return nil, errors.Trace(err)
   217  	}
   218  	// After a retry, insert the results into the original result slice.
   219  	for j, res := range results.Results {
   220  		resCopy := res
   221  		result[retryIndices[j]] = resCopy
   222  	}
   223  	return result, nil
   224  }
   225  
   226  // WatchRelationUnits returns a watcher that notifies of changes to the
   227  // units in the remote model for the relation with the given remote token.
   228  func (c *Client) WatchRelationUnits(remoteRelationArg params.RemoteEntityArg) (watcher.RelationUnitsWatcher, error) {
   229  	args := params.RemoteEntityArgs{Args: []params.RemoteEntityArg{remoteRelationArg}}
   230  	// Use any previously cached discharge macaroons.
   231  	if ms, ok := c.getCachedMacaroon("watch relation units", remoteRelationArg.Token); ok {
   232  		args.Args[0].Macaroons = ms
   233  	}
   234  
   235  	var results params.RelationUnitsWatchResults
   236  	apiCall := func() error {
   237  		// Reset the results struct before each api call.
   238  		results = params.RelationUnitsWatchResults{}
   239  		if err := c.facade.FacadeCall("WatchRelationUnits", args, &results); err != nil {
   240  			return errors.Trace(err)
   241  		}
   242  		if len(results.Results) != 1 {
   243  			return errors.Errorf("expected 1 result, got %d", len(results.Results))
   244  		}
   245  		return nil
   246  	}
   247  
   248  	// Make the api call the first time.
   249  	if err := apiCall(); err != nil {
   250  		return nil, errors.Trace(err)
   251  	}
   252  
   253  	// On error, possibly discharge the macaroon and retry.
   254  	result := results.Results[0]
   255  	if result.Error != nil {
   256  		mac, err := c.handleError(result.Error)
   257  		if err != nil {
   258  			result.Error.Message = err.Error()
   259  			return nil, result.Error
   260  		}
   261  		args.Args[0].Macaroons = mac
   262  		c.cache.Upsert(args.Args[0].Token, mac)
   263  
   264  		if err := apiCall(); err != nil {
   265  			return nil, errors.Trace(err)
   266  		}
   267  		result = results.Results[0]
   268  	}
   269  	if result.Error != nil {
   270  		return nil, result.Error
   271  	}
   272  
   273  	w := apiwatcher.NewRelationUnitsWatcher(c.facade.RawAPICaller(), result)
   274  	return w, nil
   275  }
   276  
   277  // RelationUnitSettings returns the relation unit settings for the given relation units in the remote model.
   278  func (c *Client) RelationUnitSettings(relationUnits []params.RemoteRelationUnit) ([]params.SettingsResult, error) {
   279  	var (
   280  		args         params.RemoteRelationUnits
   281  		retryIndices []int
   282  	)
   283  
   284  	args = params.RemoteRelationUnits{RelationUnits: relationUnits}
   285  	// Use any previously cached discharge macaroons.
   286  	for i, arg := range args.RelationUnits {
   287  		if ms, ok := c.getCachedMacaroon("relation unit settings", arg.RelationToken); ok {
   288  			newArg := arg
   289  			newArg.Macaroons = ms
   290  			args.RelationUnits[i] = newArg
   291  		}
   292  	}
   293  
   294  	var results params.SettingsResults
   295  	apiCall := func() error {
   296  		// Reset the results struct before each api call.
   297  		results = params.SettingsResults{}
   298  		err := c.facade.FacadeCall("RelationUnitSettings", args, &results)
   299  		if err != nil {
   300  			return errors.Trace(err)
   301  		}
   302  		if len(results.Results) != len(args.RelationUnits) {
   303  			return errors.Errorf("expected %d result(s), got %d", len(args.RelationUnits), len(results.Results))
   304  		}
   305  		return nil
   306  	}
   307  
   308  	// Make the api call the first time.
   309  	if err := apiCall(); err != nil {
   310  		return nil, errors.Trace(err)
   311  	}
   312  	// On error, possibly discharge the macaroon and retry.
   313  	result := results.Results
   314  	args = params.RemoteRelationUnits{}
   315  	// Separate the successful calls from those needing a retry.
   316  	for i, res := range results.Results {
   317  		if res.Error == nil {
   318  			continue
   319  		}
   320  		mac, err := c.handleError(res.Error)
   321  		if err != nil {
   322  			resCopy := res
   323  			resCopy.Error.Message = err.Error()
   324  			result[i] = resCopy
   325  			continue
   326  		}
   327  		retryArg := relationUnits[i]
   328  		retryArg.Macaroons = mac
   329  		args.RelationUnits = append(args.RelationUnits, retryArg)
   330  		retryIndices = append(retryIndices, i)
   331  		c.cache.Upsert(retryArg.RelationToken, mac)
   332  	}
   333  	// Nothing to retry so return the original result.
   334  	if len(args.RelationUnits) == 0 {
   335  		return result, nil
   336  	}
   337  
   338  	if err := apiCall(); err != nil {
   339  		return nil, errors.Trace(err)
   340  	}
   341  	// After a retry, insert the results into the original result slice.
   342  	for j, res := range results.Results {
   343  		resCopy := res
   344  		result[retryIndices[j]] = resCopy
   345  	}
   346  	return result, nil
   347  }
   348  
   349  // WatchEgressAddressesForRelation returns a watcher that notifies when addresses,
   350  // from which connections will originate to the offering side of the relation, change.
   351  // Each event contains the entire set of addresses which the offering side is required
   352  // to allow for access to the other side of the relation.
   353  func (c *Client) WatchEgressAddressesForRelation(remoteRelationArg params.RemoteEntityArg) (watcher.StringsWatcher, error) {
   354  	args := params.RemoteEntityArgs{Args: []params.RemoteEntityArg{remoteRelationArg}}
   355  	// Use any previously cached discharge macaroons.
   356  	if ms, ok := c.getCachedMacaroon("watch relation egress addresses", remoteRelationArg.Token); ok {
   357  		args.Args[0].Macaroons = ms
   358  	}
   359  
   360  	var results params.StringsWatchResults
   361  	apiCall := func() error {
   362  		// Reset the results struct before each api call.
   363  		results = params.StringsWatchResults{}
   364  		if err := c.facade.FacadeCall("WatchEgressAddressesForRelations", args, &results); err != nil {
   365  			return errors.Trace(err)
   366  		}
   367  		if len(results.Results) != 1 {
   368  			return errors.Errorf("expected 1 result, got %d", len(results.Results))
   369  		}
   370  		return nil
   371  	}
   372  
   373  	// Make the api call the first time.
   374  	if err := apiCall(); err != nil {
   375  		return nil, errors.Trace(err)
   376  	}
   377  
   378  	// On error, possibly discharge the macaroon and retry.
   379  	result := results.Results[0]
   380  	if result.Error != nil {
   381  		mac, err := c.handleError(result.Error)
   382  		if err != nil {
   383  			result.Error.Message = err.Error()
   384  			return nil, result.Error
   385  		}
   386  		args.Args[0].Macaroons = mac
   387  		c.cache.Upsert(args.Args[0].Token, mac)
   388  
   389  		if err := apiCall(); err != nil {
   390  			return nil, errors.Trace(err)
   391  		}
   392  		result = results.Results[0]
   393  	}
   394  	if result.Error != nil {
   395  		return nil, result.Error
   396  	}
   397  
   398  	w := apiwatcher.NewStringsWatcher(c.facade.RawAPICaller(), result)
   399  	return w, nil
   400  }
   401  
   402  // WatchRelationSuspendedStatus starts a RelationStatusWatcher for watching the life and
   403  // suspended status of the specified relation in the remote model.
   404  func (c *Client) WatchRelationSuspendedStatus(arg params.RemoteEntityArg) (watcher.RelationStatusWatcher, error) {
   405  	args := params.RemoteEntityArgs{Args: []params.RemoteEntityArg{arg}}
   406  	// Use any previously cached discharge macaroons.
   407  	if ms, ok := c.getCachedMacaroon("watch relation status", arg.Token); ok {
   408  		args.Args[0].Macaroons = ms
   409  	}
   410  
   411  	var results params.RelationStatusWatchResults
   412  	apiCall := func() error {
   413  		// Reset the results struct before each api call.
   414  		results = params.RelationStatusWatchResults{}
   415  		if err := c.facade.FacadeCall("WatchRelationsSuspendedStatus", args, &results); err != nil {
   416  			return errors.Trace(err)
   417  		}
   418  		if len(results.Results) != 1 {
   419  			return errors.Errorf("expected 1 result, got %d", len(results.Results))
   420  		}
   421  		return nil
   422  	}
   423  
   424  	// Make the api call the first time.
   425  	if err := apiCall(); err != nil {
   426  		return nil, errors.Trace(err)
   427  	}
   428  
   429  	// On error, possibly discharge the macaroon and retry.
   430  	result := results.Results[0]
   431  	if result.Error != nil {
   432  		mac, err := c.handleError(result.Error)
   433  		if err != nil {
   434  			result.Error.Message = err.Error()
   435  			return nil, result.Error
   436  		}
   437  		args.Args[0].Macaroons = mac
   438  		c.cache.Upsert(args.Args[0].Token, mac)
   439  
   440  		if err := apiCall(); err != nil {
   441  			return nil, errors.Trace(err)
   442  		}
   443  		result = results.Results[0]
   444  	}
   445  	if result.Error != nil {
   446  		return nil, result.Error
   447  	}
   448  
   449  	w := apiwatcher.NewRelationStatusWatcher(c.facade.RawAPICaller(), result)
   450  	return w, nil
   451  }
   452  
   453  // WatchOfferStatus starts an OfferStatusWatcher for watching the status
   454  // of the specified offer in the remote model.
   455  func (c *Client) WatchOfferStatus(arg params.OfferArg) (watcher.OfferStatusWatcher, error) {
   456  	args := params.OfferArgs{Args: []params.OfferArg{arg}}
   457  	// Use any previously cached discharge macaroons.
   458  	if ms, ok := c.getCachedMacaroon("watch offer status", arg.OfferUUID); ok {
   459  		args.Args[0].Macaroons = ms
   460  	}
   461  
   462  	var results params.OfferStatusWatchResults
   463  	apiCall := func() error {
   464  		// Reset the results struct before each api call.
   465  		results = params.OfferStatusWatchResults{}
   466  		if err := c.facade.FacadeCall("WatchOfferStatus", args, &results); err != nil {
   467  			return errors.Trace(err)
   468  		}
   469  		if len(results.Results) != 1 {
   470  			return errors.Errorf("expected 1 result, got %d", len(results.Results))
   471  		}
   472  		return nil
   473  	}
   474  
   475  	// Make the api call the first time.
   476  	if err := apiCall(); err != nil {
   477  		return nil, errors.Trace(err)
   478  	}
   479  
   480  	// On error, possibly discharge the macaroon and retry.
   481  	result := results.Results[0]
   482  	if result.Error != nil {
   483  		mac, err := c.handleError(result.Error)
   484  		if err != nil {
   485  			result.Error.Message = err.Error()
   486  			return nil, result.Error
   487  		}
   488  		args.Args[0].Macaroons = mac
   489  		c.cache.Upsert(args.Args[0].OfferUUID, mac)
   490  
   491  		if err := apiCall(); err != nil {
   492  			return nil, errors.Trace(err)
   493  		}
   494  		result = results.Results[0]
   495  	}
   496  	if result.Error != nil {
   497  		return nil, result.Error
   498  	}
   499  
   500  	w := apiwatcher.NewOfferStatusWatcher(c.facade.RawAPICaller(), result)
   501  	return w, nil
   502  }